SQL Injectionの仕組みと対策


はじめに

最近Webアプリケーションの脆弱性に攻撃側の関心が高まり、数多くのWebアプリケーションのセキュリティホールによる情報漏えい事件が発生しています。

その中でもSQL Injectionは静岡新聞やワコール等で話題となり、大いに注目を集めるようになりました。ところが、未だに問題の本質を十分に理解していないため、対策漏れにより、情報漏えいが発生するなどの事件を起こしています。

そこで、この文書では、SQL Injectionの仕組みと対策について解説していきます。


SQL Injectionの仕組み

SQL Injectionとは、アプリケーションにおいてRDBで任意の値を検索条件、更新内容等に使用する場合に、任意のSQL文を実行させてしまう手法です。これは、Webアプリケーションに限らず、RDBを使用しているクライアント・サーバー型のアプリケーションでも発生する可能性があります。

この攻撃手法を悪用することで、ユーザーは開発者、運用管理者が予期しないようなSQL文を発行し、RDBを自由に操作することが出来るようになります。つまり、データの削除、追加、改ざん、取得といったことから、RDBMSによってはDBサーバーでOSコマンドすら実行できるようになってしまいます。

特に昨今のWebサービスでは背後にRDBがおかれ、個人情報など重要な情報がRDBに格納されていることが非常に多くなっています。このため、SQL Injectionに対策することは重要です。

では、何故SQL Injectionというものが発生してしまうのでしょうか?それを見ていきます。

多くのアプリケーションでは、情報の検索、追加、削除、更新といった処理を行う際にSQL文を使ってRDBを操作します。例えば、検索を行うSQL文は以下のようになります。

例1:
SELECT userID,userName from userTable where userID = 20060001;

userTableのテーブル構造

項目名

属性

userID

数値項目

userName

文字項目

上記SQL文では、テーブルuserTableからuserIDが「20060001」のものを検索し、その条件に合うuserIDとuserNameを取得します。この「20060001」を変更することで、他の検索条件、例えば、userIDが「20060002」といったレコードのデータを取得することができるようになります。

例2:
SELECT userID,userName from userTable where userID = 20060002;

アプリケーションではユーザがどのような検索条件が必要となるか分からないので、「20060001」の部分を固定化せずに変数で条件を変更することが一般的です。そのときのプログラム例が以下の例3です。

例3:

10

渡された[データ1]をuserに代入する

20

sql = "SELECT userID,userName from userTable where userID = " + user + ";"

30

[SQL文「sql」の実行]

上記例3で[データ1]の内容が適切な値かどうかの判断がなされていない場合、SQL Injectionが発生します。

例えば、userIDは数値項目としてテーブルに登録されていますが、データ1が数値だけのデータかのチェックがなされていなかった場合、データ1に文字が入ってきます。

例4:

10

渡された"user1"をuserに代入する                   // user1が検索条件として入力された

20

sql = "SELECT userID,userName from userTable where userID = " + user + ";"

30

[SQL文「sql」の実行]

そうすると20行で変数sqlの内容は以下のようになります。

例5:
SELECT userID,userName from userTable where userID = user1;

これを実行すると、数値項目であるuserIDを文字データ「user1」で検索しようとするので、SQLエラーが発生します。ところが、「user1」を「1 or 1 = 1 」という文字列に変更すると、20行で変数sqlの内容は以下のようになります。

例6:
SELECT userID,userName from userTable where userID = 1 or 1 = 1;

このSQL文を実行すると、1=1という条件が成り立つので、userTableの全てのレコードが出力されます。この結果、本来取得できてはいけないデータを取得することが可能となります。これが、SQL Injectionの最も簡単な仕組みです。

ワコール等ではこういった手法を使って本来見えないはずのデータを見ることが可能でした。

さてここで、userIDは数値項目なので、数値しか取らないことをチェックしていればこのタイプのSQL Injectionを防げるでしょうか?そこで、数値項目のチェックをSQL文を実行する前に入れて考えてみます。

