读软件开发安全之道:概念、设计与实施06模式(下)

1. 强力执行

1.1. 强力执行组的模式关注的重点是如何通过彻底强制执行规则的方式来限制代码的行为

1.2. 漏洞是任何法律法规的毒药,强力执行组的模式就展示了如何避免给破坏系统创造机会

1.3. 我们应该在结构上进行设计,让那些错误的操作根本不可能发生

1.4. 完全仲裁原则

  • 1.4.1. 保护所有访问路径,强制执行相同的访问,没有任何例外

  • 1.4.2. 所谓完全仲裁原则,即对受保护资源的访问执行一致的安全检查

  • 1.4.3. 防守点

  • 1.4.3.1. 瓶颈(bottleneck)

  • 1.4.3.2. 要保证对各类访问执行的所有校验都必须在功能上保持一致

  • 1.4.4. 不同的级别

  • 1.4.4.1. 高合规性:只能通过一种公共的方式访问资源(瓶颈防守点)​

  • 1.4.4.2. 中合规性:可以在不同位置对资源发起访问,但每个位置都使用相同的授权校验进行保护(多个公共防守点)​

  • 1.4.4.3. 低合规性:可以在不同位置对资源发起访问,这些位置分别使用不同的授权校验进行保护(不符合完全仲裁原则)​

  • 1.4.5. 不要提供多种路径来访问相同的资源,又给每种路径定义专门的代码,且每组代码工作方式又不尽相同

  • 1.4.5.1. 任何差异都有可能意味着一部分路径的保护措施比另一部分的弱

  • 1.4.6. 使用多个防守点就会给出错制造更多的机会,也需要我们付出更大的努力才能进行比较彻底的测试

1.5. 最少共同机制

  • 1.5.1. least common

  • 1.5.2. 独立进程之间的共享机制越少越好,这样才能保持这些进程之间相互隔离

  • 1.5.3. 桥接(bridge)是指泄露信息,或者让一个进程在未经授权的情况下影响其他进程

  • 1.5.4. 在设计服务时,应该让它们与各个独立进程或用户进行交互,这就是实现最少共同机制模式的方法

2. 冗余

2.1. 冗余是工程安全的核心策略,这个策略可以反映在大量日常行为当中

  • 2.1.1. 汽车有备胎

2.2. 深度防御

  • 2.2.1. 把大量独立的保护措施组合在一起,就可以形成强大的总体防御措施,这种总体防御措施可以协同发挥作用,其效果远比其中任何一项措施都更加有力

  • 2.2.2. 鉴于软件系统不可避免地包含大量错误,这个措施可以让我们的软件系统比系统的组件更加安全

  • 2.2.3. 叠加两层或者多层保护机制来实施特别重要的安全策略,这就是实现深度防御的方式

  • 2.2.4. 沙盒

  • 2.2.4.1. 所谓沙盒是指任何一个让不可靠的代码可以安全运行的容器

  • 2.2.4.2. 当今的网页浏览器会在安全沙盒中运行WebAssembly

  • 2.2.4.3. 相同的缺陷同时存在于这两层的可能性很低,如果它们是分别独立开发的,那么可能性就更低了

  • 2.2.4.4. 即使有百万分之一的概率,分析器没有检测出某种攻击方式,同时它也能够欺骗解释器,但一旦这两种机制结合起来,整个系统发生故障的概率就可以降低到十亿分之一以下,这就是这种模式的强大之处

2.3. 权限分离

  • 2.3.1. 两方总比一方可靠

  • 2.3.2. 权限分离也称为责任分离(separation of duty)

  • 2.3.2.1. 如果我们上两把锁,同时把两把钥匙委托给两个不同的人,这样做的安全性比只上一把锁要强

  • 2.3.3. 如果一项受保护资源存在明显相互重叠的职责,就应该采用这种模式

  • 2.3.4. 在大型企业中,不同的团队可能会负责数据中心内部的不同数据集,这也是为了进行更大程度的责任分离

  • 2.3.5. 把一项责任划分成多个职能,避免任何人无意或蓄意给系统造成严重后果

  • 2.3.5.1. 为了防止备份数据泄露,我们可以由不同团队用各自的密钥对数据进行两次加密,这样只有两个团队的人员同时提供密钥,人们才能解密备份的数据

  • 2.3.5.2. 在激活核武器这类极端情况下,则需要两把钥匙同时转动且钥匙孔相互距离3米以上,这种设计的目的是防止任何一个人独立激活核武器

  • 2.3.6. 审计日志也可以通过权限分离的方法进行保护,由一个团队负责记录和查看事件,同时让另一个团队发起事件

  • 2.3.6.1. 管理员负责审计用户的行为

  • 2.3.6.2. 一个独立的团队负责对管理员进行审计

  • 2.3.6.3. 犯罪人员可能会阻止记录他们自己的腐败行为,或者篡改审计日志来掩盖他们的踪迹

  • 2.3.7. 我们不可能在一台计算机中实现权限分离,因为拥有超级用户权限的管理员永远可以完全控制这台计算机

  • 2.3.7.1. 如果希望针对管理员执行强大的权限分离,就需要要求管理员通过特殊的ssh网关进行工作,让他们的工作处于独立的控制之下,他们的会话也会被详细地记录下来,同时还可以针对管理员实施其他限制

  • 2.3.8. 内部人员给系统带来的威胁很难消除,有时甚至根本不可能消除,但是这并不代表采取缓解措施是在浪费时间

  • 2.3.8.1. 哪怕让内部人员知道,有人正看着他们,这本身也是一种强大的震慑

  • 2.3.8.2. 诚信的员工一定会欢迎任何权限分离的措施,因为这样可以增强对系统的审计,还可以降低因为他们的错误操作给系统带来的风险

  • 2.3.8.3. 在逼迫内部的恶意人员把注意力放在掩盖自己的踪迹上,这样他们发起攻击的效率也会有所降低,同时还可以增大抓现行的概率

