カテゴリー
Computer Development Programming

PHPが文字エンコーディング攻撃に強い理由 – HTMLエスケープ

(Last Updated On: 2018/08/13)

PHPが文字エンコーディング攻撃に比較的強い理由は入出力の文字エンコーディングのバリデーション(サニタイズ)が行えるだけではありません。PHPが提供するHTMLエスケープ関数が文字エンコーディング攻撃に対して強い事も理由の一つです。

PerlでHTMLエスケープと言えば、<,>,&,”,’をエンティティ変換するコードが一番に見つかります。

「perl html escape」でググると一番に見つかったページは次のページです。このページではまだ3バイトEUCの場合の例、CGIモジュールを使った例も載っているので良い方でしょう。

http://saboten009.blogspot.com/2008/04/perlhtml-xss.html

少し前にPerl, Ruby,Pythonユーザは検索で有用なセキュリティ情報を得られるのか?と疑問に思い調べました。これだけ知っていれば取り合えず十分というページはそう簡単には見つかりませんでした。

いつも問題になるのは PHP だけど Perl は問題ないのか、すでに議論し尽くされた問題なのか、PHPer のモラルが低いせいか。

Perl,Ruby, Pythonで議論し尽くされ対策が浸透している、とは到底思えません。Railsで文字エンコーディングを利用したXSS脆弱性が話題になっていることからも明らかです。PHPがいつも問題になるのはよく使われていて、初心者も多く、公開されているWebアプリも圧倒的に多いからです。モラルの問題ではありませんし、このページで紹介されているPerlのエスケープ方法だけではPHPのhtmlentities()やhtmlspecialchars()よりも脆弱です。文字エンコーディングを考慮するようになっていないからです。

問題はPHPerだけではありません。「PHPerのモラルが低いせいか?」と疑問に思われていますが、Perlアプリでも、Rubyのアプリでも似た様な状況です。例えば、Rubyならrhtmlで

<%=h(string) %>

とh(html_escape)関数を使い、デフォルトでHTMLエスケープ可能なHTML出力すべてをエスケープすべきです。しかし、私が読んできたRailsアプリの多くはPHPアプリとそう変らない状況です。必要だろう、と思われる文字列のみエスケープしているアプリが多数です。Javaだとエスケープがデフォルトである事が随分前から当たり前なのですがLLはどれも同じような状況です。この状況は次第に変りつつありますがまだまだです。

PHPアプリに対する文字エンコーディングを利用した攻撃は何年も前からあったのでPHPはhtmlentities()かhtmlspecialchars()を利用すれば文字エンコーディングを考慮したエスケープが行われ、サポートしている文字エンコーディングなら文字エンコーディングベースの攻撃が行えないようになっています。最近書いてきた入出力の文字エンコーディングバリデーションをしていなくても、HTMLエスケープをしていれば攻撃できません。

PHPのhtmlentities()とhtmlspecialchars()は何年も前から文字エンコーディングを考慮したエスケープを行っているので、htmlentities()とhtmlspecialchars()が文字エンコーディングを利用した攻撃に脆弱としてレポートされた事もあります。しかし、開発者はなかなか他の環境の脆弱性から学べないのは、自分も含めどの開発者でも同じです。

以下の定義はPHP 5.2.11のext/standard/html.cからのコピーです。