例7:

10

渡された" 1 or 1 = 1 or 1 = 0"をuserに代入する   // 「 1 or 1 = 1 or 1 = 0」が検索条件として入力された

15

if (userが数値以外か?) then エラー処理

20

sql = "SELECT userID,userName from userTable where userID = " + user + ";"

30

[SQL文「sql」の実行]

この場合、15行でuserが数値以外が入っているため、エラー処理が行われます。この結果予想しないSQL文(全てのレコードを検索結果として返すSQL文)が実行されることを防ぐことが出来ます。

このように入力されたデータを適切にチェックを行うことでSQL Injectionを防ぐことが可能です。


SQL Injection対策

では、実際にどのような対策をすればよいのでしょうか?それをここでは見ていきます。

まず、もっともよい対策は言語、フレームワークが備えているバインドメカニズムを利用する方法です。これを用いると言語やフレームワークが自動的に型をチェックし、不適切な文字が含まれている場合にはエスケープ処理を行ってくれます(但し、全てがこれで安全になるわけではありません。DBで使用する文字コードとフレームワークで使用している文字コードが異なる場合、対策にならないことがあります。DBで使用する文字コードとフレームワークで使用する文字コードは同一にする必要があります。)。

しかし、そのような仕組みがない、若しくは何らかの理由で使用できない場合、不適切な文字をエスケープ処理を行う必要があります。また、WAF(Web Application Firewall)を使用してアプリケーションを守る場合はやはり、不適切な文字が何かを理解していないと防御することが出来ません。

そのため、一つずつケースを見ていきましょう。


1.数値項目を検索条件にする場合

hoeが数値項目の場合、下記のようなSQL文が利用されます。

SELECT * FROM hoge WHERE hoe = [データ];
DELETE FROM hoge WHERE hoe = [データ];
UPDATE hoge SET hau = 'hauu' WHERE hoe = [データ];

これらの場合、項目hoeが数値項目であるため、正常な検索条件の場合、[データ]に数値以外が入ることはありません。したがって、検索条件として数値以外が入ってくるようなことがあれば、明らかにエラーとしてしまってよいはずです。

この為、このケースでは[データ]が数値のみで構成されていることを確認します。


2.数値項目のデータを更新する場合

hoeが数値項目の場合、下記のようなSQL文が利用されます。

INSERT INTO hoge (hau,hoe) VALUE ('hauu',[データ]);
UPDATE hoge SET hoe = [データ] WHERE hau = 'hauu';

これらの場合も、項目hoeが数値項目であるため、代入できるものは数値のみとなります。したがって、このケースも[データ]に数値以外が入ってくることがあれば、明らかにエラーとしてしまっても問題ないはずです。

この為、このケースも1.と同様に数値のみで[データ]が構成されていることを確認します。


3.文字項目を検索条件にする場合

hoeが文字項目の場合、下記のようなSQL文が利用されます。

SELECT * FROM hoge WHERE hoe = '[データ]';
SELECT * FROM hoge WHERE hoe = "[データ]";
DELETE FROM hoge WHERE hoe = '[データ]';
DELETE FROM hoge WHERE hoe = "[データ]";
UPDATE hoge SET hau = 'hauu' WHERE hoe = '[データ]';
UPDATE hoge SET hau = 'hauu' WHERE hoe = "[データ]";

この場合[データ]にどのような文字が入ってくるかわからないことがほとんどです。SQL文では文字列の区切りに「"」、「'」が使用されます。このような文字がデータに入ってくることもあります。例えば、[データ]の内容が「aaa' or 'b'='b」であった場合、,SQL文は以下のようになります。

' SELECT * FROM hoge WHERE hoe = 'aaa' or 'a' = 'a';

本来であれば、hoeが「aaa' or 'b' = 'b」であるものを検索したいのですが、実際に検索されるのはテーブルhogeにある全てのデータを検索結果として返してしまいます。

このようにまったく意図しないSQL文が出来てしまいます。

