Drupalの安全なコード:データベースの操作



(p1。 クロスサイトリクエストフォージェリ; p3。 ユーザー入力の操作

Drupalは、データベースにアクセスするための独自の機能を提供します。

まず、使用するDBMSの種類に依存しないようにします。 ところで、現時点では、MySQLとPostgreeSQLのレイヤーは完全に機能しています。 7番目のDrupalでは、このリストはOracleとSQLiteによって拡張されます。

第二に、データベース層により、SQLインジェクションから身を守ることができます。

データベースを操作するときに知っておくべき最初の関数はdb_query()です。

おそらく、ほとんどすべての初心者drupallerが書いているスタイルの例から始めましょう。

/**
* 1 -
* $type (, )
*/
$result = db_query( "SELECT nid, title FROM node WHERE type = '$type'" );

$items = array();
while ($row = db_fetch_object($result)) {
$items[] = l($row->title, "node/{$row->nid}" );
}
return theme( 'item_list' , $items);


この例では、いくつかのことが根本的に間違っています。

テーブル名のエイリアス



テーブルの名前は中括弧で囲み、エイリアスを割り当てます。これは、列を参照するときに常に使用することをお勧めします。 変更された呼び出しは次のようになります。

$result = db_query( "SELECT n.nid, n.title FROM {node} n WHERE n.type = '$type'" );


それは私たちに何を与えますか? これにより、プレフィックス付きのテーブルを簡単に処理できます。 つまり、データベースに「pr_node」、「pr_users」などと呼ばれるすべてのテーブルがある場合、Drupalは括弧内のテーブルの正しいプレフィックスを自動的に置き換えます。 この場合にエイリアスを指定すると、中括弧を複数回使用する必要がなくなります。

引数のフィルタリング



クエリ引数のフィルタリングがありません。 これは、SQLインジェクションへの直接パスです。 story' UNION SELECT s.sid, s.sid FROM {sessions} s WHERE s.uid = 1/*$typeにある場合、クエリ全体はすでに次のようになっています。

SELECT n.nid, n.title FROM {node} n WHERE n.type = 'story' UNION SELECT s.sid, s.sid FROM {sessions} s WHERE s.uid = 1/*'


これにより、詐欺師がセッションIDを制御できるようになり、正しいセッションCookieを作成するときに、サイトへの直接の管理者アクセスを取得できます。

これから身を守ることは、クエリのパラメーター化を使用して非常に簡単です。 要求を生​​成するとき、Drupalはsprintf関数の構文を使用します。 スタブはクエリ行に挿入され、個別に移動するパラメーターに置き換えられます。 同時に、パラメータがテストされ、シールドされるため、このアプローチを使用した注入を忘れることがあります。 以下に例を示します。

db_query( "SELECT n.nid FROM {node} n WHERE n.nid > %d" , $nid);
db_query( "SELECT n.nid FROM {node} n WHERE n.type = '%s'" , $type);
db_query( "SELECT n.nid FROM {node} n WHERE n.nid > %d AND n.type = '%s'" , $nid, $type);
db_query( "SELECT n.nid FROM {node} n WHERE n.type = '%s' AND n.nid > %d" , $type, $nid);



代替リスト:



IN (... , ... , ...)コンストラクトの場合は、 db_placeholders()関数を使用します。これにより、指定されたパラメーターの配列に従って、必要な置換シーケンスが作成されます。次に例を示します。

$nids = array(1, 5, 449);
db_query( 'SELECT * FROM {node} n WHERE n.nid IN (' . db_placeholders($nids) . ')' , $nids);



Develモジュールを使用する場合、デバッグの目的で最終リクエストを取得する非常に簡単な方法があります。 db_queryd()呼び出すのとまったく同じパラメーターでdb_queryd()関数を呼び出すdb_query()です。


これで、リクエストは次のようになります。

$result = db_query( "SELECT n.nid, n.title FROM {node} n WHERE n.type = '%s'" , $type);


クエリ結果のランキング



大規模なサイトでの例では、ノードの膨大なリストが表示されます。 最初の10人に制限できるとしたらどうでしょう? 最初の呼び出しでは、SQL LIMIT構文を使用します。たとえば、

SELECT n.nid, n.title FROM {node} n WHERE n.type = '%s' LIMIT 0, 10


すべてがうまくいくように見えますが、Postgree SQLではこのコードはエラーになります。この管理サーバーでは、 OFFSET 0 LIMIT 10構造を使用する必要があるためです。 また、一部のOracleでは、構文が再び異なります。 どうする?

答えは、 db_query_range()を使用してクエリ結果の数を制限することです。 その使用法はdb_queryと似ていますが、すべての引数の後に、最初の行の数と結果の数の2つのパラメーターを指定する必要がある点が異なります。 リクエストは次のように変換されます。

// 10
$result = db_query_range( "SELECT n.nid, n.title FROM {node} n WHERE n.type = '%s'" , $type, 0, 10);



最後に、ページ分割された出力が必要な場合は、 pager_query()関数を使用します。 db_query_range()と異なるのは、オプションのパラメーターが1つだけ存在することです。このパラメーターについては、ドキュメントのページで確認できます。 この関数を使用すると、ページリストの出力は2回の2倍になります。

/**
* 2 - ,
*/
//
$result = pager_query( "SELECT n.nid, n.title FROM {node} n WHERE n.type = '%s'" , $type, 0, 10);

$items = array();
while ($row = db_fetch_object($result)) {
$items[] = l($row->title, "node/{$row->nid}" );
}

$output = theme( 'item_list' , $items);

//
$output .= theme( 'pager' );

return $output;



ご覧のとおり、変更は2行のみです。 現在のページの取得、処理などのルーチン全体 Drupalが引き継ぎます。

要求モジュールを変更する機能



多くの場合、他のモジュールにリクエストに影響を与える能力を与えることは理にかなっています。 Drupalでは、これはdb_rewrite_sql()関数の束によって実装され、モジュールのフック実装hook_db_rewrite_sql()によって実装されます。 リクエストは次のようになります。

$result = pager_query(db_rewrite_sql( "SELECT n.nid, n.title FROM {node} n WHERE n.type = '%s'" , 'n' , 'nid' ), $type, 0, 10);


そして、ここにフック実装の例がありますので、何が起こっているのかがわかります:

// ,
function my_module_db_rewrite_sql($query, $primary_table, $primary_field, $args) {
switch ($primary_field) {
case 'nid' :
if ($primary_table == 'n' ) {
$ return [ 'join' ] = "LEFT JOIN {users} u ON $primary_table.uid = u.uid" ;
$ return [ 'where' ] = 'u.login > ' . time() - 60 * 60 * 24;
}
return $ return ;
break ;
}
}



「join」フックから返された要素はリクエストに添付され、「where」は条件のリストに追加され、処理後のリクエストは次のようになります。

SELECT n.nid, n.title FROM {node} n LEFT JOIN {users} u ON n.uid = u.uid WHERE n.type = '%s' AND u.login > 199976743


その後、実際にはpager_query()に到着し、通常どおり処理されます。

最終的なサンプルコード



/**
* 3 - ,
*/
// db_rewrite_sql
$result = pager_query(db_rewrite_sql( "SELECT n.nid, n.title FROM {node} n WHERE n.type = '%s'" , 'n' , 'nid' ), $type, 0, 10);

$items = array();
while ($row = db_fetch_object($result)) {
$items[] = l($row->title, "node/{$row->nid}" );
}

$output = theme( 'item_list' , $items);

$output .= theme( 'pager' );

return $output;


* This source code was highlighted with Source Code Highlighter .


便利なリンク


セーフコードシリーズの記事

Source: https://habr.com/ru/post/J52469/


All Articles