2015年5月18日月曜日

『最初に「読む」PHP』は全体的にとても良いが惜しい脆弱性がある

最初に「読む」PHP(クジラ飛行机)を読みました。本書にはセキュリティ用語(クロスサイトスクリプティング、SQLインジェクション等)はほとんど出てきませんが、脆弱性についてよく配慮された記述となっています。しかし、その細部の詰めが甘く、脆弱性が混入してしまいました。その内容を報告したいと思います。

クロスサイトスクリプティング

出力時にHTMLエスケープするという原則を比較的早い段階で説明しています。その説明が素晴らしいと思いました。
ユーザーから送信されたデータを、PHPを使ってそのまま画面に表示することは、レイアウトの崩れや セキュリティ上の危険につながります。必ず、HTMLに変換してから表示します。そこで、画面に「(^o^)<Hello」と表示するプログラムを作ってみましょう。
「HTMLに変換して」という表現がとてもいいですね。難しくいうと、Content-Typeをtext/plainからtext/htmlに変換するということですが、平たくいうと、(プレーンテキスト文字列を)HTMLに変換するということです。
そして、以下のサンプルプログラムが続きます。
<?php
$s = "(^o^)<hello";
echo $s;
?>
実行結果は以下の通りです。「<Hello」がタグとみなされて消えていますね。


このように、htmlspecialcharsというのは、文字列をHTMLに変換するものという説明は本質をついているように思います。そして、本書には「クロスサイトスクリプティング」という用語は出てきませんが、作成するアプリケーションにはクロスサイトスクリプティング脆弱性が入り込まないように配慮して説明されています。
ただし、残念ながら細かい詰めが甘いため、本書で紹介されているスクリプトにはクロスサイトスクリプティング脆弱性が含まれますが、それについては後述します。

SQLインジェクション

