ニシキヘビってかわいいよね、実際みたことないけど。

無職がいよかん国でプログラミングとかの備忘録を書いてます。 一日一食たまごかけごはん。

DVWAでブラインドSQLインジェクションを試す

つづいた。タイトルのまんまです。

設定は以前書いた記事のまま、Level:Highで行います。

DVWAの「SQL Injection (Blind)」を選択すると、「SQL Injection」の時と同じようなページに飛ぶ。
f:id:nyanmao:20171109193323p:plain

以前と違う点は、入力idがデータベース内に存在するかしないかのみが返されること。

id=1 (データベースに存在する値)の場合
f:id:nyanmao:20171109193342p:plain

id=wakame (存在しない値)の場合
f:id:nyanmao:20171109193356p:plain

とりあえず、攻撃できるか試す。いつものアレを入力。
f:id:nyanmao:20171109193418p:plain

「存在する」と返ってくる。脆弱性はある......が、出力が真偽値だけでは、直接データベースの情報を取得できない。

ある式の真偽値の判定だけはできることを利用する。たとえはこんなものを注入。

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文字目は'@'より大きいか」という問い合わせになる。

実行してみると
f:id:nyanmao:20171109193459p:plain

「存在する」と応答、すなわち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
f:id:nyanmao:20171109193534p:plain

相変わらずプリペアドステートメントではなく、文字列結合でSQLを組み立てている。 cookie経由でデータを受け渡ししているところも相変わらず。表面上なにも見えないから安全という考えでコーディングしているのだろうか......

エラー度には1/6の確率で2~4秒sleepをかけているが、仮にTime-basedしか使えない状況でも成功時に4秒+各種遅延より長いスリープをかける方法なら理論上可能っぽい? 根本解決にはなっていない上、正常な利用者が割りを食う羽目になる。

Impossibleも見る
f:id:nyanmao:20171109193552p:plain

SQL Injection の時のImpossibleなコード同様、PDOのプリペアドステートメントCSRFトークンを用いている。 さらに11行目で入力されるデータの仮定をチェックし、18行目では得られるデータ数の確認もしっかり行っている(Highでは > による値返却の有無チェックであった)。 めんどくさがらず、可能な限りのバリデーションを行ってる。あまり意味を成さないsleepも消えてる。

名前から威圧感を感じたが、対策自体は普通のインジェクションと同様、コードの入る可能性がある部分を塞げば良い。 今回の攻撃で理解したことは、1bitでも情報を得られる抜け穴があれば、色々とデータを抜き出せてしまうということ。 マジ怖いっす。

つぎ、いってみよー。