読者です 読者をやめる 読者になる 読者になる

No Programming, No Life

新しいNPNLです。http://d.hatena.ne.jp/fumokmm/ から引っ越してきました。

SQLインジェクションのまとめ

SQL 情報処理技術者試験 セキュリティ

ケース1 SQLインジェクション攻撃による不正ログイン 〜シングルクォート挿入〜

まずは、SQLインジェクションの最も基本的な攻撃パターンであるシングルクォート「'」を使った手法のご紹介から。

攻撃手法

以下のようなテーブルがあるとする。



[テーブル名:ユーザマスタ]

ユーザID パスワード
admin admin
fumo hoge
cynthia piyo
rose fuga

SELECT * FROM ユーザマスタ WHERE ユーザID = '{$userId}' AND パスワード = '{$passwd}'


ユーザによって入力されたユーザID、パスワードをそれぞれ{$userId}、{$passwd}に代入し、上記SQLにてその存在が確認できればログインできるWEBページがある。
悪意あるユーザがユーザID「fumo」としてパスワードを入力せずにログインを試みる際に入力するパラメータは、以下のようになる。

$userId
「fumo」
$passwd
「' OR 'A' = 'A」


結果、生成されるSQL文は以下のようなものになる。

SELECT * FROM ユーザマスタ WHERE ユーザID = 'fumo' AND パスワード = '' OR 'A' = 'A'


パラメータとして入力値「'」を含ませることで、パスワードに関するWHERE句の条件式を一旦終端させ、次にORを含ませると、「'A' = 'A'」という恒真式がORの対象になる。
よって、WHERE句全体が常に真となり、パスワードを入力せずにユーザID「fumo」としてログインすることが可能となる。


ちなみに、「'A' = 'A」の部分は「'1' = '1」でも「'' = '」でもよい。
OR以降が常に真となるような恒真式が記述されることが重要である。

ケース2 SQLインジェクション攻撃による任意のSQL文実行 〜セミコロンで分割〜

さてお次は、ケース1の応用例として任意のSQL文を実行させる手法をご紹介する。ここではレコードの全件削除を例にとって説明する。

攻撃手法

SQL文におけるセミコロン「;」は、ステートメントを分割するデリミタである。この「;」を使って複数のSQL文を連結させることができる。
利用するテーブルはケース1と同じものとして、今回は以下のようにパラメータを入力してみる。

$userId
「 (何も入力しない) 」
$passwd
「'; DELETE FROM ユーザマスタ WHERE 'A' = 'A」


さて、どんなSQLが生成されるでしょうか。

SELECT * FROM ユーザマスタ WHERE ユーザID = '' AND パスワード = ''; DELETE FROM ユーザマスタ WHERE 'A' = 'A'


となる。これは以下のように二つのSQL文が続けて実行されることを意味する。

SELECT * FROM ユーザマスタ WHERE ユーザID = '' AND パスワード = '';
DELETE FROM ユーザマスタ WHERE 'A' = 'A'


一つ目のSELECT文は実行されるが無害である。
このようにパラメータに「;」を含ませることで、WHERE句を一旦終端させ、全件削除のDELETE文をさせることができる。
なお、「'」の数をあわせるために、ケース1と同様に「'A' = 'A'」という恒真式となるようにして、DELETE文のWHERE区を無効化している。

ケース3 SQLインジェクション攻撃による任意のSQL文実行 〜コメントアウトで無効化〜

更に応用例として、コメントアウト「--」を使用してパスワード入力部分のSQLを無効化する手法をご紹介する。

攻撃手法

利用するテーブルはケース1と同じものとして、以下のようにパラメータを入力してみる。

$userId
「'; DELETE FROM ユーザマスタ WHERE 'A' = 'A' --」
$passwd
「 (何も入力しない) 」


これで、以下のようなSQL文が実行される。

SELECT * FROM ユーザマスタ WHERE ユーザID = '';
DELETE FROM ユーザマスタ WHERE 'A' = 'A' --' AND パスワード = ''


「--」以下の「' AND パスワード = ''」の部分はコメントアウトされるので無効となり、結果としてケース2と同様に全件削除のSQL文として機能する。

ケース4 SQLインジェクション攻撃によるパスワード変更 〜シングルクォート挿入〜

