31 为什么安全的代码这么重要? 从今天开始,我们进入本专栏的“安全模块”。首先,我们通过一个具体的安全漏洞的案例,来感受下计算机代码是多么的脆弱,以及编写安全的代码为什么如此重要。

评审案例

在Web开发中,“multipart/form-data“类型经常被用来上传文件。比如下面这段描述表单的代码,就是使用multipart/form-data上传文件的一段HTML代码。

Upload the file:
</FORM> 文件上传的操作,会被浏览器解析成类似下面的HTTP请求。 Content-Type: multipart/form-data; boundary=AaB03x --AaB03x Content-Disposition: form-data; name="upload-file"; filename="myfile.txt" Content-Type: text/plain ... contents of myfile.txt ... --AaB03x-- Web服务器接收后,会解析这段请求,然后执行相关的操作。下面的这段代码,是2017年3月之前Apache Struts 2解析“multipart”请求的实现。 ![](https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/%e4%bb%a3%e7%a0%81%e7%b2%be%e8%bf%9b%e4%b9%8b%e8%b7%af/assets/9a01e8ba62aa08e35d116d93e1e42e60.jpg)- 其中,蓝色标注的代码,LocalizedTextUtil.findText(),用来查找错误的本地化信息。如果“multipart”请求解析出错,就会触发这个方法。它的规范大致如下: ![](https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/%e4%bb%a3%e7%a0%81%e7%b2%be%e8%bf%9b%e4%b9%8b%e8%b7%af/assets/7ecef79094e02086610f20dcc5be0773.jpg)- 对于LocalizedTextUtil.findText()的规范,我们要留意蓝色字体的部分。这一部分告诉我们,如果信息里包含了OGNL(Object Graph Navigation Language)的表达式,表达式会被执行。 我们把上面的信息放到一块儿来看看:如果“multipart”请求解析出错,会调用LocalizedTextUtil.findText()来查找本地化的错误信息;如果错误包含OGNL表达式,表达式会被执行,以获取解释后的信息;本地化的错误信息会返回给请求者(比如浏览器)。 能不能构造一个包含OGNL表达式的“multipart”请求?对于熟悉HTTP协议和OGNL表达式的用户来说,这是一件轻而易举的事情。如果“multipart”请求不合法,OGNL表达式会被执行,执行的结果以错误信息的形式返回给请求者。 通过“巧妙地”设计OGNL表达式,攻击者可以定制执行的指令,从而定制返回错误信息的内容。这样,攻击者几乎可以获得任何他想要的有价值的内部信息。这就是一个由代码引起的安全漏洞。这个安全漏洞的[危险等级是10.0分](https://www.first.org/cvss/calculator/3.0#CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H)(请参见下一节“如何评估代码的安全缺陷”),是一个危险等级最高的漏洞。 我们回头看FileUploadInterceptor.intercept()的这段实现代码时,它的危险性其实很清楚,主要有两点: * 没有充分了解调用接口(LocalizedTextUtil.findText()); * 允许执行远程请求的表达式(OGNL表达式)。 这两点分别违反了下面的安全编码原则: * 清楚调用接口的行为; * 跨界的数据不可信任。 ## 真正的威胁 我们一起来看看这个漏洞的几个关键时间点: * 2017年1月29日,NIST的NVD(National Vulnerability Database )接收到了这个漏洞报告。 * 2017年3月6日,GitHub上出现了漏洞的描述和攻击示例。 * 2017年3月7日,Apache Struts发布了这个漏洞的修复版本,Struts 2.3.32和2.5.10.1。 * 2017年3月7日以及随后的几天,出现了更多的攻击示例,很多媒体和专家开始分析这个漏洞,推荐可能的漏洞防范措施,提醒升级Apache Struts到安全的版本。 我们要特别留意两段时间,第一段时间是1月29日到3月7日。这一段时间,安全漏洞已经被发现,但是并没有被公开。这说明这个安全研究者极有专业素养。我猜想,这位名字叫“Nike Zheng”的研究者,在2017年1月29日之前,把他的研究成果通知了Apache Struts。然后双方共同努力,将这个漏洞一直保密到2017年3月7日。这一段时间的保密工作非常重要,要不然漏洞修复之前,会有大批的应用暴露在黑客的攻击之下。 寻找并且通知受到安全漏洞影响的软件供应商,然后双方共同保密一段时间,给漏洞修复留出足够的时间,这是安全研究者的通常做法。**如果你认真学习了本专栏的“安全”模块,发现现存代码的安全问题,并且构造出可行的攻击方案,并不是一件特别困难的事情。如果以后你通过阅读代码,发现了一个漏洞,公布漏洞之前,请务必联系代码的维护者,做好漏洞的保密工作,并给他们预留充足的修复时间。** 第二段时间是2017年3月7日,这一天漏洞的修复版本发布,漏洞的补丁公之于众,漏洞的细节也就随之公开。专业的研究者和黑客会迅速地解剖漏洞,研究攻击方式。留给应用系统的时间并不多,一定要想方设法在最短的时间内,升级到修复版本。做到这一点并不容易。**大部分有效的安全攻击,都是发生在漏洞公布之后,修复版本升级之前。这一段时间,是最危险的一段时间。** ## Equifax的教训 2017年9月7日,美国最大的征信公司Equifax宣称,7月29日公司发现遭遇黑客攻击,该攻击始于5月中旬,大约有1.45亿条信用记录被盗取,其中包括20多万用户的支付卡信息。在美国,包括社会保障号、出生日期在内的信用记录是高度敏感的信息。有了这些信用记录,一个人不用出面,甚至不需要支付一分钱,就可以买车、买房、申请信用卡。 果然,有人报告自己被冒名顶替买了车、买了宠物。对于一个依靠安全生存的公司,这种情况的发生无疑是令人沮丧的。随后的几天时间里,Equifax的股票下跌超过了30%,蒸发了折合大概60亿美元的市值。 是什么样的安全漏洞导致了这么大的损失?Equifax公司后来确认,引起黑客攻击的漏洞,最主要的就是我们上面讨论过的Apache Struts漏洞。 Apache Struts于2017年3月7日发布了针对该漏洞的修复版本。但是Equifax一直到7月底,都没有完成安全版本的升级,将自己敞露在风险之下。 从3月漏洞细节公布,到5月中旬,黑客用了两个月的时间,设计了攻击方案;然后,从5月中旬到7月底,又用了两个多月的时间,从容地获取了数亿条信用记录。 如果按照严重程度来算,这一次黑客攻击可以排进21世纪已知的重大信息安全事故的前三名。而且,这次安全事故的影响范围,远远超出Equifax公司本身。 人们对征信公司的信任,降低到了前所未有的程度,纷纷冻结自己的征信记录,不允许任何人查询;银行的信用部门,必须更加谨慎地防范信用欺诈,要投入更多的财力、人力。所有受到影响的用户,必须采取更加严格的措施保护自己在其他征信机构、金融机构、保险机构的信用状态。 所有的这些问题,归根到底,都是因为没有及时地完成安全修复版本的升级。这里面固然有技术的问题,但更多的是管理的问题。2017年9月15日,Equifax的首席信息官和首席安全官宣布退休。 五行不起眼的代码,酿造了一起损失数十亿美元的安全事故。受到影响的人群,也可能包括这个漏洞的研究者和修复者,系统的运营者,甚至是攻击者本人。这种不对称的破坏性让人唏嘘,这也正是我们为什么要重视代码安全的背后的原因。 Equifax的教训给我们带来三点启示: * **不起眼的代码问题,也可以造成巨大的破坏**; * **安全修复版本,一定要第一时间更新**; * **安全漏洞的破坏性,我们很难预料,每个人都可能是安全漏洞的受害者**。 ## 编写安全的代码 一般来说,安全的代码是能够抵御安全漏洞威胁的代码。 传统上,我们说到信息安全的时候,最常接触的概念是防火墙、防病毒、防攻击。其实,大部分的安全事故(80%-90%)是由软件的代码漏洞引起的。没有安全保障的代码,是随时都可以坍塌的空中楼阁。 ## 小结 通过对这个案例的讨论,我想和你分享下面三点个人看法: * **不起眼的小问题,也会有巨大的安全缺陷,造成难以估量的损失**; * **编写安全的代码,是我们必须要掌握的基础技能**; * **安全问题,既是技术问题,也是管理问题**。 下一节,我们接着聊安全漏洞的威胁该怎么衡量。再接着,我们来讨论一些常见的编写安全代码的原则和实践。 ## 一起来动手 Equifax公司的问题之一,就是没有及时地更新安全修复。这一般不是疏漏的问题,而是没有充分认识到安全更新的重要性,或者没有把安全修复的计划执行到位。 要想升级到安全修复的版本,我们需要知道两件事: * 第一时间获知,某个依赖的软件有了安全更新; * 最快速地行动,升级到安全修复版本。 有时候,安全版本升级之前,安全漏洞的细节就已经暴露出来了。这时候,我们也要采取必要的措施: * 第一时间知道出现了安全漏洞; * 快速寻找、部署漏洞修复的临时方案。 人力总是有限的,我们接触到的信息也是非常有限的。上面的两种措施中,人工都没有办法做到第一点的,除非你使用的是一个完全封闭的系统(完全封闭的系统,一般也是漏洞更多的系统);而第二点,或多或少的,都需要人工的参与。 我们利用讨论区,来讨论三个问题: 第一个问题是,你有没有使用最新版本软件的习惯? 第二个问题是,你的公司是如何获取安全漏洞信息和安全更新信息的? 第三个问题是,你的公司有没有安全更新的策略?如果有,又是怎么执行的,能不能执行到位? 欢迎你在留言区留言、讨论,我们一起来学习、思考这些老大难的问题! 如果你觉得这篇文章有所帮助,欢迎点击“请朋友读”,把它分享给你的朋友或者同事。 # 参考资料 https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/%e4%bb%a3%e7%a0%81%e7%b2%be%e8%bf%9b%e4%b9%8b%e8%b7%af/31%20%e4%b8%ba%e4%bb%80%e4%b9%88%e5%ae%89%e5%85%a8%e7%9a%84%e4%bb%a3%e7%a0%81%e8%bf%99%e4%b9%88%e9%87%8d%e8%a6%81%ef%bc%9f.md * any list {:toc}