delphij's Chaos

选择chaos这个词是因为~~实在很难找到一个更合适的词来形容这儿了……

03 Feb 2010

安全:该做什么和不该做什么

很多事情都有Do’s and Don’ts,这里试着整理一下与安全有关的。

本文版权所有 © 2010 Xin LI delphij@FreeBSD.org 保留所有权利;非商业转载请注明出处 http://blog.delphij.net/, 谢绝商业转载。

安全不能建立在"别人不知道"的基础上

“别人不知道"是一种非常常见的安全 假象,举例来说,一种自己设计的山寨加密算法、一个系统中一般人不知道的位置等等,都属于这一类。

将安全建立在"别人不知道"的基础上是非常危险的。首先它会给设计者和用户带来"安全"的幻像,这会直接导致与系统交互的人放松警惕;其次,这样的设计往往留有"后门”,甚至是设计者不知道的后门(因为往往他们并不对这类设计进行充分的、专业的审计),容易被攻击者利用;最后,这种做法存在第三方泄密问题,即,使用这种系统的人,需要提防设计系统的人被其他人买通并泄漏一些秘密的情况。

延缓攻击的手段不能用来阻挡攻击

有许多延缓攻击的手段,例如改变服务的端口(比较常见的如将 ssh 改为 tcp/22 以外的端口),或禁止服务程序显示自己的版本等等,或仅仅简单地启用防火墙,这些手段起到的作用只是延缓攻击,而不应作为一种安全屏障。对于多层次式的安全设计来说,采取这些措施有助于提高检测到入侵的机会,但是它们本身并不会提高安全性。

与前一种情况类似,这种做法也只是让管理员放松警惕。例如以 ssh 为例,有人认为将端口改为一个非知名端口可以避免相关的攻击,但事实是,攻击者依然可以利用 ssh 实现或协议设计中存在的一些漏洞来攻破系统。拥有特定资源的攻击者甚至不需要直接对目标系统实施攻击。在较复杂的攻击手段中,包括简单的 port knocking 一类的保护手法,都可以使用类似分组重放这样的方法来逐步攻破。

采用层次式的安全设计

所谓层次式的安全设计,说的是在一套安全系统中包含不同层次的、存在层次式监控关系的安全结构。例如,将本地包含执行文件的那些文件系统通过一定的方式导出给监控网段的机器,就可以让那些机器在攻击者不知情,或至少不太容易注意到的情况下对入侵进行检测;通过将一些重要日志发到以不同的访问控制机制,甚至不同网络协议的记录设备上,则可以有效地检测入侵者的入侵行为,并为日后的分析留下更多的有用信息。

层次式安全在现实中也有应用。例如产品的质检,除了制造商自己进行的质量控制之外,有时分销商或政府也会进行一些抽样的检查。我们注意到,这些设计中的一个重要的特点是在不同的系统中使用不同的访问控制逻辑。例如,日志服务器必须从特定的客户端,甚至只能从某些隔离的内网登录。此时,延缓攻击的手段可以作为它的一项辅助设施,即其目的并不是阻止攻击,而是吸引攻击者在攻击目标上花费更多的时间,从而帮助入侵检测机制更容易地检测这些攻击。

不要轻信任何东西,包括X.509证书

安全系统的设计者必须对安全有全面的理解和认知。有一句很著名的话叫做 In God we trust, all others must submit an X.509 Certificate,需要注意的是,这里说的是 must submit,并没有说 submit 了就可以 trust 了。

和前面所说的层次式安全设计类似,我们的一个基本假定应该是,一个安全系统中的任何参与者,无论是用户还是计算机或程序,都是可能存在弱点的。安全系统,或用户,都不应轻信任何东西,例如,在特权隔离 (Privilege Separation) 这样一种设计中,特权进程除了完成那个非特权的子进程的请求之外,还有一个任务是维护一个"理性状态机"(Sanity DFA),这个状态机的作用是检测非特权进程的异常状况,如果发生这样的情况,则特权进程有拒绝提供服务,并杀掉非特权进程的责任。作为用户,对于系统给出的响应,除了验证对方的证书之外,也应有常识性的了解和适当的判断。

不要把安全建立在不靠谱的基础设施上

举个现实生活中的例子,修一座桥,结果水泥标号不够;修一座楼,结果钢筋用竹签代替,有可能稳当吗?

然而在安全系统的设计中,很多人却往往忽视这样显而易见的问题。比较典型的就是许多网站所采用的所谓"安全控件"的技术。每一个这样的新设施,都意味着新增的攻击面和一组可能可以利用的新漏洞。

在一个安全系统中,防守的一方一定是弱势群体,因为他们必须按照一定的规矩去设计系统;而攻击者则可以采用最便宜的攻击方式。时刻牢记,在你花费时间去加强系统中最坚固的部分的时候,你的对手则正在靠近这个系统中最薄弱的环节,甚至可能是在你不知道的情况下!