さて、今度はデータを変更するパターンの攻撃についてご紹介する。

攻撃手法

利用するテーブルはケース1と同じものとして、ユーザIDと現在のパスワードおよび新しいパスワードを受け取り、データベースの更新が行われる例で考えてみる。

UPDATE ユーザマスタ SET パスワード = '{$newPasswd}' WHERE ユーザID = '{$userId}' AND パスワード = '{$oldPasswd}'


ユーザによって入力されたユーザID、旧パスワード、新パスワードをそれぞれ{$userId}、{$oldPasswd}、{$newPasswd}に代入し、上記SQLにてその存在が確認し、合致すればパスワードの変更が完了する。
悪意あるユーザ「rose」が、自身のパスワード「fuga」とユーザ「admin」のパスワードを「malice」*1に書き換えを試みる際に入力するパラメータは、以下のようになる。

$userId
「rose」
$oldPasswd
「fuga' OR ユーザID = 'admin」
$newPasswd
「malice」


結果、生成されるSQL文は以下のようなものになる。

UPDATE ユーザマスタ SET パスワード = 'malice' WHERE ユーザID = 'rose' AND パスワード = 'fuga' OR ユーザID = 'admin'


ユーザIDが「rose」でパスワードが「fuga」であるレコード、またはユーザIDが「admin」であるレコードを更新対象としてしまう。
つまり、「admin」のパスワードが分からなくても「admin」のパスワードを変更することが可能となる。
更新後のテーブルの様子は以下のようになる



[テーブル名:ユーザマスタ(更新後)]

ユーザID パスワード
admin malice
fumo hoge
cynthia piyo
rose malice

ケース5 SQLインジェクション攻撃によるパスワード変更 〜コメントアウトで無効化〜

さて、今度はケース4の応用で全データを変更するパターンの攻撃についてご紹介する。

攻撃手法

利用するテーブル、条件はケース4と同様とし、悪意あるユーザが全ユーザのパスワードを「malice」に書き換えを試みる際に入力するパラメータは、以下のようになる。

$userId
「' OR 'A' = 'A' --」
$oldPasswd
「 (何も入力しない) 」
$newPasswd
「malice」


結果、生成されるSQL文は以下のようなものになる。

UPDATE ユーザマスタ SET パスワード = 'malice' WHERE ユーザID = '' OR 'A' = 'A' --' AND パスワード = ''


ケース3と同様に「'A' = 'A'」の恒真式にしつつ、それ以降をコメントアウトしてWHERE句を無効化する。
結果、全員のパスワードが「malice」に変更されてしまう。
更新後のテーブルの様子は以下のようになる



[テーブル名:ユーザマスタ(更新後)]

ユーザID パスワード
admin malice
fumo malice
cynthia malice
rose malice

ケース1〜5の回避方法・対策

今まで見てきたSQLインジェクションの攻撃に対する回避方法・対策についてご紹介する。

特殊文字のエスケープ(サニタイジング)

SQL文に含まれたパラメータに対し、危険な文字列を検出し、特殊文字として認識させないように適切に置換・除去を行うエスケープ処理(サニタイジング)で対応する。
エスケープ対象は処理系によって異なるが、一般的には次の通りである。


特殊文字 エスケープ処理
' ''
; 受理しない
その他の特殊文字 エスケープ文字(\)を前に付加する


「'」→「''」とシングルクォートを二つ重ねてエスケープすることで、文字列を囲むシングルクォートでなはく、シングルクォートを文字列定数として扱うようにする。


このようにSQLインジェクション特殊文字を悪用することによって行われるので、入力チェックとサニタイジングが有効である。
ただし、データベースエンジンごとにSQLが独自拡張されていて、サニタイジングが必要な特殊文字が変わってくる。データベースエンジンごとに機能を持つ特殊文字をリストアップし、正しくエスケープ処理を実装する必要がある。

SQL文の組み立てにバインド機構を使用する(準備済みSQLの利用)

あらかじめコンパイルしたSQL文をデータベース側に持たせておき、値だけをデータベースに渡して実行させるためのDBMSの機能である。
具体的にはプレースホルダと呼ばれる一般的な特殊文字を使用してSQL文の雛形を用意しておき、後で実際の値(変数)を割り当ててSQL文を完成させる。
バインドメカニズム(バインド機構)、準備済みSQL文、プリペアードステートメント、パラメータクエリ(クエリの一部が変数のようになっており、その部分は実行時にバインドするクエリ)、ストアードプロシージャとも呼ばれる。


