[PR]小規模ECサイトに最適なWAF、SiteGuard Lite
徳丸浩の日記
2008年10月29日
_書籍「はじめてのPHPプログラミング基本編5.3対応」にSQLインジェクション脆弱性
id:hasegawayosuke氏にそそのかされるような格好で、「はじめてのPHPプログラミング基本編5.3対応」という書籍を購入した。
本書は、ウノウ株式会社の下岡秀幸氏、中村悟氏の共著なので、現役バリバリのPHP開発者が執筆しているということ、下記のようにセキュリティのことも少しは記述されているらしいという期待から購入したものだ。
目次から抜粋引用 07-07 Webアプリケーションのセキュリティ [セキュリティ] 08-04 データベースのセキュリティ [SQLインジェクション] 09-13 セキュリティ対策 [セキュリティ]
本書をざっと眺めた印象は、「ゆるいなぁ」というものであるが、その「ゆるさ」のゆえんはおいおい報告するとして、その経過で致命的な脆弱性を発見したので報告する。
問題の報告
それは、本書P280に登場する「SQLインジェクション対策用の関数(dbescape)」だ。この関数を本書から引用する。
// SQLインジェクション対策用の関数 function dbescape($sql, array $params) { foreach ($params as $param) { // パラメータの型によって埋め込み型を変える switch (gettype($param)) { case "integer": case "double": $replacement = $param; break; case "string": // 文字列の場合はエスケープ処理をおこなう $replacement = sprintf("'%s'", sqlite_escape_string($param)); break; default: die("パラメータの型が正しくありません"); } // SQLを置換し、パラメータを埋め込む $sql = substr_replace($sql, $replacement, strpos($sql, "?"), 1); } // すべてパラメータを埋め込んだSQLを返す return $sql; }
この関数は、「穴埋め形式のSQL文字列と、埋め込むパラメータの配列を受け取り、必要なエスケープ処理を施したSQL文字列を返します」とのことで、以下のように用いる。
$sql = dbescape("SELECT COUNT(id) FROM friend WHERE from_name = ? AND to_name = ?", array($from_name, $to_name));
これに対して、$from_name = "Johnson"、$to_name = "M'Intosh" として上記を実行すると、以下のような文字列が返る
SELECT COUNT(id) FROM friend WHERE from_name = 'Johnson' AND to_name = 'M''Intosh'
すなわち、バインド機構を自前で実現したようなインターフェースである。
この実装を見た瞬間、違和感を感じた。これは書式文字列の処理に属するものであるので、通常は書式文字列($sql)を左から調べて、書式記号(?)が出てくるたびに、対応するパラメータの処理を行うのが定石的な実装だと思う。そうでないと(上記の場合は出てこないが)書式などのエスケープを上手く処理できない。しかるに、引用した関数では、パラメータの方を調べながら、対応する書式記号(?)を探している。しかも、未処理の部分と処理済の部分がごちゃまぜになっているので、まずいことが起こりそうである。
そう、この関数にはバグがある。パラメータとして"?"を含む文字列を与えた場合、元の穴埋め式SQLに存在した"?"と、新たに埋め込まれた"?"がごっちゃになる。試してみよう。先の例に、第一パラメータとして"?"、第二パラメータとして"AAA"を与えた場合の処理の流れは以下のようになる
0:SELECT COUNT(id) FROM friend WHERE from_name = ? AND to_name = ? ↑ '?' に置き換え 1:SELECT COUNT(id) FROM friend WHERE from_name = '?' AND to_name = ? ↑ 'AAA' に置き換え 2:SELECT COUNT(id) FROM friend WHERE from_name = ''AAA'' AND to_name = ?
ご覧のように、第一パラメータの変換結果である '?' から、さらに?部分が'AAA'に置き換わることから、意図した結果を得られない。しかも、右側の"?"があまってしまい、SQLの文法違反となる。

