一、什么是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
浏览器不会自动带这个 Header。 hack.com 根本没法伪造。
- 同源策略保护 :
hack.com无法读取bank.com的 localStorage/sessionStorage - 无法猜测Token:JWT是签名的,攻击者无法凭空生成有效Token
- 无自动携带机制 :浏览器不会自动添加
Authorization头,必须JS主动设置
所以这个方法免疫CSRF,但是却容易被XSS(跨站脚本攻击)。
三、什么是XSS(跨站脚本攻击)
一个典型的XSS攻击流程包含三个角色:攻击者、目标网站、受害者。
-
注入:攻击者找到目标网站的一个输入点(如评论框、搜索框、个人资料编辑页),输入一段精心构造的恶意脚本代码,并提交给网站服务器。如果网站没有做足够的过滤或转义,这段代码就会被存储在数据库中,或者直接被反射回浏览器。
-
触发:当受害者访问包含这段恶意代码的页面时,受害者的浏览器会从服务器接收这个页面。浏览器在解析HTML的过程中,会无差别地执行页面中的所有脚本,包括攻击者注入的那段。
-
危害:恶意脚本在受害者的浏览器上下文中执行,这意味着它可以访问受害者在该网站上的所有数据,比如:
document.cookie:窃取用户的会话凭证。localStorage/sessionStorage:窃取本地存储的敏感数据。- DOM树:读取或修改页面内容,进行钓鱼或篡改。
- 发起网络请求:冒充用户执行操作(如发帖、转账、修改密码)。
防御策略
1.第一层:永远不要把用户输入当成 HTML(前端最重要)
在 React 里,下面是安全的:
css
<div>{content}</div>
这是自动转义:
xml
<script> → <script>
❌ 危险写法:
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)
渲染时也做转义。
第八层:设置 Cookie 的 SameSite
ini
SameSite=Lax 或 Strict
可以同时减少 CSRF + XSS 联动风险。
一句话总结
XSS 真正的防御核心不是"转义"
而是:让恶意 JS 即使插进页面,也拿不到东西、也执行不了
这才是完整防御思路。