基于本文回答
0
评论

为什么将 API 设计为只接受 JSON 格式(Content-Type: application/json)可以降低 CSRF 风险?

知识点图片

将 API 设计为只接受 JSON 格式(即强制要求 HTTP Header 中包含 Content-Type: application/json),确实可以显著降低 CSRF(跨站请求伪造)的风险。

这背后的核心原因在于:浏览器的安全机制(同源策略和 CORS)对“简单请求”和“非简单请求”的处理方式不同。

以下是详细的技术原理解析:

1. 传统的 CSRF 攻击依赖于 HTML Form

传统的 CSRF 攻击通常利用 HTML 的 <form> 标签。
攻击者会在恶意网站上构造一个表单,诱导受害者点击,或者通过脚本自动提交:

html
<!-- 传统攻击方式 -->
<form action="https://bank.com/transfer" method="POST">
  <input type="hidden" name="to" value="attacker">
  <input type="hidden" name="amount" value="1000">
</form>
<script>document.forms[0].submit();</script>

HTML 表单只支持三种 Content-Type

  • application/x-www-form-urlencoded (默认)
  • multipart/form-data (用于文件上传)
  • text/plain

关键点: HTML 表单无法设置 Content-Typeapplication/json

如果你的 API 严格检查 Content-Type 必须是 application/json,那么上述通过表单发起的攻击请求会被服务器直接拒绝(通常返回 415 Unsupported Media Type),因为请求头不匹配。

2. 触发 CORS 预检请求 (Preflight Request)

既然 HTML 表单做不到,攻击者如果要发送 Content-Type: application/json,就必须使用 JavaScript(如 XMLHttpRequestfetch API)。

当攻击者在恶意网站(evil.com)通过 JS 向你的网站(bank.com)发起请求,并试图设置 Content-Type: application/json 时,会触发浏览器的 CORS(跨域资源共享) 机制。

根据 CORS 规范,请求分为两类:

  • 简单请求 (Simple Request): 使用 GET/HEAD/POST 方法,且 Content-Type 是上述表单支持的三种之一。浏览器会直接发送请求(带上 Cookie)。
  • 非简单请求 (Non-simple Request): 使用了自定义 Header,或者 Content-Type 为 application/json

防御流程如下:

  1. 攻击者的 JS 试图发送 JSON 请求。
  2. 浏览器发现这是一个“非简单请求”(因为 Content-Type 是 JSON)。
  3. 浏览器暂停发送真实的业务请求。
  4. 浏览器先自动发送一个 OPTIONS 请求(预检请求,Preflight)给服务器,询问:“你允许 evil.com 发送 JSON 格式的 POST 请求吗?”
  5. 你的服务器(如果没有错误配置 CORS)会发现来源是 evil.com,于是拒绝或者不返回允许的 Header。
  6. 浏览器收到预检失败的结果,彻底拦截那个真实的 JSON POST 请求。

结论: 攻击者的请求甚至无法到达你的业务逻辑层,在浏览器层面就被拦截了。

3. 为什么说只是“降低”风险,而不是“彻底根除”?

虽然强制 JSON 格式非常有效,但它不能作为唯一的防御手段,原因如下:

  1. CORS 配置错误: 如果你的服务器端 CORS 配置过于宽松(例如 Access-Control-Allow-Origin: * 且允许了携带凭证 Access-Control-Allow-Credentials: true),那么上述的“预检保护”就会失效,攻击者依然可以跨域发送 JSON 请求。
  2. 历史遗留漏洞(Flash/Java): 虽然现代浏览器已经淘汰了 Flash 和 Java Applet,但在过去,这些插件有时可以绕过浏览器的 Header 限制发送 JSON 请求。虽然现在不用担心这个,但在安全理论上这是一个缺口。
  3. Content-Type 嗅探或解析漏洞: 如果某些后端框架在处理请求时比较“宽容”,比如它虽然宣称只接受 JSON,但如果攻击者发送 text/plain 格式的有效 JSON 字符串,后端依然解析并执行了,那么攻击者就可以利用 <form enctype="text/plain"> 进行攻击。因此,后端必须严格校验 Header,不仅仅是尝试解析 Body。

总结

将 API 设计为只接受 Content-Type: application/json 可以防御 CSRF,是因为:

  1. HTML 表单无法伪造这种请求头。
  2. 通过 JS 发起这种请求会强制触发 CORS 预检,只要服务器不通过 CORS 显式允许攻击者的域名,浏览器就会阻止请求发送。

最佳实践建议:
尽管这很有效,依然建议采用 深度防御 (Defense in Depth) 策略:

  • 强制使用 JSON。
  • 严格配置 CORS。
  • 依然使用 CSRF Token(双重 Cookie 验证或同步 Token 模式)作为核心防御。
  • 设置 Cookie 的 SameSite 属性为 LaxStrict
右滑查看面试常问