« auからのメールをPCで受信した際に、文末に「忞」の文字が入っている場合(文字化け解読) | トップページ | 【PHP関連】 htmlspecialchars関数は万能で、神聖不可侵か? »

2007/12/25

【PHPネタ】htmlspecialchars関数はサニタイズ関数?



EUCの補助漢字をPHPでどのように処理するかを調べていて、1年前に自分で書いた記事(「IE7と補助漢字(「森鴎外」と「森鷗外」)」)やそのリンク先などを読み返したりしていました。EUC-JP(細かい定義はカットします。Windowsで作成した所謂EUCでエンコードされたもの。)のページで、フォームを作成したとします。その際、サイト訪問者がEUCの補助漢字の大御所?「森鷗外」を入力したとします。すると、「鷗」の字は、「%26%2340407%3B」にURLエンコードされますから、それをデコードすれば、「鷗」になります。それをそのままブラウザに表示すれば、「鷗」になります。これで、めでたしめでたし、となるはずですが、そう簡単にはいきません。

 (2008年1月1日午前6時10分ごろ追記) 今読み返してみると、舌足らずでした。数値文字参照で表示されるのは、IE7やI6 SP2での話です。Firefoxなどでは、(良いのか悪いのかは別にして)補助漢字をサポートしています(=該当文字を「0x8F」で始まる3バイト文字として処理する。IEとの互換性がありません。参照: http://i.hatena.ne.jp/idea/1740 )ので、数値文字参照になりません。これは1年前の記事「IE7と補助漢字~」でも書いていたり、そのリンク先ページでも書かれていることですが、今回この記事を書くにあたって、そのことを改めて書くのを忘れていました。私が記事を書くにあたって、1年前の記事が自分の頭の中には入っていたものですから、てっきり説明していると思い込んでいました。失礼しました。

なお、文字コード指定のメタタグをしっかりと入れていない場合、このテストはうまくいかないようです。数値文字参照にならないというより、「鷗」と一緒に送信した別の漢字も一緒に文字化けします。「鷗」の後続の文字が文字化けするというだけでなく、「鷗」の前の漢字も文字化けしますので、検証テストする方はメタタグを入れてテストしてください。

htmlspecialchars関数を通して表示させると、「鷗」の「&」もエスケープされて、「鷗」の字は表示されず、「鷗」がそのまま表示されてしまいます。これは問題です。htmlspecialchars関数を使わないと、セキュリティ上宜しくないですし(この「セキュリティ上」という文脈に限定するのが正しいのか?というのが本稿の目的ですが、これについては後述します。)、かといって、htmlspecialchars関数を通すと、一部の漢字が「文字化け」します。

それで他のプログラマーはどのように処理しているのかと調べていて、まず、
● NobuNobu XOOPS別館 » めもらんだむ : MyTextSanitizerのhtmlSpecialChars考 (1) by nobunobu
http://www.nobunobu.com/blog/2006/05/14/mytextsanitizer-1/

が目に入りました。天下のXOOPSでも、結局、htmlspecialchars関数を実行後、「&」を「&」に戻す処理を入れているみたいです。じゃ、何でそもそもhtmlspecialchars関数は、「&」を「&」に変換する処理をやってくれているのかが疑問になってきました。

「<」や「>」などの変換はもちろん、スクリプトを実行させないためにも重要ですし、「"」や「'」もその理由は分かります。ですが、「&」は何のために変換してくれているのでしょうか?

さらに原点に立ち返るために、PHPの総本山のマニュアルにあるユーザーコメント(beer UNDRSCR nomaed AT hotmail DOT comさんやjspalletta at gmail dot comさん)でも、やはり、preg_replaceで「数値文字参照」や「文字実体参照」を変換する方法を紹介しています。これの方が全ての「&amp;」を「&」に戻すわけではないので、「いろんな意味」で問題なさそうです。

ここで、「いろんな意味で」と書いたのには、理由があります。私は、下記に紹介しますサイトの論争(2年前のもの)をつい最近読むまで、htmlspecialchars関数はサニタイズ関数というのかセキュリティ上の関数だと思い込んでいました。そのために、htmlspecialchars関数でせっかくサニタイズしてくれているのに、「&amp;」を「&」に戻す処理をプログラマーが勝手に入れてしまうと、思わぬセキュリティホールが入り込んでしまうのではないかと危惧していて、いろいろ調べまくっていたわけです。で、思わぬセキュリティホールを生み出すぐらいなら、「鷗外」の「鷗」がPOST(or GET)によって「&#40407;」に「文字化け」してしまっても、そういうのは極めてレアな入力パターンであり、韓国語の「안녕하세요?」がPOST(別にGetでもいい。)で、「&#50504;&#45397;&#54616;&#49464;&#50836;?」に「文字化け」して表示されるのと本質的には同じ理屈であり、日本語のサイトに韓国語を入力して期待通りに動作することを要求するのは、或る意味嫌がらせなので、無視した方がまだマシという観念がありました。




15.4型ワイド液晶搭載のVista搭載ノートパソコンが9万円台から。送料無料

htmlspecialchars関数はサニタイズ関数である(そして、それゆれに「神聖不可侵」)という理解でした。そういう面も確かにあるのですが、下記の記事を読んで、本来的にはそうでない部分もあると捉えたほうが正確かもしれないと思い出しました。

● 高木浩光@自宅の日記 - プログラミング解説書籍の脆弱性をどうするか, 「サニタイズ言うなキャンペーン」とは何か, ASPとかJSPとかPHPとかERBとか、逆だ..
http://takagi-hiromitsu.jp/diary/20051227.html

最初、この記事を読んで、ものすごく違和感を感じました。正直な話、セキュリティの第一人者の記事にしては、かなり一方的というか、ドグマ的というかそういう印象を受けました。批判されている人(書籍「PHPサイバーテロの技法―攻撃と防御の実際 」の著者。私は発売されてすぐぐらいに買って読んでいます。)が非常に気の毒になりました。

その後、批判されている人がこの高木氏の記事に反論する形で、
● PHP : 「プログラミング解説書籍の脆弱性をどうするか」への反論のようなもの2
http://xoops.peak.ne.jp/md/news/article.php?storyid=81

を読んだのですが、これによりますます、本の著者(GIJOE氏)に同情するようになりました。

Amazon.co.jpの書評でもそうですが、どんなに良い本でも悪く言う人はいるもの(ライバル書籍の人の嫌がらせも混じっている?)で、心が痛むことがありますが、それと同じような感じがしました。この時点では、GIJOE氏にくみしたい気持ちが9で、高木氏を支持する気持ちは1ぐらいでした。

しかし、一晩たって、改めて考えてみると、高木氏の言われていることも分からなくはないと思えるようになりました。正直、最初読んだときは、何を言いたいのかよく分からず、重箱の隅を突きたいのか、文句を単に言いたいのか、ぐらいにしか思えなかったのです。しかし、一晩経って、だいぶ印象が変わりました。htmlspecialchars関数の「&」に関する処理について考えていく中で、htmlspecialcharsとは何のための関数なのか?ということをずっと考えていたからです(後述します)。その結果、GIJOE氏が6で、高木氏が4ぐらいになりました。

さらに、2・3日が経って、もう一度、高木氏の文章を読み返していると、非常に論理的で趣旨一貫している文章であることが分かりました。最終的には、5:5になりました。どちらも理があると感じています。

htmlspecialchars関数の「&」に関する処理についての話に話を戻します。一旦、「&」を「&amp;」にしておいて、それをまた、「&」にするなんて、なんととも無駄な話であり、何のために、htmlspecialchars関数は「&」を「&amp;」にするような一見無意味に見えることをやっているのかを知る必要があるように思いました。セキュリティ的な意味合いがあるのかどうかを考えてみる必要があるように思えてきました。そうでないと、「&amp;」を「&」に変換してセキュリティ上大丈夫なのか分からないからです。

その際に参考になったのが(結論は出ていません。)、上記の高木氏の記事の中の次の部分です。

元々、HTMLを出力するときは、その出力全体に対して「<」「>」「&」のエスケープ処理の検討が要求されているのであって、CGI入力に依存しているかどうかは無関係である。

htmlspecialchars関数の実際の挙動及び上記引用部分をヒントに、私は、htmlspecialcharsの元々の意義を下記のように推論しました。(間違っているかもしれません。)

1.フォームでユーザー(サイト訪問者)が入力してPOSTしたものそのものが確認画面で表示されるのが原則。

2.この原則に照らしたとき、ユーザーが「<PHPについて>」と入力したものが、確認画面では、タグのように解釈されるとブラウザには何も表示されないことになります。あるいは、「x=5、y=13の時、『x<y』は真であり、『x>y』は偽である。」という文章を入力された場合に、「<」と「>」がタグのように解釈されると、確認画面では、「x=5、y=13の時、『xy』は偽である。」になってしまいます。これはまずい。

3.同様に、「&copy;」と入力したものが確認画面で「©」が表示されるのはまずい。「&copy;」と表示されるべきである。

4.「2」と「3」の問題を解決するために、エスケープ関数としてhtmlspecialcharsは存在する。そのような意味で、第一義的には、htmlspecialchars関数はセキュリティ対策を目的としたものではない。

5.その証拠に関数名をよく見ると、「無茶苦茶長い」のは意味があるかもしれません。文字通り、「html」で「special」な「chars」です。HTMLで特別な意味を持つ文字を処理するための関数です。その処理が何なのかについて説明がなく、escape_htmlspecialcharsとかにすると分かりやすいかもしれないけれど、長い関数名がさらに長くなり、嫌だったのかもしれません。いずれにせよ、もし、セキュリティ対策が第一義ななら、「sanitize関数」とかいう名称でも良さそうですが、実際にはそのようになっていません。もしくは、「securityspecialchars関数」でも良さそうですが、あくまでも「HTML」上で特別な意味を持つ文字の処理を主体に考えていることが、関数名からも推察できます。

6.PHPのマニュアルにあるとおり、「文字の中には HTML において特殊な意味を持つものがあり、 それらの本来の値を表示したければ HTML の表現形式に変換してやらなければなりません。 (中略)この関数は、掲示板やゲストブックなどでユーザが書きこんだテキストから HTML のマークアップ用文字を取り除く場合に有用です。 」とある通り、そういうセキュリティ対策にも有用だけれど、第一義的には違うのではという結論になりました。


ただ、ここで不思議なのは、「&copy;」を入力したユーザーが本当に確認画面で「©」ではなくて、「&copy;」と表示されてほしいと思っているユーザーがどれだけいるかということです。私が思うには、そのようなケースは極めて稀であり、「HTMLで、copyrightを表示する際に使う『©』は「&copy;」と入力すれば表示することができます。」のような特殊なHTML解説の本とかぐらいしかないのではないでしょうか?

また、「1.」の「フォームでユーザー(サイト訪問者)が入力してPOSTしたものそのものが確認画面で表示されるのが原則。」に照らして考えてみた時に、ユーザーがUTF-8以外のページで、「鷗」「Ä」を入力したものは、確認画面でも「鷗」「Ä」と標準で表示されるべきです。IEをはじめとするブラウザが数値文字参照・文字実体参照で送っていることは、PHP開発者の方も十分に知っておられるであろうに、何ゆえ、htmlspecialchars関数では、「&」を「&amp;」に単純に変換してしまうのか? そのような弊害があることを知りながら、あえてこのような仕様になっているということは、やはり、「&amp;」を「&」に再変換するのはまずいという何か重要な理由があるのだろうか? 結局は結論が出ていません。

まあ、少なくとも「&amp;」を「&」にするのではなくて、PHPのhtmlspecialchars関数のページに寄せられいるコメントにありますように、preg_replace関数を用いて、

のように限定的なものにした方が無難そうです。

-- 2007年12月31日15時21分ごろ追記 start --
「無難」と書きましたが、このように限定しても問題になる事例が見つかりました。

【PHP関連】「&」(アンパサンド)をエスケープしなければならない実例
http://shimax.cocolog-nifty.com/search/2007/12/php_f864.html
をご参照ください。少なくとも上記のhscFixed関数に例外処理を加えないとまずそうです。
-- 2007年12月31日15時21分ごろ追記 end --

何で最初から数値文字参照・文字実体参照を考慮したhtmlspecialchars関数にPHP開発者はしてくれないのか(もしかして、「バグ」とまでは言えないまでも、英語圏以外の人が後回しになるよくあるパターンというのか発展途上ゆえの現象なのでしょうか?)は疑問に残りますが、少なくとも、「&」の変換が無意味なら、最初からPHPは「&」の変換をしないように設計することは極めて容易のはずなのに実際はそのようにしていないのですから、ここはいじくらない方が良さそう(無難そう)です。

どうしても、「&」の変換を止めたいのなら、そもそもhtmlspecialchars関数は、PHPのマニュアルにもありますように、最大でも、たかだか「&」「"」「'」「<」「>」の5文字しか変換しないのですから、自作することも極めて容易です(気をつけるべきは、「&」を先に変換しないと、「<」→「&lt;」→「&amp;lt;」と二重にエスケープしてしまい、訳が分からなくなることぐらいでしょうか?)から、「&」以外の4文字だけを変換するように自作すれば良いでしょう。

 (実際、PerlからPHPに入った人にしてみれば、自前でこの類のエスケープ関数は、サブルーチンを作って対処するのが当たり前というのがあるのかもしれません。私自身、PerlからPHPに入った当初は、Perlのサブルーチンを翻訳して使っていましたが、ある時点で、PHPにはhtmlspecialchars関数という便利な関数が最初から準備されていて、自前で準備しなくてもいいんだと後から気づいて、それ以降、htmlspecialchars関数を使い出したという経緯があります。)


どなたか、「&」を「&amp;」に変換する意味を、実例を挙げて、しっかりと説明できる人おられますでしょうか? もし、おられたら、コメントをやさしいトーンでお願いします。

-- 2008年1月1日午前5時58分ごろ追記 start --
その後、いくつか自分でも実例を見つけることができました。
http://shimax.cocolog-nifty.com/search/2007/12/php_f864.html
をご覧ください。
-- 2008年1月1日午前5時58分ごろ追記 end --

他にも書きたい論点(htmlspecialchars関数を単純に通すと表示がおかしくなるケースについての実例などを挙げながら、htmlspecialchars関数は神聖不可侵かつ万能なのかを書きたかった。)はあったのですが、長くなりましたので、今日は一旦ここまでにしたいと思います。


---- 2007年12月26日午後21時8分ごろ追記 start ----
続きを書きました。
【PHP関連】 htmlspecialchars関数は万能で、神聖不可侵か?
http://shimax.cocolog-nifty.com/search/2007/12/php_htmlspecial_4c35.html
---- 2007年12月26日午後21時8分ごろ追記 end ----

----- 2007年12月31日午後0時25分ごろ追記 start ------
● 文字集合範囲外の文字とパーセントエンコード
http://oshiete1.goo.ne.jp/qa3639860.html
で当ページの記述に関連して質問がなされているようです。

私のテスト方法に問題があったかと思い調べてみたのですが、どうも、質問者のサンプルコードに メタタグがないことが原因のようです。

<meta http-equiv="Content-Type" content="text/html; charset=EUC-JP">
をhead部に入れるとIE7では鷗外の「鷗」の字は数値文字参照になりました。

また、メタタグによる文字コードの指定が無い質問者のサンプルコードだと、理由は分かりませんが、「鷗外」の「外」の字まで「%CD%E2」にURLエンコードされ、文字化けします。「%B3%B0」になりません。「外」一文字とか「外あいうえお」なら、「外」は「%B3%B0」にエンコードされます。「鷗」が入ると駄目になります。「送信あいうえお鷗」でも、「送信」の部分が「%CB%CD%D0%C5」にURLエンコードされ文字化けします(平仮名の部分はなぜか文字化けしない)。「送信」単独なら「%C1%F7%BF%AE」に期待通りURLエンコードされ文字化けしません。メタタグによる文字コード指定がないとこういう現象が起こるようです。

なぜ「外」までが「%CD%E2」になるのかは分かりません。違う文字コードと誤認しているのかとShift_JISなどのコードを調べてみましたが、全然違うようです。

----- 2007年12月31日午後0時25分ごろ追記 end ------

|

« auからのメールをPCで受信した際に、文末に「忞」の文字が入っている場合(文字化け解読) | トップページ | 【PHP関連】 htmlspecialchars関数は万能で、神聖不可侵か? »

コメント

投稿: shi | 2007/12/28 14:02

shiさん、ありがとうございます。

教えていただいたサイトがきっかけで、「&」をエスケープしなければならない事例のいくつかに気づきました。

http://shimax.cocolog-nifty.com/search/2007/12/php_f864.html
にまとめました。

ありがとうございました。

投稿: 管理人 | 2007/12/31 15:24

コメントを書く



(ウェブ上には掲載しません)


コメントは記事投稿者が公開するまで表示されません。



トラックバック

この記事のトラックバックURL:
http://app.cocolog-nifty.com/t/trackback/67411/17472782

この記事へのトラックバック一覧です: 【PHPネタ】htmlspecialchars関数はサニタイズ関数?:

« auからのメールをPCで受信した際に、文末に「忞」の文字が入っている場合(文字化け解読) | トップページ | 【PHP関連】 htmlspecialchars関数は万能で、神聖不可侵か? »