これにより、入力データは数値定数や文字列定数として組み込まれるため、シングルクォートなどの特殊文字は、エスケープ処理せずとも強制的にただのパラメータ文字として認識され、SQL文の意味も改変される危険性がなくなる。
また、入力前にコンパイルされているので、SQLインジェクションによってSQL文を変更することが不可能になる。
特殊文字のエスケープよりも有効な手法であり、SQLインジェクション対策の最も有効的な方法である。

ケース6 セカンドオーダSQLインジェクション

最後に入力データに適切にサニタイジング処理を施している場合でも攻撃が可能となるセカンドオーダSQLインジェクションについてご紹介しよう。

攻撃手法

例によってケース1と同じテーブルを利用し、「(1)ユーザ登録機能」 と「(2)パスワード変更機能」があるWebシステムを考える。
「(1)ユーザ登録機能」では「ユーザID」と「パスワード」を入力して新たにユーザを登録する。
「(2)パスワード変更機能」ではログイン後に、ログインしているユーザのパスワードを変更するもので、「旧パスワード」および「新パスワード」を入力してパスワードの変更を行う。


この、「パスワード変更機能」は以下のように二つのSQL文によって実行されるものとする。

SELECT * FROM ユーザマスタ WHERE ユーザID = '{$userId}' AND パスワード = '{$oldPasswd}'
UPDATE ユーザマスタ SET パスワード = '{$newPasswd}' WHERE ユーザID = '取得したユーザID'


二つ目のUPDATE文で指定するユーザIDは一つ目のSQL文で取得したユーザIDを用いる。
なお、適切にサニタイジング処理が行われるものとする。


セカンドオーダSQLインジェクションは、このように一度挿入されたデータが再利用されるような機能を利用して、間接的に攻撃を行う手法である。
具体的には以下のようにパラメータを入力すればよい。


(1)ユーザ登録
ユーザID=「admin' --」、パスワード=「passwd」として新規登録する。
するとシングルクォートを正しくエスケープした以下のようなINSERT文によりユーザが登録される。

INSERT INTO ユーザマスタ VALUES ( 'admin'' --', 'passwd' )

結果、ユーザID「admin' --」としてユーザ登録される。



[テーブル名:ユーザマスタ(登録後)]

ユーザID パスワード
admin admin
fumo hoge
cynthia piyo
rose fuga
admin' -- passwd


(2)パスワード更新
次に「admin' --」のパスワードを変更するために、例えば旧パスワード「passwd」を新パスワード「passwd2」を入力する。
この入力により、以下SELECT文となり、ユーザIDをを引き当てる。

SELECT * FROM ユーザマスタ WHERE ユーザID = 'admin'' --' AND パスワード = 'passwd'


外部から受け取る値は新パスワードだけなので、新パスワードのみエスケープ処理される。
ユーザIDはエスケープされずにデータベースに格納された値「admin' --」がそのまま用いられる。

UPDATE ユーザマスタ SET パスワード = 'passwd2' WHERE ユーザID = 'admin' --''


このSQL文の末尾の「--''」の部分は無視されるため、以下の「admin」のパスワードを「passwd2」に変更するSQL文が実行されることとなる。

UPDATE ユーザマスタ SET パスワード = 'passwd2' WHERE ユーザID = 'admin'


悪意ある攻撃者はこの手法により、管理者「admin」のパスワードを自由に書き換えてしまうことが可能となる。

回避方法

一般のSQLインジェクション対策としては、バインド機構+サニタイジングの組み合わせが採用されているが、セカンドオーダSQLインジェクションはこれらの対策を潜り抜けて実行される。
これは、SQLインジェクションを防ぐのに、外部から受け取るパラメータのみエスケープしているときに発生する。


この攻撃も踏まえた正しいSQLインジェクション対策としては、文字列連結でSQL文を構築しようとするすべての箇所でエスケープ処理を適切に行う必要がある。
データベースに格納済みの値ももれなく行う必要がある。

*1:悪意、敵意、害意という意味。