2420


PHPでスクリプト挿入攻撃やXSS対策として、サイタイズのためにhtmlspecialchars()を使うことがあると思いますが、使用する際の詳細が気になったので質問します。具体的には以下の２点です：

  * サニタイズ関数を適用するタイミングについて  
【ユーザからの入力時】【HTMLへの出力時】【その中間】の３パターンが考えられますが、ベストなのはどれでしょうか？GET・POSTパラメータの処理と、その出力がほぼ同時に行われる場合では違いが顕在化しませんが、そうでないより一般的な場合に注意する点が知りたいです。

『パーフェクトPHP』330・334頁によると「出力時にエスケープ」とだけ書いてありますが、それに関連する詳細の記述やサンプルコードがないため背景知識があってそうなるのか、それとも単なる著者の思いつきなのか判断できません。

【ユーザからの入力時】にサニタイズしてしまうと、HTML出力以外でサニタイズ前の文字列が必要になった際にhtmlspecialchars_decode()等で元に戻す必要があるため、やはり【HTMLへの出力時】がベストなのでしょうか？

  * htmlspecialchars()とhtmlentities()の使い分けについて  
いずれも第２引数にENT_QUOTESを指定すると思いますが、サニタイズ目的に限る場合、htmlspecialchars()で十分でしょうか。それともhtmlentities()まで使用する必要があるでしょうか？

[get_html_translation_table()](http://php.net/manual/ja/function.get-html-
translation-table.php)で変換される文字の違いは分かるのですが、どこまでやればサニタイズ目的で十分なのかよく分かりません。

以上詳しい方いらっしゃいましたら、ご教示ください。


CC BY-SA 3.0
2014-12-23T02:27:37.523
2408
2017-03-14T06:55:54.167
2014-12-23T02:58:40.227
728
5428
question
php security
脆弱性対策におけるhtmlspecialchars()の使用箇所について


# ドメインレベルでの対策を行っていない場合

**「出力時にエスケープ」** 1択です。他の選択肢は考えられません。著者の思いつきではなく、ここ数年万人によって当たり前のように言われ続けていることです。

>
> 【ユーザからの入力時】にサニタイズしてしまうと、HTML出力以外でサニタイズ前の文字列が必要になった際にhtmlspecialchars_decode()等で元に戻す必要があるため、やはり【HTMLへの出力時】がベストなのでしょうか？

その通りですね。他にも **エスケープ忘れ** や **多重エスケープ**
といったミスを防ぐ意図もあります。「格納時にエスケープ」という手段を採用している某日本の大手Q&Aサイトは、今までに私が確認できただけでXSS脆弱性を3回に加えて多重エスケープも同じ回数ぐらい発生させてしまっています。後者に関しては今も放置されている箇所があります。

また、エスケープされた状態だと **_「n文字以内でトリミングする」という処理を書くのが困難_**
になります。上で挙げたQ&Aサイトや某短文投稿サイトでもこれに関して、エンティティ表記されている部分が途中でぶった切られる問題も発生していたりします。

# ドメインレベルでの対策を行い、ユーザの自由なＨＴＭＬ記述を許可している場合

はてなブログやFC2ブログなどでは、 **「ログインした状態で何かするページ」** にはHTML記述を一切認めず、 **「記事を表示するだけのページ」**
ではHTMLの使用を認めています。 そもそもエスケープしていないことになりますね。 **_記事を表示しているページ上で直接コメント等が出来ない_**
ことに気付くと思います。はてなブログはインラインフレーム埋め込み、FC2ブログは別ページへの移動となります。

# どちらを選ぶべきか

セキュリティに関して十分な知識があり、ドメインを用意できるならこちらの選択肢もアリです。但し基本的にはやはり前者の「出力時にエスケープ」の方が選択されるべきです。

# `ENT_QUOTES` の必要性

シングルクオートで括られた属性内に表示する際には必須です。但し一般的にはダブルクオートで括る方が主流なので、デフォルトの `ENT_COMPAT`
が必ずしも悪いというわけではありません。思考停止したように `ENT_QUOTES` を採用する必要はないです。

但し…

  * 文字セットのデフォルト値がPHPのバージョンによって異なるという大きな変更が過去にあった
  * 使用頻度が多い割に関数名自体が長い

という背景も踏まえて、どうせなら

[code]

    function h($str) {
        return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
    }
    
[/code]

という関数を作ってしまっておいてそれを流用する方が良いでしょう。

# `htmlspecialchars` と `htmlentities`

セキュリティ対策が目的であれば前者で十分です。

