Laravel の Blade テンプレートから JavaScript に構造体データを渡すとき、しばしば json_encode が使用されます。次はその例です。
<script> window.hoge = {!! json_encode(['a' => 1]) !!} </script>
これを実行すると次の様に window オブジェクトに PHP 中で定義したデータをオブジェクトとして渡せます。
{!! !!}
でHTMLエスケープ機能を外しているのは、エスケープ付きでは次の様な文字列になり JSON として不適切になるためです。
window.hoge = {"a":1}
この方法は便利なのですが、このままでは問題もあります。XSS の脆弱性があります。
例として特にダメなパターンで絶対にしてはいけないパターンが次です。
<script> window.hoge = {!! json_encode(['a' => "</script></head><pre>XSS</pre>"], JSON_UNESCAPED_SLASHES) !!} </script>
JSON_UNESCAPED_SLASHES オプションを与えた json_encode 関数はデフォルトで入っている / をエスケープしてくれる挙動を使わなくなります。これにより script タグを閉じて任意の HTML を直に注入できるようになります。あえて危険な方にコードを倒すことはまずありませんが、こういうパターンもあります。
PHP: 定義済み定数 – Manual
危険なオプションを使わないことにより致命的な動作を防ぎやすくなりますが、まだよろしくない挙動は残ります。例えば次の様にプロトタイプ汚染的な攻撃ができます。
<script> window.hoge = {!! json_encode(['__proto__' => ['toString' => "XSS"]] ) !!} </script>
例は window.hoge の toString を壊すだけのコードです。例ではテンプレート文字列や String 型へのキャストができなくなります。これにより JavaScript の関数として実行されずともプログラムの正常な挙動を妨げることは十分できます。
これらの対策として信用できない情報源からの情報は決してアンエスケープしない、というのがありますが正直なところめんどくさいです。せいぜいデータベース中の数値型の値をひっぱってきているだけ、定数をまとめてひっぱってきているだけ、といったところまでが限度です。どこからか入力されて変化する文字列型が入って来たあたりで嫌になってきます。
エスケープしつつ正常にオブジェクトとして認識させたくなります。これは次でできます。
<meta id='hoge' content='{{ json_encode(['__proto__' => ['toString' => "XSS"]] ) }}'> <script> window.hoge = JSON.parse(document.getElementById('hoge').content); </script>
ある要素の属性としてエスケープされた安全な HTML 文字列の JSON 文字列を配置し、それを JavaScript で読み取ってパースします。これにより次の画像の様に期待した通りのオブジェクトになり、プロトタイプ汚染も起きません。
画像の hogeXSS は以下のコードで作成されたプロトタイプが壊されたオブジェクトです。
<script> window.hogeXSS = {!! json_encode(['__proto__' => ['toString' => "XSS"]] ) !!} </script>