多くの開発者はSQLクエリがどのように改竄されるかということを余り 気にかけておらず、またSQLクエリは信用できるものと考えているようです。 実際にはSQLクエリはアクセス制限を回避することが可能で、従って 通常の認証や権限のチェックを無視することができます。時には、 OSレベルのコマンドを実行できてしまうこともあります。
ダイレクトSQLコマンドインジェクション(SQLコマンドの直接実行)という手法は、 攻撃者がSQLコマンドを生成もしくは既存のコマンドを変更することで 隠蔽すべきデータを公開したり、重要なデータを書き換えたり、データベース ホストで危険なシステムレベルのコマンドを実行したりするものの事です。 この手法は、ユーザからの入力をスタティックなパラメータと組み合わせて SQLクエリを生成するアプリケーションにおいて使用されます。以下の例は 不幸なことに実際の事例に基づいたものです。
入力のチェックを怠っており、スーパーユーザもしくはデータベース作成権限を 持つユーザ以外のユーザでデータベースに接続していない ために、攻撃者はデータベースにスーパーユーザを作成することが出来ます。
例1 表示するデータを分割し ... そしてスーパーユーザを作成します。 (PostgreSQLの例)
$offset = $argv[0]; // 入力チェックが行われていません!
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
$result = pg_query($conn, $query);
0; insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd) select 'crack', usesysid, 't','t','crack' from pg_shadow where usename='postgres'; --
注意: SQLパーサにクエリの残りの部分を無視させるために開発者によく使わ れる技法として、SQLのコメント記号である--があ ります。
パスワードを取得する恐るべき手段に、サイトの検索結果のページを欺く というものがあります。攻撃する者が必要とするものは、投稿された変数 の中でSQL命令で使用される際に正しく扱われていないものがあるかどう かを確かめるだけです。これらのフィルタは、通常、 SELECT文のWHERE, ORDER BY, LIMIT及びOFFSET句をカスタマイズするた めに前に置かれる形で設定されます。使用するデータベースが UNION構造をサポートしている場合、 攻撃者は元のクエリに任意のテーブルからパスワードのリストを取得する クエリを追加しようとするかもしれません。 暗号化されたパスワードフィールドを使用することが強く推奨されます。
例2 記事...そして(全てのデータベースサーバーの)いくつかのパスワード のリストを表示する
$query = "SELECT id, name, inserted, size FROM products
WHERE size = '$size'
ORDER BY $order LIMIT $limit, $offset;";
$result = odbc_exec($conn, $query);
' union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable; --
SQL UPDATE もデータベースを攻撃するために使用されます。これらのク エリも切捨てたり新しいクエリを元のクエリに追加することによる攻撃 を受けます。しかし、攻撃者はSET句を使用する可 能性があります。この場合、クエリを成功させるためにいくつかのスキー マ情報を保有する必要があります。これは、フォームの変数名や総当た り法により調べることができます。パスワードまたはユーザ名を保存す るフィールド用の命名記法はそう多くはありません。
例3 パスワードのリセットから ... (全てのデータベースサーバーで)より多 くの権限を得るまで
$query = "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';";
// $uid == ' or uid like'%admin%'; --
$query = "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%'; --";
// $pwd == "hehehe', admin='yes', trusted=100 "
$query = "UPDATE usertable SET pwd='hehehe', admin='yes', trusted=100 WHERE ...;"
恐ろしい例として、いくつかのデータベースホストのオペレーティン グシステムレベルのコマンドがアクセス可能となる方法を示します。
例4 データベースホストのオペレーティングシステムを攻撃する (MSSQLサーバー)
$query = "SELECT * FROM products WHERE id LIKE '%$prod%'";
$result = mssql_query($query);
$query = "SELECT * FROM products
WHERE id LIKE '%a%'
exec master..xp_cmdshell 'net user test testpass /ADD'--";
$result = mssql_query($query);
注意: 上記のいくつかの例は、データベースサーバの種類に依存しています。 これは、他の製品に対して同様な攻撃ができないことを意味するもので はありません。使用しているデータベースが他の手段で攻撃可能である 可能性もあります。
ほとんどの例では攻撃する者は特定のデータベースのスキーマに関して 若干の情報を保有している必要があると弁解されるかもしれません。 これは正しいですが、いつ何時これが外部にもれうるかを知ることはで きませんし、いったんこの情報がもれると、データベースが外部に開示 される可能性があります。オープンソースまたは一般的に入手可能なデー タベース処理パッケージを使用している場合(これはコンテンツ監理シス テムまたはフォーラムに含まれている可能性があります)、侵入者は簡単 に使用されているコードの一部を入手することができます。そのコード の設計が悪い場合にもセキュリティリスクを生じる可能性があります。
これらの攻撃は、セキュリティを考慮して書かれていないコードを攻撃 する方法です。特にクライアント側から入力されるあらゆる種類の入力 を決して信用しないでください。これは、selectボックスやhidden input フィールド、Cookieの場合も同様です。最初の例は、このような欠点の ないクエリが破滅をもたらしうることを示すものです。
アプリケーションが、数値入力を期待している場合、データを is_numeric()で検証するか、 settype()により暗黙の型変換を行うか、 sprintf()により数値表現を使用することを検討 してみてください。
例5 ページング用のクエリを構築するためのより安全な方法
settype($order, 'integer');
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
// フォーマット文字列の%dに注意してください。%sを使用しても意味がありません。
$query = sprintf("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %d;",
$offset);
これらのケースにおいて、スクリプトまたはサポートされている場合は データベース自体でクエリのログをとることが有益です。 明らかにログは破壊的な行為を防止することはできませんが、攻撃され たアプリケーションを追跡する際には有効です。ログ自体は有益ではあ りませんが、含まれている情報は有益です。通常、より詳細なログをと る方が良いでしょう。