これがなぜ脆弱性なのかという理由については、先のエントリを参照ください。情報漏洩の可能性はないのか?前述のように、任意の記事を削除することは簡単にできてしまい、これ自体重大な問題ですが、「個人情報は漏洩するのか」という点に関心を持つ方も多いことでしょう。一般論として、SQLインジェクション攻撃により情報を窃取する方法として下記があります。SELECTはこのケースでは使えません。SELECTは、脆弱性のあるSQL文がSELECT文である場合に、別のテーブルと検索条件を追加することにより、本来表示されない情報を表示させる手法です。SELECTは使えませんし、そもそも情報を表示する機能が元々ありません。エラーメッセージを使う方法は、徳丸本でも説明しているものです。同書P121には下記の問い合わせにより、「本来表示されない情報」を表示させています。integer)これは、表usersから、先頭の行を取り出し、列IDと列pwdを連結した文字列を数値にキャストしています。数値には変換できない文字が混ざっているために、以下のエラーメッセージが表示されています。7しかし、MySQLの場合、上記と同じことをしてもエラーになりません。sec)金床本では、load_file関数の「存在しないファイル名」としてエラーメッセージ中に、クエリ結果を混入させる手法が紹介されていますが、手元のMySQL5.1.63で試したところ、下記のようにエラーメッセージは表示されず、単にNULLが返りました。金床本のリファレンスとするMySQLは4.1.16なので、MySQLのバージョン違いによるものと思います。sec)それでは、MySQLではエラーメッセージからの情報漏洩はできないのかというと、寺田さんのブログ記事「MySQLのエラーメッセージ」にその方法が載っています。MySQLのextractvalue関数を使う方法です。extractvalue関数は、XPATH式に従って文字列の切り出しをするものです。以下のように使います。sec)extractvalue関数の第2引数はXPATH式を指定しますが、これが構文エラーの場合、以下のようにエラーになります。pen'これを用いて、情報を漏洩させることができます。pen'の代わりに、SQLの副問い合わせを指定すればよいことになります。'$tokumaru@example.jp'上記は、members表の1行目のemail列の値を副問い合わせで指定しており、「tokumaru@example.jp」を得ました。これを繰り返せば、任意表の、任意行、任意列を得ることができます。email列とpassword列を一度に得たければ、以下のようにします。'$tokumaru@example.jp:5baa61e4c9b'おっと、パスワードはSHA-1ハッシュ値(40文字)で格納されているのに、途中でちぎれていますね。$まであわせて32文字しか表示されないようです。これを回避するには、部分文字列の関数を用いて、何回かにわけて取得すればよいでしょう。上記をSQLインジェクション攻撃に応用しましょう。email列の取得に戻りますが、mysql_real_escape_string関数が呼ばれているので、シングルクォートは攻撃に使えません。そのため、文字列はchar関数を使って指定します。|+------------------+先の、email列を取得するSQL文は下記となります。'$tokumaru@example.jp'シングルクォートを使っていないので、mysql_real_escape_string関数を回避できますね。これを副問い合わせの形で指定してやるわけですが、攻撃対象のスクリプトはSQL文を2回呼び出しており、1回目はSQLインジェクション脆弱性がなく、当該の文書IDの投稿者でないと、2回目のSQL文が呼び出されません。このため、このチェックを回避してやる必要があります。文書IDそのまま1回目はsprintfの%d書式により、指定した文書IDは数値化され、2回目は数値化がありません。攻撃者が投稿した文書のIDが35だと仮定して、以下のように指定すればこのチェックを回避できます。文書IDとして下記を指定します。35-(攻撃用副問い合わせ)これを呼び出すと、1回目は数値化により-(引き算)以降が削除され、投稿者のチェックは通り抜けます。2回目は数値がないので、全体がSQL文として解釈されます。URLと実行結果を以下に示しましょう。http://example.jp/minibbs/delete.php?id=35-(select+extractvalue(char(60),concat(char(47,36),(select+email+from+members+limit+1+offset+0))))無事に(?)email列が表示できました。表名、列名、offset値を変更すれば、データベース内の任意の情報が取得できます。ブラインドSQLインジェクションSQLインジェクション攻撃により情報を窃取する第3の方法として、ブラインドSQLインジェクション攻撃があります。これは、クエリの結果が表示されない前提で、SQLインジェクションによりデータを盗みだす手法です。ブラインドSQLインジェクションにも複数の方法があります。OUTFILE構文が使えますが、私が試した範囲では、DELETE文にうまくはめこむことができませんでした。それに、書き出しができたとして、それを取り出す手段が別途必要です。簡単なのは、Webの公開領域に書き込むという方法ですが、MySQLの実行権限ではApacheのドキュメントルートに書き込むことはできない場合が多いと考えられます。置かれる場合、PHPのセッション形式にあわせて書き込んでおくという方法もありますが、このミニブログの場合、セッションの中味をそのまま表示する箇所がなさそうです。そこで、SQL文の問い合わせから1ビットの情報を取り出す手段を考えます。具体的には下記があります。実行の時間差を使うデータの更新・削除の成功・失敗を使うレスポンスのステータスの違い(200と500等)を使うSQL文からping等通信手段を呼び出すSQL文の実行エラーを使う(厳密なブラインドではない)実行の時間を使うには、MySQLのsleep関数が利用できます。以下は、members表の1行目、email列の1文字目が'a'であれば3秒待つ問い合わせ。実際には't'なので、直ちにnullが返ります。sec)以下は、同じく一文字目が't'であれば3秒待つ問い合わせ。't'なので約3秒待っていますね。sec)これは理論的には正しく動きますが、容易に想像されるように膨大な時間がかかるので、「最後の手段」という感じになります。副問い合わせを調節して、条件が成立すれば削除、しなければ削除しない、というSQL文も書けますが、削除できたかどうかを問い合わせるリクエストと、削除した行を「補充する」リクエストが余分に必要です。いうことで、ここでは完全なブラインドではありませんが、エラーメッセージを使うことにします。いっても、先のextractvalue関数を使うとブラインドにする意味がないので、ここではESCAPE句を使うことにします。ESCAPE句というのは、SQL文のLIKE述語において、ワイルドカード文字「%」や「_」をエスケープする文字を指定する手段です。以下は、#をエスケープ文字として指定して、%を含む行を問い合わせるSQL文です。sec)ESCAPE句に指定する文字は1文字でなければならず、それ以外はエラーになります。ESCAPEこれを利用して、1ビットの情報を得ることができます。(2文字)となり、エラーとなる例。(1文字)となり、エラーにはならない例。sec)これを何回も繰り返すことにより、テーブルの内容を盗み出すことができます...が、さすがに人手でやるのは苦行なので、スクリプトでやります。ブラインドSQLインジェクション攻撃のスクリプトをPHPで書いてみましょう。ブラインドSQLインジェクションのPHPスクリプト攻撃対象とはHTTPで接続するので、適当なHTTP接続ライブラリが必要になりますが、ここではcURL関数を使いました。前にphpMyAdminのexploitで使っていたので、試してみたかっただけです。スクリプトの中味ですが、攻撃対象は認証が必要ですので、Cookieを有効にして、IDとパスワード(攻撃用の捨てアカウント)をPOSTします。'email=sato@example.jp&password=password&save=on');curl_exec($ch);この状態で、sato@example.jpでログインしてリクエストができるようになります。攻撃で得る情報は、members表のemailとpassword列にしましょう。これらの文字種は、英数字、ドット、ハイフン、アンダースコアとして、これらの文字の集合を求めましょう。バイナリサーチの都合で文字コード順に並べておきます。}}探索のループは以下のようになります。行、列、n文字目の三重ループがあり、そのなかで、おおまかな範囲を二分探索で絞り、そこからリニアサーチで該当文字を求めています。以下、$p文字目を求める二分探索。}}check1関数は、ブラインドSQLインジェクションの一リクエストを組み立てて送信するものです。エラーメッセージがレスポンスになければTRUE}また、char_array()関数は、文字列をMySQLのchar()関数の呼び出しに変換して、シングルクォートを回避するための関数です。')';}実行結果を以下に示します。1行あたり1秒程度で求められていますから、結構実用的ですね。0m0.508s高速化するためには、クエリを並列化すればよいでしょう。外側のループをスレッド(あるいはプロセス)に割りあてて並行処理させると高速化できます。サーバーに負荷がかかり、ばれやすくなりますので、むしろゆっくり時間を掛けて攻撃した方がよいかもしれません(攻撃するなよ、絶対するなよ)。サーバーのログを見ると、以下のようなリクエストが並びます。-まとめ『よくわかるPHPの教科書』の脆弱性のあるサンプルを題材として、SQLインジェクション攻撃により個人情報を窃取する方法を説明しました。善良な皆様が攻撃方法を深く理解する必要はないわけですが、ここに示すような手法を駆使して、攻撃者は情報をとっていくという、その様子を理解いただければ幸いです。当然ながら、インターネット上のサイトにこの種の攻撃をかけると、不正アクセス禁止法その他の法令違反ですので、試験環境での実験にとどめて下さい。共有2012年12月17日月曜日セッションフィクセイション脆弱性の影響を受けやすいサイトとは最近、セッションフィクセイション脆弱性に対する関心がなぜか高まっています。同脆弱性は割合地味な脆弱性であり、話題になることはあまりありません。そこで、関心の高いこの時期に、セッションフィクセイション脆弱性の影響を受けやすいサイトについて説明し、注意を喚起したいと思います。セッションフィクセイション脆弱性とはセッションフィクセイション(セッションIDの固定化)脆弱性は、なんらかの方法で利用者(被害者)のブラウザ上でセッションIDを強制的に指定して、その後利用者がログインしたタイミングで、攻撃者が「ログイン済みのセッションID」を知ることができるという脆弱性です。「安全なウェブサイトの作り方(改訂第五版)」P17から図を引用します。図の「2.何らかの方法で自分が取得したセッションIDを利用者に送り込む」手法は、安全なウェブサイトの作り方では説明されていません。そこで、以下に「セッションIDを利用者に送り込む」手法について説明します。URL埋め込みのセッションID携帯電話向けコンテンツなどで、セッションIDをURLに埋め込んでいるサイトがあります。この場合は、セッションIDを埋め込んだURLをリンクとして用意しておき、利用者に閲覧させるだけでセッションIDを送り込むことができます。この場合、利用者が罠のリンクを踏んだ後、他のサイトに遷移する前に攻撃対象サイトにログインしないと、セッションフィクセイション攻撃は成立しません。Cookieに保存したセッションID多くのWebサイトでは、セッションIDをCookieに保存しています。この場合、ブラウザかWebアプリケーションに脆弱性があれば、セッションIDを利用者のCookieに送り込むことができます。Bugというバグ(脆弱性)があります。domain=tokyo.jpという属性のCookieをIEは受け入れてしまいます。属性のCookieをセットする罠を仕掛け、例えばwww.metro.tokyo.jp(東京都のホームページ)にも有効なセッションIDを利用者に「送り込む」ことが可能です(東京都のホームページはあくまで一例です)。攻撃対象のサイトにXSS脆弱性や、HTTPヘッダインジェクション脆弱性があると、そのサイトの利用者にCookieを送り込むことができる場合があります。XSSやHTTPヘッダインジェクション脆弱性は単独でもなりすまし攻撃可能な場合が多いのですが、例えばリダイレクト処理にHTTPヘッダインジェクション脆弱性がある場合、「HTTPボディにJavaScriptは埋め込めないが、Cookieのセットならでき」ます。この場合、HTTPヘッダインジェクション攻撃とセッションフィクセイション攻撃の合わせ技で、なりすましができることになります。当該サイトに脆弱性がなくても、同じドメイン上の別ホストに脆弱性がある場合、脆弱性のあるホスト上でセットしたCookieによりセッションフィクセイション攻撃ができます。これはブログ記事「セッションアダプションがなくてもセッションフィクセイション攻撃は可能」で攻撃例として示した方法です。地域型JPドメイン名や都道府県型JPドメイン名の上のサイトにXSSやHTTPヘッダインジェクション脆弱性がある場合、同じ都道府県に有効なCookieをセットして、同じ都道府県の地域型JPドメイン名や都道府県型JPドメイン名のサイトにセッションフィクセイション攻撃を仕掛けることができます。domain=tokyo.jpのCookieを指定することにより、例えばwww.metro.tokyo.jp(東京都のホームページ)にも有効なセッションIDを「送り込む」ことが可能です。影響を受けやすいサイトここまで、セッションフィクセイション攻撃の経路を示しました。上記から、セッションフィクセイション攻撃の影響を受けやすいサイトは下記に示す性質のものです。同じドメイン上に脆弱なサイトがある(可能性の高い)サイトHTTPヘッダインジェクション(などCookie設定可能な)脆弱性のあるサイト対策ここまで「セッションIDを送り込む」方法と、それが可能な条件を示しましたが、条件に該当するサイトが直ちに危険という訳ではありません。一般的に、「セッションIDが送り込まれてしまう」ことを防ぐことは困難な場合があるため、これを受け入れ、「セッションIDが送り込まれてもセッションハイジャックはされない」ように対策することが一般的です。その方法は、「安全なウェブサイトの作り方」(P19)に下記のように書かれています。ログインが成功した時点から新いセッションを開始する(新しいセッションIDでセッション管理をする)ようにします。新しいセッションを開始する際には、既存のセッションIDを無効化します。PHPでこれを実現するには、ログイン成功直後に下記を実行します。session_regenerate_id(true);これは容易に実現できるため、セッションフィクセイション攻撃を受けやすいか否かに関わらず、かならず上記を実施するようにしましょう。影響の受けやすさを検討しなければならないケースとしては、既存サイトが上記対策を実施しておらず、かつ対策がすぐにできない場合です。その場合は、セキュリティの専門家にチェックをしてもらうとよいと思いますが、セッションフィクセイション脆弱性に関してはとっとと直してしまった方が安い気がします。