それだけならまだよいのだが、AAAの部分に着目いただきたい。この部分は外部から与えた文字列なので、シングルクォートで囲まれてなければならないのだが、上記の過程で、文字列リテラルからはみ出した、すなわちSQL文の式の一部として解釈される状態となった。この時点でSQLインジェクション脆弱性といえる(ライオン(=外部からの文字列)が檻(=文字列リテラル)から抜け出した状態)。
従って、AAAの代わりにSQL文をセットしてやれば、任意のSQLが実行できることになる。やってみよう。今度は第二パラメータとして"or 1=1--"をセットしてやる。変換後のSQLはこうなる。
SELECT COUNT(id) FROM friend WHERE from_name = ''or 1=1--'' AND to_name = ?
本書で想定しているRDB(SQLite)では、--は標準SQL同様コメントとなるので、--から先は無視される。すなわち、SQLの意味が書き換えられた。SQLiteではUNIONをサポートしているし、更新系SQLでは複文をサポートするようなので、様々な悪用が可能となる。
教訓
あらゆるバグは脆弱性になり得る
どうすべきだったか
汎用的なsqlエスケープ関数を用意して、対策をこの関数にカプセル化しようという心意気はよかったのだが、あいにくこの関数にバグがあって、意図がかえって仇となる結果となった。では、どうすればよかったか。
思うに、バインド機構に似た機能を自作しようというのが間違いで、そんなに簡単にできるものではない。PHPのsqlite_xxxx系の関数にはバインド機構が用意されていないようだが、PDOを利用することで、SQLiteでもバインド機構が利用できる。
あるいは、SQLiteの使用をあきらめ、MySQLを使ってもよかった。本書のカバーには「MySQL(データベース)」とある(これはCD-ROMにMySQLが添付されているということらしい)。WindowsでもMySQLは動作するし、実務でも利用機会はMySQLの方がずっと多いだろう。MySQLであれば、mysqli系の関数でバインド機構が利用できる。さらに大切なこととして、「SQLインジェクション対策は原則としてバインド機構を用いるべし」という原則を教えることもできるのだから。
- https://www.google.co.jp/ ×116
- http://ask.fm/tokumaru ×18
- http://ask.fm/tokumaru/answer/107652549693 ×13
- http://www.doyouphp.jp/book/book_hajimete.shtml ×10
- http://d.hatena.ne.jp/ockeghem/20081109/p1 ×7
- http://d.hatena.ne.jp/shimooka/20081029/1225247796... ×3
- http://www.tokumaru.org/ ×3
- https://www.google.com/ ×2
- http://websearch.excite.co.jp/?q=はじめてのPHPプログラミング&s... ×2
- http://ask.fm/tokumaru/best ×2
- http://www42.tok2.com/home/sqldatabase/tag/28795/1... ×1
- http://www42.tok2.com/home/sqldatabase/tag/70677/1... ×1
- http://www42.tok2.com/home/sqldatabase/tag/3455/1_... ×1
- https://www.google.co.th/ ×1
- http://www42.tok2.com/home/sqldatabase/tag/13811/1... ×1
- http://www.sogou.com/web?query=昆明哪有卖玩具枪的&page=7 ×1
- http://www.sogou.com/web?query=成人影片WW&page=19 ×1
- http://www.ji2.co.jp/forensics/ForensicsAQ/artifac... ×1
- http://search.fenrir-inc.com/?hl=ja&channel=sleipn... ×1
- http://kjunichi.cocolog-nifty.com/misc/ ×1
- http://favicon.qfor.info/ ×1
- http://d.hatena.ne.jp/ockeghem/touch/20081109/p1 ×1
- http://bakera.jp/ebi/topic/3330 ×1
- http://bakera.jp/ebi/2008/10/29 ×1
- http://bakera.jp/ebi/2008/10 ×1
- http://ask.fm/account/wall ×1
- http://9jp.info/archives/3560 ×1
- heartbeat 脆弱性 どうゆような対応 ×3 / SQL エスケープすべきもの ×1 / sql バインド 自前 変換 ×1 / php oracle 脆弱性 ×1 / php 書籍 徳丸浩 ×1 / dbEscape ×1 / sqlite substr バグ ×1 / PHP dbEscape ×1 / PHP SQL エスケープ 汎用 ×1 / 脆弱性 プログラミング ×1 / 文字列配列穴埋め ×1 / sql バインド機構 ×1
[PR]小規模ECサイトに最適なWAF、SiteGuard Lite
HASHコンサルティング株式会社
最近の記事
- 2011年08月30日
- 1. RSSフィードをリダイレクトします
- 2011年07月01日
- 1.
- 2011年03月29日
- 1. PDO/MySQL(Windows版)の文字エンコーディング指定の不具合原因
- 2011年03月22日
- 1. PHP5.3.6からPDOの文字エンコーディング指定が可能となったがWindows版では不具合(脆弱性)あり
- 2011年01月27日
- 1. CSRF対策のトークンをワンタイムにしたら意図に反して脆弱になった実装例
- 2011年01月04日
- 1. escapeshellcmdの危険な実例
- 2011年01月01日
- 1. PHPのescapeshellcmdの危険性
- 2010年10月03日
- 1. 問題点の概要
- 2010年09月27日
- 1. 文字コードに起因する脆弱性を防ぐ「やや安全な」php.ini設定
- 2010年07月25日
- 1. ツッコミSPAM対策で、ツッコミ抜きのRSSフィードを用意しました
- 2010年07月01日
- 1. ぼくがPDOを採用しなかったわけ(Shift_JISによるSQLインジェクション)
- 2010年04月06日
- 1. PROXY(プロキシ)経由でのDNSリバインディングと対策
- 2010年04月05日
- 1. JavaアプレットのDNSリバインディングはJRE側で対策済みだった
- 2010年03月29日
- 1. DNSリバインディングによる無線LANパスフレーズの読み出しに成功
- 2010年03月25日
- 1. DNSリバインディングによるルータへの侵入実験
- 2010年02月22日
- 1. ケータイtwitter(twtr.jp)においてDNS Rebinding攻撃に対する脆弱性を発見・通報し、即座に修正された
- 2010年02月12日
- 1. かんたんログイン手法の脆弱性に対する責任は誰にあるのか
- 2010年01月18日
- 1. iモードブラウザ2.0のXMLHttpRequestでPOSTデータの扱いが困難になった
- 2009年10月19日
- 1. quoteメソッドの数値データ対応を検証する
- 2009年10月14日
- 1. htmlspecialchars/htmlentitiesはBMP外の文字を正しく扱えない
- 2009年10月09日
- 1. htmlspecialcharsのShift_JISチェック漏れによるXSS回避策
- 2009年09月30日
- 1. htmlspecialcharsは不正な文字エンコーディングをどこまでチェックするか
- 2009年09月24日
- 1. SQLの暗黙の型変換はワナがいっぱい
- 2009年09月18日
- 1. 文字エンコーディングバリデーションは自動化が望ましい
- 2009年09月14日
- 1. 既にあたり前になりつつある文字エンコーディングバリデーション
- 2009年08月05日
- 1. 携帯JavaScriptとXSSの組み合わせによる「かんたんログイン」なりすましの可能性
- 2009年03月28日
- 1. IPAは脆弱性の呼び方を統一して欲しい
- 2009年03月27日
- 2009年03月11日
- 1. U+00A5を用いたXSSの可能性
- 2008年12月22日
- 1. JavaとMySQLの組み合わせでUnicodeのU+00A5を用いたSQLインジェクションの可能性