读软件开发安全之道:概念、设计与实施04缓解

1. 缓解

1.1. 安全思维转换为有效行动的方法就是首先预判威胁,然后针对可能的漏洞加以保护

1.2. 主动响应的做法就叫做“缓解”

  • 1.2.1. mitigation

  • 1.2.2. 喂宝宝的时候给孩子围上围嘴,避免掉下来的食物粘在宝宝的衣服上,还有安全带、限速、火灾警报、食品安全操作规范、公共卫生措施和工业安全法规,这些统统都属于缓解措施

  • 1.2.3. 降低问题的严重性、危害性或者缩小影响范围

  • 1.2.4. 都通过主动采取手段来规避或者减少风险所带来的预期损失

1.3. 缓解措施可以降低风险,但不能彻底消除风险

  • 1.3.1. 如果我们可以设法彻底消除风险​,那无论如何都应该采取这样的措施

  • 1.3.2. 比如,删除了一个不安全的旧特性

1.4. 缓解措施关注的是降低攻击发生的可能性,提升发起攻击的难度,或者降低攻击造成的危害

1.5. 那些让利用漏洞的操作更容易被检测出来的措施也可以算是缓解措施

  • 1.5.1. 给自己的商品采用防篡改包装一样,它们都可以促成人们更快做出反应并且进行补救

1.6. 每一项微小的努力都可以提高整个系统的安全性,即使是有限的胜利也可以不断改善系统,构成更理想的保护措施

2. 解决威胁

2.1. 威胁建模可以向我们展示哪里可能出现问题,这样我们就可以把安全的重心放在“刀刃”上

2.2. 明确风险点(即重要事件或者决策阈值)才是缓解风险的最好机会

  • 2.2.1. 我们永远应该首先解决那些最重大的风险,对它们进行最大程度的限制

  • 2.2.2. 对于那些处理敏感个人信息的系统,未经授权而泄露信息的威胁总是如影随形

2.3. 手段

  • 2.3.1. 把能够访问这类数据的范围降至最低

  • 2.3.2. 减少收集的信息总量

  • 2.3.3. 主动删除那些不会再使用的过时数据

  • 2.3.4. 在出现入侵事件的情况下通过审计执行早期检测

  • 2.3.5. 采取行动来降低攻击者泄露数据的能力

2.4. 对最高安全级别的风险提供了保护之后,我们需要有选择地对较低程度的风险进行缓解

  • 2.4.1. 只要缓解手段不会增加太多负担,也不会增加设计的复杂性

2.5. 例子

  • 2.5.1. 将每次登录时提交的密码与加盐密码散列值(salted hash)进行比较,而不是与明文的真实密码进行比较

  • 2.5.1.1. 可以避免保存明文密码

  • 2.5.1.2. 哪怕攻击者入侵了系统,他们也无法获得实际的密码

2.6. 只要有机会就要降低风险

3. 把攻击面减到最小

3.1. 一旦我们判断得出一个系统的攻击面,我们就知道利用漏洞的行为最有可能源自哪里,因此我们采取的一切可以加固系统“外部城墙”的行为都是一场重大的胜利

  • 3.1.1. 考虑每个入口点下游涉及多少代码和数据

  • 3.1.2. 可以让包含漏洞的代码数量更少

3.2. 在客户端/服务器系统中,我们可以把服务器的功能推送给客户端,这样就可以减小服务器的攻击面

  • 3.2.1. 如果客户端拥有必要的信息和计算资源,就不仅可以减少服务器的负载,还可以减小服务器的攻击面

3.3. 把功能从一个面向公众、任何人都可以匿名调用的API转移到需要进行认证的API,也可以有效地减小攻击面

  • 3.3.1. 创建账户不仅可以减缓攻击速度,也可以追踪攻击者,还可以实施速率限制

3.4. 使用内核服务的库和驱动器可以通过最小化内核内部代码和连接内核的接口来达到减小攻击面的目的

  • 3.4.1. 不仅有更少的内核转换被攻击,而且即使攻击取得了成功,用户态代码也无法造成大规模的破坏

3.5. 部署和运维都可以提供很多减小攻击面的机会

  • 3.5.1. 最简单的做法就是把所有资源都挪到防火墙的后面

