DVWAでブラインドSQLインジェクションを試す
つづいた。タイトルのまんまです。
設定は以前書いた記事のまま、Level:Highで行います。
DVWAの「SQL Injection (Blind)」を選択すると、「SQL Injection」の時と同じようなページに飛ぶ。
以前と違う点は、入力idがデータベース内に存在するかしないかのみが返されること。
id=1 (データベースに存在する値)の場合
id=wakame (存在しない値)の場合
とりあえず、攻撃できるか試す。いつものアレを入力。
「存在する」と返ってくる。脆弱性はある......が、出力が真偽値だけでは、直接データベースの情報を取得できない。
ある式の真偽値の判定だけはできることを利用する。たとえはこんなものを注入。
1' AND ORD(MID(DATABASE(), 1, 1)) > 64; #
実行されるSQLは
SELECT first_name, last_name FROM users WHERE user_id = '1' AND ORD(MID(DATABASE(), 1, 1)) > 64 #' LIMIT 1
あえて日本語に訳すると「現在利用中のデータベース名の1文字目は'@'より大きいか」という問い合わせになる。
実行してみると
「存在する」と応答、すなわちWebアプリケーションが利用しているデータベースの1文字目はASCII文字列の@以降の文字。
ASCII文字列を検索する文字として、2分探索と同じ要領で範囲を狭めていく。
アプリに渡した値とその結果を列挙すると
1' AND ORD(MID(DATABASE(), 1, 1)) > 96; # ...True 1' AND ORD(MID(DATABASE(), 1, 1)) > 112; # ...False 1' AND ORD(MID(DATABASE(), 1, 1)) > 104; # ...False 1' AND ORD(MID(DATABASE(), 1, 1)) > 100; # ...False 1' AND ORD(MID(DATABASE(), 1, 1)) > 98; # ...True 1' AND ORD(MID(DATABASE(), 1, 1)) > 99; # ...True
したがって、データベースの1文字目はASCIIコードの100、すなわち'd'となる。 同様の方法で2文字目, 3文字目...と試すと'dvwa'となり、真偽値のみでデータベース名を得ることができる。
あとはこのデータベース名とINFORMATION_SCHEMAの情報を用いることで、テーブル名、カラム名、...と芋づる式にdvwa内の全データを抜き取ることができる。 人力ではしんどいでゴザル。
このように、間接的な方法でデータベースの情報を得るSQL注入攻撃を「ブラインドSQLインジェクション」と呼ぶらしい。 今回のように、真偽値判定の結果を直接得られるものはBoolean-based Blind SQL Injectionと呼ばれる。 他に、真偽値判定が直接できない場合には、ifとsleepする関数を組み合わせ、応答時間で真偽値判定を行うTime-based Blind SQL Injection など、いろいろ種類があるみたい。
一通り理解したので、Level:Impossibleなソースコードと比較する。
まずはHigh
相変わらずプリペアドステートメントではなく、文字列結合でSQLを組み立てている。 cookie経由でデータを受け渡ししているところも相変わらず。表面上なにも見えないから安全という考えでコーディングしているのだろうか......
エラー度には1/6の確率で2~4秒sleepをかけているが、仮にTime-basedしか使えない状況でも成功時に4秒+各種遅延より長いスリープをかける方法なら理論上可能っぽい? 根本解決にはなっていない上、正常な利用者が割りを食う羽目になる。
Impossibleも見る
SQL Injection の時のImpossibleなコード同様、PDOのプリペアドステートメントとCSRFトークンを用いている。 さらに11行目で入力されるデータの仮定をチェックし、18行目では得られるデータ数の確認もしっかり行っている(Highでは > による値返却の有無チェックであった)。 めんどくさがらず、可能な限りのバリデーションを行ってる。あまり意味を成さないsleepも消えてる。
名前から威圧感を感じたが、対策自体は普通のインジェクションと同様、コードの入る可能性がある部分を塞げば良い。 今回の攻撃で理解したことは、1bitでも情報を得られる抜け穴があれば、色々とデータを抜き出せてしまうということ。 マジ怖いっす。
つぎ、いってみよー。