You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

160 lines
10 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 31 | 为什么安全的代码这么重要?
从今天开始,我们进入本专栏的“安全模块”。首先,我们通过一个具体的安全漏洞的案例,来感受下计算机代码是多么的脆弱,以及编写安全的代码为什么如此重要。
## 评审案例
在Web开发中“multipart/form-data“类型经常被用来上传文件。比如下面这段描述表单的代码就是使用multipart/form-data上传文件的一段HTML代码。
```
<FORM action="http://upload.example.com/"
enctype="multipart/form-data"
method="post">
<P>
Upload the file: <INPUT type="file" name="upload-file"><BR>
<INPUT type="submit" value="Send">
</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://static001.geekbang.org/resource/image/9a/60/9a01e8ba62aa08e35d116d93e1e42e60.jpg)
其中蓝色标注的代码LocalizedTextUtil.findText()用来查找错误的本地化信息。如果“multipart”请求解析出错就会触发这个方法。它的规范大致如下
![](https://static001.geekbang.org/resource/image/7e/73/7ecef79094e02086610f20dcc5be0773.jpg)
对于LocalizedTextUtil.findText()的规范我们要留意蓝色字体的部分。这一部分告诉我们如果信息里包含了OGNLObject 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()的这段实现代码时,它的危险性其实很清楚,主要有两点:
1. 没有充分了解调用接口LocalizedTextUtil.findText()
2. 允许执行远程请求的表达式OGNL表达式
这两点分别违反了下面的安全编码原则:
1. 清楚调用接口的行为;
2. 跨界的数据不可信任。
## 真正的威胁
我们一起来看看这个漏洞的几个关键时间点:
* 2017年1月29日NIST的NVDNational 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的教训给我们带来三点启示
1. **不起眼的代码问题,也可以造成巨大的破坏**
2. **安全修复版本,一定要第一时间更新**
3. **安全漏洞的破坏性,我们很难预料,每个人都可能是安全漏洞的受害者**。
## 编写安全的代码
一般来说,安全的代码是能够抵御安全漏洞威胁的代码。
传统上我们说到信息安全的时候最常接触的概念是防火墙、防病毒、防攻击。其实大部分的安全事故80%-90%)是由软件的代码漏洞引起的。没有安全保障的代码,是随时都可以坍塌的空中楼阁。
## 小结
通过对这个案例的讨论,我想和你分享下面三点个人看法:
1. **不起眼的小问题,也会有巨大的安全缺陷,造成难以估量的损失**
2. **编写安全的代码,是我们必须要掌握的基础技能**
3. **安全问题,既是技术问题,也是管理问题**。
下一节,我们接着聊安全漏洞的威胁该怎么衡量。再接着,我们来讨论一些常见的编写安全代码的原则和实践。
## 一起来动手
Equifax公司的问题之一就是没有及时地更新安全修复。这一般不是疏漏的问题而是没有充分认识到安全更新的重要性或者没有把安全修复的计划执行到位。
要想升级到安全修复的版本,我们需要知道两件事:
1. 第一时间获知,某个依赖的软件有了安全更新;
2. 最快速地行动,升级到安全修复版本。
有时候,安全版本升级之前,安全漏洞的细节就已经暴露出来了。这时候,我们也要采取必要的措施:
1. 第一时间知道出现了安全漏洞;
2. 快速寻找、部署漏洞修复的临时方案。
人力总是有限的,我们接触到的信息也是非常有限的。上面的两种措施中,人工都没有办法做到第一点的,除非你使用的是一个完全封闭的系统(完全封闭的系统,一般也是漏洞更多的系统);而第二点,或多或少的,都需要人工的参与。
我们利用讨论区,来讨论三个问题:
第一个问题是,你有没有使用最新版本软件的习惯?
第二个问题是,你的公司是如何获取安全漏洞信息和安全更新信息的?
第三个问题是,你的公司有没有安全更新的策略?如果有,又是怎么执行的,能不能执行到位?
欢迎你在留言区留言、讨论,我们一起来学习、思考这些老大难的问题!
如果你觉得这篇文章有所帮助,欢迎点击“请朋友读”,把它分享给你的朋友或者同事。