# 参考リンク

  * [「サニタイズ言うなキャンペーン」とは何か](http://takagi-hiromitsu.jp/diary/20051227.html#p02)
  * [「サニタイズ言うなキャンペーン」私の解釈](http://kmaebashi.com/zakki/zakki0042.html)
  * [htmlspecialcharsは不正な文字エンコーディングをどこまでチェックするか](http://www.tokumaru.org/d/20090930.html)
  * [$_GET, $_POSTなどを受け取る際の処理](http://qiita.com/mpyw/items/2f9955db1c02eeef43ea)
  * [htmlspecialchars関数やhtmlentities関数で使用されるフラグの検証](http://qiita.com/mpyw/items/939c526dd5de6619b565)


CC BY-SA 3.0
2014-12-23T07:55:29.013
2420
2016-11-26T18:09:48.797
2016-11-26T18:09:48.797
940
940
2408
answer


# エスケープの箇所

エスケープが何なのかを考えれば答えは明らかになります。

質問の場合のXSS対策では、HTMLではタグが特別な意味を持つからそれを無効化するということです。そのための方法がエスケープです（なお、タグをタグとして出力する場合はエスケープしませんが、その場合は、許可するタグをきちんと制限できないとXSSが可能になります）。

「<script>」という文字をHTMLで表現するには「&lt;script&gt;」とエスケープする必要があります。つまり、HTMLで出力するからhtmlspecialchars()関数で処理する必要があります。

なので、いつhtmlspecialchars()関数を使うかは、HTMLで出力する直前がベストになります。その他の場合にはエスケープの必要性がないだけでなく、HTML以外で出力する場合に（htmlspecialchars_decode()等で元に戻さないと）変な文字列を出力してしまったり、多重エスケープしてしまうかもしれないなど、ややこしくなるだけでいいことはありません。

参考

  * そもそもエスケープとは何なのか？ <http://blog.ohgaki.net/what-is-escape>

# htmlspecialchars()とhtmlentities()の使い分け

XSS対策としてはどちらでも十分です。

ENT_QUOTESについてはENT_QUOTESでいいです。ENT_COMPATではHTML属性値がすべて "
で括られていることを保証する必要があります。保証できるなら ENT_COMPAT でも問題はありませんが。


CC BY-SA 3.0
2014-12-23T23:01:07.723
2442
2014-12-23T23:01:07.723
5489
2408
answer


**出力時にエスケープ** が妥当な判断だと思います。

ただしXSS対策を行う場合、ただ単にhtmlspecialcharsだけでは防げない場合があるので注意が必要です。

例えば

ユーザーからの入力として`http://someware.com/`のURLを受け取り、あるページでそのURLを以下の様にプログラムが出力した場合

[code]

    <a href="http://someware.com/">
    
[/code]

`http://someware.com/` を `javascript:alert(0);`

とすると < > " / ' は含まれてないので

[code]

    <a href="javascript:alert(0);">test</a>
    
[/code]

なりますが、XSSが発動します。

質問では

[code]

    どこまでやればサニタイズ目的で十分なのかよく分かりません。
    
[/code]

とありますが、出力するコンテンツタイプ application/javascript. application/json, text/html
によってエスケープすべき、条件等が異なります。

htmlにおいてXSSの対策を行う際に、上記の例の様にユーザーから受け付けた入力値をプログラムがHTMLのどこに出力するのか考慮が必要となります。
※JSON,DomでのXSS等もあるので興味があればgoogle等で調べてみてください。

参考のURL
<https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet#XSS_Prevention_Rules_Summary>


CC BY-SA 3.0
2014-12-24T06:17:32.330
2474
2014-12-25T00:31:39.777
2014-12-25T00:31:39.777
570
570
2408
answer


出力先に依ってエスケープの方法は異なることを考えれば、HTML
の出力を大前提にして、入力時にそこに特化したエスケープをあらかじめかけてしまうやり方の不自然さは理解できると思います。そうやって保持されたデータは、HTML
以外の出力先に利用するときにエスケープを戻す処理が必要になりますし、その他のすべてのデータに対して、すでにエスケープされているのかどうかをいちいち判断しなければならなくなります。

アプリケーションが保持するデータはなるべく生に近い形で持っておいて、出力時に用途に合わせてエスケープするという原則が、結果的に処理をシンプルに保つことになります。


CC BY-SA 3.0
2014-12-26T08:31:09.820
2631
2014-12-26T08:31:09.820
1048
2408
answer


「出力時にエスケープ」を支持する事例として、古いフィーチャーフォンのために全角文字を半角文字に変換する処理を例に挙げます。次のコードはエスケープの失敗例です。htmlspecialchars
を呼び出した後で mb_convert_kana や normalizer_normalize を呼び出しているために U+003C
のエスケープが回避されています。

[code]

    // U+FF1C
    $str = '＜';
    $str = htmlspecialchars($str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
    // U+003C
    $ret = '<';
    
    var_dump(
       $ret === mb_convert_kana($str, 'a', 'UTF-8'),
       $ret === normalizer_normalize($str, Normalizer::FORM_KC),
       $ret === normalizer_normalize($str, Normalizer::FORM_KD)  
    );
    
[/code]

U+FF1C 以外に U+003C に変換される可能性のある文字は Unicode Security Guide の [Best-fit
Mappings](http://websec.github.io/unicode-security-guide/character-
transformations/#best-fit) の項をご参照ください。

htmlentities を使う理由はセキュリティ以外に ASCII しか扱えない古いシステムでも ISO 8859-1
のような1バイト文字コードを扱えるようにするためということが挙げられます。

もし、日本語の文字列を HTML の数値文字参照および文字実体参照に変換したいのであれば、mb_encode_numericentity
を使うか、mb_convert_encoding の変換先のエンコーディングに HTML-ENTITIES
を指定します。2つの関数の違いは前者が16進数、後者が10進数で表現されることです。

[code]

    $str = 'あいうえお';
    echo mb_encode_numericentity($str, [0, 0x10FFFF, 0, 0xFFFFFF], 'UTF-8', true), PHP_EOL;
    echo mb_convert_encoding($str, 'HTML-ENTITIES', 'UTF-8'), PHP_EOL;
    
[/code]

**追記**  
数値文字参照が必要になる事例は MySQL で utf8mb3 のテーブルを使っている場合です。utf8mb3 の制約により、4バイト文字 (U+10000
- U+10FFFF) をそのまま保存することができません。


CC BY-SA 3.0
2015-01-02T13:53:13.427
2954
2015-01-15T00:52:47.517
2015-01-15T00:52:47.517
568
568
2408
answer