3.6. 通过网络进行远程管理的设置也是一例

  • 3.6.1. 如果使用率不高,就应该考虑禁用这类特性,只在必要的情况下使用有线接入的方式发起访问

3.7. 不断思考各种方法来减少外部访问、把功能和接口减到最少,对那些不需要暴露给公众的服务提供保护

  • 3.7.1. 我们越是能够理解一个特性应该应用在哪里、应该如何应用,我们就可以找到越多的缓解手段

4. 缩小漏洞窗口

4.1. 缩小漏洞窗口类似于减小攻击面,但是这种策略的目标并不是减小承受攻击的范围,而是把漏洞有可能出现的有效时间间隔减到最小

  • 4.1.1. 猎手会在射击之前打开保险,然后在射击之后重新挂上保险一样

4.2. 低信任数据或者请求与高信任代码进行互操作的那个地方

  • 4.2.1. 为了能够更好地隔离高信任代码,我们需要把这些代码的执行操作减到最少

  • 4.2.2. 应该在调用高信任代码之前首先执行错误校验,确保代码可以继续完成工作后退出

4.3. 代码访问安全

  • 4.3.1. Code Access Security,CAS

  • 4.3.2. 一种如今已经很少使用的安全模型,它完美地说明了缩小漏洞窗口这种缓解措施,因为它提供了对代码有效权限的细粒度控制

4.4. 重要的限制手段包括限制时间窗口、限制地理位置、仅限制账户内金额等

  • 4.4.1. 所有这些缓解手段都有助益,因为这些手段会避免出现入侵行为时发生最坏的情况

5. 把暴露的数据减到最少

5.1. 针对数据泄露的结构性缓解策略是限制内存中敏感数据的保存时间

5.2. 把数据的生命周期减到最小,而不是限制代码以高权限运行的时间

  • 5.2.1. 私钥或者密码之类的认证凭据,我们应该在不需要这些数据的时候,立刻在内存中把它们覆盖掉,这样做是绝对值得的

5.3. Heartbleed漏洞威胁到大量网页的安全,暴露了存储空间的各类敏感数据

  • 5.3.1. 限制这种敏感数据的保留时间完全可以成为一种合理的缓解措施

  • 5.3.1.1. 有可能的话,应该先止损​

5.4. 主动清除数据副本是一种极端的情况,这种操作只应该针对那些最敏感的数据,或者仅在关闭账户这种重要操作发生时执行

6. 访问策略与访问控制

6.1. 标准的操作系统权限机制都会提供非常基本的文件访问控制

  • 6.1.1. 这些权限根据进程的用户和组的所有权,采用全有或全无的方式来控制读(机密性)或写(完整性)访问

6.2. Web服务和微服务被设计为代表那些一般不对应进程所有者的主体来工作

  • 6.2.1. 一个进程会对所有通过了认证的请求提供服务,并要求获得权限来访问所有客户端数据

  • 6.2.2. 只要存在漏洞,所有客户端数据就都有风险

6.3. 缩小“可以访问的资源”与“系统正好允许访问的资源”之间的差异

  • 6.3.1. 可以限制每分钟或者每小时的访问次数

  • 6.3.2. 实施最大的数据量限制

  • 6.3.3. 实施与工作时间相对应的时间限制策略

  • 6.3.4. 根据同行的策略或者历史数据来采取可变的访问限制

6.4. 确定安全访问限制是一项艰难的工作,但是绝对物超所值,因为这可以帮助我们理解应用的安全需求

6.5. 在设定策略的时候留有余地,避免严格的策略妨碍了人们的正常工作

  • 6.5.1. 让个别代理人员提交合理的解释,从而在短时间内提升针对自己的限制值

  • 6.5.2. 通过设置这种“减压阀”​,我们就可以对基本的访问策略实施严格的限制

  • 6.5.3. 申请可以接受审计,如果这种申请次数越来越多,管理人员就应该研究需求是否已经提升,以及需求提升的原因,并且在掌握了这些情况的基础上对限制值进行放松

  • 6.5.4. 使用软限制来创建访问策略,而不用拍脑袋想出来的参数执行“一刀切”政策

7. 接口

7.1. 安全分析中的重中之重

7.2. 接口可以揭示数据流和控制流

