そういう方々が、preg_replace(u修飾子つき)やmb_ereg_replaceを用いて代替関数を作成している解説も見かけますが、それではこれら正規表現関数は不正な文字エンコーディングをチェックしているのだろうかという疑問が生じます。
ざっと調べたところ、以下の様な状況のようです。
- preg_replace : 不正な文字エンコーディングをチェックしている
- mb_ereg_replcae : 不正な文字エンコーディングをチェックしていない
不正な文字エンコーディングとは
マルチバイトの文字を表現するエンコーディング(Shift_JIS、EUC-JP、UTF-8など)には、バイト列の並びのルールが決まっています。例えば、Shift_JISの2バイト目としてありえるバイト値とあり得ないバイト値があります。UTF-8の場合は、先頭バイトで文字のバイト数が決まり、2バイト目以降に使えるバイトは 0x80 ~ 0xBF と決まっています(参考)。これらの決まりに従わないバイト列は、「不正な文字エンコーディング」ということになります。また、UTF-8には「非最短形式」というものがあり、禁止されているので、これも不正な文字エンコーディングの一種です。
mb_ereg_replaceは不正な文字エンコーディングをどう扱うか?
ここでは、UTF-8の場合を例として、mb_ereg_replaceが不正な文字エンコーディングのデータをどのように処理するかを見てみます。まず、不正なデータとして以下を例に取ります。
このデータは以下の意味でUTF-8として不正です。$a = "\xFC../.."; // 6バイト
- 0xFCは、かつてUTF-8の6バイト形式として定義されていたが、現在は5バイト以上の形式は禁止されている
- 2バイト目以降が 0x80~0xBF の範囲にない
結果は以下の通り。<?php mb_regex_encoding('UTF-8'); $a = "\xFC../.."; $s = mb_ereg_replace('[\./]', '', $a); // . と / をすべて取り除く echo bin2hex($s) . "\n";
先頭の0xFCの影響を受けて、2バイト目以降の . や / は取り除かれていません。ここから以下のことが分かります。fc2e2e2f2e2e
- mb_ereg_replaceはUTF-8の6バイト形式を許容している
- mb_ereg_replaceはUTF-8の2バイト目以降のバイト値の範囲をチェックしていない
脆弱となる例
ディレクトリトラバーサルの例は作れませんでしたので、XSSならどうだろうと思い、ちょっと人工的ですが、不正な文字エンコーディングによるXSS脆弱性の例を考えてみました。以下のスクリプトはmb_ereg_replaceを使った「手作りの」HTMLエスケープ関数を使っています。このスクリプトに対して、x=<script>alert(1);</script> というクエリ文字列を与えると以下のように正しくエスケープ処理が行われています。<?php header('Content-Type: text/html; charset=UTF-8'); function mb_htmlescape($s) { mb_regex_encoding('UTF-8'); $s = mb_ereg_replace('&', '&', $s); $s = mb_ereg_replace('<', '<', $s); $s = mb_ereg_replace('>', '>', $s); $s = mb_ereg_replace('"', '"', $s); return $s; } ?> <html><body> <?php echo mb_htmlescape($_GET[x]); ?> </body></html>
しかし、以下のクエリ文字列だと、JavaScriptが起動されてしまいます。
x=%C2<script+%C2>alert(1);//%C2</script+%C2>
%C2はUTF-8の2バイト形式の1バイト目になるバイト値です。これを■で表現すると、入力文字列は下記の通りです。
そして、この文字列は、先の手作りエスケープ関数を素通りしてそのままブラウザに送られます。■<script ■>alert(1);//■</script ■>
ブラウザ側では、この■<等が「不正なUTF-8文字」と認識して、■と < という別々の文字として扱われます。この「不正な文字に対する扱いの差異」が脆弱性の原因です。
対策
以下の対策を推奨します。- エスケープ関数等セキュリティ目的の処理は極力自作しない(htmlspecialchars関数では上記の問題は起きない)
- 各入力値についてmb_check_encoding関数により文字エンコーディングの妥当性チェックを行う
まとめ
PHPのhtmlspecialcharsはかつていけてなかった(参考)とか、basename関数は今もいけてないなどの理由でこれらの関数を自作したくなる衝動にかられる場合がありますが、中途半端に自作するとかえって危険になる場合があります。PHPの主要な関数群は色々ディスられながら改良され、安全になってきた歴史がありますので、よほど明確な理由がない限りはPHPで提供されているものを使うほうがよいと考えます。また、PHPが提供する関数群には文字エンコーディングのチェックを厳密に行うもの(mb_check_encoding、htmlspecialchars等)と、チェックをあまりしないもの(mb_strlen、basename、mb_ereg等)がありますので、入力値のバリデーション時にmb_check_encodingで文字エンコーディングの妥当性を確認しておくとよいでしょう。
0 件のコメント:
コメントを投稿