2015年4月21日火曜日

PHP入門書のSQLインジェクションとXSS対策をあらためて調べてみた

継続的にPHP入門書のセキュリティ問題を確認していますが、今回は「やさしいPHP 第3版」を取り上げ、今どきのPHP入門書のセキュリティ状況を報告したいと思います。
上記のように、2008年に初版が出版された後2回の改版がありました。
第2版ではクロスサイトスクリプティング(XSS)の説明が追加され、第3版ではXSSに加えSQLインジェクションの説明が追加されました。つまり、初版ではこれらの説明はなかったということです。

第3版におけるSQLインジェクションの対策方法はプレースホルダによるもので、結果として本書にSQLインジェクション脆弱性は見当たりません(パチパチパチ)。本書にはSQLの(文字列リテラルの)エスケープに関する説明はありませんが、これは、「PHPとセキュリティの解説書12種類を読んでSQLエスケープの解説状況を調べてみた」で報告したように最近のPHPの解説書のトレンドで、私の記事にも書いたようにPHP入門書レベルならそれで十分かと思います。最初のうちは、安全なプログラムを自然な形で書けることが重要だと思うからです。 ということで、以下を宣言させてもらいたいと考えます。
PHPの入門書ではSQLインジェクション脆弱性がないことが当たり前になった
一方、XSSはどうでしょうか。本書第3版では、P362に以下の説明があります。
フォームからの入力情報に注意する
Webアプリケーションでは、セキュリティに注意しなければなりません。特にフォームから入力する場合には注意すべきことが多くあります
この後、XSSの攻撃例やHTMLエスケープの方法などが続きます…が、「フォームからの入力情報に注意」という表現にとても嫌な予感がしますね。「フォームからの入力」以外はどうなっているかと調べてみると、同書P400のSample8.phpにあるように、入力値以外にはHTMLエスケープ処理が入っていません。以下に引用しますが、入力した内容のデータベースへの登録と、データベースの内容の表示処理があります。
// 以下、データの追加
if(isset($_POST["insert"])){
  $ipd = $_POST["insproduct"];
  $ipc = $_POST["insprice"];
  $qry ="INSERT INTO product(name, price) VALUES(:ipd, :ipc)";
  $stmt = $db->prepare($qry);
  $stmt->bindParam(":ipd", $ipd);
  $stmt->bindParam(":ipc", $ipc);
  $stmt->execute();
}

// 中略

$qry = "SELECT * FROM product";
$data = $db->query($qry);

// 中略。以下、データの表示

while($value = $data->fetch()){
  $id = $value["id"];
  $name = $value["name"];
  $price = $value["price"];
  print "<tr><td><input type=\"radio\" name=\"delid\" value=\"{$id}\"/></td>
             <td>{$id}</td><td>{$name}</td><td>{$price}</td></tr>\n";
}
フォームから入力された文字列がデータベースに登録され、それが表示される過程でHTMLエスケープは行われていません(上記赤字部分参照)。その結果、クロスサイトスクリプティング脆弱性が存在します。以下の文字列を製品名の欄に入力してやると…
<script>alert(1)</script>
下記のようにJavaScriptが実行されます。



ところで、この画面表示、よく見ると文字が化けています。調べてみると、本来UTF-8でレスポンスが送信されているのに、レスポンスに文字エンコーディングの指定がないので、ブラウザにShift_JISとみなされ、文字化けの原因になっています。これはXSSの原因になりえます。また、そもそも文字化けが起こるということは、アプリケーションとして不完全な状態です。
PHPの場合、HTTPレスポンスの文字エンコーディングを指定する方法には以下の3種類がありますが本書では特に説明されていません。
  • header関数で指定する
  • php.iniのdefault_charsetで指定する
  • HTML内に指定する
header関数で指定する
header関数によりContent-Typeヘッダを出力してそこでcharsetを指定する方法がもっとも確実です。
header('Content-Type: text/html; charset=UTF-8');

php.iniのdefault_charsetで指定する
php.iniにおいて以下の指定を有効にすることでも、同様の指定ができます。PHP-5.6.0以降ではこれがデフォルトになりました。
default_charset = "UTF-8"

HTML内に指定する
HTMLのhead要素内で以下を指定することでもcharsetを指定できます。php.iniの変更ができない環境等では便利です。
<meta charset="UTF-8">

結局XSS対策として最低限説明すべきことは何か?

今まで説明したように、「やさしいPHP」のXSS対策の解説は不十分です。それではどう書けばよいかですが、IPA「安全なウェブサイトの作り方 改訂 第7版」には、以下の説明があります(抜粋)。
5-(i) ウェブページに出力する全ての要素に対して、エスケープ処理を施す。
   解説内:また、HTMLタグを出力する場合は、その属性値を必ず「"」(ダブルクォート)で括る
5-(viii) HTTPレスポンスヘッダのContent-Typeフィールドに文字コード(charset)を指定する。
本書の場合、これらのいずれもが十分ではありません。

まとめ

書籍「やさしいPHP 第3版」のSQLインジェクションとXSSの解説状況について報告しました。SQLインジェクションに関してはプレースホルダの使用により脆弱性や説明の誤りはありませんでした(パチパチパチ)。一方、XSSに関しては、htmlspecialcharsを用いてHTMLエスケープを行うという概要の説明は正しかったものの、その適用が十分ではありませんでした。
実はこの問題は多くのPHP入門書に共通する問題です。対策自体は十分に行えている解説書でも、入力した文字列をまずHTMLエスケープしてから扱っている書籍も多く、「表示の際にエスケープする」という基本が実践できている書籍は、入門書にはあまりなく、中級者以上向けの書籍でないと解説されていない傾向があります。
しかし、せっかくPHPを勉強するのであれば、最初から正しい方法を学びたいものです。ということで、PHPの入門書を書く方には、「出力の際にHTMLエスケープ」を徹底していただきたいと希望します。

おまけ

今回紹介した以外に、書籍「やさしいPHP 第3版」には以下のセキュリティ上の問題があります。

ファイルアップロードのサンプルにおいて、ファイル名の拡張子チェックなどがないので、そのまま使うと任意スクリプトのアップロードと実行ができる。すなわち、スクリプトインジェクション相当の脆弱性がある(10.6)

ファイルの読み書きのサンプルにディレクトリトラバーサル脆弱性がある。とくに、ファイルへの書き込みのサンプルが深刻で、任意のスクリプトをPHPファイルとして書き込めるのでスクリプトインジェクション脆弱性相当の危険性がある(12.2)。

外部コマンドの実行において、OSコマンドインジェクションの説明が一応あるが、エスケープ用関数としてescapeshellcmd()を紹介しているので、こちらで紹介しているような問題が残る(12.2)。

0 件のコメント:

コメントを投稿

フォロワー