深入理解JS同源策略与CSRF攻击机制

作者:极速慢跑时间:2026-10-24 10:33:20浏览:446

本文详细解析JavaScript同源策略(SOP)的核心机制及其对跨域请求的限制,阐明SOP并非禁止跨域请求而是拦截响应。深入探讨跨站请求伪造(CSRF)如何利用SOP的漏洞发起攻击,并介绍通过Token验证和CORS预检请求等有效防御手段,帮助开发者构建更安全的Web应用。

概述

本文主要涉及三个关键词:

  • 同源策略(Same-origin policy,简称 SOP)
  • 跨站请求伪造(Cross-site request forgery,简称 CSRF)
  • 跨域资源共享(Cross-Origin Resource Sharing,简称 CORS)

同源策略 SOP

同源

首先解释同源的判定标准:只要协议、域名和端口三者完全一致,即视为同源。

url 同源
https://niconico.com 基准
https://niconico.com/spirit o
https://sub.niconico.com/spirit x
http://niconico.com/spirit x
https://niconico.com:8080/spirit x

限制

开发者遇到跨域问题,根源在于 SOP 施加的各种限制。具体而言,这些限制体现在哪些方面?

若认为 SOP 仅限制获取非同源资源,这种观点并不准确。最简单的反例是,浏览器允许跨域引用图片、CSS 或 JS 文件等资源。

若认为 SOP 禁止跨域请求,这也是误解。SOP 的本质并非阻止请求发出,而是在请求完成后拦截响应数据。这一机制正是后续讨论 CSRF 问题的基础。

实际上,SOP 并非单一固定的定义,而是根据不同场景有不同的具体解释:

  • 限制 cookies、DOM 访问以及 JavaScript 的命名空间
  • 限制对 iframe、图片等嵌入资源的内容操作
  • 限制 Ajax 请求,准确说是限制对 Ajax 响应结果的操作,其本质与上一条类似

以下是实际应用中常见的三种场景:

  • 使用 Ajax 请求其他跨域 API,这是最常见的情况,也是前端新手的常见困扰
  • iframe 与父页面之间的通信,这种情况较少见,且解决方案较为直观
  • 对跨域图片(例如通过 <img> 标签加载)进行操作,在使用 Canvas 处理图片时可能会遇到此问题

假设没有 SOP:

  • 若浏览器打开多个标签页,用户数据将面临泄露风险
  • 若通过 iframe 嵌入银行网站,攻击者可随意读取页面内容,进而窃取用户输入的信息
  • 这将导致 CSRF 攻击变得更加肆无忌惮

绕过跨域

SOP 虽然保障了安全,但也带来了一定不便,因为某些场景下确实存在跨域需求。由于篇幅限制且网上已有大量相关文章,此处不展开具体解决方案,仅列出几个关键概念:

针对 Ajax 请求

  • 使用 JSONP
  • 后端配置 CORS
  • 后端反向代理

针对 iframe

  • 使用 location.hash 或 window.name 进行信息交互
  • 使用 postMessage

跨站请求伪造 CSRF

简述

CSRF(Cross-site request forgery)即跨站请求伪造,是一种常见攻击手段。指用户在 A 网站正常登录后,Cookie 被保存,此时其他网站 B 通过某种方式调用 A 网站的接口,而 A 的接口在接收请求时会自动携带该用户的 Cookie。

前文提到,SOP 允许通过 HTML 标签加载资源,且 SOP 不阻止接口请求本身,而是拦截请求结果,CSRF 正是利用了这两点特性。

因此,SOP 不能作为防范 CSRF 的手段。

对于 GET 请求,直接将其放入 <img> 标签即可神不知鬼不觉地发起跨域接口请求。

对于 POST 请求,许多示例使用 form 表单提交:


<form action="<nowiki>http://bank.com/transfer.do</nowiki>" method="POST">
  <input type="hidden" name="acct" value="MARIA" />
  <input type="hidden" name="amount" value="100000" />
  <input type="submit" value="View my pictures" />
</form>

归根结底,这两种方法之所以不触发跨域报错,是因为请求由 HTML 控制,开发者无法通过 JavaScript 直接获取和处理返回的结果。

SOP 与 Ajax

