1.1. 模式分类
1.1.1. 设计属性
1.1.2. 暴露最少信息
1.1.3. 冗余
1.1.4. 强力执行
1.1.5. 信任与责任
1.1.6. 反模式
1.2. 模式可以缓解或者避免很多种类的风险,它们可以形成一个重要的工具箱,帮我们解决潜在的安全威胁
1.3. 不需要为了解决一个问题就把所有设计模式全都使用一遍
1.3.1. 如果所有模式都可以使用,就从中选择最好的一两种
1.3.2. 对于关键的安全需求也可以多使用几种模式
1.3.3. 过度使用这些模式反而会带来反效果
1.3.4. 增加复杂性和开销所造成的损失很快就会超过额外增加的安全性
2. 设计属性2.1. 极简设计
2.1.1. 保持简单
2.1.2. 设计方案应该尽可能简单
2.1.3. 极简设计提高了安全标准,因为设计方案越是简单,出现错误的可能性就越小,没有检测出来的漏洞自然也就越少
2.1.4. 所有特殊的设计都有可能要求我们有大量备用组件,技术层面的挑战也更艰巨
2.1.5. 将功能分解成更小的、独立的组件,让它们共同执行复杂的操作
2.1.6. 极简设计并没有强制规定所有元素必须永远是最简单的
2.1.7. 简单性给系统带来的巨大优势,强调我们只有在复杂性可以带来巨大价值的情况下才应该拥抱“复杂”
2.1.7.1. 各类Linux平台的权限更简单,也更容易正确地使用
2.1.8. 并不是说越简单的方法就一定越好,或者越复杂的方法就一定存在越多的问题
2.2. 透明设计
2.2.1. 你不应有所隐瞒
2.2.2. 强大的保护方案永远不应该靠隐瞒信息来实现
2.2.2.1. 透明设计(而不是完全透明)
2.2.3. 我们应该把它的强大展现在人们面前,从而起到阻吓攻击者的效果,让攻击者更不容易入侵这个系统
2.2.4. 对于透明设计对应的反模式,知道的人更多,即通过隐匿信息来实现的安全(security by obscurity,隐晦式安全)
2.2.5. 这种模式反对依靠设计中的机密信息来提升系统的安全性
2.2.5.1. 不是说我们必须公开披露设计方案的信息,也不是说隐藏信息有什么不对
2.2.6. 如果把设计方案彻底公开,这个设计方案的安全性就会降低,就说明这个设计方案应该进行改进,而不是依赖那些没有公开的部分来保持系统的安全性
2.2.7. 通过隐秘来实现的安全之所以不靠谱,是因为虽然这种方式也可以暂时让对手知难而退,但这种机制非常脆弱
2.2.8. 应该努力建立起一套稳固的安全机制,确保无论设计方案的具体信息是否公开,这个设计方案都没有什么需要刻意隐瞒的
3. 暴露最少信息3.1. err on the safe side
3.1.1. 这是所有模式组别中模式数量最多的一组,也需要我们格外注意
3.2. 最小权限
3.2.1. 只给予刚好能够完成工作的权限,这就是最安全的做法
3.2.2. 永远不要擦一把上膛的枪
3.2.3. 在给电锯更换刀片的时候一定要拔掉电源
3.2.4. 最小权限模式的案例
3.2.5. 这种模式的目的就是在执行任务时降低犯错造成的风险
3.2.6. 也是重要系统的管理员不应该在工作中随便浏览互联网的原因
3.2.6.1. 任何一位称职的管理员都不会输入sudo再加上命令来意外破坏这个系统
3.2.7. 火警警报控制开关上面那一层写着“在紧急情况下打破玻璃”的玻璃
3.2.7.1. 这层玻璃的存在,没有人可以说自己是无意拉响了火警警报
3.2.8. 哪怕攻击者利用了漏洞,我们也希望他们获取到的权限越小越好
3.2.9. 只有在确有必要的情况下,才应该使用那些拥有全部权限的授权(比如超级用户权限),而且应该把使用这类权限的时间窗口减到最小
3.2.10. 正确地使用权限肯定更加安全
3.2.10.1. 即使漏洞被攻击者利用,也可以分成轻微的入侵和整个系统遭到入侵
3.2.10.2. 采用最小权限也可以减少因软件错误或者人为失误所造成的破坏
3.2.11. 最小权限并不意味着系统永远都应该赋予用户最低程度的授权
3.2.11.1. 需要控制授权机制的粒度,同时控制调整权限所产生的成本
3.2.11.2. 代码应该尽可能在比较低的权限下运行,只有在必要的情况下才在自然决策点转换到比较高的权限
3.3. 最少信息
3.3.1. 任何情况下,我们都应该收集和访问尽可能少的、完成工作必不可少的个人信息
3.3.2. 最少信息模式(相当于数据隐私方面的最小权限模式)可以帮我们把信息泄露的风险降到最低
3.3.2.1. 应该避免提供不必要的个人信息,一有机会就减少不必要的信息流
3.3.3. 很多时候,软件都不满足这种模式,因为随着时间的推进,接口设计就会服务于越来越多的目的
3.3.4. 在实施层面,最少信息设计方案也包括清除本地缓存的信息(在这些信息已经不需要的情况下),只在系统中显示可用数据的一部分信息,并只在用户明确发出请求的情况下才显示信息的详情
3.3.4.1. 显示密码的一般做法就是把密码显示成星号(*),这样可以降低有人在身边窥探密码的风险
3.3.5. 可以让主叫方指定他们需要的信息,同时也只提供他们指定的字段,这就可以把私有信息的数据流减到最低限度
3.4. 默认防御
3.4.1. 软件永远应该是“开箱即用”的安全软件
3.4.2. 在设计软件时,应该坚持默认防御原则(包括其初始状态),这样工作人员不加操作也不会给系统带来威胁
3.4.3. 默认防御模式适用于整个系统的配置,以及组件和API参数的配置可选项
3.4.3.1. 默认防御适用于一切有可能影响安全性的设置或者配置,绝不仅仅是默认密码
3.4.3.2. 权限应该默认采用以最严格的限制方式进行设置
3.4.3.3. 用户在确有需要且绝对安全的情况下才能手动修改以使用限制更少的权限
3.4.3.4. 默认情况下,应该禁用一切有可能带来危险的可选项
3.4.3.5. 应该默认启用所有能够提供安全保护的特性,让这些特性从一开始就能够生效
3.4.3.6. 需要始终保持软件更新到最新的版本
3.4.3.6.1. 不要使用旧版本且很可能包含已知漏洞的软件,然后盼着它未来自己就能更新
3.4.3.7. 在理想情况下,我们永远都不应该使用那些不安全的可选项
3.4.4. 如果我们希望严肃对待系统安全的问题,就永远不要给系统配置一个不安全的状态,并指望日后再去提升它的安全性,这样做不光会制造漏洞,我们事后还常常会把这件事抛诸脑后
3.4.4.1. 如果我们使用的设备有默认的密码,就应该首先在一个安全的、位于防火墙后面的私有网络中配置好这台设备,然后再把它部署到网络当中
3.4.5. 默认防御比配置各个可选项的应用范围要大得多
3.4.5.1. 不指定API参数的默认值是一种比较安全的做法
3.4.5.2. 浏览器应该默认站点使用的是HTTPS,只有在站点无法连接的时候才切换回HTTP
3.4.5.3. 协商建立新的HTTPS连接的两个对等体设备应该默认首先接受更安全的密码套件
3.5. 放行列表与阻塞列表
3.5.1. 在设计安全机制的时候,应该优选放行列表(allowlist)而不是阻塞列表(blocklist)
3.5.2. 放行列表列举的是安全的情形,所以这种列表在本质上是一个有限枚举的列表
3.5.2.1. 放行列表没有进行穷举也不会给有可能造成人们大面积感染的高风险行为打开大门
3.5.2.2. 使用放行列表可以确保我们的安全
3.5.3. 阻塞列表则正好相反,这种列表希望列举出所有不安全的情形,这样做相当于隐含地放行了其他所有我们希望是安全访问的情形,这类被放行的访问在数量上是无限的
3.5.3.1. 阻塞列表的问题在于,所有它忽略的情形都是这类列表的缺陷
3.5.4. 最安全的阻塞列表是那种包含所有限制情形的列表,但这样的列表往往也过于严格,所以无论如何,阻塞列表都很难满足设计要求
3.5.5. 使用放行列表是一种比较简单的逻辑,我们一般不会把这种做法视为一种模式
3.6. 避免可预测性
3.6.1. 任何可以预测的数据或者行为都没有机密性可言,因为攻击者可以通过猜测来学习到这些内容
3.6.2. 数据的可预测性可以导致严重的缺陷,因为这样的系统可能会导致信息泄露
3.6.3. 可预测性还可以给攻击者提供其他机会
3.6.4. 可预测性的问题有很多不同的形式,不同的设计方案也可能会导致不同类型的信息泄露
3.6.5. 采用安全的随机ID
3.6.5.1. 伪随机数生成器或者使用安全随机数生成器
3.6.5.2. 除非我们确定可预测性不会给我们带来损失,否则应该选择后者,即使安全随机数生成器的生成速度会慢一点
3.7. 失效安全
3.7.1. 如果发生了问题,我们至少要确保问题最终能够被妥善解决
3.7.2. 在物理世界中,失效安全本身属于一种常识
3.7.2.1. 物理定律就决定了这种电路不可能长时间维持过量的电流,否则其最终会被烧毁
3.7.3. 错误情形一般很难进行彻底的测试,如果多种错误组合在一起,出现在新的代码路径上,就更难测试出来了,出现错误的地方就是发起攻击的沃土
3.7.4. 很容易把错误处理当成一项可有可无的工作,然后把这项任务抛诸脑后,很多常见漏洞就是这样产生的