2015年6月

某用户在被Rails开发组驳回其漏洞报告之后黑掉了Github网站来演示该漏洞

这是一篇翻译的文章(原文链接),已经是老掉牙的旧闻了,不过感觉这件事很有意思,所以翻译过来。

某用户在被Rails开发组驳回其漏洞报告之后黑掉了Github网站来演示该漏洞

作者:Lucian Constantin, IDG News Service 2012年3月5日

一名用户于上周日黑掉了Ruby on Rails托管在GitHub上代码仓库和Bug跟踪系统,以向Rails开发组展示问题有多么严重。

Ruby on Rails一般简称作Rails,是一种逐渐流行的Ruby语言的Web开发框架,它的设计目标是让开发人员专注于构建应用程序,而无需考虑底层的工作原理。

GitHub是基于Rails框架开发的最受欢迎的Web站点之一,它是一个大型代码托管和协作开发平台,Ruby on Rails项目的代码库和Bug跟踪系统也托管在GitHub上。

上周二,一个名叫Egor Homakov的俄国用户报告了一个Rails框架中存在的漏洞。利用该漏洞,黑客可以从Web表单直接向Rails程序的数据库中注入未经验证的数据,就像SQL注入一样。

这个问题与Rails中的mass assignment功能有关,滥用此功能会导致站点不安全。滥用这一功能的可能性早在几年前就已经知晓,但Rails开发组认为,定义哪些属性可以被这个功能修改的限制条件是应用开发人员的责任。

这个问题的实质是Rails开发人员究竟应该对这一功能应用黑名单策略还是白名单策略。到底是应该默认允许所有属性修改,然后让开发人员定义不许修改的黑名单(现在就是这样的策略);还是应该默认阻止所有属性的修改,再由程序员仔细考虑安全问题之后,定义允许修改的白名单。

很遗憾,历史一再证明把安全决策推给用户极不明智,这往往导致大量的不安全程序在线上运行,这也是Homakov声称问题持续存在了数年的原因。

在尝试说服Rails开发组该功能应该默认关闭未果之后,Homakov决定演示这个漏洞的存在是多么的普遍,即使最成功的Rails应用之一GitHub中也存在这个漏洞导致的安全问题。

星期天,Homakov利用这个漏洞向Ruby on Rails在GitHub上的Bug跟踪系统中加入了一条非法条目,该条目的创建时间居然是1001年后的未来。然后他又利用这个漏洞把他自己的公钥注入GitHub的数据库中,替换了一名Rails开发组成员的的公钥,从而取得了Rails官方代码库的提交权限。

“太平洋时间上午8:49,一名GitHub用户利用公钥更新表单中存在的安全漏洞把他的公钥加入了Rails组织,”——GitHub开发人员Tom Preston-Werner在一篇周日发表的博文中如是说——“后来他推送了一个新文件到该项目中以演示这个安全漏洞。”

GitHub在不到一小时后修复了这个漏洞,并暂时冻结了Homakov的帐号以调查他的行为。GitHub团队在不久后确认了他并无恶意,随即解冻了他的帐号。

“在对攻击行为进行调查的同时,我们对GitHub的代码库展开了彻底的审查,以确保没有其它地方存在相同问题,”Preston-Werner说道“审查工作仍在进行,而我则要确保我们有相应的流程制度避免此类问题再次发生。”

这一事件引起了大量关注,Rails开发组现在愿意更多地讨论这一问题,以期找到解决方案。然而,由于这个问题已经公开,不安全的Rails应用程序遭到攻击的风险也更高了。


我的感想

因为我不是Ruby程序员,所以我也是最近才知道这个发生在三年前的事件。在了解了mass assignment的功能之后,发现这和PHP早年的register global引发的安全问题简直一模一样。在GitHub的讨论中,网友DrPizza的留言很有道理: Insecure-by-default means insecure. (默认设置不安全就意味着不安全)。很难指望程序员在加班加点写完调通功能代码之后再去研究一下手册,发现“啊,我用的方法不安全”一般他们都是直接提交然后就回家睡觉了。只有等到某一天网站被攻击了造成了损失,他可能才会在求助别人之后得知当时犯下了多大的错误。

