SQLインジェクション攻撃はDB上の任意データを盗み出す

 前回に引き続き、Think IT上の連載「SQLインジェクション大全」の第4回:ケース別、攻撃の手口を読んで感じたことを書きたい。
 まず、この記事は以下のような書き出しから始まっている。

本記事は、システムを防御するにはまず敵を知らなければならない、という意図の下に、攻撃手法を紹介する。

dfltweb1.onamae.com – このドメインはお名前.comで取得されています。

 この趣旨に異論があるわけではないが、しかしこの表現は誤解を生みやすいものだと思う。
 というのは、「敵」(攻撃方法)を知ったからと言って、防御の方法が導き出せる訳ではないからだ。例えば、開発者が「最近のSQLインジェクションの自動化攻撃にはT-SQLのDECLARE文が用いられる」という情報を得て独自に対策を考えた場合、DECLAREという単語をチェックしてエラーにしたり、単語を削除することを考えがちだと思う。現に「セキュリティ専門家」の多くがその種の対策手法を推奨してきた歴史があるわけだが、現在ではこの種の手法はサニタイズと呼ばれ、推奨されない対策となっている。本質的な対策方法を検討するには、脆弱性が混入する根本原因をつきとめなければならないが、攻撃手法を見せられただけで、その攻撃が成立してしまう根源的な原因を考えられる人は少数派だと思うのだ。

 では、攻撃手法を紹介する意味はなにか。私は三つあると考えている。

(1)攻撃がもたらすリスクを説明する
 セキュリティ対策には多少なりとも費用や時間が掛かるわけで、その費用や時間を掛けてもよいと判断をする人(経営者やマネージャ)に対して、なぜそうするべきかを納得してもらう必要がある。セキュリティ対策の場合、それを怠った場合のリスクと可能性が問題となる。「ほら、こんなに簡単に個人情報がとれるんですよ」と手法紹介やデモを見せることは、リスクや可能性を素早く納得してもらうのに有効な手段だ。
 しかし、手法はあくまで攻撃の「例」に過ぎないわけであるから、それを一般化し、発生しうるリスク全般について簡潔に説明することを忘れてはならない。

(2)開発者が対策を行うモチベーションを高める
 セキュリティ対策は開発者にとっては面倒な作業であることが多い。開発者は機能の実現にはモチベーションが高まるが、機能に直結しないチェック処理などは「面倒」で「できればやらずにすませたい」仕事だろう。ましてや、セキュリティ対策の中身が理解できていない場合には、いっそうネガティブな感情が高まる。
 開発者に攻撃手法を説明することは二つの効果がある。まず、前項と同じでセキュリティリスクを理解することで、セキュリティに対する正しい恐怖心を身につけること。もう一つは、対策の有効性を開発者として納得してもらうことだ。これらにより、セキュリティ対策を実装するモチベーションを高めることが期待される。

(3)エンターテイメントとしての攻撃手法
 攻撃手法の多くは巧妙で知的好奇心を刺激するものだ。従って,攻撃手法の説明やデモは、やり方次第では楽しいエンターテイメントになる。
 もちろん、攻撃手法を面白おかしく紹介するだけでは、手法の悪用など、良くない副作用が考えられるわけだ。しかし、一方で正しいセキュリティ知識を身につけるべき開発者がセキュリティに対して興味を持っていない現状では、「セキュリティって面白いんだよ」という啓蒙も必要だろう。
 このため「面白くてためになる」セキュリティ講座の出発点として、「エンターテイメントとしての攻撃手法」というやり方もあり得ると考えている。

 このように,攻撃手法を説明する理由を考えていくと、つきつめれば「リスクに対する正しい理解」が重要であることになる。そこで、SQLインジェクションがもつリスクとは何かをもう一度考えてみよう。私の考えるところ下記の3種類に分類される。

  • 情報漏洩のリスク
  • データ改ざんのリスク
  • その他のリスク

 以下、順に説明しよう。

