SQLインジェクション
その前に、やはりSQLインジェクションについて説明が必要かもしれない。SQLインジェクション攻撃は以前から存在してはいたが、特に2005年頃から猛威を振いはじめ、2010年もその衰えは見せてはいない。Gumblar(ガンブラー)と並ぶウェブサイトを経由した攻撃の2大トレンドといってもよいだろう。「SQLインジェクション」と検索すれば、もっと詳細な情報のサイトにたどり着くことはできると思うが、念のためこのSQLインジェクションの仕組みを簡単に説明すると、
商品検索のWEBサイトがあったとしよう。アプリケーションは、製品を検索する画面なので当然、商品名や商品コードなどの文字が入ってくるものだという前提で作成されている。そこで、上記のようなSQLを入れてみて検索したらどうだろう。すると、本来実行されるべきSQLを強制的に書き換え、顧客情報を検索するSQLを注入(インジェクト)されてしまった。これが、SQLインジェクションである。通常のWEBサイトへのHTTPアクセスからSQLを操作し、任意のSQLを実行し、データの搾取や改ざんを行うことができてしまうのだ。実際にサイト改ざんやクレジットカード情報を含む個人情報を抜き取られてしまった事件が多く発生している。
このSQLインジェクション攻撃が成功するには、以下の二つの条件を満たす必要がある。
- WEBサイトにアプリケーションの脆弱性がある
- データベースに格納したデータを用いてWEBページを動的に生成するWEBサイトである
上記の例の場合、アプリケーションには致命的な脆弱性がある。SQLの組み立て方に問題があるのだ。最も基本的な例を見てみよう。
SELECT 列1, 列2, 列3 FROM 製品情報 WHERE NAME = ' + 検索フォーム文字 + "'"
というように単純にユーザーの入力した値を文字列連結してSQLを組み立てている。
ユーザーは必ずしも期待した文字列を入力してくれるわけでないので、この検索フォーム文字に' UNION SELECT列1, 列2, 列3 FROM 顧客情報 - と入力されたとすると、
SELECT列1, 列2, 列3 FROM 製品情報 WHERE NAME = '' UNION SELECT列1, 列2, 列3 from 顧客情報 -- '
製品情報表を検索しているSELECT分に、顧客情報表を検索するSQLがUNION ALLによってくっつけられてしまい、SQLが成立し実行されてしまうのである。(※データベースの種類によって攻撃が成功するSQLは異なる)。これは、あくまでも一例であって攻撃方法は多種多様に存在する。WEBサイトにインジェクション可能な入口が見つかれば、そこから様々なSQLを注入しながらデータ・ディクショナリの情報を検索し、クレジットカード情報や個人情報などの換金性の高い情報が入っている表を探り当てられてしまうかもしれない。また、攻撃者はWEBサイトを一つ一つ攻撃をしているわけでなく、自動化されたツールを使って世界中のWEBサイトをランダムにアクセスし、攻撃可能なWEBサイトの目星をつけてからより詳細な攻撃が行われる。ツールさえ手に入れば、だれにでも実現できてしまうことが被害の増加を招いていると言えよう。
このようなSQLインジェクションの対策は、アプリケーションの脆弱性をなくすことである。
SQLにとって特別な意味を持つ記号(メタ文字)が含まれていないかをチェックすること、これは、ユーザーが入力した値を判別して排除するようにするエスケープ処理が当てはまる。(※Oracle Databaseには入力値を検証するDBMS_ASSERTパッケージが用意されている)。しかし、メタ文字はデータベースによって異なる場合があり、チェックが漏れる可能性は否定できない。
抜本的な対策としては、静的プレースホルダ(prepared Statement)を使用することである。バインド変数を使うといったほうが分かりやすいかもしれない。
例えば、SELECT * FROM PRODUCTS WHERE NAME = ' + バインド値 + "'"
バインド変数とは、上記のようなSQLを予めデータベース側に用意をしておき、SQLの実行する段階でバインド値を送信することである。
仮に、上記のSQLインジェクションを目的とした不正なSQLをバインド値に入れたとしても、SQLの文法上正しくないので、攻撃として成立しない。バインド変数はSQLの解析が最小化されることからSQLの再利用性が高くなりパフォーマンス向上のメリットが大きくなることが良く知られているが、セキュリティの面からも大きな恩恵を受けることができる。IPAに「安全なSQLの呼び出し方」として公開されている資料があるので、是非参照して欲しい。