当年PHP的作法就是从某个版本开始,把register global默认关闭,然后在该项配置旁边写了一堆注释强调开启这个功能的潜在风险,以及不开这个功能的替代方案。

瀑布式照片墙排版算法研究

产品中有一个个人照片墙的页面,设计想出了瀑布式的版式,大家都认为视觉效果不错,下面的原型图大致说明了这种版式的效果。

3074727575.png

照片是用户自己上传的,所以照片的尺寸是不确定的。当然我们也可以完全不在意图片的尺寸,可以用css把它们排版成版式要求的样子。不过由于图片的宽高比并不确定,如何安排每一张图的位置就成为需要解决的关键问题。

按序号奇偶性分发图片

最简单也最直观的方法是直接把图片编号,奇数号放入左栏,偶数号放入右栏。如果用户每张图片的宽高比都差不多,可以用这种方法取得比较好的效果。但是这种方法存在两栏高度差异巨大的风险,如果恰好用户上传照片是横、竖、横、竖……这种顺序,那就会出现左矮右高的悲剧。

3784789452.png

向较矮栏分发图片

针对上面方法出现的问题,一个简单的优化是直接在每次置入图片前计算左右两栏各自的高度,然后将图片置入较矮的一栏。因为只有图片加载出来才能知道它的高度,所以不能像前面的方法那样一股脑儿全塞进左右栏中,需要等图片载成功后一张一张放入。

正因为要等图片加载成功才放入,所以这种算法是不稳定的,可能每次刷新时图片的顺序都不一样。另外这个算法提供的只是一个小改进,它并不能得出让左右栏高度最接近的方案,在出现特长图的时候问题更为明显。

3589395582.png

动态规划解

有没有可能找到一个最优的方案,保证所有图片置入后,左右栏高度最为接近呢?图片的宽度是固定的,变化的只会是高度,问题可以转化为一个抽象的数学问题:将一个由n个整数元素构成的集合N,划分成两个子集AB,确保N = A + B,求能使SUM(A)最为接近SUM(B)的划分方法。

把问题抽象化之后就比较好解了。根据前述条件可知SUM(B) = SUM(N) - SUM(A),要求SUM(A)最为接近SUM(B),不妨假设SUM(A) ≤ SUM(N)/2 ≤ SUM(B),那么问题就变成了从集合N中选中若干元素构成集合A,使SUM(A)最为接近SUM(N)/2。这下问题变得很眼熟了吧,俨然就是背包问题啊。

背包问题及其各种变种的详细解法请参考Tianyi Cui写的《背包问题九讲》,这里我只对这个问题求解。不妨先考虑集合N中的第一个元素N[1],需要决定把它放入或者不放入集合A中,从而使得SUM(A)最接近SUM(N)/2。如果不把N[1]放入集合,那么需要知道对于第二到第n个元素,能构成的最接近SUM(N)/2的和是多少;而如果把N[1]放入集合中,则需要知道对于第二到第n个元素,能构成的最接近SUM(N)/2 - N[1]的和是多少。通过比较就可以确定是否应该放入N[1]。同理,对于第二个元素N[2],决策前需要比较的是第三到第n个元素的放置加和情况。直到第n个元素,放入或者不放一目了然。当记录下这些放置组合之后,可以找出最优决策链。

总结

奇偶分发法最为简单,也最容易遇到麻烦,只适合所有图片尺寸相同(比如Instagram)的情况下使用。

向最矮栏分发法虽然无法取得最优解,但也能得到不错的结果。因为无需提前知晓全部图片的尺寸,比较适合用在分批无限载入的流式布局中。这种方法的最大问题在于图片载入顺序对排版结果影响巨大,如果先成功载入短图(短图一般比较小,很容易先完成载入),较均匀地分发后方才载入长图,可能出现严重的分配不均情况,可以由服务器端下发图片尺寸信息来帮助缓解这一情况,从长到短分发能取得更好的效果。

动态规划的方法可以求得最优解,但算法的复杂度相对较高,而且需要知道所有图片的高度之后才能开始运行,不适合用在图片可以加载更多的流式场景。当然,如果服务器端能提供图片尺寸信息,就能绕过上述缺点。

前面提供了三种实现思路。最后可以看一下例子程序

系统维护完成

