3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【セキュリティ】「とりあえず `Access-Control-Allow-Origin: *` で動いたからヨシ!」が招くデータ全流出

3
Posted at

導入:「CORSエラー」との不毛な戦い

フロントエンド(React)からバックエンドAPIを叩いた瞬間、真っ赤なエラーがコンソールに殴り込んできます。

Access to fetch at 'https://api.example.com' from origin 'http://localhost:3000'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header...

「CORS…お前またか…」
開発中にこのエラーに遭遇した時、私たちがやりがちなこと。それは、バックエンドの設定ファイルを開いて、こう書くことです。

# Django
CORS_ALLOW_ALL_ORIGINS = True
# Express
app.use(cors()); // デフォルトで全オリジン許可

「よし!エラー消えた!動いた!」
この「とりあえず全許可」が、本番環境にそのまま残った時、あなたのサービスに何が起きるか。今日はその恐怖を解説します。

技術解説:CORSは「ブラウザが張っている結界」である

そもそもCORSとは何か

ブラウザには**「同一オリジンポリシー(Same-Origin Policy)」**という、Webセキュリティの大前提となるルールが組み込まれています。

「あるオリジン(ドメイン)のWebページから、別のオリジンのリソースへのアクセスをデフォルトでブロックする」

例えば、https://evil.com に埋め込まれたJavaScriptが、ユーザーのログイン済みCookieを使って https://api.your-service.com/users/me のデータを fetch しようとしても、ブラウザが「オリジンが違うからダメ!」と弾いてくれます。これが「結界」です。
CORS(Cross-Origin Resource Sharing)は、この結界に「穴」を開ける仕組みです。
サーバーが Access-Control-Allow-Origin: https://your-frontend.com というヘッダーを返すことで、「このオリジンからのアクセスだけは許可するよ」とブラウザに伝えます。

*(ワイルドカード)の恐怖

Access-Control-Allow-Origin: * は、「この世の全てのWebサイトからのアクセスを許可する」という意味です。
これを認証付きのAPI(withCredentials: true / credentials: 'include')で使うとどうなるか。

  1. ユーザーが your-service.com にログインしている(Cookieを持っている)
  2. ユーザーが evil.com を踏む
  3. evil.com のJavaScriptが fetch('https://api.your-service.com/users/me', {credentials: 'include'}) を実行
  4. ブラウザは「* で全許可だし、Cookie付きリクエストを送ってOK」と判断
  5. ユーザーの個人情報がレスポンスとして evil.com のJavaScriptに渡される
    (※実際にはブラウザの仕様で credentials: include* の組み合わせはブロックされますが、一部のバックエンドが Origin ヘッダーをそのままエコーする実装をしている場合、この防御は突破されます)

「Originエコー」という最悪のパターン

# 最悪のアンチパターン:リクエストのOriginをそのまま返す
response['Access-Control-Allow-Origin'] = request.headers['Origin']
response['Access-Control-Allow-Credentials'] = 'true'

これは「全てのオリジンを信頼する」のと同義であり、* よりも遥かに危険です。攻撃者のオリジンがそのまま許可オリジンとして返されるため、Cookie付きリクエストの制限も回避されてしまいます。

CORSを正しく設定するための鉄則

1. 許可オリジンをホワイトリストで管理する

ALLOWED_ORIGINS = ['https://your-frontend.com', 'https://staging.your-frontend.com']
if request.headers['Origin'] in ALLOWED_ORIGINS:
    response['Access-Control-Allow-Origin'] = request.headers['Origin']

2. 開発環境と本番環境の設定を分離する
CORS_ALLOW_ALL_ORIGINS = True は開発環境の .env だけに留め、本番の設定ファイルには絶対に含めない仕組み(環境変数での切り替え)を作りましょう。
3. null オリジンを許可しない
Access-Control-Allow-Origin: null も危険です。ローカルHTMLファイルや data: URI、サンドボックスiframeからのリクエストは Origin: null を送信するため、攻撃に悪用される可能性があります。
CORSエラーは「邪魔な門番」ではありません。それはあなたのユーザーを守る「最後の砦」です。開発中のストレスに負けて砦を取り壊すのではなく、正しい通行証(ホワイトリスト)を発行してあげてください。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?