クロスサイトスクリプティング(XSS)
表示の際にHTMLエスケープするという原則を忠実に守っています。そのため、下記の e() という関数を定義して呼び出しています。その他にもXSS対策として重要な下記の内容を本文でもしっかり説明しています。XSSのためという文脈ではありませんが、むしろXSS対策ではなく自然な形で以下を実践することが重要です。function e($str, $charset = 'UTF-8') { return htmlspecialchars($str, ENT_QUOTES, $charset); }
- 文字コードの指定(php.iniのdefault_charset指定、meta要素による指定の両方)
- 属性値をダブルクォートで囲む
SQLインジェクション
プレースホルダの使用を徹底することによりSQLインジェクション対策をしています。大きな隙はありません。ライブラリとしてはPDOを使い、以下のスクリプトで接続しています。文字エンコーディング指定もしっかりしていて素晴らしいです。ただし、欲を言えば以下を指定しているともっとよかったでしょう。$db = new PDO('mysql:host=localhost;dbname=php10;charset=utf8', 'phpusr', 'phppass');
- $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); // 静的プレースホルダを指定
- $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); //: 例外を投げる
クロスサイトリクエストフォージェリ(CSRF)
PHPの入門書では、ほぼ例外なくCSRFの解説がありませんが、本書ではしっかりCSRFの解説があります。素晴らしいですね。ただし、その実装は少々残念な感じです。CSRF対策の手法としてワンタイムトークンを使っているのですが、トークン生成のアルゴリズムが暗号学的に弱いのです。具体的には以下のスクリプトです。
mt_rand()は暗号学的な擬似乱数生成器ではないので、厳密に言うと、上記で生成するトークンには予測可能性があるということになります。$token = md5(uniqid(mt_rand(), TRUE));
「そんな馬鹿な」と思う読者もいるでしょうが、上記と似て非なる方法で生成されるPHPのセッションIDの予測可能性について検証した論文があり、hnwさんの素晴らしい翻訳で読むことができます。
セッションIDはシードとしてIPアドレスと時刻を用います。上記のmt_randの初期シードはプロセスIDと時刻であり、プロセスIDの変化にはかなり規則性があります。擬似乱数生成器として、セッションID生成のほうがLCG(線形合同法)に対して、こちらのトークンはメルセンヌ・ツイスタですが、予測困難でないことは両者に共通しています。つまり、この程度の仕組みだと、予測困難とはいえないということです。
ではどうすればよいかですが、PHP5.3.0以降を使う前提であれば、openssl_random_pseudo_bytes() 関数を使えばよいでしょう。
なお、ワンタイムトークンにしたことにより、ワンタイムでないセッション限りのトークンと比べて、トークンの予測は容易になってしまいます。これに関しては、以前の記事「CSRF対策のトークンをワンタイムにしたら意図に反して脆弱になった実装例」を参照ください。
パストラバーサル
パストラバーサル(ディレクトリトラバーサル)脆弱性の解説が同書P231にあります。対策方法は、対象ファイルが保存されているディレクトリからファイル一覧を取得し、外部から指定したファイル名がその中に存在するかを確認するという独特な方法です。対策にはなりますが、ファイル数が多い場合に効率が悪そうですね。basename()関数を使うという標準的な方法を解説してくれたらもっと良かったのにと思いました。メールヘッダインジェクション
本書には、なんとメールヘッダインジェクションの説明もあります(P169)。素晴らしいですねぇ。なまじっかなセキュリティ解説書にもメールヘッダインジェクションがない場合も多いのに…対策としては、外部から指定するメールアドレスをバリデーションするというオーソドックスな方法です。
セッションフィクセーション
セッションフィクセーションについて明示的な説明はありませんが、本書では認証にPear::Authを用いていて、Pear::Authの内部でセッションフィクセーション対策が取られています。ファイルアップロード
PHP入門書の多くでファイルアップロード機能を扱っており、本書も例外ではありませんが、ファイルアップロード機能には脆弱性が入りやすくので気をつける必要があります。本書は、かなり注意深い実装がされています。まず、拡張子のチェックです。これに関して以下の説明があります。
例えば、有害なスクリプト(.phpファイル)をアップロードされてしまえば、自分のサーバーを踏み台に好き勝手な処理を実行されてしまう恐れもあります。最低でも、アップロード可能なファイルの拡張子は制限しておくべきです。加えて、getimagesize()関数により、アップロードされたファイルの中身が画像であることを確認しています…が、セキュリティ上の効果はあまり期待できません。セキュリティ目的ではなく、画像であることを少しでも確実にするというチェックなのかもしれません。
実は、もうひと踏ん張り確認して欲しい内容があるのですが、これについては後述します。
残念な点
先に紹介したワンタイムトークンの生成アルゴリズムに加えて、以下の様な「残念な点」があります。重大な脆弱性というわけではありませんが、他がよく書けているだけに余計に残念な感じがしました。IE7で画像XSSが可能
Internet Explorer7(IE7)以前では、画像を用いたクロスサイトスクリプティング攻撃が可能です。原理と対策の方針については下記の記事を御覧ください。攻撃のために用いる画像は以下のようにして作成可能です。
- 適当な.png画像を用意する
- IDATの中身にJavaScriptを埋め込む
- 拡張子をgif(あるいはjpg)に変更する
これをアップロードしてIE7で表示させると下記の結果になります。
これに該当するブラウザは、現在サポートが有効なもの(サーバー除く)ではWindows Vista上のIE7だけで、2016年1月でサポートが終了します。対策するかどうか迷うラインではありますが、幸いgetimagesize()関数を用いて比較的容易に対策できます。具体的には、
- getimagesize()関数を用いてマジックバイトから画像のタイプを取得する(参考)
- ファイル名から得た拡張子が、画像のタイプと一致していることを確認する
- 両者が一致していな場合はエラーとして画像を受け付けない
パスワードの保存形式がソルトなしMD5ハッシュ値
前述のように、本書の認証機能はPear::Authを用いており、そのデフォルトの挙動として、パスワードはMD5ハッシュ値として保存されます。現在の状況として、ソルトなしのMD5では安全な保存とはいえません(下記の記事参照)。PHP5.5以降が使える環境ではpassword_hash()関数を使いたいところですが、Pear::Authとは共存できないと思われるので、これは本書のカバー範囲を超える「ぜいたくな要求」かと思います。しかし、「本当はこれではダメなんだ」ということは言及していただきたいです。
まとめ
「10日でおぼえるPHP入門教室 第4版」について、脆弱性対処の状況を報告しました。一部残念な点はあるものの、全体としてはPHP入門書の中ではセキュリティの説明が非常に充実していると感じました。要点を以下にまとめます。- SQLインジェクション対策はプレースホルダ、PDOの文字エンコーディング指定で確実に
- XSS対策は、表示の際のエスケープを徹底
- CSRF対策はワンタイムトークンを用いる
- パストラバーサル対策、メールヘッダインジェクション対策もある
しかし、せっかくPHPを勉強するのであれば、最初から正しい方法を学びたいものです。ということで、PHPの入門書を書く方には、「出力の際にHTMLエスケープ」を徹底していただきたいと希望します。そして、それが十分に実践されている本書を読んで、私は上機嫌になりました。
0 件のコメント:
コメントを投稿