PHP入門書としては珍しくエスケープとプレースホルダ(プリペアードステートメント)の両方が説明されています。まずはエスケープですが、以下のように説明されています。
次に、データベースにデータを挿入します。exec()メソッドでSQLを実行するという点は同じなのですが、ここで1つ注意したいことがあります。それは、PHPでSQLを実行するときには、SQLは文字列で指定しなくてはならないという点です。
そのため、挿入する値の中に、SQLの意味を変えてしまうような値(例えば、文字列を表す引用符「'」や「"」)があると、開発者の意図に反したSQLになってしまうことがあります。
そこで、データベースに値を挿入する際には、$db->quote()メソッドを使って、あらかじめ、文字列に含まれる引用符「'」や「"」をエスケープしておきます。
「SQLインジェクション」という用語を用いずに、エスケープの必要性を説明しています。本来こうあるべきですよね。私はこの説明の方針に賛同します。エスケープのスクリプトは以下の通りです。
$item_id = intval($i["item_id"]);
$name    = $db->quote($i["name"]);
$price   = intval($i["price"]);
$result = $db->exec(
    "INSERT INTO items (item_id, name, price)".
    "VALUES($item_id, $name, $price)");
整数列はintval関数で整数に変換し、文字列型の列はPDOのquoteメソッドにより、エスケープした文字列をクォートしています。この書き方は間違える要素は少ないので、エスケープ手法を取る場合には良い方法だと思います。

※ 本来、quoteメソッドで整数リテラルも作成できるべきなのですが、PDOのquoteメソッドはできがよくない(参考)ので、文字列に限定して使うのは賢明です。

その後、プレースホルダ(プリペアードステートメント)を説明し、本番のスクリプトはプレースホルダで書いています。
ところが、人間はうっかりするのが得意な生き物です。うっかりすると、クォート処理を書き忘れる可能性があります。
そこで、SQLのひな型を先に書いておいて、そこに値を当てはめる方法でSQLを実行する「プリペアードステートメント」という機能が用意されています。プリペアードステートメントを使うと、ひな型をあらかじめコンパイルしておいて、そこに後から値を差し込むことができます。また、自動的に面倒なクォート処理を行ってくれるので、とても便利です。
微妙に説明がおかしい(動的プレースホルダと静的プレースホルダの説明が混ざっている)のが気になりますが、説明の方針としては良い感じです。その後、本書では一貫してプレースホルダによりSQLを呼び出しているので、本書内にSQLインジェクション脆弱性はないようです。

本書ではトランザクションの説明もある

以前ブログ記事「嵐のコンサートがあるとダブルブッキングしてしまうホテル予約システムを作ってみた」にて私は以下のように書きました。
PHP入門書ないし中級レベルの解説書では、通常トランザクション処理や排他制御の解説はありません。
ところが本書は、「最初に『読む』」といううたい文句の本であるにも関わらず、トランザクションの説明があります。素晴らしいですね。
トランザクション(transaction)とは、分けることのできない一連の情報処理の単位です。このサランザクションを理解するには、銀行の決済処理を考えると分かりやすいと思います。
例えばAさんがBさんんの口座へ50万円の振り込みを行うとします。このとき銀行システムが行うのは、次の2つの動作です。

(1)Aさんの口座から50万円減らす
(2)Bさんの口座を50万円増やす

このとき、もし、Aさんの口座から50万円を減らした時点で何かしらのトラブルが起きて、決済処理が止まってしまったとします。すると、Bさんの口座に振り込まれていないにも関わらず、Aさんの口座だけが減っているという、困った状況が発生してしまいます。
これを避けるためにトランザクションが存在します。つまり、一連の振り込み処理を最終的に完了してはじめて、実際にデータベースの値が変更されるという仕組みなのです。
上記の処理ではトランザクションに加えて行ロックなどによる排他制御も必要ですが、それは説明されていません。そこは残念ですが、入門書の範囲を超えるということなのでしょう。本書の読者は、本書をきっかけとしてトランザクションなど本格的なSQLの勉強に進むことを期待します。

本書の残念なところ

本書のSQLインジェクション対策は十分なようですが、クロスサイトスクリプティングについては抜けがあり、エスケープの詳細に問題があります。それは、属性値の扱いです。
本書では、素のHTMLでは属性値をダブルクォートで囲み、PHPスクリプトではシングルクォートで囲んでいます。属性値をシングルクォートで囲む場合、HTMLエスケープの際にシングルクォートをエスケープするためにhtmlspecialcharsでENT_QUOTESを指定する必要がありますが、それが抜けています。
下記は、P141のselect-color.phpというスクリプトの一部で、クエリ文字列colorにより背景色を選択するというものです。
// 色が送信されているか調べて背景色を決定する
$color = "#FFFFFF";          // デフォルトは白色
if (isset($_GET["color"])) { // 色が送信されているか?
  $color = htmlspecialchars($_GET["color"]);
}
echo "<html><body bgcolor='$color'>";        // HTMLのヘッダを表示
ご覧のように、属性値bgcolorをシングルクォートで囲っているにも関わらず、htmlspecicalcharsにENT_QUOTESが指定されていません。そのため、colorとして、下記の文字列を指定するとクロスサイトスクリプティング攻撃ができます。
'+onload%3d'alert(1)
この結果生成されるHTMLは下記の通りです。
<html><body bgcolor='' onload='alert(1)'>
表示は下記のようになり、JavaScriptが実行されます。


これを避けるには、以下のいずれか(両方でもよい)を実施する必要があります。
  • 属性値をダブルクォートで囲む
  • htmlspecialcharsの第2引数にENT_QUOTESを指定する

まとめ

『最初に「読む」PHP』の脆弱性対処の状況について説明しました。一言で言うと、以下の感想です。
  • セキュリティの専門用語を使わずに脆弱性対処を自然な形で説明するという全体方針はとてもよい
  • 属性値の扱いでクロスサイトスクリプティング脆弱性が入ってしまったのは痛恨の極みだ
入門書に対して細かいことを言うと思われるかもしれませんが、セキュリティは細かいことが重要なのです。本書は、全体としてはとても良くかけていて、セキュリティに関しても注意深く取り扱われています。それだけになおのこと、この記事で指摘した「漏れ」で脆弱性が入ってしまったことはとても残念に思いました。
一方、SQLインジェクションについては特に問題なく、こちらの記事で書いたように、以下の状況が継続しています。
PHPの入門書ではSQLインジェクション脆弱性がないことが当たり前になった
これは、PDOでプレースホルダを使うという、PHPのSQLインジェクション対策の決定版といえる書き方が普及したからと言え、とても好ましいと感じました。


【HASHコンサルティング広告】
HASHコンサルティング株式会社は、セキュリティエンジニアを募集しています。
興味のある方は、twitterfacebookのメッセージ、あるいは問い合わせページからお問い合わせください。


0 件のコメント:

コメントを投稿

フォロワー