CORS、CSRF和XSS

一、什么是CORS(跨域资源共享)?

  • 请求和响应只要是协议、域名、端口三者其中之一不同就是跨域,跨域不是请求发不出去,而是根据浏览器的同源策略,请求成功发送,但浏览器拦截了响应,目的是防止恶意网站盗取另一个网站的敏感数据。
  • 比如我登录了一个银行网站,然后又打开了一个恶意网站,恶意网站发送请求到银行网站,因为请求地址相同,所以浏览器自动带上了银行网站的cookie,恶意网站的请求成功发送到银行网站,银行网站也成功响应,但是浏览器会检查响应头里有没有这个恶意网站的地址,如果没有,会在响应交给js之前直接丢弃,所以恶意网站的js根本读不到数据,所以跨域不是阻止请求发送,而是阻止js读取响应结果。
  • 为了解决前后端分离跨域访问的问题,服务器可以通过 CORS 在响应头中声明允许哪些源读取响应。 服务器在响应头里加:
arduino 复制代码
Access-Control-Allow-Origin: http://localhost:5173

浏览器看到这个头,才会把响应交给 JS。否则依然会拦截。

  • 很多人以为 CORS 能防止跨站攻击,但其实 CORS 只防止数据被恶意读取,却无法防止 CSRF,因为 CSRF 利用的是浏览器自动携带 Cookie 发请求,根本不需要读取响应。

cookie携带规则:请求的目标域名,匹配 Cookie 的 Domain/Path,浏览器就自动带上

二、什么是CSRF(跨站请求伪造)

利用浏览器"自动携带 Cookie"的特性,替用户发起恶意请求,让服务器误以为:这是用户本人操作,比如登录了银行网站,然后又打开一个恶意网站,恶意网站发送一个转账请求到银行网站,因为请求地址相同,所以浏览器自动带上了银行网站的cookie,银行网站以为这是一个已登录用户的合法转账请求,于是执行转账,恶意网站不需要读取响应,但转账已经成功了。

如何防御CSRF

1.SameSite Cookie(第一道防线,最重要)->不让你带钥匙来

  • 现代浏览器支持 Cookie 属性:
ini 复制代码
Set-Cookie: session=abc123; SameSite=Strict; Secure; HttpOnly
属性值 跨站请求是否携带 Cookie 典型受影响场景
Strict 从不携带 从其他网站点击链接跳转到你的网站时,用户会处于未登录状态(体验差)
Lax 允许部分安全的 GET 请求(如 <a> 跳转) 从其他网站链接跳转过来,依然保持登录; 但 POST 表单、AJAX 等跨站操作不携带
None 总是携带(必须配合 Secure 用于第三方嵌入等必要场景,需额外用 CSRF Token 防护

2.CSRF Token ->你必须出示暗号

  • 服务器生成一个随机 token,提交时必须带上。因为有浏览器的同源策略,所以恶意网站 拿不到这个 token,所以伪造失败。

3.验证 Origin / Referer ->你必须从正门进来

浏览器发请求一定带:

arduino 复制代码
Origin: https://bank.com

或者

arduino 复制代码
Referer: https://bank.com/page

服务器只需判断:

scss 复制代码
if (origin !== 'https://bank.com') reject()

hack.com 发来的请求是:

arduino 复制代码
Origin: https://hack.com

直接拦截。

成本极低,效果极强

4.不用 Cookie,用 JWT 放 Header ->干脆不给你钥匙可带

如果你用:

makefile 复制代码
Authorization: Bearer xxxxx

浏览器不会自动带这个 Headerhack.com 根本没法伪造。

  • 同源策略保护hack.com 无法读取 bank.com 的 localStorage/sessionStorage
  • 无法猜测Token:JWT是签名的,攻击者无法凭空生成有效Token
  • 无自动携带机制 :浏览器不会自动添加 Authorization 头,必须JS主动设置

所以这个方法免疫CSRF,但是却容易被XSS(跨站脚本攻击)。

三、什么是XSS(跨站脚本攻击)

一个典型的XSS攻击流程包含三个角色:攻击者、目标网站、受害者

  1. 注入:攻击者找到目标网站的一个输入点(如评论框、搜索框、个人资料编辑页),输入一段精心构造的恶意脚本代码,并提交给网站服务器。如果网站没有做足够的过滤或转义,这段代码就会被存储在数据库中,或者直接被反射回浏览器。

  2. 触发:当受害者访问包含这段恶意代码的页面时,受害者的浏览器会从服务器接收这个页面。浏览器在解析HTML的过程中,会无差别地执行页面中的所有脚本,包括攻击者注入的那段。

  3. 危害:恶意脚本在受害者的浏览器上下文中执行,这意味着它可以访问受害者在该网站上的所有数据,比如:

    • document.cookie:窃取用户的会话凭证。
    • localStorage / sessionStorage:窃取本地存储的敏感数据。
    • DOM树:读取或修改页面内容,进行钓鱼或篡改。
    • 发起网络请求:冒充用户执行操作(如发帖、转账、修改密码)。

防御策略

1.第一层:永远不要把用户输入当成 HTML(前端最重要)

React 里,下面是安全的:

css 复制代码
<div>{content}</div>

这是自动转义

xml 复制代码
<script>  →  &lt;script&gt;

❌ 危险写法:

css 复制代码
<div dangerouslySetInnerHTML={{ __html: content }} />

这行代码 = 直接允许 XSS

结论:99% 的 XSS 来自 innerHTML / dangerouslySetInnerHTML


第二层:富文本 / Markdown 必须做白名单过滤(后端最重要)

用户可输入富文本时,必须过滤标签和属性

允许:

css 复制代码
<b> <i> <p> <img>

删除:

ini 复制代码
<script>
onerror=
onload=
onclick=
iframe
style

推荐思路(业界标准做法):

使用 DOMPurify 这种"白名单消毒器"。

css 复制代码
DOMPurify.sanitize(html)

这一步是真正拦住存储型 XSS 的关键


第三层:绝对不要信任前端过滤(必须后端再过滤一遍)

很多人犯的致命错误:

"前端已经过滤了"

黑客可以直接绕过前端,请求接口把恶意内容存进数据库。

必须后端再做一次 sanitize。


第四层:Cookie 使用 HttpOnly(防 XSS 窃取登录态)

后端设置:

ini 复制代码
Set-Cookie: token=xxx; HttpOnly; Secure; SameSite=Lax

结果:

javascript 复制代码
document.cookie  // 读不到 token

即使发生 XSS:

黑客 JS 拿不到你的登录凭证

这是对抗 XSS 的核武器级防线


第五层:开启 CSP(浏览器级防御,极其有效)

CSP 全称:

Content Security Policy

后端返回 Header:

css 复制代码
Content-Security-Policy: script-src 'self'

含义:

页面只允许加载自己域名的 JS

黑客注入的:

xml 复制代码
<script src="https://hacker.com/x.js"></script>

浏览器直接拦截,不执行。

CSP 是现代网站防 XSS 的标配


第六层:不要使用 innerHTML / eval / new Function

这些 API 天生为 XSS 打开后门:

scss 复制代码
element.innerHTML = userInput
eval(userInput)
new Function(userInput)

全部禁止。


第七层:对 URL 参数做编码(防反射型 XSS)

ini 复制代码
const q = encodeURIComponent(userInput)

渲染时也做转义。


ini 复制代码
SameSite=Lax 或 Strict

可以同时减少 CSRF + XSS 联动风险。


一句话总结

XSS 真正的防御核心不是"转义"

而是:让恶意 JS 即使插进页面,也拿不到东西、也执行不了

这才是完整防御思路。

相关推荐
木斯佳3 小时前
前端八股文面经大全:腾讯WXG暑期前端一面(2026-05-15)·面经深度解析
前端·面试·笔试
张元清4 小时前
useEffect 之外:专门处理异步、深比较和 SSR 的 Effect Hook
前端·javascript·面试
Moment5 小时前
AI 为什么总喜欢写防御性代码?
前端·后端·面试
nJI74egg16 小时前
JavaEE初阶---《JUC 并发编程完全指南:组件用法、原理剖析与面试应答》
java·面试·java-ee
ricardo19739 小时前
资源加载提速四件套:dns-prefetch / preconnect / preload / prefetch 实战
前端·面试
豹哥学前端9 小时前
JavaScript 异步编程完全指南:从回调地狱到 async/await,一次通关
前端·javascript·面试
kyriewen9 小时前
面试官让我手写Promise,我打开Cursor三秒生成,他愣了两秒说“你过了”
前端·javascript·面试
蝎子莱莱爱打怪9 小时前
我花两年业余时间做了个IM系统,然后呢😂??
后端·flutter·面试
努力成为AK大王10 小时前
Java并发线程核心知识(一)
java·开发语言·面试