json_encodeでHTML埋め込み用に安全にエスケープする

  • PHP
  • JSON
  • Security

この記事は、構成と文章の整理にAIを使っています。
あくまで読みやすくするための補助で、内容そのものをAIにすべて任せて書いているわけではありませんが、ところどころ相談しながら整えています。

何のためのオプションか

PHPのjson_encode()をHTMLの中(<script>タグ内とか、属性値とか)に埋め込む用途で使う場合、そのままだと<や"や&がエスケープされずに出力されて、XSSの原因になり得る。これを防ぐための組み合わせが以下。

public function jsonSafeEncode($data) {
return json_encode($data, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
}

4つのフラグをORで組み合わせて、HTML的に危険な文字を軒並みUnicodeエスケープに変換する狙い。

各フラグが対象にしている文字

フラグ 対象文字 変換後
JSON_HEX_TAG < > (U+003C, U+003E) \u003C \u003E
JSON_HEX_QUOT " (U+0022) \u0022
JSON_HEX_AMP & (U+0026) \u0026
JSON_HEX_APOS ' (U+0027) \u0027

どれもASCII範囲の記号だけが対象で、見た目が似ているUnicodeの記号(全角引用符とか)は含まれない。

たとえばJSON_HEX_APOSは半角のアポストロフィ(U+0027)だけを変換する。

json_encode("it's", JSON_HEX_APOS);
// => "it\u0027s" 半角アポストロフィは変換される
json_encode("it’s", JSON_HEX_APOS);
// => "it’s" U+2019(カーブクォート)はそのまま、対象外

U+2019のような見た目が似ている引用符はエスケープ対象にならない。これをエスケープするかどうかはJSON_UNESCAPED_UNICODEの有無の話で、JSON_HEX_APOSとは別軸。自分でメモを書いてたときにこの2つの文字を混同してて、エディタの自動置換かコピペで半角'が見た目の似た’に化けてたのが原因だった。

このメモに無かった注意点

  • デフォルト(フラグ無し)だと/が\/にエスケープされる。これを避けたいならJSON_UNESCAPED_SLASHESが別途要る。今回の4フラグには含まれてない。
  • JSON_HEX_TAG|AMP|APOS|QUOTの組み合わせはHTML埋め込み用のXSS対策として定番で、jsonSafeEncodeのような実装自体は妥当。

分かったこと

  • HEX系フラグが対象にしているのはASCII範囲の記号(< > " & ')だけ
  • 見た目が似ているUnicodeの記号(’など)はエスケープ対象外
  • HTML埋め込み用に安全化したいならJSON_HEX_TAG|JSON_HEX_AMP|JSON_HEX_APOS|JSON_HEX_QUOTの組み合わせが定番、/も避けたいならJSON_UNESCAPED_SLASHESも追加する