对于 Ajax 请求,在获取数据后,JavaScript 可以随意操作。此时虽然同源策略会阻止响应数据的访问,但请求本身依然会发出。因为执行响应拦截的是浏览器而非后端程序。事实上,请求已发送至服务器并返回结果,但出于安全策略,浏览器不允许 JavaScript 继续操作,从而报出熟悉的错误:blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.。

因此再次强调,同源策略不能作为防范 CSRF 的方法。

不过,存在可以防范 CSRF 的例外情况。浏览器并非让所有请求都成功执行,上述情况仅限于简单请求,相关知识将在后续 CORS 章节详细解释。

CSRF 对策

SOP 被 CSRF 利用了,难道就一无是处吗?

并非如此!是否记得 SOP 限制了 Cookie 的作用域?虽然请求会自动携带 Cookie,但攻击者无论如何也无法直接获取 Cookie 的内容本身。

因此,应对 CSRF 的一种思路是:将 Token 同时写入 Cookie,在发起请求时再通过 query、body 或 header 携带该 Token。服务器收到请求后校验 Token,若正确,则确认为本域可见 Cookie 的请求,而 CSRF 攻击无法做到这一点。(此方法适用于前后端分离架构,后端渲染则可直接将 Token 写入 DOM)

示例代码如下:


var csrftoken = Cookies.get('csrfToken')

function csrfSafeMethod(method) {
  // these HTTP methods do not require CSRF protection
  return /^(GET|HEAD|OPTIONS|TRACE)$/.test(method)
}
$.ajaxSetup({
  beforeSend: function(xhr, settings) {
    if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
      xhr.setRequestHeader('x-csrf-token', csrftoken)
    }
  },
})

跨域资源共享 CORS

跨域限制由浏览器施加,但如果服务器配置了 CORS 相关选项,会在返回信息的头部添加 Access-Control-Allow-Origin。浏览器检测到该字段的值与当前源匹配,便会解除跨域限制。

HTTP/1.1 200 OK

Date: Sun, 24 Apr 2016 12:43:39 GMT

Server: Apache

Access-Control-Allow-Origin: http://www.acceptmeplease.com

Keep-Alive: timeout=2, max=100

Connection: Keep-Alive

Content-Type: application/xml

Content-Length: 423

对于 CORS,请求分为两种类型。

简单请求

  • 请求方法使用 GET、POST 或 HEAD
  • Content-Type 设为 application/x-www-form-urlencoded、multipart/form-data 或 text/plain

符合上述两个条件的均为 CORS 简单请求。简单请求会直接发送至服务器,从而可能导致 CSRF 攻击。

预检请求

不符合简单请求要求的请求,需先发送预检请求(Preflight Request)。浏览器会在正式请求前发送 OPTIONS 方法的请求,向服务器询问当前源是否符合 CORS 目标,验证通过后才发送正式请求。

例如,使用 application/json 传参的 POST 请求属于非简单请求,会在预检阶段被拦截。

再例如,使用 PUT 方法发起的请求,也会发送预检请求。

前文提到的可防范 CSRF 的例外,即指预检请求。即使跨域成功发起预检,正式请求也无法发出,从而确保 CSRF 攻击无法成功。

CORS 与 Cookie

与同域请求不同,用于跨域的 CORS 请求默认不发送 Cookie 和 HTTP 认证信息,前后端都需在配置中明确设定请求时携带 Cookie。

这就是为什么在进行 CORS 请求时,axios 需要设置 withCredentials: true。

以下是 node.js 后台 koa 框架的 CORS 配置示例:


/**
 * CORS middleware
 *
 * @param {Object} [options]
 *  - {String|Function(ctx)} origin `Access-Control-Allow-Origin`, default is request Origin header
 *  - {String|Array} allowMethods `Access-Control-Allow-Methods`, default is 'GET,HEAD,PUT,POST,DELETE,PATCH'
 *  - {String|Array} exposeHeaders `Access-Control-Expose-Headers`
 *  - {String|Array} allowHeaders `Access-Control-Allow-Headers`
 *  - {String|Number} maxAge `Access-Control-Max-Age` in seconds
 *  - {Boolean} credentials `Access-Control-Allow-Credentials`
 *  - {Boolean} keepHeadersOnError Add set headers to `err.header` if an error is thrown
 * @return {Function} cors middleware
 * @api public
 */

以上就是详解JS同源策略和CSRF的详细内容,更多关于JS同源策略和CSRF的资料请关注本站其它相关文章!

作者最新文章