7.3. 接口会充当明确定义的信息节点,我们就应该在这里实施缓解措施

  • 7.3.1. 只要存在信任边界,那么最主要的安全关注点都应该着眼于数据流和控制流从低信任组件流向高信任组件的地方

7.4. 在大型系统中,网络之间、进程之间,甚至进程内部一般都会包含接口

  • 7.4.1. 端点设备之间的交互几乎必然会通过线路来完成,其他类型的接口情况更加复杂

  • 7.4.2. 进程之间通信接口的可信度几乎和网络接口的别无二致

  • 7.4.2.1. 进程间的通信和网络接口也就成了威胁建模的主要焦点

7.5. 从攻击者的角度看,进程内的边界是非常容易突破的

7.6. 所有大型软件的设计方案都面临相同的问题,那就是如何构建组件才能把高权限访问的区域降到最低限度,以及如何限制敏感信息流从而减小安全风险

  • 7.6.1. 把信息访问限制到一个最小的组件范围,相关组件又得到了良好的隔离,那么攻击者想要访问敏感数据,难度就会大得多

7.7. 接口架构是决定系统能否成功保护资产的核心因素

8. 通信

8.1. 通信对几乎任何软件系统都是基本组件,当然这里的通信包括互联网络通信、私有网络通信,或者通过蓝牙、USB等协议实现的外围连接通信

  • 8.1.1. 要么信道在物理上足够安全,针对窃听和嗅探提供了防护手段

  • 8.1.2. 要么数据进行了加密,使数据的完整性和机密性能够得到保障

8.2. 物理安全往往都不十分可靠,因为只要攻击者绕过了这些物理安全防护手段,往往就能够访问到完整的数据,而且这种入侵方式很难被发现

8.3. 现有计算负载的基础上增加加密运算也没有什么问题,所以不对通信进行加密的理由一般都不怎么充分

8.4. 哪怕是最好的加密措施也不是什么灵丹妙药,因为还有一种威胁存在,那就是加密无法掩饰通信的发生

  • 8.4.1. 如果攻击者可以读取到信道中的原始数据,即使他们无法对数据的内容进行解密,也可以看到有数据在信道中进行收发,因此也就可以粗略地估算出数据总量

  • 8.4.2. 如果攻击者可以对通信信道进行篡改,他们就有可能让通信造成延迟,甚至直接阻塞通信传输

9. 存储

9.1. 保存数据就相当于把数据发送给“未来”​,以备人们在未来提取使用

9.2. 存储介质中的静态数据很容易遭到攻击,就像在线缆中传输的通信数据很容易遭到攻击一样

  • 9.2.1. 保护静态数据不受篡改和泄露需要同时借助物理安全措施和加密

  • 9.2.2. 所存储数据的可用性也依赖数据备份或者物理层面的保护措施

9.3. 在系统设计方案中,存储同样是无处不在的,这类系统常常把保护数据安全的具体措施推迟到操作的时候再来处理,这就会错失在设计方案中减少数据丢失的机会

9.4. 我们备份的既可以是完整的数据集,也可以是增量数据、交互记录,这些信息累加起来就可以准确地重建数据

  • 9.4.1. 它们都应该进行独立、可靠的存储,并且按照一定的频率进行备份,同时保证延迟时间在合理范围之内

  • 9.4.2. 云架构可以用近乎实时的方式提供冗余数据复制功能,这可能就是最理想的连续备份解决方案,当然这种解决方案不是免费的

9.5. 所有静态数据(包括备份数据)都存在被非法访问的风险,所以我们必须在物理上对数据进行保护或者加密

  • 9.5.1. 我们创建的备份数据越多,泄露的风险也就越大,因为每份副本都有可能泄露

  • 9.5.2. 要求我们在数据丢失风险和数据泄露风险之间进行权衡,我们不可能同时把两种风险都降到最低,但是我们可以用很多方式来判断两种风险之间最理想的平衡点

9.6. 推荐大家使用那些广泛使用的开放标准,因为一旦官方不再支持私有格式,就只能进行逆向工程了

  • 9.6.1. 保存的时间周期越长,转换文件格式的必要性就越高,因为软件标准是在不断进化的,应用也会放弃对“古老”格式的支持