| SQLインジェクション対策はおすみですか? 開発開始時点からのコンサルティングから、公開済みWebサイトの脆弱性検査、 脆弱性発見後の適切な対策まで |
2008-10-14
●書籍「PHP×携帯サイト デベロッパーズバイブル」の脆弱性
「PHP×携帯サイト デベロッパーズバイブル」という携帯サイト開発のノウハウを解説した書籍が今月初頭に発売され、話題になっている。Amazonの「インターネット・Web開発」カテゴリで1位ということで、たいしたものだ。私も発売前から予約して購入した。
私がこの書籍を購入した動機は大きく二つある。一つは、携帯サイトの最新の開発ノウハウをまとめた書籍に対する期待をしていたということ。もう一つは、セキュリティに対する記述がどの程度あるのかを見てみたいというものだった。
このうち、前者については、期待は叶えられた。非常に盛りだくさんのテーマが手際よくまとめられていて、かつ読みやすい。あまり原理・理屈のことは書いていないが、開発現場では、コピペの情報源として重宝されることだろう。
しかし、問題はセキュリティについての記述である。
本社のサンプルをざっと眺めた限りでは、クロスサイトスクリプティング(XSS)やSQLインジェクションが問題になりそうなサンプルは見あたらなかった(DBは使用しておらず、表示は固定的なものが多いため)。一方、携帯向けWebアプリケーションのセキュリティ問題の定番である認証とセッション管理については問題がある。具体的には、本書の7章である。
著者のブログから、本書の7章の目次を引用する。
Chapter.7 ログイン状態の管理 7-1 本章のゴール 7-2 携帯を使ったログイン 7-2-1 IDとパスワードのログイン 7-2-2 個体識別情報によるログイン 7-3 個体識別情報を理解する 7-3-1 個体識別情報の種類 7-3-2 docomoの個体識別情報 7-3-3 auの個体識別情報 7-3-4 SoftBankの個体識別情報 7-4 ログイン状態を維持する方法 7-4-1 ログイン状態を維持するとは 7-4-2 携帯サイトでのセッション管理 7-5 かんたんログインを実装する
このうち、「7-4-2 携帯サイトでのセッション管理」と「7-5 かんたんログインを実装する」が問題である。
携帯サイトでのセッション管理については、Cookieを実装していないdocomoやSoftbankの一部の端末を考慮して、URLにセッションIDを付与するように説明している。これ自体は正しいのだが、URLにセッションIDを付与した場合のセキュリティ上の注意点が抜けている。既によく知られたことだが、セッションIDをURL上に保持した場合、HTTP Refererにより、他のドメインにセッションIDが漏洩する。また、ユーザの不注意により、セッションID付きのURLをソーシャルブックマークなどで公開したり、メールで知人に送信した結果、セッション情報が外部に漏洩するような事故が現実に発生している。
「かんたんログインを実装する」については、PCで個体識別番号やユーザエージェントを偽装することによる「なりすまし」対策について記述されていない。すなわち、PCからだと自ら収集するなどした個体識別情報により簡単になりすましができるので、ケータイのゲートウェイからのIPアドレスのみアクセスできるように限定しなければならないのだが、その説明が抜けている。
いや、厳密には、以下のように、7章の冒頭の図(本書P250から引用)を見ると、キャリアゲートウェイの「IPアドレスチェック」をせよと指示しているようにも受け取れるのだが、3章「キャリア/機種の判別」を読むと、キャリアの識別をする理由は、HTMLや画像の規格の違いを吸収して適切な表示を行うことが目的と説明されており、セキュリティに関する記述はない。そのため、IPアドレスのチェックが必須だとも書いていない。これでは、本書の読者の多くが、IPアドレス制限をかけないまま「かんたんログイン」機能を実装・公開する懸念がある。
「かんたんログイン」機能そのものは高木浩光氏によりセキュリティ上の問題が疑問視されている(無責任なキャリア様に群がるIDクレクレ乞食 ―― 退化してゆく日本のWeb開発者)のだが、そこで議論されている問題は、キャリアゲートウェイのIPアドレス情報が正しくサイト運営者に行き渡るのかという問題であって、本書のように、キャリアのゲートウェイの確認をしていないのは問題外といえよう。
本書が価値の高い情報を満載していて全体として価値の高い書籍であること、冒頭で述べたように多くの読者が購入しているだろうことを考えると、セキュリティに対する配慮の欠如を惜しむとともに、本書のコピペによる脆弱なサイトが乱造されることを強く懸念する次第である。
参考文献:
WASForum Conference 2008: 携帯電話向けWebのセキュリティ
携帯電話向けWebアプリの脆弱性事情はどうなっているのか
携帯電話向けWebアプリケーションのセッション管理手法
プログラミング解説書籍の脆弱性をどうするか
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であれば、mysql_xxxx系の関数でバインド機構が利用できる。さらに大切なこととして、「SQLインジェクション対策は原則としてバインド機構を用いるべし」という原則を教えることもできるのだから。
| SQLインジェクション対策はおすみですか? 開発開始時点からのコンサルティングから、公開済みWebサイトの脆弱性検査、 脆弱性発見後の適切な対策まで |
★ りゅう [以下のように全く書いていないという訳ではないですが、書いてある位置が実際に影響のある機能の章とかけ離れていたり、問題..]