JSON・HTML・SQL のエスケープ:3つの文脈を混同しない
対象の文脈(JSON 文字列、HTML エンティティ、SQL リテラル)を選び、テキストを貼り付けて「エスケープ」を押します。逆方向は「元に戻す」です。JSON モードが返すのは文字列の内側の内容で、前後のダブルクォートは含まれません。
エスケープが本当に必要な場面
JSON のリクエストボディを手で書いていて、ユーザー入力を1つの文字列値に入れたいとき、JSON モードは引用符の内側に入れるべき内容をそのまま返します。バックスラッシュ、ダブルクォート、改行、タブはすでに処理済みです。
多層エスケープの確認では、" のような断片を HTML モードで1回戻してみます。まだ " が残っていれば2回以上エスケープされているので、読める形になるまで戻します。
データを目視確認するために SQL リテラルを手で組み立てるのもよくある場面です。文法エラーで止まらないようにするためのもので、アプリケーションのコードに使うものではありません。
- JSON のリクエストボディに文字列値を手で入れる
- HTML エンティティが何層エスケープされているか確認する
- デバッグ用の SQL リテラルを組み立てる
- 設定ファイルやテンプレートに埋め込む前にエスケープする
失敗しないための手順
JSON モードは1つの文字列値だけを扱い、文書全体の妥当性は検証しません。括弧の対応もカンマも見ないため、全体を検証したいときは JSON 整形ツールを使ってください。
元に戻すときは入力全体を1つの JSON 文字列として解析します。そのためエスケープされていない引用符があると、途中までの結果ではなくエラーになります。欠けたエスケープ列の意味を推測する用途には使えません。
結果を HTML の <script> の中に入れる場合、JSON のエスケープだけでは足りません。加えて </script> と U+2028、U+2029 も処理しないと、スクリプトが途中で閉じたり文法エラーになったりします。
HTML モードが置換するのは &、<、>、ダブルクォート、シングルクォートの5文字だけです。テキストがタグとして解釈されるのを防げますが、XSS 対策にはなりません。on* イベント属性、javascript: の URL、引用符のない属性はそのまま通ります。ユーザー投稿の HTML を無害化するには DOMPurify のようなホワイトリスト方式のサニタイザーが必要です。
SQL モードはシングルクォートを2つに増やすだけで、標準 SQL でリテラルを引用する書き方です。MySQL は既定でバックスラッシュをエスケープ文字として扱うため、バックスラッシュを伴う引用は破れます。数値の位置、ORDER BY の列名、LIKE のワイルドカードは守備範囲外です。
エスケープは冪等ではありません。1回目で & が & になり、2回目で & の中の & がさらに &amp; になります。画面に & や &amp; がそのまま表示されるときは、想定より1層多くエスケープされている可能性があります。対象の文脈へ書き込む瞬間に1回だけ行ってください。
- JSON モードは文字列値のみ。文書全体の検証はしない
- 結果を <script> に入れるときは </script> と U+2028/U+2029 も処理する
- HTML モードは5文字のみで XSS 対策にはならない
- SQL モードはシングルクォートを2倍にするだけで、注入は防げない
代替手段と限界
JSON は言語標準のシリアライザーに任せてください。JSON.stringify はエスケープを正しく処理し、オブジェクト全体を一度に扱えます。
HTML はテンプレートエンジンの自動エスケープか、標準ライブラリの html.escape を使ってください。リッチテキストの無害化は、自作の正規表現ではなく DOMPurify のようなホワイトリスト方式のライブラリで行います。
SQL はプレースホルダー(? や $1)を使ったパラメーター化クエリやプリペアドステートメントを使ってください。値はデータとして渡し、文字列に連結しません。数値、識別子、ワイルドカードまで守れるのはこの方法だけです。
このツールの限界は明確です。JSON モードは1つの文字列値のみ、HTML モードは5文字のみ、SQL モードはシングルクォートを2倍にするだけで、MySQL のバックスラッシュモードや数値の位置は守れません。
- JSON → JSON.stringify
- HTML → テンプレートの自動エスケープか DOMPurify
- SQL → プレースホルダーを使ったパラメーター化クエリ
- エスケープは1回だけ。重ねて行わない
よくある質問
SQL のエスケープだけで SQL インジェクションは防げますか?
それだけでは防げません。SQL モードはシングルクォートを2つに増やすだけで、これは標準 SQL でリテラルを引用する書き方です。MySQL は既定でバックスラッシュをエスケープ文字として扱うため、バックスラッシュを伴う引用は破れます。GBK のようなワイド文字エンコーディングには歴史的に独自の回避手法があり、数値パラメーター、ORDER BY の列名、LIKE の % や _ は引用符では守れません。アプリケーションのコードでは、プレースホルダーを使ったパラメーター化クエリやプリペアドステートメントを使ってください。エスケープは補助的な手段にとどめます。
JSON のエスケープ結果に前後のダブルクォートがないのはなぜですか?
文字列の内側の内容を返しているためです。既にある引用符の間にそのまま入れられるようになっています。完全な JSON リテラルが必要なときは、外側の引用符を自分で補ってください。元に戻すときは入力全体を1つの JSON 文字列として解析するため、エスケープされていない引用符があると、途中までの結果ではなくエラーになります。
2回エスケープすると & が &amp; になるのは不具合ですか?
不具合ではありません。エスケープは冪等ではなく、1回目で & が & になり、2回目で & の中の & がさらにエスケープされます。画面に & や &amp; がそのまま表示されるのは、想定より1層多くエスケープされているためです。対象の文脈へ書き込む瞬間に1回だけエスケープしてください。すでにエスケープされたデータを受け取った場合は、何層になっているかを確認してから、まとめて元に戻します。
関連ツール:Text Escape