2015年4月16日木曜日

Time-based SQL Injectionは意外に実用的だった

このエントリでは、Time-based SQLインジェクション、すなわち時間差を利用したSQLインジェクションが意外に実用的だったという報告をします。デモ映像ありです。

はじめに

Time-based SQL Injectionという攻撃があります。これはブラインドSQLインジェクションの一種で、ある条件の場合に一定時間(例えば5秒)スリープし、そうでない時との応答時間の差で情報を盗もうというものです。1回のHTTPリクエストで1ビットの情報が得られるので、それを積み重ねることによって、いくらでも情報を盗めるはずです…理論的には。
しかし、「理屈はそうでも、時間が掛かりすぎるよね」ということで、深くは追っかけていませんでした。SQLインジェクションの検査には有効でも、悪用としての実用性はあまりないと考えていたのです。

きっかけ

きっかけは、以下のYahoo!知恵袋に以下の質問です。
SQLインジェクションについて教えて下さい
【中略】
$result = mysql_query("INSERT INTO tbl(address, mail) VALUES('$address', '$mail')", $con);
【中略】
ずばり、上記のような場合にどんな文字列でも入力可能な住所欄に何らかの文字列を入れるとSQLインジェクション攻撃は成立しますか?
http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q14140304564 より引用
ごらんのようにINSERT文の箇所にSQLインジェクション脆弱性がある場合に、どのような攻撃が成立しうるかという質問です。
私からは、ON DUPLICATE KEY UPDATE 句により、他人が登録した行を改編する攻撃を指摘したところ、「それ、副問い合わせを使えば情報を盗めるよ」と指摘をいただきました。
これで、実行されるSQL文は以下になり、mailコラムにMySQLのrootユーザのパスワードハッシュが挿入されました。
INSERT INTO tbl(address, mail) VALUES('test', (select password from mysql.user where user='root' limit 0,1)) -- -', 'dummy@example.jp')
あとは、自分のメールアドレスを表示するページで挿入した情報を表示させればOKというシナリオです。
http://blog.a-way-out.net/blog/2015/01/06/sql-injection-insert/ より引用
これは興味深い攻撃なのですが、『自分が登録した情報が自分でも閲覧できない』というサイトも多いです。たとえば、キャンペーン応募サイトなどが該当します。投稿しっぱなしで後からはその内容は閲覧できません。

一方、SQLのエラーメッセージが表示されるサイトの場合は、こちらで指摘したようにエラーメッセージから情報を漏洩させることが可能です。
問題は、どちらもできないサイトの場合です。すなわち、INSERTした情報を後から閲覧できず、SQLのエラーメッセージも表示されないキャンペーン応募サイト等において、SQLインジェクションで情報を盗む方法です。完全ブラインドとなると、Time-based SQL Injectionの出番です。

Time-based SQL Injectionの基本的な考え方

多くのSQLデータベースにおいて、一定時間スリープするという関数が用意されています。MySQLにはsleep()が、PostgreSQLにはpg_sleep()があります。これを利用して、以下のように5秒間待つという攻撃ができます。
INSERT INTO tbl(address, mail) VALUES('test', (select sleep(5))) -- ','dummy@example.jp')
これはSQLインジェクション検査で用いられる方法です。これを更にすすめて、特定条件の場合のみ5秒待つというSQL文を考えます。
INSERT INTO tbl(address, mail) VALUES('test',(select if(substr((select email from mini_bbs.members limit 0,1),1,1) = 'a',sleep(5),0))) -- ','dummy@example.jp')
これは、mini_bbs.membersテーブルのemail列、第1行1文字目が ‘a’ の場合のみ5秒待つSQL文です。これを繰り返すことにより、任意のデータを好きなだけ取り出すことができます。

高速化の試み

文字をa, b, c …と1つずつ調べるのはあまりにも不効率ということで高速化の方法を考えてみます。以前紹介したブラインドSQLインジェクションのスクリプトでは、バイナリサーチを用いて高速化を図っています。
バイナリサーチを用いると、8ビットのデータを8回程度のリクエストで特定できることになります。sleep(5)を用いる場合、1回のリクエストの平均待ち時間は2.5秒ですから、2.5秒×8で 20秒で1バイトを確定できることになります。遅いといえば遅いですが、実用にならないこともないですね。以下は、バイナリサーチを用いたSQLインジェクション攻撃(Time-based)のデモ映像です。sleep(1)を用いているので、かなり実用的です。



リニアサーチの方が速かった

ところが、その後私は勘違いをしていることに気がつきました。バイナリサーチを用いる場合、平均すると1バイトの情報を得るのに4回(8÷2)のsleepが発生します。通常のHTTPリクエストに掛かる時間を無視出来る場合(『滑車の質量は無視できるとする』と同じw)、単純なリニアサーチの方がsleepは1回だけで済むので、ずっと高速になります。以下の映像は、リニアサーチに変更したバージョンです。



文字の種類が多い場合はバイナリサーチと併用すると実用的

しかし、マルチバイトの文字を求めるような場合、リニアサーチだと数千から数十万のHTTPリクエストが必要となり、さすがにリクエストの時間を無視できなくなります。すなわち、

バイナリサーチ: O(log (N)) だが定数係数が大きい
リニアサーチ: O(N) だが定数係数が小さい

ということで、文字の種類が少ない場合はリニアサーチの方が速いものの、文字の種類が多くなってくると、バイナリサーチの方が速いという傾向があります。
実際には両者の併用が可能です。バイナリサーチである程度文字の範囲を絞っておき、その後はリニアサーチで文字を確定させるという方法です。
実は、以前紹介したブラインドSQLインジェクションのスクリプトでもこの方法を採用しているのでした。
おおまかな範囲を二分探索で絞り、そこからリニアサーチで該当文字を求めています
http://blog.tokumaru.org/2012/12/blind-sql-injection-php-exploit.html

まとめ

Time-based SQL Injection攻撃が意外に実用的であることを示しました。
結論としては、Yahoo!知恵袋でも回答した以下の内容になるかと思います。
ただし、「SQLインジェクション脆弱性はあるが攻撃はできそうもない」というケースもあり得ます。あり得ますが、思わぬ攻撃を受ける可能性もあり、その場合の被害は甚大ですので、攻撃可能性の有無に関わらず、必ずSQLインジェクション対策はしておくべきです。
そして、Time-based SQL Injectionが実用的に使えるとなると、いかなる場合でもSQLインジェクション脆弱性は許容してはならないことがはっきりした、と言えるかと思います。

0 件のコメント:

コメントを投稿

フォロワー