クロスサイトスクリプティング
出力時にHTMLエスケープするという原則を比較的早い段階で説明しています。その説明が素晴らしいと思いました。ユーザーから送信されたデータを、PHPを使ってそのまま画面に表示することは、レイアウトの崩れや セキュリティ上の危険につながります。必ず、HTMLに変換してから表示します。そこで、画面に「(^o^)<Hello」と表示するプログラムを作ってみましょう。「HTMLに変換して」という表現がとてもいいですね。難しくいうと、Content-Typeをtext/plainからtext/htmlに変換するということですが、平たくいうと、(プレーンテキスト文字列を)HTMLに変換するということです。
そして、以下のサンプルプログラムが続きます。
実行結果は以下の通りです。「<Hello」がタグとみなされて消えていますね。<?php $s = "(^o^)<hello"; echo $s; ?>
このように、htmlspecialcharsというのは、文字列をHTMLに変換するものという説明は本質をついているように思います。そして、本書には「クロスサイトスクリプティング」という用語は出てきませんが、作成するアプリケーションにはクロスサイトスクリプティング脆弱性が入り込まないように配慮して説明されています。
ただし、残念ながら細かい詰めが甘いため、本書で紹介されているスクリプトにはクロスサイトスクリプティング脆弱性が含まれますが、それについては後述します。
SQLインジェクション
PHP入門書としては珍しくエスケープとプレースホルダ(プリペアードステートメント)の両方が説明されています。まずはエスケープですが、以下のように説明されています。次に、データベースにデータを挿入します。exec()メソッドでSQLを実行するという点は同じなのですが、ここで1つ注意したいことがあります。それは、PHPでSQLを実行するときには、SQLは文字列で指定しなくてはならないという点です。「SQLインジェクション」という用語を用いずに、エスケープの必要性を説明しています。本来こうあるべきですよね。私はこの説明の方針に賛同します。エスケープのスクリプトは以下の通りです。
そのため、挿入する値の中に、SQLの意味を変えてしまうような値(例えば、文字列を表す引用符「'」や「"」)があると、開発者の意図に反したSQLになってしまうことがあります。
そこで、データベースに値を挿入する際には、$db->quote()メソッドを使って、あらかじめ、文字列に含まれる引用符「'」や「"」をエスケープしておきます。
整数列はintval関数で整数に変換し、文字列型の列はPDOのquoteメソッドにより、エスケープした文字列をクォートしています。この書き方は間違える要素は少ないので、エスケープ手法を取る場合には良い方法だと思います。$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)");
※ 本来、quoteメソッドで整数リテラルも作成できるべきなのですが、PDOのquoteメソッドはできがよくない(参考)ので、文字列に限定して使うのは賢明です。
その後、プレースホルダ(プリペアードステートメント)を説明し、本番のスクリプトはプレースホルダで書いています。
ところが、人間はうっかりするのが得意な生き物です。うっかりすると、クォート処理を書き忘れる可能性があります。微妙に説明がおかしい(動的プレースホルダと静的プレースホルダの説明が混ざっている)のが気になりますが、説明の方針としては良い感じです。その後、本書では一貫してプレースホルダによりSQLを呼び出しているので、本書内にSQLインジェクション脆弱性はないようです。
そこで、SQLのひな型を先に書いておいて、そこに値を当てはめる方法でSQLを実行する「プリペアードステートメント」という機能が用意されています。プリペアードステートメントを使うと、ひな型をあらかじめコンパイルしておいて、そこに後から値を差し込むことができます。また、自動的に面倒なクォート処理を行ってくれるので、とても便利です。
本書ではトランザクションの説明もある
以前ブログ記事「嵐のコンサートがあるとダブルブッキングしてしまうホテル予約システムを作ってみた」にて私は以下のように書きました。PHP入門書ないし中級レベルの解説書では、通常トランザクション処理や排他制御の解説はありません。ところが本書は、「最初に『読む』」といううたい文句の本であるにも関わらず、トランザクションの説明があります。素晴らしいですね。
トランザクション(transaction)とは、分けることのできない一連の情報処理の単位です。このサランザクションを理解するには、銀行の決済処理を考えると分かりやすいと思います。上記の処理ではトランザクションに加えて行ロックなどによる排他制御も必要ですが、それは説明されていません。そこは残念ですが、入門書の範囲を超えるということなのでしょう。本書の読者は、本書をきっかけとしてトランザクションなど本格的なSQLの勉強に進むことを期待します。
例えばAさんがBさんんの口座へ50万円の振り込みを行うとします。このとき銀行システムが行うのは、次の2つの動作です。
(1)Aさんの口座から50万円減らす
(2)Bさんの口座を50万円増やす
このとき、もし、Aさんの口座から50万円を減らした時点で何かしらのトラブルが起きて、決済処理が止まってしまったとします。すると、Bさんの口座に振り込まれていないにも関わらず、Aさんの口座だけが減っているという、困った状況が発生してしまいます。
これを避けるためにトランザクションが存在します。つまり、一連の振り込み処理を最終的に完了してはじめて、実際にデータベースの値が変更されるという仕組みなのです。
本書の残念なところ
本書の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として、下記の文字列を指定するとクロスサイトスクリプティング攻撃ができます。この結果生成されるHTMLは下記の通りです。'+onload%3d'alert(1)
表示は下記のようになり、JavaScriptが実行されます。<html><body bgcolor='' onload='alert(1)'>
これを避けるには、以下のいずれか(両方でもよい)を実施する必要があります。
- 属性値をダブルクォートで囲む
- htmlspecialcharsの第2引数にENT_QUOTESを指定する
まとめ
『最初に「読む」PHP』の脆弱性対処の状況について説明しました。一言で言うと、以下の感想です。- セキュリティの専門用語を使わずに脆弱性対処を自然な形で説明するという全体方針はとてもよい
- 属性値の扱いでクロスサイトスクリプティング脆弱性が入ってしまったのは痛恨の極みだ
一方、SQLインジェクションについては特に問題なく、こちらの記事で書いたように、以下の状況が継続しています。
PHPの入門書ではSQLインジェクション脆弱性がないことが当たり前になったこれは、PDOでプレースホルダを使うという、PHPのSQLインジェクション対策の決定版といえる書き方が普及したからと言え、とても好ましいと感じました。
【HASHコンサルティング広告】
HASHコンサルティング株式会社は、セキュリティエンジニアを募集しています。
興味のある方は、twitterやfacebookのメッセージ、あるいは問い合わせページからお問い合わせください。
0 件のコメント:
コメントを投稿