不触发预检的CSRF攻击:Content-Type:text/plain的魔法

admin 2025-12-22 03:45:19 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 这篇文章介绍了一种绕过CORS预检请求的CSRF攻击技术,通过设置Content-Type为text/plain将请求标记为简单请求,避免触发OPTIONS预检。尽管请求体包含JSON数据,浏览器不会触发预检,而多数后端框架仍能正确解析JSON数据,使攻击者能绕过CORS限制执行CSRF攻击。 综合评分: 88 文章分类: WEB安全,漏洞分析,渗透测试


cover_image

不触发预检的 CSRF 攻击:Content-Type:text/plain 的魔法

原创

Pwn1

漏洞集萃

2025年12月21日 13:40 山东

免责声明 本公众号所发布的文章内容仅供学习与交流使用,禁止用于任何非法用途。

我们都知道一些站点会设计 CORS,这就有可能让我们的 CSRF 攻击无法奏效。

然而我们也知道,CORS 的检测和拦截动作是由浏览器实现的,那么只要我们搞清楚 CORS 拦截的逻辑,就可以绕过它。

简单请求

简单请求 是指满足以下所有条件的请求:

方法

使用其中一种允许的方法:

  • • GET
  • • HEAD
  • • POST

除了用户代理自动设置的标头(例如 ConnectionUser-Agent 或禁止的请求标头)之外,唯一允许手动设置的标头是 CORS 安全列表请求标头,它们是:

  • • Accept
  • • Accept-Language
  • • Content-Language
  • • Content-Type
  • • Range (仅限单个范围标头值,例如 bytes=256- 或 bytes=127-255

Content-Type 限制

Content-Type 还必须是以下指定的媒体类型(只允许以下类型/子类型组合):

  • • application/x-www-form-urlencoded
  • • multipart/form-data
  • • text/plain

额外限制

  • • 对于 AcceptAccept-LanguageContent-Language 和 Content-Type 的值:

  • • 不能包含 CORS-unsafe 请求头字节(包括禁止的控制字符,如 0x00-0x1F 中的大多数、HTTP newline bytes,以及某些特殊字符)。

  • • 值也只能包含有限的允许字节:数字 (0-9)、大小写字母 (A-Z, a-z)、空格以及特定标点符号(如 *, , – . ; =)。

  • • 无领先/尾随的 HTTP tab 或 space。

  • • 所有 CORS-safelisted 请求头的值总字节长度不得超过 1024 字节(超过则视为非简单请求)。

  • • Range 头(如使用)必须仅包含单个范围值(例如 bytes=0-1023),且符合简单范围头规范。

参考:https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS#simple_requests

OPTIONS 预检请求

OPTIONS 预检 是现代浏览器实现 CORS 规范的一部分(写死在浏览器里面了,无论服务器是否设定 CORS,都会默认执行)。

触发 OPTIONS 预检的条件

  • • 跨源 + 简单请求 → 不触发预检
  • • 跨源 + 非简单请求 → 触发预检

如何通过 text/plain 绕过 CORS?

我们只需要在构造 payload 的时候全程使用简单请求,那么就可以规避 CORS 的限制(当然这里不讨论 CSRF token 的场景)。

示例 1:使用表单

<form&nbsp;method="POST"&nbsp;action="https://target.com/change-email">
&nbsp; <input&nbsp;type="hidden"&nbsp;name="email"&nbsp;value="[email protected]">
&nbsp; <input&nbsp;type="hidden"&nbsp;name="csrf_token"&nbsp;value="">&nbsp;<!-- 可以为空或伪造 -->
&nbsp; <input&nbsp;type="submit"&nbsp;value="点我">
</form>

示例 2:使用 fetch

fetch("https://target.com/api/update", {
&nbsp; method:&nbsp;"POST",
&nbsp; headers: {
&nbsp; &nbsp; "Content-Type":&nbsp;"text/plain"
&nbsp; },
&nbsp; body:&nbsp;JSON.stringify({&nbsp;email:&nbsp;"[email protected]"&nbsp;})
});

原理说明

  • • 这里的paylaod将 Content-Type 设定为 text/plain,那么浏览器久会认为这是一个简单请求
  • • 在这样的情况下,即使 body 里实际是 JSON 数据(比如 {"email":"[email protected]"}),浏览器也不会因为 body 的格式而触发预检,毕竟它只看 Content-Type 头。
  • • 于此同时,很多后端框架(比如 Express、Spring、Django 等)通常会自动解析 JSON 格式的 body,无论 Content-Type 是什么,只要 body 是有效的 JSON 字符串,后端就会正常处理他。

觉得本文内容对您有启发或帮助? 点个关注➕,获取更多深度分析与前沿资讯!

👉 往期精选

攻防演练中的“降维打击”:逃逸出内网边界的影子资产与SaaS供应链挖掘

谷歌登录也防不住?实战劫持 GIS SDK 实现无感账号接管

【漏洞挖掘Tips】一种新的 GraphQL 绕过角度


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:漏洞集萃 Pwn1《不触发预检的 CSRF 攻击:Content-Type:text/plain 的魔法》

评论:0   参与:  4