情報漏洩のリスク

 SQLインジェクションの代表的なリスクが情報漏洩であるが、しかしまだ正しく理解されているわけではない。SQLインジェクションによる情報漏洩が恐ろしい理由は、データベース内の情報ならどれでも取り出しが可能な手法が分かっていることである。
 よくある誤解としては、「この画面は大した情報を扱ってないから対策しなくても大丈夫ですよ」という反応だ。たとえば、その画面はエラー番号を受け取って、エラメッセージをDBから検索して表示するだけの「つまらない」ページかもしれない。しかし、その「つまらない」ページであっても、DB内に個人情報があれば,漏洩させることが可能というのがSQLインジェクションの怖さだ。代表的な手法としては、以下の3種の方法が知られている。

 それぞれ、簡単に説明する。

(1)エラーメッセージを利用する方法
 冒頭で触れたdfltweb1.onamae.com – このドメインはお名前.comで取得されています。で、エラーメッセージから取得できる情報として、テーブル名/カラム名/カラムの型を紹介しているが、現実にはDB内の内容も取得できる手法がある。
 具体例を示そう。まず、エラーメッセージを取得するSQLが以下のようだったとする。


select message from errmsg where errnum = $n

ここで、$nに対してSQLインジェクションを掛け、以下のようにSQLを改変する。


select message from errmsg where errnum = 1 and 1 < (select top 1 card_name + '!' + card_number + '!' + card_date from t_card)

 追加したSQLでは以下の処理を行う。

  • クレジットカード情報を保持するテーブルt_cardから、氏名(card_name)、カード番号(card_number)、有効日付(card_date)を取り出す
  • 取り出した情報を、記号「!」をセパレータとして連結する
  • 連結後の文字列を数値1と比較する

 すると、連結後の文字列は数値と比較できないので、以下のようなエラーメッセージが表示される(SQL Server 2005にて検証)。


varchar の値 'yamada!1234-5678-9012-3456!07/11' をデータ型 int に変換できませんでした。

 この例では、先頭の1レコードが得られるだけだが、SQLのハックにより、任意のレコードを得ることができる。

(2)UNIONを利用する方法
 先ほどの例と同様にSQLインジェクションにより、SQLを以下のように改変する。


select message from errmsg where errnum = -1 union select card_name + '!' + card_number + '!' + card_date from t_card

 ここでerrnum = -1としているのは、存在しないエラー番号としてである。UNIONはSQLの和集合を求める演算で、UNIONの左辺(元のSQL)と右辺(追加したSQL)をマージした結果を返す。左辺は1件もヒットしないので、結果として,新たに追加された問い合わせ結果が、このSQL全体の結果となる。この結果、以下のような値を返すことになる。


yamada!1234-5678-9012-3456!07/11

 やはり先頭一件のみの表示だが、前項と同様、任意のレコードを得る方法があり、繰り返し問い合わせることにより全てのクレジットカード情報を抜き取ることができる。
 また、UNIONを使う場合は、一覧表形式の表示箇所に用いるとより有効で、一度に複数件のレコードを得ることが可能だ。

(3)ブラインドSQLインジェクション
 今回の例ではブラインドSQLインジェクションを用いる理由はないのだが、SQLインジェクション脆弱性箇所によっては、WHERE句の真偽値のみ、あるいはSQLがエラーになったか正常終了したかのみが判別できる場合がある。このような代表例としては、ログイン画面がある。ログインが成功したか否か、あるいはログイン機能が正常終了したか異常終了したかによって、1ビットの情報が得られる。
 この1ビットの情報を積み重ねることによって、データベース内の任意の情報を取得する手法がブラインドSQLインジェクションである。詳しい手法の紹介は省略する。ブラインドSQLインジェクションは,一回のHTTPリクエストで得られる情報は1ビットなので、非常に多数のHTTPリクエストが必要となる。このため、手動での攻撃は困難で,専用のツールやスクリプトを用いることになる。
 dfltweb1.onamae.com – このドメインはお名前.comで取得されています。の2ページ目末尾には「スリープ機能」によるSQLインジェクションが紹介されている。

