面试中经常被问到跨域问题,可以说这是前端网络安全必问的一道题。我感觉之前每次都答得不是很好,要搞清楚几个关键的点:
- 跨域问题是浏览器才有的问题,如果其他程序请求后端,是不存在跨域问题的,比如爬虫,webpack server,后端程序
- 前端跨域问题的关键在于后端是否允许跨域,如果后端允许,则也是不存在跨域问题的,正是利用了这一点,XSS 攻击才能成功。
- 跨域请求是能发送成功的,只是请求结果读取不到而已。正是利用了这一点,CSRF 攻击才能成功。
浏览器的同源策略
浏览器的同源策略(Same-Origin Policy),所谓同源指的是三个相同:
- 协议
- 域名
- 端口
具体情况如下:
- http 和 https 协议算作两种不同的协议
- 域名要完全相同,子域名和上层域名不算作同一域名
- 端口默认是 80,可以不写
CORS 跨域资源共享
cross origin resource share
CORS 需要浏览器和服务器共同支持。目前所有浏览器都支持该功能,IE 浏览器不能低于 IE10。整个 CORS 通信过程,都是浏览器自动完成,对开发者来说不用写额外代码。浏览器一旦发现 AJAX 请求跨源,就会自动添加一些附加的头信息,有时还会多出一次请求。
简单请求
- 请求方法限制:GET、POST、HEAD
- 请求头字段限制:Accept、Accept-Language、Content-Language、Content-Type、Last-Event-ID
- Content-Type 限制:application/x-www-form-urlencoded、multipart/form-data、text/plain
简单请求无预检请求
以下是 HTTP 协议的请求报文片段:
1 | GET /cors HTTP/1.1 |
服务器根据 Origin 字段,决定是否同意这次请求。如果 Origin 在许可范围内,服务器就会在返回的头信息中多出这些字段:
1 | Access-Control-Allow-Origin: http://api.bob.com |
浏览器检测是否有Access-Control-Allow-Origin
字段,如果没有就知道出错了,从而抛出一个错误,被XMLHttpRequest
的onerror
回调函数捕获。注意,这种错误无法通过状态码识别,因为 HTTP 回应的状态码有可能是 200。
非简单请求
非简单请求有预检请求(OPTIONS 请求)
CSRF
CSRF 全称是:cross-site request forgery,中文译为:跨站请求伪造。也可以缩写为:XSRF
攻击原理
- 用户用浏览器登录被攻击的网站,如:bank.com。从而前端产生了 cookie,后端产生了 session 等身份信息
- 用户访问恶意网站,如 evil.com,该网站立即执行脚本发送请求给 bank.com。由于浏览器只对跨域请求的响应进行拦截,而不限制请求发送,从而攻击成功。
预防手段和原理
浏览器原生防护:SameSite Cookie。
浏览器通过 Cookie 的 SameSite 属性,限制跨站请求携带 Cookie
- Strict 模式:仅同源可携带 Cookie
- Lax 模式(默认):允许安全方法(如 GET)携带 Cookie,阻止 POST 等非安全方法携带 Cookie
- None 模式:允许跨站携带 Cookie,但需配合 Secure 属性(仅 HTTPS)
服务器返回 HTTP 响应头:
1 | Set-Cookie: sessionId=abc123; SameSite=Lax; Secure; HttpOnly |
适用场景:
- 常规会话 Cookie 使用 Lax 模式(平衡安全与用户体验)
- 支付、改密等高敏感操作使用 Strict 模式
局限:
- 旧版浏览器(如 IE)不支持
- Lax 无法防御 GET 型 CSRF,如果支付等高敏感操作使用 GET 请求,就会面临攻击
CSRF Token
- 生成:服务器为每个会话生成一个唯一随机 Token(如 crypto.randomBytes(32).toString('hex'),注意 math.random()生成的是伪随机数)
- Token 的传递方式安全,Token 通常通过以下方式传递:
- 表单隐藏字段:如
<input type="hidden" name="csrf_token" value="随机值">
- HTTP 请求头:如
X-CSRF-Token: 随机值
(用于 AJAX 请求)
- 表单隐藏字段:如
- 验证:服务端比对请求中的 Token 与会话存储的 Token,不一致则拒绝请求
为什么攻击者拿不到 CSRF Token?
浏览器限制 :浏览器禁止跨域脚本读取其他域的资源。例如:
- 恶意网站 evil.com 的 JavaScript 无法通过 XMLHttpRequest 或 fetch 读取 bank.com 的页面内容(包括 Token)。
- 即使攻击者在 evil.com 嵌入
<iframe>
加载 bank.com 页面,也无法通过脚本读取其 DOM 中的 Token(受同源策略限制)。
关键点 :Token 通常嵌入在目标站点的 HTML 表单或 HTTP 头中,攻击者无法跨域提取。
安全优化:
- Token 绑定:与用户 IP 或设备指纹关联,防重放攻击
- 单次有效性:每次验证后刷新 Token
Referer 请求头校验
局限 :部分浏览器禁用 Referer,且可能被篡改
敏感操作二次验证
关键操作(如转账)需额外验证身份(短信验证码、生物识别等)
XSS
XSS 全称:Cross-Site Scripting,中文译为:跨站脚本攻击
攻击流程
- 注入:攻击者通过表单、URL 参数等输入点提交恶意脚本(例如
<script>alert('XSS')</script>
) - 存储/反射:脚本被服务器存储(存储型)或直接返回给其他用户(反射型)
- 执行:其他用户访问含恶意脚本的页面,浏览器执行恶意脚本触发攻击
防御措施
- 输入过滤:对用户提交的数据(URL 参数、表单等)进行严格校验,过滤特殊字符(如
<
、>
) - 输出编码:将用户输入的内容在输出到 HTML 时进行转义(如将
<
转为<
),防止浏览器解析为代码。
CSRF 和 XSS 的区别
CSRF 是在恶意网站访问被攻击的网站,XSS 是在被攻击的网站注入了恶意脚本。