static const struct {
const char *codeset;
enum entity_charset charset;
} charset_map[] = {
{ "ISO-8859-1",     cs_8859_1 },
{ "ISO8859-1",      cs_8859_1 },
{ "ISO-8859-15",    cs_8859_15 },
{ "ISO8859-15",     cs_8859_15 },
{ "utf-8",          cs_utf_8 },
{ "cp1252",         cs_cp1252 },
{ "Windows-1252",   cs_cp1252 },
{ "1252",           cs_cp1252 },
{ "BIG5",           cs_big5 },
{ "950",            cs_big5 },
{ "GB2312",         cs_gb2312 },
{ "936",            cs_gb2312 },
{ "BIG5-HKSCS",     cs_big5hkscs },
{ "Shift_JIS",      cs_sjis },
{ "SJIS",           cs_sjis },
{ "932",            cs_sjis },
{ "EUCJP",          cs_eucjp },
{ "EUC-JP",         cs_eucjp },
{ "KOI8-R",         cs_koi8r },
{ "koi8-ru",        cs_koi8r },
{ "koi8r",          cs_koi8r },
{ "cp1251",         cs_cp1251 },
{ "Windows-1251",   cs_cp1251 },
{ "win-1251",       cs_cp1251 },
{ "iso8859-5",      cs_8859_5 },
{ "iso-8859-5",     cs_8859_5 },
{ "cp866",          cs_cp866 },
{ "866",            cs_cp866 },
{ "ibm866",         cs_cp866 },
{ "MacRoman",       cs_macroman },
{ NULL }
};

不完全ではありましたがPHP4の頃から文字エンコーディングを考慮したHTMLエスケープは行われていました。長い年月の間にエンコーディングサポートが次々に追加され、今は上記の文字エンコーディングを考慮したHTMLエスケープが可能になっています。UTF-8は勿論、日本で広く利用されているShift_JIS, EUC-JPもサポートされています。mbstringモジュールが利用可能であれば、mbstringも利用されるコードになっています。mbstring.internal_encodingが設定されていれば、そのエンコーディングにあったHTMLエスケープが行われるようになっています。(つまり、mbstringを使っている方がより良いエスケープ処理を期待できる。しかし、この辺りの動作はPHP 5.3/6で変っている)

 

#if HAVE_MBSTRING
#if !defined(COMPILE_DL_MBSTRING)
/* XXX: Ugly things. Why don't we look for a more sophisticated way? */    switch (MBSTRG(current_internal_encoding)) {
case mbfl_no_encoding_8859_1:
return cs_8859_1;

case mbfl_no_encoding_utf8:
return cs_utf_8;

 

セキュリティ専門家が勧めるようにエスケープできる文字列は全てエスケープする、というコーディングポリシーを守っていればHTMLエスケープするだけで文字エンコーディング攻撃に対して強くなります。このポリシーではなくても、エスケープするデータ=ユーザが送信した生データ、であるので攻撃される箇所は減ります。Ruby 1.9では正規表現で不正な文字エンコーディングを検出するのでPHPと同じような状況にあると言えると思います。

htmlentitiesやhtmlspecialcharsを利用していれば、入出力のバリデーションで文字エンコーディングのチェックをしなくても良いということでは無いので誤解しないで下さい。CSSやJavaScriptへの出力にもエスケープは必要です。エスケープ関数でのチェックは入出力でバリデーションもしない開発者の為のフェイルセーフ機能だと思っておくべきです。

PHPも.NetのMicrosoft.Security.Applicationで利用できるAntiXSS(HtmlEncode, HtmlAttributeEncode, CssEncode, JavaScriptEncode,UrlEncode,XmlEncode, XmlAttributeEncode,VisualBasicScriptEncodeとそれぞれに必要なエスケープ(エンコード)処理とデコード処理がある。名前も解り易い)のような仕組みを実装を標準ですべきだと思うのですが先は長そうです。「自分で作くれば」という声が聞こえてきそうですね… とにかく、必要な関数が用意されるまではフレームワークで同等の物を用意している場合も多いのでそれらを利用するようにすると良いでしょう。

Javaの場合、言語が生まれた時から文字エンコーディングはUnicodeと決まっているので不正な文字エンコーディングでの攻撃は冗長なエンコーディング以外は難しくなっています。昔Shift_JISの取り扱いが問題になったりしましたが、文字エンコーディング問題では結局文字エンコーディングを決め打ちしていたJava環境が最も安心感があるプラットフォームだと思います。

UnicodeにはUnicodeの問題がありますが、私を含め多くの方が問題を少しでも簡単にする為にUnicodeに文字エンコーディングを統一する事を勧めています。

互いに良い部分はどんどん取り込んで安全なWebサイトが当たり前のように作られるようになれば良いです。

 

関連エントリ

出力文字エンコーディングのバリデーション