テキストエスケープ
引用符、改行、山かっこ——文字列を組み立てるときに一番事故りやすいところ。ここで先にエスケープしておきましょう。
SQL のエスケープはシングルクォートを二重にするだけで、パラメータ化クエリの代わりにはなりません。SQL の文字列連結には常にインジェクションのリスクがあるため、ここではデバッグ用のリテラル生成にのみ使ってください。
使う場面
- JSON のリクエストボディを手で書き、ユーザー入力の文字列を 1 つ値の中に入れたいとき。JSON モードは引用符の内側に入る形のエスケープ結果をそのまま返すので、バックスラッシュも改行もタブも自分で処理する必要はありません。
- ログや API の値に " が現れ、何層エスケープされているのか知りたいとき。HTML モードで 1 回戻したときに " が残っていれば、2 層以上エスケープされていると判断できます。
使わないほうがよい場面
- 信頼できない入力を SQL 文に連結しようとしている場合。SQL モードはシングルクォートを 2 つにするだけで、MySQL は既定でバックスラッシュもエスケープ文字として扱うため \' で抜けられます。数値、ORDER BY の列名、LIKE のワイルドカードは守れません。プリペアドステートメント(? / $1)を使ってください。
- ユーザー投稿の HTML を無害化してから描画したい場合。ここで置換するのは & < > " ' の 5 文字だけで、on* イベント属性、javascript: スキーム、引用符なし属性はそのまま通ります。DOMPurify のような許可リスト方式のサニタイザを使ってください。
エスケープと復元はすべてブラウザ内で完結し、入力は送信も記録もされません。制限:JSON モードは単一の文字列値のみで、文書全体の検証はしません。SQL モードは引用符を 2 つにするだけで、MySQL のバックスラッシュモードや数値・識別子の位置では無防備であり、パラメータ化クエリの代わりにはなりません。HTML モードは 5 文字のみで XSS サニタイザではありません。エスケープは冪等ではありません。
よくある質問
SQL エスケープで SQL インジェクションは防げますか?
単独では防げません。SQL モードはシングルクォートを 2 つにするだけで、これは標準 SQL でリテラルを書く作法です。しかし MySQL は既定でバックスラッシュをエスケープ文字として扱うため \' で抜け出せますし、GBK のような可変長エンコーディングには専用の回避手法が古くから知られています。数値パラメータ、ORDER BY の列名、LIKE の % や _ は引用符では対処できません。手元で一度だけクエリを叩く用途なら構いませんが、アプリのコードではプリペアドステートメントを使ってください。エスケープは保険であって防御線ではありません。
JSON モードの結果に前後の二重引用符が付かないのはなぜ?
文字列の内側だけを返しているためです。既存の "..." の中にそのまま貼れるようにしています。完全な JSON リテラルが必要なら外側の引用符は自分で付けてください。復元時は入力を 1 つの JSON 文字列として解析するので、未エスケープの引用符が混じっていると途中までの結果ではなくエラーになります。壊れたエスケープ文字列の意味を推測する用途には向きません。また HTML の <script> の中に入れる場合は、</script> と U+2028 / U+2029 も別途処理しないと、スクリプトが途中で閉じたり構文エラーになります。
2 回エスケープすると & が &amp; になるのは不具合ですか?
いいえ。エスケープは冪等ではありません。1 回目で & が & になり、2 回目でその中の & がまた変換されて &amp; になります。画面に & という文字がそのまま出る不具合の典型的な原因は、2 回エスケープして 1 回しか戻していないことです。出力先のコンテキストに書き込む瞬間に 1 回だけ変換してください。他人がエスケープした値を受け取ったときは、何層あるかを見極めて一度に戻します。& を見るたびに反射的に 1 回押すのは避けてください。