本来望む結果を得るためには[データ]をエスケープ処理する必要があります。SQL文では「'」を「''」、「"」を「""」で表します。このため上記例では「aaa'' or ''b''=''b」というように変換する必要があります。この結果'は以下のようになります。

'' SELECT * FROM hoge WHERE hoe = 'aaa'' or ''a'' = ''a';

この結果本来望んでいた「aaa' or 'b' = 'b」を得ることが出来ます。

上記以外にも検索条件をあいまいにしたい場合などでは「=」の代わりに「LIKE」を使用しますが、このとき「%」や「_」をワイルドカードとして使用するのでこういった文字もエスケープ処理をする必要があります。


4.文字項目のデータを更新する場合

hoeが文字項目の場合、下記のようなSQL文が利用されます。

INSERT INTO hoge (hau,hoe) VALUE ('hauu','[データ]');
INSERT INTO hoge (hau,hoe) VALUE ("hauu","[データ]");
UPDATE hoge SET hoe = '[データ]' WHERE hau = 'hauu';
UPDATE hoge SET hoe = "[データ]" WHERE hau = "hauu";

この場合も3と同様に[データ]に文字列の区切り文字「"」、「'」が含まれていたとき、予想もしない動きをすることがあります。
例えば、の場合に[データ]が「aaa';--」であったとすると、以下のようなSQL文になります。

' UPDATE hoge SET hoe = 'aaa';--' WHERE hau = 'hauu';

SQL文では「--」以降はコメントとしてみなされるため、'はhogeテーブルの全ての項目hoeが「aaa」に変換されてしまいます。
したがって、「"」、「'」だけでなく、「--」なども気をつける必要があります。


5.データがStored Procedureの引数である場合

処理を少しでも早くする、複数のSQL文が一度に使われる等の場合、Stored Procedureを使う場合があります。このような場合、以下のようなSQL文が用いられることになります。

exec hoehoe [データ];
exec henyo "[データ]";
exec punyu '[データ]';

これらも4までと同様に適切なチェックやエスケープ処理がなされていないと予期しないSQL文を実行されてしまうことになります。例えば、,[データ]が「1;delete from hoge;」であった場合、以下のようなSQL文になります。

' exec hoehoe 1;delete from hoge;

この結果、Stored Procedure「hoehoe」が実行された後、テーブルhogeのレコードが全て削除されてしまいます。
このように、適切にデータの数値かどうかのチェックがなされていないと、作成者が意図しないSQL文を実行してしまうことがあります。

この為、データのチェックやエスケープ処理をしてからSQL文で使用することが必須となります。但し、危険な文字が含まれていないかどうかのチェックやエスケープ処理の際に危険な文字を除外又は無害化するようなロジックで行っていると、危険な文字のチェック項目に漏れがあった場合、作成者が意図しないSQL文を実行してしまう可能性があります。そこで、可能な限り、使用できる文字以外が含まれていないこと(例えば、数値項目のデータに英文字や「+」、「-」といった記号が含まれていないことや、文字項目で英文字しか入力されないと分かっている場合に記号が含まれていないこと等)を確認するようにしたほうがより安全になります。


SQL文で危険な文字列

具体的に問題がある文字列として以下のようなものがあります。但しこれらはあくまで基本的なものであり、各RDBMSに依存する危険な文字列が存在します。

危険な文字列

文字列の持つ意味

"

文字列の区切り

'

文字列の区切り

+

数値演算

-

数値演算

*

数値演算

/

数値演算

%

ワイルドカード

;

SQL文の区切り

--

コメントの開始

( )

INSERT文で値の代入などに使用

\

エスケープ文字として使用


第1版 2006/3/15 作成
第2版 2006/12/15 文字コードについての注意書きを追加
            危険な文字に「\」を追加 第3版 2007/4/4 色々修正
Ikeda Masakazu
ゲストブックへ
Secure Coding に入ろう!
FreeML  メールアドレス