下面是一些典型的不靠谱安全设施:

  • 自己设计的算法。(公开的算法经过了更多的专业人士的验证;而自己设计的算法则没有,每个人的知识面都会有些局限,即使第一流的密码学设计者也可能设计出糟糕的算法,因此同行的验证很重要)
  • 监控键盘的驱动程序。(这类做法不仅不能对安全产生助益,反而会给其他木马带来监控用户键盘,从而攫取其他机密信息的便利)。
  • 基于明文的通讯协议。(当数据需要经过不受控制的网络节点的时候,端对端加密是非常重要的)。

操作复杂不等于安全

一些安全措施在实施了之后,用户可能会发觉与之前相比会带来一些不便。有些不便可能是无法避免的,但这并不意味着,使用的时候给用户带来不适的安全设施就一定会提高安全性。

举例来说,一些银行在登录时,会在用户输入用户名之后,先显示一张用户自己定义的图片,然后再让用户输入密码。这种设施本身并没有改善任何安全性,因为中间人攻击仍然可以获得那个图片,更糟糕的是,由于看到了希望看到的那张图片,用户反而会对这个网站产生安全幻觉。

而另一种做法则是给用户一个小的token,这个token能够根据网站和token之间共享的信息和用户自己的密码来计算出一个登录用的一次性密码。拿到这个一次性密码的攻击者,并没有很好的办法再次使用这个密码来做别的事情。

代码开放和安全是不是一回事

许多人认为,开放源代码的东西更安全一些。这种说法并不完全正确。还有一些人认为,因为代码大家都可以看到,因此开放源代码的东西更安全,事实上,后面这种看法完全是逻辑混乱。

举个例子,面对来要求审计账目的税务局,一个CEO的回答是,我的账目绝对没有问题,因为我这家公司上上下下200多人都可以随时去看。毫不意外地,这个审计师决定自己再看一遍,因为这是一家广告公司,而"能看"和"看过的人都是内行"以及"有多少内行看过"也是完全不同的概念。

在选择一个解决方案的时候,如果一定要参考其他人的意见的时候,一定要看这个意见是来自什么人。开放源代码的产品一样有可能会存在漏洞,看它代码的人很可能并不是很有安全方面知识和经验的人,有这些经验的人可能在看其他的开放源代码,甚至不开放源代码的产品,等等。真正重要的是,找一个值得信任的专业人士去做这样的评估,而不是仅仅看一个和安全与否关系不大的指标就匆忙决定。

使用公开的、经过验证的安全算法和协议

算法和协议是实际应用中的安全系统中最关键的两个部件。经过验证的、公开的算法和协议有这样一些无法替代的好处:

  • 设计它们的坏人有足够的专业知识。
  • 试图攻击它们,并且也有足够专业知识的坏人已经证明,攻破它们需要的代价大到不值得从这个方面去突破它们。
  • 坏人之间为了证明自己做了充分的对抗性的同僚复审。
  • 它们被破解的时候,你可能不是最先知道这件事的,但是也不会是最需要担心这件事的人。
  • 最重要的一点:设计一个自己破解不了的东西很容易,而设计一个 别人 破解不了的东西很难。我要提醒读者的是,你需要的是别人破解不了你的系统,而不仅仅是你自己破解不了。

避免同一类型的弱点

攻击者在攻击的时候,往往会采用最"便宜"的方法去进行。例如,在发现一个由于程序设计问题而导致的安全漏洞时,除了修正这个问题本身之外,还应对系统中可能存在类似问题的其他环节进行类似的代码审计。对于开源项目来说这一点尤其重要。

例如,如果OS允许在地址0附近映射内存页,则由于欠锁一类原因导致的竞态条件所导致的可能就不仅仅是崩溃了。这类问题的典型用例是先将自己的代码映射到地址0附近,然后设法触发内核的一个可能导致空指针引用的函数指针调用,这样系统将会在内核的上下文运行那些代码(说明:出于性能考虑,一般来说内核并不会切换到一个完全不同的地址空间去运行)。尽管修正竞态条件本身很重要(例如FreeBSD SA 09:13.pipe),但通过禁止在地址0附近映射内存页,可以将这一类型的潜在漏洞全部由特权提升降级为崩溃或死锁,因此也就有了 FreeBSD EN 09:05.null 所做的改动。

避免过度设计,优先改进最薄弱的环节

过度设计是许多工程师会遇到的问题。例如,在内网交换机上传输加密数据,在多数情况下都是不必要的(听不到发到其他节点的包,而听包本身所需要的权限已经足够做其他事情)。

攻击者不需要遵循任何规则,而防守者则必须遵守一定的标准。因此,作为原则,防守的一方应假定攻击者会找到系统中最薄弱的环节,或者说,站在攻击者的角度去思考从什么地方去攻陷系统,并加强系统中最薄弱的那些环节。

不过,想要避免这些设计问题,必须拥有许多相关的知识和经验。

今天先写到这里,改天继续总结。