ITproの連載中に、id:hasegawayosukeさんから以下のようなコメントをいただいていました。
対策遅らせるHTMLエンコーディングの「神話」:ITpro - 葉っぱ日記
全ての文字をエスケープしようなどと非現実的なことは言わないけれど(とはいえMicrosoft Anti-Cross Site Scripting Libraryのようにほとんど全ての文字を実体参照に置き換えるものもあるので、あながち非現実的とも言えないのかも知れない)、エスケープ対象を「'」「"」「<」「>」「&」の5文字に限定しているのは何かこの記事に書かれていない理由があるはずだと思ったからです。
はてぶの方は見ていましたが、日記の方を見落としていまして、お返事が遅れました(_ _)。
なぜ、「<」、「>」、「&」、「"」、「'」の5種類の文字をエスケープするのかについては、色々考えるところがあります。
その前に、金床さんからご指摘いただいたように、
金床 『わたしも最初はせがわさんと同じ勘違いをしましたw
本記事は「すべての項目についてHTMLエンコードする」といっているのであって
「すべての文字を」という意味ではないですね(たぶん)
そういう意味です。どうも紛らわしい表記をしてしまったようです。
それとは別のテーマとして、XSS対策としてどの文字をエスケープするのが正しいのか、ということですね。
まず、連載時にリファレンスとして意識していたのは、本文中にも何度か現れるように、「安全なウェブサイトの作り方 改訂第2版」です。同じIPAから出ているIPA ISEC セキュア・プログラミング講座に比べて内容が現代的だし、なんといっても執筆協力者の筆頭に高木浩光氏がのっていることからも、広く入手できる資料としてはもっとも信頼性が高いと思ったわけです。しかし、意見が異なる部分も当然ながらあるわけです。全部同意だったら、「安全なウェブサイトの作り方 改訂第2版」を読めで終わりですものね。
同書には、
エスケープ処理には、ウェブページの表示に影響する特別な記号文字(「<」、「>」、「&」など)を、HTML エンティティ文字(「<」、「>」、「&」など)に置換する方法があります。また、HTML タグを出力する場合は、その属性値を必ず「"」(ダブルクォート)で括るようにします。そして、「"」で括られた属性値に含まれる「"」を、HTML エンティティ文字「"」にエスケープします。
とあるように、
エスケープ対象項目 | エスケープ対象文字 |
---|---|
要素内容(一般のテキスト) | 「<」、「>」、「&」 |
属性値 | 「<」、「>」、「&」、「"」 |
と推奨しています。
しかし、こうする理由は自明ではありません。
要素内容について言えば、タグとタグにはさまれた部分になるわけなので、ミクロに見れば
>○○○○○○○○○○○○<
ということで、終端をあらわす「<」と、エスケープに使用する「&」の二種類が必要最低限のエスケープ対象文字となります。既に、えむけいさんから以下のようにコメントがついているとおり、「>」をエスケープする理由は自明ではありません。
えむけい 『「>」のエスケープは(XSS対策上はともかくHTML/XMLの仕様的には)ほとんどの場合不要ですが、たいてい無条件にエスケープしますね。』
また、属性値については、
"●●●●●●●●●●●●"
となるので、終端を示す「"」と、エスケープに使用する「&」の二種類が必要最低限ということになります。ただし、W3CのHTML4.01の仕様を見ると、
5.3.2 Character entity references
【中略】
Similarly, authors should use ">" (ASCII decimal 62) in text instead of ">" to avoid problems with older user agents that incorrectly perceive this as the end of a tag (tag close delimiter) when it appears in quoted attribute values.
とあるように、古いブラウザが属性値(「"」で囲ってあっても)中の「>」によりタグの終端とみなすという問題(バグでしょうね)を回避するために、属性中の「>」を>にエスケープする「べきである」と記述されています。すなわち、最低限のエスケープだと以下のようになります。
エスケープ対象項目 | エスケープ対象文字 |
---|---|
要素内容(一般のテキスト) | 「<」、「&」 |
属性値 | 「"」、「>」、「&」 |
ただし、引用したW3Cの部分の記述が属性値のみを指すのか、一般のテキスト(Element Content)をも指すのか曖昧であること、「<」と「>」の対称性から、普通は、要素内容中であっても「>」をエスケープするし、属性値の中であっても「<」をエスケープするのだと解釈しています。すると、以下のように、「安全なウェブサイトの作り方 改訂第2版」の推奨内容にたどり着きます。
エスケープ対象項目 | エスケープ対象文字 |
---|---|
要素内容(一般のテキスト) | 「<」、「>」、「&」 |
属性値 | 「"」、「<」、「>」、「&」 |
私が、シングルクォート「'」もエスケープ対象に加えているのは、属性値をシングルクォートで囲むケースが少なからず見受けられるからで、かならずダブルクォート「"」で囲むように徹底されていれば、このガイドラインは必要ありません。また、要素内容中のクォート文字「"」、「'」もエスケープ対象として推奨しているのは、文字二種類の違いのためにわざわざ記述を分けるのも煩雑なので安全な方に寄せたらどうかという提案であって、テンプレートエンジンなどで自動的に区別がつくのであれば、クォート文字のエスケープは属性値のみでもいいかな、とも思います(連載で指摘したような半端な二バイト文字のケースはありますが)。
同業者の中には、非常にたくさんの特殊文字をエスケープするように推奨しているところもあって、それはそれで意味がなくはないのですが、金床さんの指摘のように、
それとは関係なく全ての文字をエスケープするのはいいと思いますよ。
デバッグやりづらいですけどw』
デバッグなどがやりづらくなるのも問題なので、バランスを見て適当なところで線を引いています。デバッグがやりづらいということは、バグが残りやすいということであって、脆弱性(=一種のバグ)が残りやすいと言えなくもないですしね。
従来のエスケープは「危険な文字を特定して、それを処理する」というブラックリスト方式ですが、「すべての(あるいは英数字以外すべての)文字をエスケープしてしまう」というのはホワイトリスト方式といえると思います。この違いは実は大きいかなぁと考えている今日このごろです。
私は金床さんとは違うアプローチをとっていまして、エスケープはあくまで記述言語を正しく使用するために必要なものなので、いわゆるブラックリストではないと思うのですね。しかし考えてみると、言語の性格上本質的に必要なエスケープもあれば、ブラウザなどのバグを回避するためにやむを得なく実施するものもあり、後者はブラックリスト方式といってよいでしょうね。だからといって、すべての文字をエスケープしてしまうというのは、ちと抵抗があるし、ホワイトリストといってよいのだろうかと思うのですが・・・
非常に興味深いです。
ウェブ以前からプログラミングの仕事などに接されている方には、そのような考え方をする人が多いように思います。
「きちんと整理された規格など」を大事にする考え方ですね。
(引用)
■言語の性格上本質的に…
■ブラウザなどのバグを回避するために…
(ここまで)
という2点をあげられていますが、まさにおっしゃる通りで、私個人は前者は本当にどうでもいい(失礼)と考えており、後者のみ考えてしまいます。
ウェブ開発の現場で多くの種類のウェブブラウザを相手に安い開発費でやってると、どうにもすさんでしまうんですよねw
ちょっと疑問に思ったことがあるので書かせていただきます。
疑問:HTMLの要素内のエスケープ対象は「<」「>」「&」とありますが、「<」「>」で充分だと思います。
例:
print ”<p>$form</p>”; # $form = フォームからの任意入力
のような場合、「<」「>」を実体参照で充分だと思うのです。
返答よろしくお願いします。
「&」のエスケープは必要ですよ。そうしないと、ユーザがフォームから「&」と入力した際に、画面上には「&」と表示されることになります。これは一種のバグですよね。それを避けるために、「&」のエスケープも必要です。
>「&」と入力した際に、画面上には「&」と表示されるバグ
たしかにそうですね。
でも一方で、ユーザーが実体参照を使えるというメリット(オタク向けか?)もあると思うのです。(♥←こんな風に)
そして何より、「セキュリティ」の場で考えると(HTMLの要素間の中では)「&」は
脅威の対象にはならないのだから、エスケープは不要だと思った次第です。
(W3Cは「&」と書けと言ってるので、strict なHTMLを記述する上では置換が
必須だと思います。ただし、ここではあくまでも「セキュリティ」についてですので...)
ご意見お願いします。
上記訂正
>W3Cは「&」と書けと言ってる……