「ログイン後のリダイレクト先」を信じてはいけない
多くのWebサービスには、「未ログイン状態で特定のページにアクセスすると、ログイン画面に飛ばされ、ログイン完了後に元のページに自動で戻る」という便利な機能があります。
https://your-service.com/login?redirect_to=/dashboard
redirect_to パラメーターに元のURLが入っていて、ログイン処理の後にそこにリダイレクトする。よく見る実装ですよね。
では、このURLを攻撃者が以下のように書き換えて、標的のユーザーにメールで送りつけたらどうなるでしょうか。
https://your-service.com/login?redirect_to=https://evil-phishing.com/fake-dashboard
ユーザーの目には your-service.com という本物のドメインが映ります。「公式サイトだ」と安心してログインすると、認証成功後にこっそり evil-phishing.com に飛ばされ、「セッションが切れました。もう一度パスワードを入力してください」という偽画面が表示される──。
これが**オープンリダイレクト(Open Redirect)**攻撃です。
なぜ危険なのか
「リダイレクトされるだけでしょ?大したことなくない?」と思うかもしれません。
しかし、オープンリダイレクトの真の恐怖は、**「攻撃者が正規のドメインを踏み台にしてフィッシングの信頼度を上げる」**ところにあります。
怪しいURLならユーザーは警戒します。しかし https://your-service.com/login?... という URL は、どこからどう見ても本物のログインページです。セキュリティ研修で「URLを確認しましょう」と言われた優秀な社員ですら、このURLには騙されます。
さらに、オープンリダイレクトはSSRFやOAuthトークン窃取との組み合わせ攻撃(チェイン攻撃)にも利用されるため、単体での危険度以上にリスクが高い脆弱性です。
防御策
1. リダイレクト先をホワイトリストで制限する
ALLOWED_REDIRECTS = ['/dashboard', '/settings', '/projects']
redirect_to = request.args.get('redirect_to', '/dashboard')
if redirect_to not in ALLOWED_REDIRECTS:
redirect_to = '/dashboard' # 不正な値はデフォルトに戻す
2. 外部URLへのリダイレクトを禁止する
リダイレクト先が自ドメインの相対パスであることを検証し、https://... で始まる外部URLは一律拒否します。
from urllib.parse import urlparse
parsed = urlparse(redirect_to)
if parsed.netloc: # ドメインが含まれている = 外部URL
redirect_to = '/dashboard'
//evil.com のバイパスに注意する
//evil.com はプロトコル相対URLとしてブラウザに解釈され、外部サイトにリダイレクトされます。単純に「httpやhttpsで始まるかチェック」だけでは不十分です。// で始まるパターンも必ずブロックしましょう。 ユーザーが「公式サイトのURLだから安全」と信じている信頼を、攻撃者に利用させてはいけません。redirect_to` は「ユーザー入力」であり、信頼してはいけないデータです。