技術者ブログ
クラウド型WAF「Scutum(スキュータム)」の開発者/エンジニアによるブログです。
金床“Kanatoko”をはじめとする株式会社ビットフォレストの技術チームが、“WAFを支える技術”をテーマに幅広く、不定期に更新中!

眠らないTime-Based SQL Injection
はじめに
SQLインジェクションがあるにもかかわらず、レスポンスボディの内容やステータスコードに変化がない、いわゆる「完全なブラインド」の状態でも、sleep系の関数等を使うことによってデータが盗まれる可能性があります。この攻撃はTime-Based SQL Injectionと呼ばれており、sleep系の関数を使うことから「実際に攻撃者にとって有用な情報を盗み出すには時間がかかる」というのが定説のようです。
しかし実はTime-Based SQL Injectionを高速に実行し、いわゆる「普通の」ブラインドな状態(DBのエラーメッセージは読めないが、ステータスコードの変化等でSQLがエラーになったかどうかを判別できる状態)と同じ速さでデータを取得することが可能です。本エントリではこの手法(眠らないTime-Based SQL Injection)についての説明を行います。
筆者は偶然これを思いついたのですが、もしかしたら既知の手法かもしれません。軽くあちこち検索したり本を読んだ感じでは、見つけることができませんでした(*1)。
基本は二分探索
「眠らないTime-Based SQL Injection」も基本的な手順は一般的なものとまったく同じで、1文字ずつ二分探索します。Time-Based SQL Injectionで二分探索する場合、通常だと50%の確率でsleepが実施されるため、レスポンスが数秒ほど遅れることになり、最終的に必要となる数十〜数百のリクエスト・レスポンスのやりとり全体ではかなり時間がかかることになります。二分探索ではなく線形探索で(sleepする確率を下げて)探索する方法もありますが、リクエスト数が増えるため、こちらもやはり遅いです。
両方に賭ける
sleepを使う二分探索は、ルーレットで赤か黒かのどちらかに賭けているのと似ています。50%の確率で当たるゲームで、当たったときはsleepせずにすみます。しかし外れたら、ペナルティとして数秒待つようなイメージです。
しかしSQLインジェクションする対象はウェブアプリケーションなので、殆どの場合並列で処理を受け付けます。そのため赤と黒の両方に賭けてしまえばよいのです。具体的には、例えば
- 1文字目のascii値が79より小さいならsleepする
- 1文字目のascii値が79以上ならsleepする
するとどちらかは必ず当たるので、sleepすることなく早めにレスポンスが返ってきます。このレスポンスが返ってきた時点であなたは結果を知ることができるので、もう一方のsleepしているHTTPリクエストを処理している通信は切断してしまい、捨ててしまいます。そしてすぐさま次の探索に取り掛かります。
この場合、サーバ側ではsleepが実行されていますが、クライアント側は実質sleepせずに済ませることになります。
実証コード
筆者が手元の環境(Tomcat+JSP+PostgreSQL)でこちらのPoCで試してみたところ、下記のように1つのメールアドレスを取得するのに935msとなりました。従来想定されていたTime-Based SQL Injectionに比べ異次元の速さだと言えるでしょう。
2つのHTTPリクエストを同時に送る処理は91行目です。2つのスレッドそれぞれで処理されます。先にHTTPレスポンスを受け取った側が、2つあるソケットの両方をcloseしてしまいます。
PoCの出力は次のようになります。
1:not less than:1 32 / 126 1:not less than:79 79 / 126 1:not less than:102 102 / 126 1:less than:114 102 / 113 1:less than:107 102 / 106 1:not less than:104 104 / 106 1:not less than:105 105 / 106 1:not less than:106 ========== 1:j ========== 2:not less than:1 32 / 126 2:not less than:79 79 / 126 2:not less than:102 102 / 126 2:less than:114 102 / 113 2:not less than:107 107 / 113 2:not less than:110 110 / 113 2:not less than:111 111 / 113 2:less than:112 ========== 2:o (中略) ========== 15:not less than:1 32 / 126 15:not less than:79 79 / 126 15:not less than:102 102 / 126 15:less than:114 102 / 113 15:not less than:107 107 / 113 15:less than:110 107 / 109 15:not less than:108 108 / 109 15:not less than:109 ========== 15:m ========== 16:less than:1 took:935 joe@example.com取得対象の文字列はアルファベット主体の、ASCII領域の32〜126の文字から構成されているという前提のPoCとなっています。ここでは1レコード目のメールアドレスである「joe@example.com」を取得しています。また、PoCのSQLを構築する行は、悪用されないよう一部省略しています。
まとめ
今回は赤と黒両方に賭けるような、ちょっとズルい発想でTime-Based SQL Injectionを高速化する手法の研究成果について共有しました。Javaのように並列プログラミングが自然に可能であるプログラミング言語を使っていると、このような「並列アルゴリズム」もふとした瞬間にひらめくことがあります。みなさん是非Javaを使ってみてください。
サーバ側にはsleepするプロセスやスレッド等が残る可能性もあるので連続して大量のデータを取得できるかどうかはサーバ側の並列度に依存しますが、昨今のクラウド化されスケールアウト可能なウェブアプリケーションでは、多くの場合この攻撃を正常に処理してしまうのではないかと思います。
このように実用性については環境毎の課題もありそうですが、本エントリではコンセプトの紹介に焦点をしぼり、考察は以上とします。
*1: こちらがやや似ていますが、探索速度の高速化を目的とはしていないようです。