「JSON 安全エンコード」で何を守りたいのか
JSON 自体はただのデータ形式ですが、「どこに出すか」で話が一気に“セキュリティの話”になります。
特に危険なのは、JSON をそのまま HTML や JavaScript の中に埋め込むときです。
例えば、サーバー側でこういうコードを書いたとします。
$data = ['name' => '</script><script>alert(1)</script>'];
echo '<script>const DATA = ' . json_encode($data) . ';</script>';
PHPjson_encode() 自体は正しいのですが、値の中に </script> が入っていると、
ブラウザ側では「script タグが途中で閉じた」と解釈され、
その後ろの <script>alert(1)</script> が“本物のスクリプト”として実行されてしまう可能性があります。
「JSON を作る」だけでなく、「HTML/JS に埋め込んでも安全な形にする」——
これが「JSON 安全エンコード」の目的です。
json_encode の基本と「フラグ」の存在を知る
まずは素の json_encode
PHP で JSON を作る基本は json_encode() です。
$data = ['name' => '山田太郎', 'age' => 20];
$json = json_encode($data);
echo $json;
// {"name":"山田太郎","age":20}
PHP配列や連想配列を渡すと、いい感じに JSON にしてくれます。
ここまではよく見る使い方ですが、「安全に出力する」ためには第 2 引数のフラグが重要になってきます。
HTML/JS に埋め込むときに効いてくるフラグ
json_encode() には、いくつかの「危険な文字をエスケープする」ためのフラグがあります。代表的なのが次の 4 つです。
JSON_HEX_TAG:<と>を\u003C\u003EにするJSON_HEX_AMP:&を\u0026にするJSON_HEX_APOS:'を\u0027にするJSON_HEX_QUOT:"を\u0022にする
これらを組み合わせることで、「タグや属性を壊しにくい JSON 文字列」にできます。
実務で使える「JSON 安全エンコード」ユーティリティ
HTML の中に直接埋め込む用
一番よくあるのが、「サーバー側で作ったデータを <script> 内の変数に埋め込む」パターンです。
$data = [
'name' => '</script><script>alert(1)</script>',
'age' => 20,
];
PHPこれをそのまま json_encode() すると危ないので、
安全フラグを全部盛りにしたユーティリティ関数を用意します。
/**
* HTML/JS に埋め込むための「安全な JSON エンコード」
*/
function json_encode_safe_for_html(mixed $value): string
{
$flags =
JSON_HEX_TAG | // < >
JSON_HEX_AMP | // &
JSON_HEX_APOS | // '
JSON_HEX_QUOT | // "
JSON_UNESCAPED_UNICODE | // 日本語を \uXXXX にしない
JSON_UNESCAPED_SLASHES; // / をエスケープしない(読みやすさ用)
$json = json_encode($value, $flags);
if ($json === false) {
// エラー時の扱いはプロジェクト方針に合わせて
throw new RuntimeException('JSON encode failed: ' . json_last_error_msg());
}
return $json;
}
PHP使い方の例です。
$data = ['name' => '</script><script>alert(1)</script>'];
echo '<script>const DATA = ' . json_encode_safe_for_html($data) . ';</script>';
PHPJSON_HEX_TAG などのおかげで、</script> の < > が \u003C \u003E に変換され、
ブラウザは「タグの終わり」と認識できなくなります。
結果として、意図しないスクリプト実行を防ぎやすくなります。
API レスポンス用の「安全」との違い
API では「HEX 系フラグ」はいらないことが多い
REST API のレスポンスとして JSON を返すだけなら、
HTML に埋め込むわけではないので、JSON_HEX_* 系は必須ではありません。
むしろ、次のようなフラグを使うことが多いです。
JSON_UNESCAPED_UNICODE: 日本語をそのまま出す(\uXXXXにしない)JSON_UNESCAPED_SLASHES:/を\/にしないJSON_PRETTY_PRINT: 開発中だけ読みやすく整形
API 用のユーティリティは、こんな感じになります。
function json_encode_safe_for_api(mixed $value): string
{
$flags =
JSON_UNESCAPED_UNICODE |
JSON_UNESCAPED_SLASHES;
$json = json_encode($value, $flags);
if ($json === false) {
throw new RuntimeException('JSON encode failed: ' . json_last_error_msg());
}
return $json;
}
PHPここでの「安全」は、主に「文字化けしない」「パースエラーにならない」という意味での安全です。
XSS 対策としての「安全」は、HTML に埋め込むときにこそ意識するポイントです。
例題で違いを体感する
危なそうな文字を含むデータ
$data = [
'html' => '<div onclick="alert(1)">クリック</div>',
'js' => '</script><script>alert("XSS")</script>',
];
PHPこれをそれぞれの関数でエンコードしてみます。
echo json_encode($data);
// {"html":"<div onclick=\"alert(1)\">クリック<\/div>","js":"<\/script><script>alert(\"XSS\")<\/script>"}
echo json_encode_safe_for_html($data);
// {"html":"\u003Cdiv onclick=\u0022alert(1)\u0022\u003Eクリック\u003C/div\u003E","js":"\u003C/script\u003E\u003Cscript\u003Ealert(\u0022XSS\u0022)\u003C/script\u003E"}
echo json_encode_safe_for_api($data);
// {"html":"<div onclick=\"alert(1)\">クリック</div>","js":"</script><script>alert(\"XSS\")</script>"}
PHPAPI 用は「そのまま返す」ので、クライアント側(ブラウザやアプリ)が JSON としてパースしてからどう扱うかを決めます。
HTML 埋め込み用は、「タグや属性として解釈されないように」危険な文字を全部 \uXXXX に変換しているのが分かります。
まとめ:今日からの「JSON 安全エンコード」ユーティリティ
押さえておきたいポイントは、次の 3 つです。
JSON 自体はただのデータだが、「HTML/JS に埋め込むとき」に XSS の入り口になりやすい。json_encode() の JSON_HEX_TAG などのフラグで、タグや属性を壊しにくい形にできる。
API 用と HTML 埋め込み用で、「安全の意味」と使うフラグが少し違う。
実務ユーティリティとしては、少なくともこの 2 本を持っておくと安心です。
function json_encode_safe_for_html(mixed $value): string
{
$flags =
JSON_HEX_TAG |
JSON_HEX_AMP |
JSON_HEX_APOS |
JSON_HEX_QUOT |
JSON_UNESCAPED_UNICODE |
JSON_UNESCAPED_SLASHES;
$json = json_encode($value, $flags);
if ($json === false) {
throw new RuntimeException('JSON encode failed: ' . json_last_error_msg());
}
return $json;
}
function json_encode_safe_for_api(mixed $value): string
{
$flags =
JSON_UNESCAPED_UNICODE |
JSON_UNESCAPED_SLASHES;
$json = json_encode($value, $flags);
if ($json === false) {
throw new RuntimeException('JSON encode failed: ' . json_last_error_msg());
}
return $json;
}
PHPもし、あなたのテンプレートのどこかで '<script>const DATA = ' . json_encode($data) . ';</script>' のようなコードが素のまま使われていたら、そこがこの「JSON 安全エンコード」に差し替えるべきポイントです。そこを一箇所直すだけで、XSS の入り口をかなり狭めることができます。
