「SQLインジェクション対策」でGoogle検索して上位15記事を検証した
このエントリでは、ネット上で「SQLインジェクション対策」でGoogle検索した結果の上位15エントリを検証した結果を報告します。
SQLインジェクション脆弱性の対策は、既に「安全なSQLの呼び出し方」にファイナルアンサー(後述)を示していますが、まだこの文書を知らない人が多いだろうことと、やや上級者向けの文書であることから、まだ十分に実践されてはいないと思います。
この状況で、セキュリティのことをよく知らない人がSQLインジェクション対策しようとした場合の行動を予測してみると、かなりの割合の人がGoogle等で検索して対処方法を調べると思われます。そこで、以下のURLでSQLインジェクション対策方法を検索した結果の上位のエントリを検証してみようと思い立ちました。
どこまで調べるかですが、以前NHKスペシャルで“グーグル革命”の衝撃が放送された際に話題になった印象的な言葉「グーグル検索で…上位15位に入らなければこの世に存在しないのと同じです」にならい、少し多いですが「この世に存在する」上位15位までを調べてみました。
検索順位は変動するものなので、11月5日の朝に調べた結果のスクリーンショットをevernoteの公開ページに保存しておきました。
それでは始めます。
1位:今夜分かるSQLインジェクション対策:Security&Trust ウォッチ(42) - @IT
「また上野宣か」の名文句が生まれるきっかけになった有名な記事です。この記事への問題意識がきっかけとなって、安全なSQLの呼び出し方が生まれたという説もあります。このあたりの経緯については、上野宣氏が高木浩光氏にインタビューした「今夜こそわかる安全なSQLの呼び出し方 〜 高木浩光氏に聞いてみた:Security&Trust ウォッチ(60) - @IT」をご覧下さい。
さて、この記事ではWebアプリケーション側のSQLインジェクション対策として以下の記述があります。
http://www.atmarkit.co.jp/fsecurity/column/ueno/42.html
- SQLを埋め込むところで特殊文字を適切にエスケープ
- シフトJISの場合には1バイト文字を整理
- SQLの記述をなくすためにO/R(Object/Relational)マッピングを活用
- 攻撃者に役立つ情報を与えないために、不要なエラーメッセージ(データベースが出力するエラーなど)の表示を抑止
- バインドメカニズムの利用
本当に箇条書きだけなんですね。これらの優先順位もなければ、andなのかorなのかも分からないので、高木浩光氏に「駄目な技術文書の見分け方」と書かれたのも仕方ない気もします。上野氏は友人なので叩かれ方が気の毒な気がしますが、これはライターとして気をつけなければ、という教訓にしたいと思います。
2位:SQL インジェクション攻撃とその対策 | Microsoft Docs
マイクロソフト社のtechnetの文書の1つですね。マイクロソフトの技術文書は非常に充実しているのですが、この文書はイマイチです。「攻撃とその対策」と題されている割には、対策については以下の記述があるだけです。
"SQL インジェクション攻撃" の対策方法には、お客様が開発・導入した Web アプリケーション、またはデータベース上のストアドプロシージャ等を改修し、意図しない SQL 文を受け入れないようにする必要があります。
http://technet.microsoft.com/ja-jp/library/dd362952.aspx
間違ってはないですが、「意図しない SQL 文を受け入れないようにする」ために何をすべきかは書いておらず、別のドキュメントにリンクされているだけです。
それでも、気を取り直して、リンク先の文書を読みましたがあまり良くありません。
Microsoft Security Advisory 954462 | Microsoft Docs
この文書の対策編は、ツールの紹介になっていますが、その筆頭に紹介されているHP Scrawlrが403エラーで参照できなくなっています(一種のリンク切れ)。
リンク切れはさておくとしても、SQLインジェクション対策としては、まずは正しいアプリケーションの書き方が先に来るべきと考えます。
第 2 回 SQL インジェクション その攻撃と対処
NECラーニングの講習会資料のようです。対策としては以下が書かれているのですが。
1.パラメータ化クエリまたはストアド プロシージャを使用
2.特殊文字のエスケープ処理
ただし、すべての特殊文字をエスケープすることは困難
「パラメータ化クエリ」というのはプレースホルダのことですが、日本ではあまり普及していないいい方ですね。また「ストアド プロシージャを使用」というのは誤りです。ストアドプロシージャの呼び出し方が間違っていると、SQLインジェクション脆弱性になる場合があるからです。ひょっとすると、「パラメータ化…ストアドプロシージャ」と言いたかった可能性はあります。Parameterized stored procedureという英語表記はあるからです。しかし、この読み方は「ウルトラC」という感じですので、たとえそうだとしても日本語としてよくありません。
また、特殊文字のエスケープについて「すべての特殊文字をエスケープすることは困難」と書いてあるのは意味不明ですね。これについては、11位のところで言及します。
SQL インジェクション | Microsoft Docs
この文書は、対策について量的に詳しく書かれていますが内容がよくありません。
書いてあることは基本的に入力値検証なのですが、入力値の検証ではSQLインジェクションの対策にはなりません。
また、「可能であれば、次の文字を含む入力は受け入れないでください。」とありますが、シングルクォート以外は対策として無意味です。詳しくは、このエントリを参照下さい。
; ' -- /* ... */ xp_
リンク先はまだあるのですが、もうよいでしょう。読者はリンク先を読んでいるうちに混乱して、訳が分からなくなりそうです。
この文書の問題点については、このエントリに先立ち日本マイクロソフトの高橋正和氏に連絡したところ、改善を検討して下さるとのことです。加えて、「それはそれとして、公開しているものは、公開している責任があるので、ズバッと行ってください!(そういうものだと思います)」というお言葉をいただきました。高橋さんのお人柄がにじみ出ていますね。ということで、改善についてはしばらく待ちたいと思います。
3位:SQLインジェクションの対策 | Think IT(シンクイット)
この記事は以前、「SQLのバインド機構は「エスケープ処理された値」をはめ込むのか - ockeghem(徳丸浩)の日記」や「続:SQLのバインド機構は「エスケープ処理された値」をはめ込むのか - ockeghem(徳丸浩)の日記」で批判しました。詳しくはそちらをお読みいただくとして、要は、元々「バインド値はエスケープ処理した後にプレースホルダにはめ込む」となっていた表現がおかしいと思って調べたら、安全なウェブサイトの作り方 改訂第3版の「バインド値はエスケープ処理されてプレースホルダにはめ込まれる」という表現を(ほぼ)丸写ししたものと判明したものです。その後、私の批判記事の一部を拾って「SQL文のひな型とバインド値は個別にデータベースに送られ、構文解析される」と修正されました。
「安全なウェブサイトの作り方」の方は、その後改訂第5版で以下のように改訂されています。少し長くなりますが、該当の項を引用します。
SQLには通常、プレースホルダを用いてSQL文を組み立てる仕組みがあります。SQL文の雛形の中に変数の場所を示す記号(プレースホルダ)を置いて、後に、そこに実際の値を機械的な処理で割り当てるものです。ウェブアプリケーションで直接、文字列連結処理によってSQL文を組み立てる方法に比べて、プレースホルダでは、機械的な処理でSQL文が組み立てられるので、SQLインジェクションの脆弱性を解消できます。
プレースホルダに実際の値を割り当てる処理をバインドと呼びます。バインドの方式には、プレースホルダのままSQL文をコンパイルしておき、データベースエンジン側で値を割り当てる方式(静的プレースホルダ)と、アプリケーション側のデータベース接続ライブラリ内で値をエスケープ処理してプレースホルダにはめ込む方式(動的プレースホルダ)があります。静的プレースホルダは、SQLのISO/JIS規格では、準備された文(Prepared Statement)と呼ばれます。
どちらを用いてもSQLインジェクション脆弱性を解消できますが、原理的にSQLインジェクション脆弱性の可能性がなくなるという点で、静的プレースホルダの方が優ります。詳しくは本書別冊の「安全なSQLの呼び出し方」のプレースホルダの項(3.2節)を参照してください。
ということで、妙なコピペのドキュメントではなくて、「安全なSQLの呼び出し方」を参照して下さい。
4位:http://www.computerworld.jp/topics/563/%E3%82%BB%E3%82%AD%E3%83%A5%E3%83%AA%E3%83%86%E3%82%A3%E3%83%BB%E3%83%9E%E3%83%8D%E3%82%B8%E3%83%A1%E3%83%B3%E3%83%88/115389/SQL%E3%82%A4%E3%83%B3%E3%82%B8%E3%82%A7%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E6%94%BB%E6%92%83%E3%81%AE%E3%80%8C%E6%9C%80%E6%96%B0%E5%82%BE%E5%90%91%E3%81%A8%E5%AF%BE%E7%AD%96%E3%80%8D
NRIセキュアテクノロジーの矢野さんの記事です。8ページにわたる長い記事ですが、対策は6ページに書かれています。このページの表1に対策が列記されています。しかし、この表1には、9項目も列挙されているので、どれが本質的な解決策で、どれが保険的な対策なのかが分かりにくいですね。答えを書くと、2番目の「バインド・メカニズムの利用」が本質的な解決策、それ以外はすべて保険的対策です。そうはっきり書いてくれればよいのに、はっきりとは書いていないのです。
また、対策が9項目もあるのに、それだけではまだ足りないようで、以下の記述もあります。
表1で挙げただけでなく、さらにWebシステム全般のセキュリティ・レベルを上げたいのであれば、WAF(Web Application Firewall)の導入も検討すべきであろう。
が、対策する側の立場に立つと、ゴージャスな対策に過ぎ、「とてもこんなにはできない」と思ってしまいそうです。著者もそう感じたのか、7ページにはコラムとして「しかし、現実の問題として、ここに記載している対策をすべて実施しようとすると、かなり大変である。」とも書いてあります。
全体としては面白いコラムですが、対策に関しては焦点が定まっていない印象を受けます。
5位:SQLインジェクション対策について
IPAのプレゼンテーション資料です。「安全なウェブサイトの作り方」を元にしているので大きな問題はありませんが、バインド機構のことを「エスケープの一種」と説明しているのが気になります。しかし、これは3位のところで説明したように、「安全なウェブサイトの作り方」が元々そういう説明になっていたことに由来しています。
6位:SQLインジェクション - Wikipedia
Wikipediaの解説です。間違ったことは書いていませんが、辞書的な記述で特に分かりやすいわけでもありません。これを参考にSQLインジェクション対策する人はあまりいないと思うのでこれくらいにとどめます。
7位:第42回 PostgreSQL 9.0に見るSQLインジェクション対策:なぜPHPアプリにセキュリティホールが多いのか?|gihyo.jp … 技術評論社
大垣さんの記事です。ブックマークコメントも参照ください。
この記事で、大垣さんは「SQLインジェクション対策の4原則」として以下を提示されています。
1. すべてのパラメータを文字列としてエスケープする
2. すべてのパラメータをプリペアードクエリのパラメータとして処理する
3. 文字エンコーディングの設定をAPIで行う
4. パラメータとして処理できない文字列はバリデーションを行う原則1と原則2は重複して適用する必要はありません。どちらかを行います。
このうち、原則1(数値も文字列として扱う)には異論があります。詳しくは「数値項目に対するSQLインジェクション対策のまとめ」および「SQLの暗黙の型変換はワナがいっぱい」を参照下さい。
大垣さんは、「プリペアードクエリの功罪」という節の中で、「多くの開発者はプリペアードクエリさえ利用していれば大丈夫であるとしてエスケープ処理を軽んじてきました」と指摘しています。この文脈でのエスケープ処理とは、列名のエスケープを指しているようですが、それはともかく、確かに、プリペアードクエリ(プレースホルダ)さえ使えばSQLインジェクションは発生しないと言い切ってしまうと、誤解を招く可能性があります。
誤解されないためには、以下のように言わなければなりません。
- SQL文を動的に(文字列連結などで)組み立てない
SQL文を動的に組み立てないとすると、パラメータを渡す際にはプレースホルダを使うしかありません。そして、SQL文を動的に組み立てなければ、SQLインジェクション脆弱性の余地はありません。
単にプレースホルダを使うというだけでは、例えば以下のようなケースが問題になります。
$sql = "select * from $table where id=?";
この例は、テーブル名を外部から指定できるという想定ですが、$table = 'mytable; drop table mytable --'; などとすることで、任意のSQL文を追加できる場合があります。MS SQL ServerやPostgreSQLの場合に、攻撃が成功する可能性が高くなります。
SQL文を動的に組み立てようとする場合、前提条件により対処が変わります。
Webアプリケーションの初心者が書いてしまった場合
Webアプリケーションの初心者の場合、そもそもテーブル名を外部から指定できる仕様を見直すべきでしょう。例えば、組み立て後のSQL文が何種類かに限定できる場合、固定のSQL文を切り替えて使うなどの対処が考えられます。
アプリケーション・フレームワーク等を作成している場合
この場合、テーブル名を指定したい動機はあり得ますが、その場合でも任意テーブル名を指定できる必要はないはずで、安全にテーブル名を指定する仕組みを考える必要があります。
phpMyAdminのようなデータベース管理ツールを作成している場合
phpMyAdminのようなツールを開発している場合、元々任意のSQL文を利用者が実行できるわけで、その場合はSQLインジェクションによる「攻撃」のシナリオを考慮する必要はありません。しかし、ツールが生成したSQL文にエラーがないことは必須条件なので、テーブル名等に記号などを許容している場合は、SQLの文法に従ってテーブル名をエスケープする必要があります。
ということで、アプリケーションプログラマの従うべき原則としては、「SQL文を動的に(文字列連結などで)組み立てない」に従うことをお勧めします。O/Rマッパーやアプリケーションフレームワークを開発するような人は上級者だと思うので、自己責任でSQL文を安全に組み立てる方法を考えてくださいw
8位:漢(オトコ)のコンピュータ道: SQLインジェクションとは何か?その正体とクラッキング対策。
奥野さんのブログ記事です。MySQLのエキスパートですね。対策としては以下のように書かれています。
SQLインジェクションの最も確実で根本的な対策は、プリペアードステートメントを利用することである。問題は、SQLを組み立てる過程にあるので、動的にSQLを組み立てなければ原理的にはSQLインジェクションは発生しない。Webのフォームから受け取った値は、パラメータとしてプリペアードステートメントに渡せばいいわけである。ただし、プリペアードステートメント自身を動的に組み立ててしまうと元の木阿弥なので注意しよう!
完璧ですね。付け加えることはありません。
9位:IPAがまとめたSQLインジェクション対策マニュアル
「安全なSQLの呼び出し方」を紹介してくださっているブログ記事です。ありがとうございます。付け加えることはありません。
10位:SQLエスケープにおける「\」の取り扱い
私のブログ記事です。「SQLのエスケープ再考」の続編なのですが、SEO上は続編の方が上位に出ています。
どうも、これらの記事が高木浩光さんの目にとまって2008年のWASForumでの講演依頼をいただいたようです。高木浩光さんの日記には以下のように書かれています。
SQLインジェクション対策については、HASHコンサルティングの徳丸さんにお願いしました。今やSQLインジェクションの対策方法は広く理解されているはずのようにも思えますが、どうもそうでもないようです。いまだに「サニタイズ」的な対策を挙げる検査業者、コンサル業者がいるようで、しばしば、そういった業者がはてなブックマークで血祭りにあげられている事例も散見されるところです。blogで「SQLのエスケープ再考」といったエントリを書かれている徳丸さんに、このあたりのことについて結論を出していただきます。
http://takagi-hiromitsu.jp/diary/20080620.html
これを読んだ私のプレッシャーを察していただけるでしょうか。
但し、このWASForumの場では結論までは出ず、その後私自身がIPAに召還されて、高木さんやIPAのスタッフとともに「安全なSQLの呼び出し方」を執筆して、ファイナルアンサーに至りました。
11位:被害が続くSQLインジェクション攻撃,もう一度対策を見直そう | 日経 xTECH(クロステック)
ITproに日本IBMの菅野さんが書かれた寄稿記事です。2008年春というと、MS SQL Serverを狙ったSQLインジェクション攻撃が頻繁に発生していた時期にあたります。記事の前半は攻撃手法に関する説明で興味深く価値の高い内容だと思いますが、問題は後半の対策手法です。
「最も重要なのはWebアプリケーションでの対策」という見出しはとても正しいのですが、その次の見出し「不審な入力値は無効な文字列に変換」を見るに、身構えてしまいますね。そして、予想通り、いや、予想をはるかに上回る内容が続きます。
今回の攻撃では,WebアプリケーションにPOSTされる文字列の中にDECLARE文やEXEC関数が含まれていた。このような本来入力値として渡される可能性がない入力値や文字列を厳格にチェックし,無効化すれば脅威は回避できる。
http://itpro.nikkeibp.co.jp/article/COLUMN/20080514/301660/?P=2
DECLARE文やEXEC関数が「本来入力値として渡される可能性がない入力値」なんて軽々しく言って欲しくないですね。それに、DECLAREやEXECなどのキーワードによるブラックリスト検査は、攻撃者により回避されやすいという問題があります。この種の検査はWAFに任せておけばよく、アプリケーション側では本質的な対策に専念すればよいでしょう。
次には以下の表があります。
【入力の例】 DECLARE%20@S%20NVARCHAR(4000);SET%20@S=hogehoge EXEC(@S); ↓ ・特殊文字を文字として扱うために「\」を挿入 / 「;」は削除 【入力値チェックの結果】 DECLARE\%20@S\%20NVARCHAR\(4000\)SET\%20@S\=hogehoge EXEC\(@S\)
ここでの指摘は以下の通りです。
- 「入力の例」として提示されている文字列は、エスケープの必要な文字は含まれていない。すなわち、何もする必要はない
- 入力の例に「%20」が含まれているのでパーセントエンコードをデコード前と思われる。入力値チェックはパーセントデコード後に行うべき
- MySQLの場合は、ここで追加した「\」は単に無視されるが、多くのDBでは通常の文字として扱われる。すなわち、勝手に「\」を挿入するとバグになる
- 勝手に「;」を削除するな。SQLインジェクション対策としては無意味(参照:SQL Serverが狙われるにはまだまだ理由がある)
ということで、この記事の著者はSQLもWebアプリケーションもまったく分かっていないようです。このような記事を参考にしてはいけません。
12位:http://lets.postgresql.jp/documents/tutorial/with_php/against_sql_injection/escape_quote
第四企画の坂井さんがLet's Postgresの記事として寄稿されたものです。
この記事は素晴らしい。PostgreSQLをエスケープ手法によりSQLインジェクション対策する際の技術的な注意点が正確きわまりなく書かれています。細部に至るまで、この内容に同意します。PostgreSQLを深く知りたい方すべてにお勧めです。また、MySQL界隈の方はぜひ、MySQL向けのこのように優れた文書がないことに切歯扼腕して、MySQL向けの優れた解説を書いてください。私が知らないだけであれば教えてください。
しかしながら、この記事の冒頭に「プレースホルダを用いてより効果的な対策を行うケースに関してはプレースホルダ編を参照してください」とあるように、対策の本命はプレースホルダを使ったものです。というわけで、実用的にはこの記事の姉妹編である「PHPでのSQLインジェクション対策 - プレースホルダ編 | Let's Postgres」を参照してください。こちらも素晴らしい内容です。
13位:MySQLとPHPにおけるSQLインジェクション対策について
表題通りの内容ですが、記事中に「今回は基本的な方法のご紹介でしたが、これらを施すだけでもSQLインジェクションに関する脆弱性はかなり軽減することと思われます」と控えめなコメントがあるのが、必ずしも謙遜ではないと思われます。不正確な内容なので、読まない方がよいでしょう。
14位:無料で使えるSQLインジェクション対策スキャナ トップ15*ホームページを作る人のネタ帳
SQLインジェクション対策スキャナの海外の紹介記事を翻訳(トップ15のうち上位4つのみ)した記事ですが、2007年という少し古い記事であること、元記事は既に削除されて読めないこと、紹介者自身も試していないと思われることなどから、読まなくて良い記事だと思います。
15位:SQLインジェクションを考える
Webサイト側ではなく、サイト利用者の側でのSQLインジェクション対策について検討した記事です。
面白い着眼点ですが、結論としては、マルウェア感染の脅威については最新のパッチ適用とウイルス対策ソフトの導入、個人情報漏洩については決定打はないということで、特別目新しい内容はありません。
結局どうすればよいか
冒頭に記した通り、SQLインジェクション対策には既にファイナルアンサーがあります。それは「安全なSQLの呼び出し方」に書いた内容ですが、まとめると以下の通りです。
この2つを守っている限り、アプリケーションやSQL呼び出しライブラリなどにバグがあっても、原理的にSQLインジェクション脆弱性は発生しません。
もしもSQLを動的に組み立てている場合、アプリケーションにバグがあると、SQLインジェクション脆弱性になります。
静的プレースホルダではなく動的プレースホルダを使っている場合、SQL呼び出しライブラリにバグがあると、SQLインジェクション脆弱性が生じる場合があります。具体手例としては、JavaとMySQLの組み合わせでUnicodeのU+00A5を用いたSQLインジェクションの可能性 | 徳丸浩の日記やぼくがPDOを採用しなかったわけ(Shift_JISによるSQLインジェクション) | 徳丸浩の日記を参考にして下さい。この2例は既にライブラリ側で改修済みです。安全なSQLの呼び出し方でも説明しています。
まとめ
「SQLインジェクション対策」でGoogle検索した上位15件の内容を検証しました。
ここまで説明したように、上位15件の中には、SQLインジェクションの対策手法を説明した上質の文書はあまりありません。12位の「http://lets.postgresql.jp/documents/tutorial/with_php/against_sql_injection/escape_quote」はとても良い解説ですが、初心者には少し難しいと思います。
初心向けの解説としては、VOYAGE GROUP須藤さんの素晴らしいブログ記事「Webアプリケーションとかの入門本みたいのを書く人への心からのお願い。」がおすすめです。現役バリバリのWebエンジニアがこれを書いたところに圧倒的な説得力があります。この記事は、次のように始まります。
SQLインジェクションについて書くときに以下のメッセージを必ず含めて欲しいです。
http://d.hatena.ne.jp/ajiyoshi/20100409/1270809525
- 単にプリペアドステートメントを使え
- 絶対に文字列結合でSQLを構築しようとしてはいけない
- IPAの「安全なSQLの呼び出し方」を読むこと
素晴らしい。以上の引用をもって、本エントリ全体のまとめに代えたいと思います。
[PR]
「安全なWebアプリケーションの作り方」DRMフリーのPDFによる電子版もあります。
体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践
- 作者: 徳丸浩
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2011/03/01
- メディア: 単行本
- 購入: 119人 クリック: 4,283回
- この商品を含むブログ (146件) を見る
「徳丸本ができるまで」スライドを公開します
まっちゃ445などで発表に使用した「徳丸本ができるまで」のスライドを公開します。
発表時の原稿の後半を少しカットして、最新の状況を加筆しました。
[PR]
「体系的に学ぶ 安全なWebアプリケーションの作り方」のDRMフリーPDFによる電子版が販売開始しました。
体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践
- 作者: 徳丸浩
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2011/03/01
- メディア: 単行本
- 購入: 119人 クリック: 4,283回
- この商品を含むブログ (146件) を見る
YAPC::Asia Tokyo 2011でパスワードについてトークしました
既にこのブログでも報告しましたが、10月14日、YAPC::Asia Tokyo 2011でパスワードについてトークしてきました。スライドとビデオは以下のサイトにあります。
私はWebアプリケーションのセキュリティを専門にしていますが、開発系のカンファレンスでしゃべる機会を増やしたいと思っています。Webアプリケーションの脆弱性は、つまるところWebアプリケーションのバグなので、開発者の方々にセキュリティのことを知ってもらわないと、脆弱性をなくすことはできないからです。
ということで、今年はYAPCの他に、PHPカンファレンスでもトークさせていただきました。PHPカンファレンスもYAPCも、トークを公募していて、誰でも応募できます。PHPカンファレンスの方は、昨年もトークに応募していて、文字コードの話をしました。このテーマは、ややこしい話なのに結構好評でしたので、それに味をしめて、今年は徳丸本の話をしました。この本はPHPの話題が多いので、PHPカンファレンスで話をするには向いています。
一方、YAPCでもトークしたいなぁという思いはずっとあったのですが、何をしゃべるかで悩んでいました。正直、Perlのモチネタがあまりありません。文字コードの話をPerlに移植することを考えたのですが、PHPの話をPerlに移してもうまくいかないのですね。Perlの場合、Perl5.8以降のUTF-8を内部形式とする書き方だと、PHPほど簡単には文字コードの問題がおきないのです。
では、「Perlの方が文字コードに関してはPHPより安全」と言えるかという、それは微妙です。このあたりのことは、「文字エンコーディングバリデーションは自動化が望ましい」に書きましたので、よろしければ参照下さい。
そんなことで、文字コードの話はあきらめ、言語に依存しない話題を選びました。PerlはWeb以外でも使われていますが、やはりWebの関係者が多いだろうと思いましたので、「Webアプリでパスワード保護はどこまでやればいいか」というテーマにしました。これは、徳丸本の5.1節に書いてある内容ですが、PHP記述のサンプルをPerlに移植して、トークすることにしました。
テーマも決まってトークに応募しましたが、そもそも採択されるかどうかが不安でしたね。これはPHPカンファレンスの方も同じですが、PHPカンファレンスの方は過去にトークした実績があるのに対して、YAPCは初めてだったので余計に不安でした。でも、facebookの「いいね!」ボタンやGoogleの「+1」でたくさんの方が応援して下さったのは心強かったです。採択が決まった時は、とても嬉しかったです。
そんなこんなで本番を迎えましたが、しゃべっている時は、あまり受けているかどうか分からないのですね。反応が悪くないことは分かりますし、あとでtwitter.comで盛り上がっていたことを知りましたが、どこまで共感いただけたかは分かりません。これは多分私の感度の問題だと思います。
そう言う状態でしたので、ベストトーク賞3位がいただけことは、本当に驚きでした。あろうことか賞が発表された時に会場にいなかったので、twitterで受賞を知ったのでした。なんてバカなんでしょう。会場にいたら、喜びも256倍になっていたのに違いないのに、と思いましたが、後の祭りでした。
私が会場にいなかったため、副賞のiPad2は後日送っていただきました(お手間を取らせて申し訳ありません)。以下のように、徳丸本電子版も快適に読めます。ありがとうございました。大切に使います。でも、iPad2より嬉しかったことは、Perlコミュニティの方々に私のトークが受け入れられたことの方です。
さて、こうしてブログも書きましたので、私のYAPC::Asia Tokyo 2011はこれで終わりますが、もし来年もYAPC::Asia Tokyoがあれば、以下のことはしたいですね。
最後までお読みいただき、ありがとうございました。
[PR]
「体系的に学ぶ 安全なWebアプリケーションの作り方」のDRMフリーPDFによる電子版が販売開始しました。
体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践
- 作者: 徳丸浩
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2011/03/01
- メディア: 単行本
- 購入: 119人 クリック: 4,283回
- この商品を含むブログ (146件) を見る
YAPC::Asia Tokyo 2011でベストトーク賞3位をいただきました
YAPC::Asia Tokyo 2011で「Webアプリでパスワード保護はどこまでやればいいか」というタイトルでトークして、投票によりベストトーク賞3位というのをいただいたそうです。
「そうです」というのは、実は発表・表彰の際に会場にいなかったという大失態を演じたからでして、少しでも受賞の可能性を考えていたら万難を排して残っていたのですが、錚々たる方々がトークされるYAPCでベストトーク賞がいただけるなど思いもよらず、所用もあったため、その場にいなかったのです。
「その場にいなかったから賞は次点の方に」なんてことにならないかハラハラしました(というのは冗談で、選んで頂けただけで光栄です)が、JPAの牧さんから、賞品の送付先住所をメールして欲しいと連絡頂きましたので、これは本当だったのだと確信して、このエントリーを書いています。私がその場にいなかったために、余計な手間を取らせてしまい、申し訳ありません。
また、副賞としてiPadを頂戴できるということで、ちょうど電子書籍を読むために安いAndroidタブレットでも買おうと思っていたところでしたので、とても良いタイミングで世界一のタブレットをいただけることになりとても嬉しいです。
今年は、PHPカンファレンス2011でもトークさせて頂きましたが、このような開発系のコミュニティでトークして、それが受け入れて頂けるというのが私にとっては一番嬉しいことです。Webアプリケーションのセキュリティの専門家にとって、セキュリティの方法論を開発者の皆様に実践してもらわないことには何も始まらないからです。そんなわけで、PHPカンファレンスやYAPCのトークに応募しているわけですが、トーク採択されただけでも嬉しいのに、聴衆の開発者たちに好意的に受け入れられ、このような賞までいただけるとは最高の喜びです。
トークのスライドは別途公開しようと思いますが、取り急ぎ、スライドに出てくる唯一のPerlスクリプトを紹介します。これは、「体系的に学ぶ 安全なWebアプリケーションの作り方」に出てくるソルトとストレッチングのサンプル(P327)をYAPCにあわせてPerlに移植したものです。
#!/usr/bin/perl use strict; use warnings; use Digest::SHA qw(sha256_hex); # FIXEDSALTはサイト毎に変更してください my $FIXEDSALT = 'bc578d1503b4602a590d8f8ce4a8e634a55bec0d'; my $STRETCHCOUNT = 1000; # ソルトを生成する  sub get_salt { my ($id) = @_; return $id . pack('H*', $FIXEDSALT); # ユーザIDと固定文字列を連結 } # ソルト化ハッシュを繰り返し求める(ストレッチング)  sub get_password_hash { my ($id, $pwd) = @_; my $salt = get_salt($id); my $hash = ''; # ハッシュの初期値 for (my $i = 0; $i < $STRETCHCOUNT; $i++) { $hash = sha256_hex($hash . $pwd . $salt); # ストレッチング } return $hash; } # 呼び出し例 print get_password_hash('user1', 'pass1') . "\n"; print get_password_hash('user1', 'pass2') . "\n"; print get_password_hash('user2', 'pass1') . "\n";
ご覧のように、古典的なPerlスクリプトでモダンでもなんでもないですが、use strict;とuse warnings;だけは忘れないように、何度も確認しました:)
最後になりましたが、YAPC::Asiaを開催されたJPAのスタッフ、ボランティア、他のトーカー、お集まり頂いた皆様の全てにお礼を申し上げます。来年もネタがあれば(ネタを作って)トークしたいですね。今後ともよろしくお願い致します。
[PR]
「体系的に学ぶ 安全なWebアプリケーションの作り方」のDRMフリーPDFによる電子版が販売開始しました。今なら、キャンペーン価格\1,800-(36%OFF)でお求め頂けます(10月17日まで)。購入はこちらから。キャンペーン終了が迫っていますので、購入検討中の方はお早めに。
CookieのDomain属性は *指定しない* が一番安全
たまに誤解があるようですが、Cookieを設定する場合のDomain属性は *設定しない* のがもっとも安全です。以下、例示により説明します。
※このエントリは、http://blog.tokumaru.org/2011/10/cookiedomain.html に移転しました。恐れ入りますが、続きは、そちらをご覧ください。
私はいかにしてOperaブラウザのCookie Monster Bugを確認したか
昨日の日記「都道府県型JPドメインがCookieに及ぼす影響の調査 | 徳丸浩の日記」にも書きましたが、Operaブラウザの最新版(11.51)には、地域型ドメインの場合にCookie Monster Bugがあります。以下は、三重県の志摩市のドメイン(www.city.shima.mie.jp)上で domain=mie.jpのCookieをセットした様子です。
この状態で三重県津市(www.info.city.tsu.mie.jp)のホームページにアクセスしたところ、このCookieが送信されることをネットワークキャプチャにて確認しました。
Operaブラウザは独自の方法でCookie Monster Bugに対応しているというのが業界の常識だと思っていましたので、当初この現象を見て驚きました。大げさに言えば「ニュートリノが光速を越えた」ことに匹敵する(大げさ過ぎ)ような驚きでしたので、CERN研究所にならって実験方法を見直しました。
実験1(hostsファイル)
最初の実験では、Windows上のlocalhost上で動くApacheを三重県のサーバーに見立てて、hostsファイルでホスト名に対するIPアドレスをセットしていました。しかし、「OperaはDNSを使ってCookie Monster Bugを解決している」という情報がありましたので、DNSで名前解決をしないこの方法ではダメかもしれないと思いました。
実験2(Burp Proxy)
次に、Burp Proxyを使って、三重県津市のホームページのレスポンスをSet-Cookieを追加しました。やはりdomain=mie.jpのCookieがセットされました。
しかし、ブラウザはPorxy経由のアクセスではDNSにアクセスする必要がないので、Proxy経由だと特別な条件になるかもしれないと思いました。少なくともProxy経由の場合にはCookie Monster Bugがあるとは言えますが、Proxy経由でない場合も確認した方がよいと思いました。
実験3(DNSキャッシュサーバーで実験用ドメインをセット)
第3の実験では、DNSキャッシュサーバーを立てて、そこに実験用のドメインを定義しました。実験用ドメイン以外についてはネット上のDNSを参照するため、通常のネットアクセスと条件は変わらないと考えました。この条件でも、domain=mie.jpのCookieをセットでき、ブラウザから送信されることを確認しました。
実験4(透過Proxy上で書き換え)
実験3でほぼOKとは思いましたが、なにせ私の脳内では「ニュートリノの光速超え問題」に匹敵する発見(大げさ)なので、万全を期すために異なる条件で実験したいと思いました。私の実験環境には透過Proxyがありましたので、これを用いてCookieをセットすることにしました。透過Proxyを用いた実験結果が、冒頭で示した志摩市のページ上でセットしたdomain=mie.jpのCookieです。具体的には、志摩市のページのレスポンスを透過Proxyで書き換え、Set-Cookieヘッダを追加しました。このCookieがOperaブラウザにセットされることを確認しました。
透過Proxyはブラウザからは見えませし、DNSもISPの提供するものを使いましたので、通常のネットアクセスと同じ環境と同じと考えられます。ここに至ってようやく、「OperaブラウザにはCookie Monster Bugがある」と確信するに至りました。
結論
Operaブラウザの最新版(11.51)には地域型ドメインに対するCookie Monster Bugがある。
[PR]
「安全なWebアプリケーションの作り方」電子書籍版は本日発売開始です。くわしくはこちら。
体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践
- 作者: 徳丸浩
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2011/03/01
- メディア: 単行本
- 購入: 119人 クリック: 4,283回
- この商品を含むブログ (146件) を見る
『よくわかるPHPの教科書』のSQLインジェクション脆弱性
このエントリでは、数値型の列に対するSQLインジェクションについて説明します。
以前のエントリで、たにぐちまことさんの書かれた『よくわかるPHPの教科書』の脆弱性について指摘しました。その際に、『私が見た範囲ではSQLインジェクション脆弱性はありませんでした』と書きましたが、その後PHPカンファレンス2011の講演準備をしている際に、同書を見ていてSQLインジェクション脆弱性があることに気がつきました。
脆弱性の説明
問題の箇所は同書P272のdelete.phpです。要点のみを示します。
$id = $_REQUEST['id']; // $id : 投稿ID $sql = sprintf('SELECT * FROM posts WHERE id=%d', mysql_real_escape_string($id) $record = mysql_query($sql) or die(mysql_error()); $table = mysql_fetch_assoc($record); if ($table['member_id'] == $_SESSION['id']) { // 投稿者であれば削除 mysql_query('DELETE FROM posts WHERE id=' . mysql_real_escape_string($id)) or die(mysql_error()); }
このスクリプト断片は一言掲示板の投稿を削除するものです。投稿ID($id)に対して、まず投稿者自身による削除要求かどうかを確認します。投稿者であることが確認できたら、DELETE FROMにより投稿を削除します。
まず、正常系の処理を確認します。id=15の投稿を削除する場合の処理で、投稿者自身による削除要求とします。実行されるSQLは以下となります。
SELECT * FROM posts WHERE id=15 -- -- 投稿者の確認を行い、OKなので以下のSQLを実行 -- DELETE FROM posts WHERE id=15
次に、SQLインジェクション脆弱性の説明です。idとして、「15 or 1=1」が指定されたとします。最初のSELECT文に対して、パラメータはsprintfの%d書式で受けているので、SQLに与えられるパラメータは 15 となります。次に、投稿者のチェックを通った後は、%d書式ではなく、文字列連結でSQLを組み立てているので、「15 or 1=1」がそのまま与えられます。実行されるSQLは以下の通りです。
SELECT * FROM posts WHERE id=15 DELETE FROM posts WHERE id=15 or 1=1
1=1は常に成立するので、全ての投稿が削除されることになります。投稿者のチェックはくぐり抜けます。
ピンポイントで別人の投稿を削除する攻撃
次に、全ての投稿を削除するのではなく、別人の投稿を1つだけ削除する例を紹介します。
攻撃者が別人の投稿(ID=12)を削除したいと想定します。攻撃者には自分の投稿(ID=15)がある時、以下のidを指定すると、ID=15とみせかけて、ID=12を削除できます。
id=15-3
発行されるSQLは以下となります。
SELECT * FROM posts WHERE id=15 DELETE FROM posts WHERE id=15-3
チェックの時はid=15、削除の時はid=15-3 (すなわちid=12)が指定されるため、攻撃者は目的を果たすことができます。
こういうことが起きる原因のひとつは、SELECTとDELETE FROMとでSQL呼び出しの方法が違うことですが、「どうして呼び出し方を統一しないの?」と思ってしまいますね。
エスケープをしているのになぜ脆弱性となるのか
元のスクリプトは、ご覧のように、mysql_real_escape_stringによりパラメータのエスケープをしていますが、脆弱性が混入しています。なぜでしょうか。
mysql_real_escape_stringは、基本的に、文字列終端を示すシングルクォート「'」をエスケープすることが目的です。文字列リテラル中にシングルクォートがあると、文字列を勝手に終わらせてSQLを追加できるというのが文字列型に対するSQLインジェクション攻撃です。
これに対して、数値はシングルクォートで囲まないため、シングルクォートは「特別な記号」ではありません。数値以外の文字であれば、どれでも数値リテラルを終わらせ、その後は追加のSQLと認識されます。
先の攻撃例では、「15 or 1=1」を指定しました。この場合、15の後のスペースが「数値以外の文字」となります。そして、その後の「 or 1=1」が数値を「はみ出し」、追加のSQLと認識されました。この文字列中には、mysql_real_escape_stringの処理対象であるシングルクォートもバックスラッシュもないので、mysql_real_escape_stringは何もしません。つまり、対策にはなりません。
数値型の列のSQLインジェクション脆弱性はどう対策すればよいか
では、数値型の列に対するSQLインジェクションはどう対策すればよいでしょうか。その答えは、引用したサンプルの前半にあります。最初のSQL(SELECT)では、パラメータを書式%dで受けていました。%d書式を用いる限り、数値以外の文字が混入することはありません。したがって、有効なSQLインジェクション対策となります。mysql_real_escape_stringは必要ありません。
しかし、%sと%dの書式が混在している場合、うっかり数値を%sで受けてしまうと脆弱性になります。複雑なSQLの場合、この対応を確認するのは煩雑です。従って、整数型のパラメータであれば、(int)でキャストすればよいでしょう。対策後のSQLを以下に示します。
$sql = sprintf('SELECT * FROM posts WHERE id=%d', (int)$id); $record = mysql_query($sql) or die(mysql_error()); // 省略 $sql = sprintf('DELETE FROM posts WHERE id=%d', (int)$id); mysql_query($sql) or die(mysql_error());
不要なmysql_real_escape_stringがなくなりスッキリした上に、脆弱性がないことの確認が容易になります。同じ内容は、「PHP逆引きレシピは概ね良いが、SQLインジェクションに関しては残念なことに - ockeghem(徳丸浩)の日記」でも説明しました。
もっと良い方法はプレースホルダを使うこと
数値列に対するSQLインジェクションの原理は上記の通りですが、これを全ての開発者が理解・咀嚼し応用することは、現実には難しいだろうと思っています。もっと単純明快な対策はないでしょうか。
あります。プレースホルダによるSQL呼び出しがそれです。上記をPDOによるプレースホルダで書き直してみましょう。
$sth = $dbh->prepare('SELECT * FROM posts WHERE id=?'); $sth->bindValue(1, (int)$id, PDO::PARAM_INT); $sth->execute(); // 省略 $sth = $dbh->prepare('DELETE FROM posts WHERE id=?'); $sth->bindValue(1, (int)$id, PDO::PARAM_INT); $sth->execute();
少し行数が増えましたが、考え方はシンプルです。文字列型の列の場合は、(int)のキャストをやめ、PDO::PARAM_INTをPDO::PARRAM_STRに変更するだけです。あるいは、PDO::PARAM_STRを省略することも可能です。プレースホルダを用いた場合、リテラルを囲むシングルクォートも必要ありません。
また、PDOのような抽象化されたライブラリを利用することにより、PostgreSQLなど別のDBMSに移植することも容易になります。PHPの入門書等でも、MySQL関数(mysql_xxxx)やPostgreSQL関数(pg_xxxx)による説明をやめ、今後はPDOやMDB2を利用したサンプルで説明するべきだと考えます。その際、文字列連結によるSQL組み立ても説明せず、最初からプレースホルダで説明するべきです。
まとめ
『よくわかるPHPの教科書』を題材として、数値列に対するSQLインジェクションについて説明しました。
数値列に対するSQLインジェクション脆弱性の対策は、SQLに与えるパラメータが数値であることを確実にすることですが、もっと良い方法は、プレースホルダを使ってSQLを呼び出すことです。
以下を守る限り、SQLインジェクション脆弱性は原理的に発生しません。仮に発生するとしたら処理系の脆弱性です。
これについては、id:ajiyoshiさんの素晴らしいエントリ「Webアプリケーションとかの入門本みたいのを書く人への心からのお願い。」も参考になさって下さい。
参考
[PR]
「安全なWebアプリケーションの作り方」電子書籍版9月28日(水)販売開始します。くわしくはこちら。
体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践
- 作者: 徳丸浩
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2011/03/01
- メディア: 単行本
- 購入: 119人 クリック: 4,283回
- この商品を含むブログ (146件) を見る