系统维护完成,测试了阿里云的CDN,速度还不错,可惜他们的https很弱,可能是为了兼容IE6,居然可以用RC4加密MD5签名的SSL3.0协议。不过考虑到可怜的中国国情,也能理解他们的无奈。

关站维护的时候还不小心点了百度的闭站保护,没有仔细看说明就点了申请按钮,没想到居然是人工审核,申请和恢复都要两三天的时间。早知道这样就不点了,这次维护时间也就一天,然后真心没有找到取消申请的地方,希望审核的人能看到站点恢复运作,拒绝申请吧。

让nginx把http来访者重定向到https

连百度都已经开始全面支持https了——https加密网络上传输的数据,它在保护用户隐私和防止http注入攻击方面有着不错的效果。以前提供https的站长为了保证SEO,不得不对百度的搜索引擎往开一面,提供http的版本。现在已经可以放心地在全站用https提供服务了。

全站https虽然好,但也需要用户主动在地址栏输入https的协议名,然而用户输入网址,往往直接从域名开始输入,比如直接打www.baidu.com,这样浏览器会默认用http协议访问,这就需要我们把用户从http自动导向https站点了。不过这倒是不难,只需往nginx的server配置中加入一个跳转的配置,就能完成:

server {
    listen 80;
    listen 443 ssl;
    server_name www.example.com example.com;

    add_header Strict-Transport-Security "max-age=31536000";
    if ( $scheme !~ "https" ) {
        return 301 https://$host$request_uri;
    }
    #......
}

新加入的Strict-Transport-Security标头是遵循HSTS标准,浏览器看到这个header后就会记住当前网站支持安全加密访问。在max-age的时间内,浏览器会自动请求https的版本,而不是http的版本。后面的if语句意思是,如果用户连接协议不是https,就301永久重定向到https的版本。

一些常用的html头标签

有一些常用的html头标签,不容易记忆,所以干脆记在这里。

viewport 控制手机浏览器虚拟屏幕大小

<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">

viewport是用来描述手机浏览器渲染区域的大小的,如果不写上面这句声明,手机浏览器很可能会把页面放在桌面大小的宽度上渲染,然后缩小显示在手机屏幕上。加上上面这句viewport的声明后,手机浏览器就会按设备的宽度来渲染页面。viewport的功能比较复杂,涉及缩放,CSS象素大小等一堆定义,想详细了解可以Google搜索viewport

refresh 延时跳转页面

<meta http-equiv="refresh" content="5; url=http://www.example.com/">

这个标签现在用的少了,论坛系统里比较常见,浏览器会在5秒后跳转到 http://www.example.com/ 上。

shortcut icon 网站图标

<link rel="shortcut icon" href="http://www.example.com/favicon.ico" type="image/x-icon">

上述声明定义了网站的图标,浏览器会把这个图标显示在网站的标签页或者历史记录中。如果没有写上面的声明,浏览器会自动试图请求网站域名根目录下的favicon.ico文件作为图标。所以这个标签仅在favicon不在站点域名根目录下时使用。默认图标应该使用微软的ico格式,不过现代浏览器对png等格式也支持,可以在type属性中定义。

apple-touch-icon 网站的大图标

<link rel="apple-touch-icon" href="http://www.example.com/apple-touch-icon.png">

如果用iOS上Safari的把网站添加桌面的功能,这个图标会显示在桌面上;另外Opera浏览器显示SpeedDial图标时也使用这个。如果没有上面的声明,默认会使用网站域名根目录下的apple-touch-icon.png文件,为了更好的兼容性,应该使用png格式。

x-ua-compatible 请求与网站兼容的渲染引擎

<meta http-equiv="x-ua-compatible" content="ie=5; ie=8">

有了这句声明,如果可以的话,对于IE8以上的浏览器,会用ie8的引擎渲染,对于IE6、7等,会用IE5的引擎渲染。

RSS 聚合源

<link rel="alternate" type="application/atom+xml" title="ATOM 1.0" href="http://www.example.com/atom.xml">
<link rel="alternate" type="application/rss+xml" title="RSS 2.0" href="http://www.example.com/rss.xml">

上面的两句声明告诉浏览器RSS聚合源的URL是什么,一个使用RSS格式标准,另一个使用ATOM格式,供支付的浏览器选择。