5つ目がスリープ機能を利用したレスポンス時間の差異だ。SQLインジェクションの攻撃を実施する際に、レスポンスに攻撃結果が表示されない場合、指定した攻撃が実行されたか否かを判断するために、攻撃の内容にレスポンス時間を遅くさせるための仕組みを組み入れる。レスポンス時間が遅れれば、攻撃が実行されたことがわかる。

dfltweb1.onamae.com – このドメインはお名前.comで取得されています。

 この手法は、WHERE句の真偽値もSQLのエラーも画面には表示されない場合の攻撃手段であり、いわば「究極のブラインドSQLインジェクション」である。画面からはなにも得られないので、問い合わせが帰ってくるまでの時間の差で1ビットの情報を得ようというものだ。ただでさえブラインドSQLインジェクションは時間がかかるところを、スリープ機能まで使うので、情報を得るのには膨大な時間がかかる。この手法は検査手法への応用としては興味深いが、現実の攻撃に用いるのは難しいかもしれない。
 そして、引用部分については、この攻撃を理解する上で必要となるブラインドSQLインジェクションの説明がないので、この説明だけでは読者はなんのことやら分からないだろう。

データ改ざんのリスク

 かつて複文が利用できるデータベースの調査: SQL Serverが狙われるには理由がある - 徳丸浩の日記(2008-05-02)SQLインジェクションのサンプルが動かない - ockeghem(徳丸浩)の日記で紹介したように、一部のDBMSでは、一回の問い合わせの際に複数のSQL文を受け付ける。これを複文(multiple statement)という。攻撃手法としては、SELECT文やUPDATE文などの末尾に新たなUPDATE文を続けることによって、データの改変を行うことができる。DBMSの種類でいうと、MS SQL Serverが一番この攻撃がやりやすく、条件付きでPostgreSQLSQLiteでも可能な場合がある。さらに、MySQLでも、複文を受け付ける機能がある(PHPのmysqli_multi_queryなど)ので、元のアプリケーションがこれを使っている場合は複文による攻撃が可能だが、開発者がわざわざそのようなプログラミングをしている可能性は低そうだ。
 通常SQLの区切りにはセミコロン「;」を用いるが、SQL Serverの場合はセミコロンなしでも複文の作成が可能だ。これについては複文が利用できるデータベースの調査(2): SQL Serverが狙われるにはまだまだ理由がある - 徳丸浩の日記(2008-06-27)にて紹介した。
 データ改変の場合は、元SQLとはまったく別のSQLを追加するので、任意テーブルの任意列を書き換え可能である。最近の攻撃例では、表示内容にIFRAME要素やSCRIPT要素を埋め込むことによって、改変されたサイトを訪問したユーザにマルウェアを感染させるという攻撃が行われている。

その他のリスク

 SQL Serverには、xp_cmdshellというストアドプロシジャが用意されており、SQLを利用して、DBサーバー上でWindowsのコマンドを実行することができる。xp_cmdshellはSQL Server 2000ではデフォルトで有効、SQL Server 2005以降ではデフォルトで無効である。SQL Serverを使った古いサイトでは特に注意を要する。xp_cmdshellが利用できる場合、任意コマンドの実行が可能と言うことから、DB内にとどまらない多様な攻撃が可能となる。
 また、SQL Server以外のDBMSでもファイルの読み書きが可能な場合が多い。これにより、DBサーバー上の機密ファイルをSQL経由で読み出したり、攻撃結果をファイル経由で効率よく持ち出すことが可能となるなど、ファイルを用いた攻撃が可能になる場合がある。

