概要
eval()を使ってJSONをパースする処理において、巧妙なペイロードによりXSS攻撃が可能になる脆弱性について解説します。
前提
JSONデータを作成する場合エスケープすべき文字があります。
今回のケースでは\ バックスラッシュがエスケープされていません。
" → \" // ダブルクォート(文字列終了を防ぐ)
\ → \\ // バックスラッシュ(エスケープ文字)
\b → \\b // バックスペース u+0008
\f → \\f // フォームフィード u+000C
\n → \\n // 改行文字 u+000A
\t → \\t // タブ文字 u+000D
\r → \\r // 復帰文字 u+0009
u+0000 ~ u+001F → \uXXXX
脆弱なコード例
// 危険なコード例
eval('var searchResultsObj = ' + this.responseText);
攻撃の仕組み
基本的な攻撃ペイロード
○入力値
\"-alert(1)}//
○生成されるJSONレスポンス
{"results":[],"searchTerm":"\\"-alert(1)}//"}
※”はエスケープされ\”となる
※そしてeval()ではjavascriptとして評価されるため\”ではなくその前の\\と”として評価される
○eval()で実行される実際のJavaScriptコード
eval('var searchResultsObj = ' + '{"results":[],"searchTerm":"\\"-alert(1)}//"}')
構文解釈の流れ
1.\\ は javascriptでは\となる
2.”searchTerm”:”\”で文字列が終了
2.-alert(1) がJavaScript演算式として実行される
3.alert(1)が副作用として実行される(ダイアログ表示)
4.”” – undefinedの演算結果はNaN
5.} でオブジェクトが終了
6.// で残りの文字列がコメントアウトされ構文エラーを回避
重要なポイント
JSON vs JavaScript の違い
要素 | JSON仕様 | JavaScript(eval) |
“-alert(1) | ❌ 構文エラー | ✅ 有効な演算子 |
// コメント | ❌ サポートなし | ✅ 有効なコメント |
関数呼び出し | ❌ 不可能 | ✅ 実行される |
修正方法
解決策としてeval()ではなくJSON.parse()を使う。
// 危険
eval('var searchResultsObj = ' + this.responseText);
// 安全
const searchResultsObj = JSON.parse(this.responseText);
サーバー側の問題
前提にも記載したように今回はサーバー側のエスケープ処理でバックスラッシュがエスケープされていませんでした。
このケースにおいてはクライアント側とサーバー側のどちらか一方を正しく修正すれば攻撃は防げますが、両方修正することで多層防御が実現できます。