【実務・中級編】JSONPのコールバック関数悪用によるXSS – アプリケーションセキュリティ & 安全な開発防御ガイド

JSONPは「レガシーの墓場」か「脆弱性の温床」か:今すぐ止めるべき理由と代替策

現場でコードレビューをしていると、未だに「他ドメインからの非同期通信」を実装するためにJSONPを使っている古いモジュールに遭遇することがある。正直に言おう。JSONPは、現代のWebアプリケーションにおいて、自ら「XSSを許可してください」という看板を掲げているようなものだ。

今回は、なぜJSONPが攻撃者の格好の標的になるのか、そして現代のエンジニアが採るべき「正攻法」について、実務的な観点から深掘りする。

—

1. なぜJSONPは「XSSの温床」なのか?

JSONP(JSON with Padding)の仕組みは至ってシンプルだ。クライアントがリクエスト時にcallback=myFuncといったパラメータを送り、サーバー側がそれをそのままJavaScriptの関数呼び出しとしてラップして返す。

// サーバーが返すレスポンス例
myFunc({“user_id”: 123, “status”: “active”});

ここでの脆弱性は、callbackパラメータの入力をサーバーが適切に検証・エスケープしていないことにある。攻撃者はここを突く。

攻撃者の視点:PoC(概念実証)

攻撃者は、コールバック関数名を指定する箇所に悪意あるスクリプトを注入する。

GET /api/data?callback=

サーバーがこれをそのまま返せば、クライアントブラウザはレスポンスを「スクリプト」として解釈し、即座に実行する。これがJSONPにおける典型的なXSSだ。さらに、callbackの値を正規表現で厳格にチェックしていない場合、alert(1);/ のような形でコメントアウトを活用した高度なエスケープ回避が行われることも珍しくない。

—

2. JSONPからCORSへの移行:実務的な解決策

もはやJSONPを使う理由は皆無だ。現代のWeb開発ではCORS(Cross-Origin Resource Sharing)を使うのが鉄則である。

CORSは、HTTPヘッダーを用いて「どのドメインからのリクエストを許可するか」をサーバー側で厳密に制御する仕組みだ。JSONPのようにスクリプトタグを悪用することなく、安全にデータをやり取りできる。

【実装サンプル】Python (Flask) によるCORSの安全な設定

Flaskなどのフレームワークを使っているなら、flask-corsなどのライブラリを使い、ホワイトリスト形式でドメインを制限するのが基本だ。

from flask import Flask, jsonify
from flask_cors import CORS

app = Flask(__name__)

全ドメインを許可するのではなく、必要なドメインのみをホワイトリスト化する
CORS(app, resources={r”/api/”: {“origins”: “https://trusted-partner.com”}})

@app.route(‘/api/user-data’)
def user_data():
# 安全にJSONを返却。コールバックによるラップは不要。
return jsonify({“user_id”: 123, “role”: “admin”})

【実装サンプル】NginxによるCORSヘッダー制御

アプリケーション層に届く前に、Webサーバー(Nginx)でヘッダーを制御することも有効な防御壁となる。

Nginxの設定例
location /api/ {
# 許可するオリジンを厳格に指定(ワイルドカードは避ける)
add_header ‘Access-Control-Allow-Origin’ ‘https://trusted-partner.com’ always;
add_header ‘Access-Control-Allow-Methods’ ‘GET, POST, OPTIONS’ always;

# プリフライトリクエストの処理
if ($request_method = ‘OPTIONS’) {
return 204;
}
}

—

3. どうしてもJSONPを直ちに廃止できない時の「泥臭い」防御策

レガシーシステムの改修には時間がかかる。その間の「延命措置」として、最低限以下の実装を死守してほしい。

コールバック関数のホワイトリスト化(PHPの例)

不特定多数の文字列を受け入れるのではなく、許可された名前のみを許容する。

‘success’]);
echo “{$callback}({$data});”;
?>

※重要ポイント:
1. 正規表現による厳格なバリデーション: [a-zA-Z0-9_] 以外を徹底的に排除する。
2. MIMEタイプの固定: Content-Type: application/javascript を強制することで、ブラウザ側の誤解釈を防ぐ。
3. X-Content-Type-Options: nosniff: ブラウザがコンテンツを勝手に推測してスクリプトとして実行するのを防ぐために、このヘッダーを必ず付与する。

—

最後に:セキュリティは「諦め」の積み重ね

セキュリティチーフとして言わせてもらえば、「JSONPが動いている」という事実自体が技術的負債であり、攻撃者に対する招待状だ。

「昔からこれで動いているから」という理由は、インシデント発生時の免罪符にはならない。もし君のプロジェクトでJSONPが使われているなら、次のスプリントで「JSONP廃止チケット」を切ることを強く推奨する。

技術は常に進化している。古いコードに固執するのではなく、CORSのようなモダンで安全な仕組みへ乗り換える勇気こそが、プロのエンジニアに求められる「真の防御力」だ。

もし修正の過程で「これって本当に安全か?」と迷ったら、いつでも相談してくれ。手を動かす前に、設計を疑うことから始めよう。

コメント

タイトルとURLをコピーしました