JSONPという「亡霊」を葬り去る:CORS時代のクロスドメイン通信とアーキテクチャの防衛論
現場でコードをレビューしていると、時折、化石のような実装に出くわすことがある。その筆頭がJSONP(JSON with Padding)だ。現代のWebアプリケーションにおいて、JSONPを使い続けることは、セキュリティの扉に鍵をかけず、かつ丁寧に「どうぞご自由にお入りください」という看板を掲げているに等しい。
なぜJSONPが危険なのか。それは、この手法が「ブラウザのセキュリティモデルの隙間」を縫うために、実行コンテキストをドメインの境界を超えて強制的に共有させるからだ。
1. JSONPが抱える「本質的な罪」
JSONPのメカニズムはシンプルだ。タグのsrc属性で外部リソースを読み込み、サーバー側でJavaScript関数を動的に生成し、その引数としてJSONデータを流し込む。
// 攻撃者が狙うJSONPの脆弱なエンドポイント例
// callbackパラメータを適切にサニタイズしていない場合
const callback = req.query.callback; // 例: alert(document.cookie)
res.send(${callback}(${JSON.stringify(data)}));
ここで起きていることは明白だ。攻撃者はcallbackパラメータに任意のJSコードを注入し、被害者のブラウザ上でセッションクッキーを盗み出したり、DOMを書き換えたりする。これは古典的なXSS(クロスサイトスクリプティング)の温床であり、WAF(Web Application Firewall)をすり抜けるための古典的だが強力なベクターだ。
2. CORSへの移行:ゲートキーパーとしてのHTTPヘッダー
現代のWeb開発において、クロスドメイン通信の正当な後継者はCORS(Cross-Origin Resource Sharing)だ。しかし、多くのエンジニアはCORSを「とりあえずAccess-Control-Allow-Origin: にしておけば動く」という設定項目程度にしか捉えていない。これは非常に危険な誤解だ。
CORSは、HTTPのプリフライトリクエスト(OPTIONSメソッド)を通じて、ブラウザがサーバーに対し「このリクエストは許可されているか?」を問い合わせる仕組みだ。この「確認プロセス」こそが、セキュリティの要となる。
実装のベストプラクティス:ホワイトリストによる厳格な制限
ワイルドカード(``)の使用は、認証情報(CookieやAuthorizationヘッダー)を伴うリクエストでは許可されない。機密性の高いAPIを設計する際は、必ずオリジンを明示的に検証する設計にすべきだ。
// Go言語でのCORSミドルウェア設定例(安全な設計)
func CORSMiddleware() gin.HandlerFunc {
return func(c gin.Context) {
origin := c.Request.Header.Get("Origin")
// 許可されたドメインのホワイトリスト
allowedOrigins := map[string]bool{
"https://app.secure-system.com": true,
"https://admin.secure-system.com": true,
}
if allowedOrigins[origin] {
c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
}
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
}
}
3. 次世代の脅威:プロンプトインジェクションとCORSの未来
我々が直面しているのは、単なるSQLインジェクションやXSSだけではない。LLM(大規模言語モデル)を統合したアプリケーションでは、フロントエンドがAPI経由でLLMを呼び出す際、JSONP的な「境界線の曖昧さ」が悪用され、プロンプトインジェクションの踏み台にされるケースが増えている。
防御側の視点として重要なのは、「APIの入口(CORS)」と「LLMの出力(ガードレイル)」の二段構えだ。
1. CORSポリシーの厳格化: フロントエンドアプリとAPIサーバー間の信頼関係を、OAuth2.0のスコープ制御と組み合わせて固定する。
2. 出力のサニタイズ(Guardrails): LLMからのレスポンスをそのままUIにレンダリングするのではなく、HTML/JSのコンテキストから隔離されたサンドボックス環境(あるいはContent Security Policyの厳格な適用)で処理する。
4. 最後に:セキュリティは「仕様」ではなく「規律」である
JSONPを廃止し、CORSへ移行することは、単なる技術的なリプレイスではない。それは、システム境界に対する信頼のあり方を「黙示的な共有」から「明示的な許可」へとシフトさせる文化的な転換だ。
パケットレベルで見れば、CORSはブラウザの自律的な制御によって成り立っている。しかし、その背後にあるのは、開発者がどれだけ深く「誰が、どこから、何を要求しているか」を理解しているかという規律だ。
脆弱性は常に、設計者の「まさかここまではしないだろう」という甘えの隙間に宿る。CORSの設定一つとっても、その裏にあるリクエストのライフサイクルを可視化できているか。それが、インシデントの最前線に立つ我々エンジニアに問われていることだ。
次のアーキテクチャ設計では、ぜひ「なぜこの通信は安全と言い切れるのか」を、CORSヘッダーの向こう側まで説明できるようにしておいてほしい。それがプロフェッショナルというものだ。
コメント