現在twitterのタイムラインで、史上空前のSQLのエスケープブームが起こっています。
- オレオレSQLセキュリティ教育は論理的に破綻している | yohgaki's blog
- 「プリペアードクエリが基本だけど、動的に SQL を組み立てる場合もあるから、そういう場合に備えてエスケープも知っておいたほうがいいかも」 - Togetterまとめ
- エスケープとプレースホルダをめぐる議論 - Togetterまとめ
- SQLインジェクション対策としてのプリペアドステートメントとエスケープについての議論 - Togetterまとめ
- IPAの「安全なSQLの呼び出し方」が安全になっていた | yohgaki's blog
ツイッターでの議論を見て「SQLエスケープを教える必要はない」とする原因は「教育の基本」と「セキュリティの基本」の理解不足が「根本的な原因」だと解ってきました。これを読んで、SQLインジェクション対策という文脈でエスケープ(SQLの文字列リテラルにおけるエスケープ)を教える必要がないという意見があるのだろうかと疑問に思いました。そのため、PHPの教科書、セキュリティの教科書や冊子(PHPにフォーカスしたもの)合計12種類について、SQLインジェクション対策がどのように説明されているかを調査しましたので報告します。
オレオレSQLセキュリティ教育は論理的に破綻しているから引用
1. よくわかるPHPの教科書(たにぐちまこと著)
エスケープの説明 | あり(バックスラッシュのエスケープが抜けている) |
---|---|
SQLライブラリ | mysql関数 |
対策方針 | エスケープ(mysql_real_escape_string) |
数値列の扱い | sprintfの%d書式+エスケープ |
文字エンコーディング指定 | SET NAMES UTF8 |
上の表のように、基本的にmysql_real_escape_stringによるエスケープ処理でSQLインジェクション対策していますが、数値列に関しては以下のようにsprintfと組み合わせています。
実は上記は冗長でして、値をシングルクォートで囲っていない場合はエスケープをしても無意味です。上記のSQLインジェクション対策のキモは%d書式による数値化にあります。$sql = sprintf('INERT INTO my_items SET maker_id=%d 【略】', mysql_real_escape_string($_POST['maker_id']));
これだけであれば、冗長なだけで脆弱性ではありませんが、同書のP272には、sprintfを忘れて単に文字列連結した箇所があり、こちらはmysql_real_escape_string関数によりエスケープ処理はしていますが、脆弱性が混入しています。詳しくは、私のエントリ『よくわかるPHPの教科書』のSQLインジェクション脆弱性を参照ください。
また、文字エンコーディングの指定をSET NAMES UTF8の呼び出しにより行っていますが、あまり良くありません。その理由については、libmysqlclientを使うプログラムはset namesをutf8であっても使ってはいけないを参照ください。ただし、大半の環境で脆弱性となることはないと思われます。正しくは、mysql_set_charset関数により指定することですが、mysql関数そのものがPHP5.5から非推奨になりましたので、PDOまたはmysqli関数に移行しましょう。たにぐち本が書かれた頃は、その情報はなかったので、mysql関数を使っていることは仕方ないと思います。
2. いきなりはじめるPHP~ワクワク・ドキドキの入門教室~(谷藤賢一著)
3. 気づけばプロ並みPHP~ショッピングカート作りにチャレンジ!(谷藤賢一著)
エスケープの説明 | なし |
---|---|
SQLライブラリ | PDO |
対策方針 | プレースホルダ |
数値列の扱い | プレースホルダのため考慮の必要がない |
文字エンコーディング指定 | SET NAMES UTF-8 |
4. 基礎からのPHP(西沢 夢路著)
エスケープの説明 | なし |
---|---|
SQLライブラリ | PDO(冒頭でmysqlの説明もある) |
対策方針 | プレースホルダ |
数値列の扱い | プレースホルダのため考慮の必要がない |
文字エンコーディング指定 | 指定していない |
本書は最終的にはPDOによりMySQLにアクセスする方法を説明していますが、その前にmysqlを用いて基本的なSQLアクセスを説明しています。この段階のサンプルにはSQLインジェクションがあり、同書P166には下記の言い訳(?)が載っています。
不謹慎ですが、P272で勉強する「SQLインジェクション対策」はまったくしていませんあくまで練習用ということでご了解ください。逆に後でSQLインジェクションを試してみるのも面白いかもしれません。最初に脆弱性のある状態で説明する方法には同意しませんが、後のPDOのサンプルは、原則としてプレースホルダを用いてSQL文を呼び出しているのでSQLインジェクション脆弱性はありません。ただし、以下の微妙なコードはあります(gz_logon2.php)。
$uはユーザIDですが、HTMLエスケープした状態でSQL文に埋め込んでいます。HTMLエスケープの際にENT_QUOTESを指定しているので、シングルクォートは'とエスケープされるので、SQLインジェクションに使えませんが、バックスラッシュ(円記号)はエスケープされません。しかし、外部から操作できるパラメータが一つだけだとかろうじて攻撃できない気がします。もしもWHERE句が以下の形であれば、SQLインジェクション攻撃が可能です。$u = htmlspecialchars($_POST['user'], ENT_QUOTES); // 略 $ps = $db->query("SELECT pas FROM table2 WHERE id='$u'");
すなわち、第1パラメータにバックスラッシュを指定すると、第2のパラメータはSQL文の一部となり、UNION SELECTなど好きなSQL機能を用いて攻撃できます。 あぶなっかしい箇所は他にもありますが、それはプレースホルダを使っていないことが原因であり、プレースホルダを使っていれば問題ないものです。WHERE id='$u' and pwd pas='$p' # WHERE句が左記の場合 $u ← \ $p ← or 1=1 # WHERE句は下記となる id='\' and pwd pas='or 1=1 #' ~~~~~~~~~~~~~~~ ここまでが文字列リテラル
5. かんたんWebプログラミング! これから始める人のPHP学習帖(小川 淳一著)
エスケープの説明 | 間違っている |
---|---|
SQLライブラリ | mysqli および PDO |
対策方針 | プレースホルダ |
数値列の扱い | プレースホルダのため考慮の必要がない |
文字エンコーディング指定 | mysqli_set_charset およびPDO接続文字列 |
エスケープの説明が間違っているところを引用します(同書P202)。
たとえば、X'masなど、送信されたデータにシングルクォートやダブルクォートが含まれていると、SQL文法エラーとなり、データ登録などの処理が実行されません。必ずhtmlspecialchars関数で無害しておきましょう。ガタッという感じですが、幸いなことに同書はSQL呼び出しの際に、かたくなにプレースホルダを用いているので、SQLインジェクション脆弱性はなさそうです。多少プログラマの知識が怪しくても(これ自体は残念ですが)、プレースホルダを用いるとSQLインジェクションの可能性をかなり減らすことができる例と言えます。
6. パーフェクトPHP(小川 雄大、柄沢 聡太郎、橋口 誠著)
エスケープの説明 | データベース専用のエスケープ関数を使え |
---|---|
SQLライブラリ | PDO |
対策方針 | 主にプレースホルダ |
数値列の扱い | プレースホルダの場合は考慮の必要がないが、 エスケープについては記述なし |
文字エンコーディング指定 | なし |
同書のSQL呼び出しの説明は基本的にPDOを用いていますが、SQLインジェクションの説明の節では、プレースホルダが使えない場合はpg_escape_stringやmysql_real_escape_stringなど「データベースエンジン毎に用意された関数」でエスケープするように推奨しています。これは正しい指摘ですが、数値列の場合にどうするかは説明されていません。
7. プロになるための PHPプログラミング入門(星野 香保子著)
エスケープの説明 | なし |
---|---|
SQLライブラリ | mysqli |
対策方針 | プレースホルダ |
数値列の扱い | プレースホルダのため考慮の必要がない |
文字エンコーディング指定 | set_charsetメソッド |
同書はセキュリティに関しては手堅い解説をしています。SQL呼び出しについても、ライブラリの選択、文字エンコーディングの指定、プレースホルダ利用の徹底などの点で模範的です。
8. PHP逆引きレシピ(鈴木 憲治他著)
エスケープの説明 | データベース専用のエスケープ関数を使うよう説明 |
---|---|
SQLライブラリ | mysql および mysqli |
対策方針 | プレースホルダを優先し、だめな場合はエスケープ |
数値列の扱い | エスケープの場合はすべての値をシングルクォートで囲む |
文字エンコーディング指定 | mysql_set_charset等 |
第1版のSQLインジェクション対策については、こちらに批判記事を書きました。問題を要約すると、エスケープすべきでないものまでエスケープしている問題と言えると思います。この問題は第2版では解決されています。
9. PHP逆引きレシピ 第2版(鈴木 憲治他著)
エスケープの説明 | データベース専用のエスケープ関数を使うよう説明 |
---|---|
SQLライブラリ | PDO |
対策方針 | プレースホルダを優先し、だめな場合はエスケープ |
数値列の扱い | プレースホルダの場合は考慮の必要がない。プレースホルダを使わない場合は、「数値以外の文字が混入しないように入力値を検証する必要があります」 |
文字エンコーディング指定 | 接続文字列に指定 |
冒頭に書いたように、この第2版は素晴らしいです。「もうペチパーは緩いなんて言わせない」と叫びたくなるほどのインパクトがあります。たとえば、暗号的に強い乱数が必要な局面では、openssl_random_pseudo_bytes関数を推奨しています。類書だと、uniqidやmt_randを進めている場合が多い状況でした。 SQLインジェクションについては、数値の扱いが厳密になったことと、SQL呼び出しの説明全般がPDOとプレースホルダを用いて記述されていているので安心です。
10. 安全なSQLの呼び出し方
IPAが公開している「安全なウェブサイトの作り方」の別冊です。SQL呼び出しに特化して詳しく説明しています。エスケープの説明 | 原理から実際まで詳細に説明 |
---|---|
SQLライブラリ | PHPサンプルではMDB2 |
対策方針 | プレースホルダ(推奨)、エスケープ |
数値列の扱い | プレースホルダの場合は考慮の必要がない、エスケープの際もquoteメソッドによる数値対応を推奨 |
文字エンコーディング指定 | MDB2の接続文字列 |
本冊子は、SQLインジェクションの前提となるSQL文法の説明、発生原理から、具体的な対策についてまとめたものです。エスケープ処理をしても、あるいはプレースホルダを用いていても、SQLインジェクション脆弱性が混入する原理や、各エスケープ処理用の関数の生成するエスケープ結果を調査して、安全な方法を解説しています。
上記説明のために、エスケープ処理についてはかなり詳細に説明していますが、それは、エスケープ処理を推奨するためではなく、エスケープ処理が難しいものであるので極力避けて欲しいという意図です。そして、原理的に(パラメータ埋め込みの文脈では)SQLインジェクション脆弱性の混入しない静的プレースホルダを推奨し、各言語での具体的な記述方法を解説しています。
また、エスケープ処理については、文字列と数値に分けて、あるべき処理結果と、各ライブラリの実際の挙動を紹介しています。
安全なSQLの呼び出し方は、公開されてから一度も改版されていないので、「安全になった」という表現は適当でありません。現在のものが安全だとすれば、それはもともと安全であったことを意味します。一方、この冊子の本体である「安全なウェブサイトの作り方」は、脆弱性の概要説明であるので、より一般的・抽象的な表現になっていますが、エスケープの説明について第1版から記述があります。以下は第1版のP4からの引用です。
ということで、「IPAの文書」にエスケープが説明されてなかったなんてことはないはずです。
11. 体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践(徳丸浩著)
エスケープの説明 | 本文中では簡潔に記して「安全なSQLの呼び出し方」を参照 |
---|---|
SQLライブラリ | MDB2 |
対策方針 | プレースホルダ |
数値列の扱い | プレースホルダのため考慮の必要がない |
文字エンコーディング指定 | 接続文字列中に記述 |
拙著ではSQL文字列リテラルのエスケープについては簡単にしか説明していませんが、SQLインジェクションの原理を説明する箇所で以下のように説明しています(P128)。
◆ 文字列リテラルの問題対策に関しては、元々拙著は初心者向けを目指して執筆されたこともあり、複数の方法を示すよりも確実に安全な方法を一つ紹介しようという判断から、実務的な対策方法としてはプレースホルダを主に紹介しています。動的に検索条件やソート列が変化するような「プレースホルダでは書きにくい」と思われてきたクエリについても、プレースホルダでの記述例を紹介しています。
SQLの標準規格では、文字列リテラルはシングルクォートで囲みます。文字列リテラル中にシングルクォートを含めたい場合は、シングルクォートを重ねる決まりです。これを「シングルクォートをエスケープする」と言います。このため、「O'Reilly」をSQLの文字列リテラルにすると、'O''Reilly'になります。
ところが SQLインジェクション脆弱性のあるプログラムでは、シングルクォートを重ねる 処理が抜けているため次のようなSQL文が組み立てられます。
また、SQLインジェクションとは別の問題ですが、LIKE述語のワイルドカード(%と_など)のエスケープが必要なため、こちらのエスケープについては詳しく説明しています。
ということで、拙著においてSQLエスケープは、以下の方針に従っています。
- 原理のところで簡単にエスケープを説明する
- 実務的な対策はプレースホルダ推奨
- どうしてもエスケープが必要な場合は安全なSQLの呼び出し方を読んでね
12. Webアプリセキュリティ対策入門 ~あなたのサイトは大丈夫?(大垣 靖男著)
エスケープの説明 | あり |
---|---|
SQLライブラリ | sqlite関数 |
対策方針 | プレースホルダ(推奨)、またはエスケープ |
数値列の扱い | 数値であることの確認 |
文字エンコーディング指定 | なし |
あらためて大垣本を確認して意外に思ったのですが、大垣本もプレースホルダ推奨だったのですね。一度は読んでいたはずですが、大垣さんの近年のエスケープ推しの印象が強いため忘れていたようです。同書P51には概要のまとめとして下記があります。
対策同じくP146には下記の記述があります。
SQL文のパラメータとなる入力が、数値型である場合は必ず数値型データであることを確認する。文字列型のデータである場合は必ずデータベースシステムにあったエスケープ方式で文字列をエスケープする。プリペアードクエリが利用できる場合は、プリペアードクエリを使用する。
基本的に、データベースが提供する文字列エスケープ関数をすべての変数に使用していれば、脆弱性は発生しません。注意しなければならないのは、データベースによって動作やエスケープしなければならない文字が異なることと、プリペアードクエリのサポートです。ということで、少なくとも同書が出たころ(2006年3月)は、大垣さんのプレースホルダ(プリペアードクエリ)推しだったようです。今はどうなのでしょうか?
データベースがプリペアードクエリをサポートしている場合は、プリペアードクエリを使用した方がよいです。PostgreSQLはプリペアードクエリが利用できます。
まとめ
PHPの教科書9種類と、セキュリティの解説書3種類を読んで、SQLインジェクション対策と関連してエスケープの説明をしているかどうかを確認しました。調査前は、私自身は「SQLインジェクションの解説なら当然エスケープは説明するだろう」と予測していたのですが、PHPの教科書ではエスケープを説明していないモノが多く、プレースホルダを用いてSQLインジェクション対策しているものが目立ちました。
一方、セキュリティの解説書の場合は、今回紹介しなかったものも含め、エスケープについては解説していました。
PHPの解説書ではSQLのエスケープを説明しない場合が多い点については、私は、これはこれでアリだろうと思いました。初学者のうちは、とにかく安全に、SQLインジェクションを混入しない書き方が求められます。この点、「プロになるための PHPプログラミング入門」には、SQLインジェクション脆弱性の発生原因として下記が指摘されています。
自分でパラメータを文字列連結しながらSQL文を組み立てている私は上記に深く同意します。そして、文字列連結なしにSQL文を呼び出そうとすると、必然的にプレースホルダを使わざるを得ません。
また、次の見方もできます。今回調べたPHP入門書の中には、失礼ながら著者自身のプログラミング力量が怪しいものが少なからず見られましたが、その割にはSQLインジェクション脆弱性は見つかりませんでした。これはプレースホルダを使っていれば、(絶対に大丈夫という訳ではないにしても)少々ミスをしてもSQLインジェクション脆弱性の混入は食い止められるということです。このエントリで紹介した唯一のSQLインジェクション例は、エスケープ処理の不適切な実装によるものでした。また、脆弱性ではないにしても、数値列を含むエスケープ処理(あるいはSQLリテラルの正しい構成)は、PHP解説書の著者でさえも難しいらしく、不適切な例が見られました。
全体のまとめは下記の通りです。
- PHP初心者がSQLを呼び出す場合は、「ともかく文字列連結でSQL文を組み立てるな、プレースホルダを使え」という指導は有効
- SQLインジェクションについて深く知るためにはエスケープ処理を知る必要がある
- SQLの動的組み立てを必要とするフレームワーク、O/Rマッパー、データベース管理ツール等の開発には、上記ルールだけでは不足だが、少数の優れた開発者が担当するものであり、必要に応じて高度な技術を学んでもらえばよい
次回はでこくん(@dekokun)ですね。よろしくお願いします。
0 件のコメント:
コメントを投稿