最近,「SQLインジェクション」の危険性について語られる機会が増えているが,SQLインジェクションの正体,その問題点,そしてそれを防ぐための方策について詳しく理解している人はまだ多くない。ここでは,SQLインジェクションとは何かを明確に定義し,どのようにして行われるかを説明し,SQLインジェクションから組織を守る方法を読者に伝えることによって,この状況を改善したい。

SQLインジェクションとは何か

 SQLインジェクションとは,アプリケーションに含まれるコーディング・エラーが原因となって引き起こされるぜい弱性,または欠陥である。SQLインジェクションは,ユーザーが入力したデータを使ってアプリケーションがSQLステートメントを作成し,それをSQL Serverに送信して実行する場合に発生する。この欠陥が及ぼす影響は,コーディング・エラーの性質によって様々である。

 具体的に言うと,その影響は,エラーがコード内のどこにあるか,そのエラーをどの程度簡単に利用できるか,そしてアプリケーションがSQL Serverに対してどのようにアクセスするかによって異なる。理論的には,SQLインジェクションはどのようなタイプのアプリケーションにも発生する可能性があるが,最も多く見られるのはWebアプリケーションである。そのためここでは,Webアプリケーションによってもたらされるリスクに絞って説明するが,このリスクはSQL Serverにアクセスするあらゆるアプリケーションに共通して存在する。

SQLインジェクションが発生する仕組み

 SQLインジェクションが可能なコードを例として,その問題が発生する仕組みを見てみよう。リスト1に示すコードは,SQLコマンドと,ユーザーが入力したLocationIDを連結してSQLステートメントを動的に作成している。LocationIDにユーザーが渡すデータは,その内容に関係なく,常にSQLステートメントの一部となり,SQL Serverによって実行されることに注意してほしい。

sSql = "SELECT LocationName FROM Locations ";
sSql = sSql + " WHERE LocationID = " + Request["LocationID"];
oCmd.CommandText = sSql;
リスト1●SQLインジェクションのぜい弱性を持つSQLステートメントの例

 次に,ユーザーがアプリケーションに渡すデータと,SQL Serverによる実行結果の例をいくつか示す。

ユーザー入力:「1」
SQLステートメント:「SELECT LocationName FROM Locations WHERE LocationID = 1」
結果:有効なSQLステートメント

ユーザー入力:「Kilroy was here」
SQLステートメント:「SELECT LocationName FROM Locations WHERE LocationID = Kilroy was here」
結果:文字列Kilroy was hereを数値に変換できないため構文エラーが発生

ユーザー入力:「1 UNION SELECT Name FROM Sysobjects」
SQLステートメント:「SELECT LocationName FROM Locations WHERE LocationID = 1 UNION SELECT Name FROM Sysobjects」
結果:ハッカーが指定したコマンドがSQL Serverで実行された

 図1は,リスト1のシナリオである。ユーザーの入力が直接SQL Serverに渡されるために,SQLステートメントが利用される可能性があることが示されている。このように,ユーザーにSQL Serverへの直接アクセスを許すことは危険である。

図1●リスト1のコードが実行されたときの処理の様子

SQLインジェクションを防ぐ方法

 SQLインジェクションを防ぐには,アプリケーションのコードを正しく記述する必要がある。アプリケーションは,ユーザーがSQL Serverに直接アクセスすることを許可してはいけない。直接アクセスを禁止する最も簡単な方法は,クエリのパラメータ化である。クエリをパラメータ化することによって,SQLコマンドと,コマンドが実行する必要があるデータを切り離せる。

 比較のため,前述のSQLステートメントを修正したバージョンを見てみよう。リスト2は,リスト1のコードをパラメータ化したものである。また,図2では,修正されたシナリオでの動作が示されている。修正されたシナリオでは,アプリケーションがSQLステートメントをSQL Serverに送信するときに,ユーザーの入力を使わない。

sSql = "SELECT * FROM Locations ";
sSql = sSql + " WHERE LocationID = @LocationID";
oCmd.CommandText = sSql;
oCmd.Parameters.Add("@LocationID", Request["LocationID"]);
リスト2●SQLインジェクションのぜい弱性がないSQLステートメントの例

図2●リスト2のコードが実行されたときの処理の様子

 代わりに,パラメータ@LocationID(type int)を使っている。SQLコマンドと@LocationIDパラメータは別々に送信されるので,ユーザーが入力したデータは,SQL Serverが実行するコマンドの一部として解釈されることがない。従って,ユーザーがLocationIDにコマンドを入力しても,そのコマンドがSQLパラメータの一部になることはなく,SQLインジェクションは成立しない。この場合もSQLエラーは返されるが,それは単純なデータ型変換エラーであり,ハッカーに利用される性質のものではない。

 このパラメータ化手法は,SQLインジェクションに対する「修正」であると一般には考えられている。この手法が100%使用されていれば,パラメータ化クエリによって完全にSQLインジェクションは防止され,SQL Serverマシンは保護される。

 しかし,長年開発に携わっている筆者の経験から言わせてもらえば,どんなに努力しても,同じ手法を100%使い続けることはできない。これはほかのほとんどの開発者についても言えることである。私たちは人間であり,人間はミスをするものだ。そして,この場合は,パラメータ化クエリを使わないというミスがSQLインジェクション攻撃につながる可能性がある。従って,SQLインジェクションからSQL Serverのセキュリティを保護するには,多層防御を実行することが重要になる。