SQLインジェクション大全はどうか

 攻撃手法を紹介する意義について考察した後に、SQLインジェクション攻撃のリスクと一部の攻撃手法を紹介した。

 私がこのエントリを書こうと思った動機は、dfltweb1.onamae.com – このドメインはお名前.comで取得されています。には、一番重要となるリスクが明確に説明されていないと思ったからだ。その説明は断片的で不正確だ。その典型が2ページ目のクレジットカード情報の取り出しの説明だ。

 例えば、ECサイトのデータベースにクレジットカード情報の含まれる会員情報が保管されているとする。その会員向けのサービスとして、クレジットカード以外の会員情報を表示するWebページがあるとする。

 その際に使用するSQL文に、クレジットカード情報の格納されたテーブルにアクセスするSQL文を挿入して、会員名などを表示する個所に、クレジットカード番号を表示させる方法がある。

dfltweb1.onamae.com – このドメインはお名前.comで取得されています。

 こう書いてあるので、引用箇所に続いて「本来画面に表示されないクレジットカード番号を表示させる」テクニックが説明されるのだろうと思うのだが、その説明はされていない。説明されているのはJOINを用いてユーザ名やクレジットカード番号を表示するSQLだ。

select u.user_name,c.card_number, c.card_date from t_users u join t_card c on u.id = c.user_id where id=’$userid’ ;

dfltweb1.onamae.com – このドメインはお名前.comで取得されています。

 しかし、改変前のSQLが示されていないので、どのような攻撃の結果このSQLが得られたのかが分からない。さらに、SQLの末尾に$useridとあるので、このSQLは元々この形であったと考えるしかない。これでは、「会員名などを表示する個所に、クレジットカード番号を表示させる方法」ではない。元々クレジットカード番号を表示する画面ではないのか。

 おかしな部分はそれだけではない。$useridはその名前の示すようにユーザIDであろうが、以下のような説明があるのだ。

その時、検索のキーワードとなる「$userid」の値がブラウザから送られるPOSTやGET、Cookieの値を参照している場合、「$userid」の値を以下のように改ざんして攻撃する。

「’ or 1=1 --」

dfltweb1.onamae.com – このドメインはお名前.comで取得されています。

 近藤氏は、よほど「' or 1=1 --」がお好きらしい。しかし、$useridの書き換えができるという前提であれば、SQLインジェクションを用いなくとも任意のユーザの個人情報を閲覧できるということである。だから、SQLインジェクション以前の話ではないのか。ユーザIDの内容を外部から書き換えられる状態は、セッション管理上のごく初歩的な脆弱性である。
 近藤氏の所属する神戸デジタル・ラボでは、Webアプリケーションのソースコード診断も提供しているらしい。

また、「ソースコード診断」は、対象サイトのプログラムをセキュリティチームが、手作業でチェックし、顕在化していなかった問題点を含めて明らかにしていきます。「ソースコード診断」の結果を擬似攻撃診断に反映して行うことで、相乗効果が期待でき、より精度の高い診断を実施することができます。

最高の品質|サービスの強み|プロアクティブ・ディフェンス

 先のサンプルプログラムも、公開前に自社の「ソースコード診断」を受けておけばよかった。

 なお、図2-1、図2-2、図2-3は前回(第3回)の図(文字コードの問題などの説明で本文との関連はない)がそのまま載せられているので説明が余計に分かりにくくなっている。これは編集部のミスだろうが、関係者は公開後も誰も閲覧しなかったのだろうか。

 今回の結論はこうだ。

  • 攻撃手法を紹介する目的は主にリスクを理解してもらうことである
  • 攻撃手法を説明する時は,余計な脆弱性が混入しないよう気をつけよう
  • SQLインジェクションのリスクには任意データの漏洩、任意テーブルの書き換えなどがある

追記(2009/3/3 23:22)

 ようやく図2-1と図2-2が差し替えられた。
 この図によると、やはり元々から「ただのJOIN」である。(クレジットカード番号がマスクされているが、意図が不明なので追求しない)。また、SQLインジェクションの結果得られる神戸太郎さんの情報は、ユーザIDをA002からA001に変更するだけで可能だ。同様に、A003、A004と変更していくだけで、他の人の個人情報も得られる。SQLインジェクションよりも簡単だ。図が差し替えられても私の主張はなんら変更する必要はなく、より理解が容易になったと思う。