3. 信任与责任

3.1. 信任和责任是合作的黏合剂

  • 3.1.1. 软件系统之间的互联越来越多,相互之间的依赖也越来越强,所以这组模式都是重要的路标

3.2. 不盲目信任

  • 3.2.1. 信任永远都应该是明确的选择,而且应该有确凿的证据

  • 3.2.2. 不盲目信任模式确认了一点,那就是信任是非常宝贵的,这也是为什么我们应该保持怀疑态度的原因

  • 3.2.3. 不盲目信任模式是在告诉我们,不要默认穿着制服的人就拥有合法的身份,要想到电话里那个自称是警察的人很可能是个骗子

  • 3.2.4. 在软件领域,这种模式适用于在安装软件之前首先校验代码的真实性,这需要在授权之前首先执行强有力的认证

  • 3.2.5. 鉴于客户端没有任何义务按照服务器的规则来发出请求,所以服务器永远应该对cookie这种手段持保留态度,不要太相信客户端会忠实地执行这项任务,否则会带来巨大的风险

  • 3.2.6. 即使没有恶意,我们也不应该盲目信任

  • 3.2.6.1. 在一个重要的系统中,我们务必要保证所有组件都保持高质量、高安全性的水准,这样整个系统才不会轻易被入侵

  • 3.2.6.2. 如果信任了不该信任的人,使用了匿名开发人员提供的代码(代码中可能会包含恶意软件,或者代码中包含很多错误)​,那么系统中的重要功能可能立刻就会面临安全风险

3.3. 接受安全责任

  • 3.3.1. 所有软件从业者都有明确的责任,他们都需要对软件的安全负责

  • 3.3.2. 在协议里面把权责写清楚,界定由谁对请求进行验证,以及如果系统会为下游提供保障的话,它会提供什么样的保障

  • 3.3.3. 如果希望系统尽可能安全,就应该采用深度防御模式,让两个组件独立对输入的内容进行验证

  • 3.3.4. 不要因为“说不定哪天就会有人用到”就给用户提供一种操作的可选项

  • 3.3.5. 如果需求本身就是不安全的,那么设计人员就应该明确告知用户这个需求存在的风险,并且对用户提供保护,防止他们在不知道后果的情况下配置了这个可选项

  • 3.3.5.1. 不仅是我们应该把风险记录下来,同时提供各种可能的缓解措施来弥补漏洞,用户也应该接收到明确的反馈信息

  • 3.3.6. 在理想情况下,我们最好不要用“你确定吗?​”或者“了解更多信息,详见下列链接”这样的对话框,这是在推卸责任

  • 3.3.6.1. 在理想情况下,我们应该把责任留给那些完全了解自己所作所为后果的专业管理员,这类选项应该留给这种专业人士进行抉择

4. 反模式

4.1. 要懂得从别人的灾祸中吸取教训

4.2. 最好的方法是观察大师们是如何工作的

4.3. 避免犯其他人犯过的错误

4.4. 代理混淆问题

  • 4.4.1. 代理混淆问题是很多软件漏洞的基本安全挑战

  • 4.4.2. 代理混淆的常见情况包括用户代码调用内核,或者用户通过互联网访问Web服务器

  • 4.4.3. 包括缓冲区溢出、输入验证不足、跨站请求伪造(Cross-Site Request Forgery,CSRF)攻击等

  • 4.4.4. 意图与恶意

  • 4.4.4.1. 开发软件的人员必须是我们信任的人,这个人必须既诚信又有能力提供高质量的产品

  • 4.4.4.2. 两个条件之间的区别就在于意图

  • 4.4.5. 可信的代理

  • 4.4.5.1. 信任边界就是最开始发生混淆的地方,因为混淆代理攻击的目的就是利用更高的权限

  • 4.4.6. 代理混淆的另一种解决形式基于代表请求所采取的行动

  • 4.4.6.1. 数据隐藏(data hiding)是一种基本的设计模式

  • 4.4.7. 在信任边界,我们一定要谨慎地处理低可靠性的数据和低权限的调用,不要让它们最终发展成混淆代理攻击

4.5. 信任回流

  • 4.5.1. 只要低可靠性的组件控制了高可靠性的组件,就会出现信任回流的情况

  • 4.5.2. 最重要的不是你对组件有多么信任,而是这些组件有多么值得你去信任

  • 4.5.3. 威胁建模可以通过明确查看信任边界来揭示这类潜在问题

4.6. 第三方Hook

  • 4.6.1. 另一种形式的信任回流反模式是系统组件中的Hook为第三方提供了不正当的访问

  • 4.6.2. 如果Hook实现了自动更新机制,它只能下载和安装新版本的软件,那么只要它的信任级别达到了这个水准,使用它就不会有太大的问题

4.7. 组件不可修补

  • 4.7.1. 对于任何流行的组件,从来都不存在人们会不会发现其中漏洞的问题,而是人们什么时候会发现漏洞

  • 4.7.2. 一旦人们都知道了它的漏洞,除非它与所有攻击面都彻底断开,否则我们必须给它打上补丁

  • 4.7.3. 系统中任何不能打补丁的组件最终都会成为这个系统永远的痛

  • 4.7.4. 主流的操作系统提供商一般都会为自己的产品提供一段时期的支持和升级,接下来它们就不会再给自己的产品发布补丁了

  • 4.7.5. 如果软件开发商不能及时地发布更新,那么即使这类软件可以更新,恐怕也好不到哪里去

  • 4.7.5.1. 如果你自己都没有把握能及时获得更新,就最好不要去碰运气