读软件开发安全之道:概念、设计与实施03威胁
1. 威胁
1.1. 威胁常常比事件本身更加可怖
- 1.1.1. 索尔·阿林斯基
1.2. 威胁无处不在
-
1.2.1. 如果妥善管理,我们也可以安然与威胁共存
-
1.2.2. 我们自己没有几百万年进化而来的本能来防御软件方面的威胁
1.3. 把视角从软件构建者转向攻击者
-
1.3.1. 理解一个系统的潜在风险是一切的起点
-
1.3.2. 在软件设计中加入可靠的防御和缓解措施
1.4. 软件的本质:它是由一堆代码和组件构成的,这些代码和组件承担着数据处理和存储的职责
1.5. 威胁包含各种可以带来伤害的方式
-
1.5.1. 刻意发动的对抗性攻击行为
-
1.5.2. 软件错误
-
1.5.3. 人为错误
-
1.5.4. 意外事故
-
1.5.5. 硬件故障
1.6. 很难穷举大型软件系统的所有威胁和漏洞
-
1.6.1. 高明的安全工作是逐步提高标准,而不是追求尽善尽美
-
1.6.2. 我们恐怕从来都对那些遭到挫败的攻击一无所知,而缺乏反馈机制往往会让人倍感失望
-
1.6.3. 我们展现出来的安全意识越强,我们就可以看到越多的威胁
1.7. 理解威胁建模可以让我们把视野从安全性上扩展出去,从而重新审视我们的目标系统
2. 对抗性视角
2.1. 人类肇事者才是万恶之源
- 2.1.1. 安全事件不会自己无缘无故地发生
2.2. 只要对软件安全性进行协同分析,就一定要考虑可能的对手会进行什么样的尝试,这样才能预测和防御潜在的攻击
-
2.2.1. 不要自欺欺人地认为自己可以准确地预测对手的一举一动
-
2.2.2. 不要花费大量时间和精力去猜测他们的具体想法
-
2.2.3. 不要自以为是永远比对手棋高一着的名侦探
2.3. 了解攻击者的思考方式的确有其价值,但我们的目标是开发安全软件,所以攻击者具体会通过什么技术手段来探测、渗透和泄露数据无关紧要
2.4. 软件错误绝对是攻击者重点关注的对象,因为它们往往就是软件的弱点所在
-
2.4.1. 攻击者如果偶然发现了某些明显的软件错误,他们就会尝试创建一些变体,看看能不能造成实实在在的破坏
-
2.4.2. 一旦攻击者发现了漏洞,他们就很有可能把更多时间和精力放在这个漏洞上,因为很多瑕疵都可以被攻击者借助协同攻击扩展出严重的后果
2.5. 两项几乎不会引人注意的细微漏洞如果结合在一起,就可以制造出一次重大的攻击,因此我们应该严肃对待一切漏洞
2.6. 攻击一个系统可能获得的收益越高,人们就越有可能应用更多的技术和资源来对这个系统发起攻击
- 2.6.1. 政府、军方、大型企业和金融机构的系统都是重大的攻击目标
2.7. 与一切形式的攻击行为一样,攻击的难度总是小于抵御攻击的难度
-
2.7.1. 攻击者只需要选择好切入点,然后下定决心去利用尽可能多的漏洞就可以了,因为攻击者其实只需要成功一次就够了
-
2.7.2. 防守方需要善加利用所有可用的优势
3. 4个问题
3.1. 我们的工作是什么?
- 3.1.1. 旨在建立项目的背景和范围
3.2. 哪里有可能出错?
- 3.2.1. 旨在尽可能地判断有可能发生的问题
3.3. 我们打算怎么办?
- 3.3.1. 旨在设法缓解我们发现的问题
3.4. 我们干得怎么样?
-
3.4.1. 旨在让我们对整个流程发问
-
3.4.1.1. 软件承担了什么工作
-
3.4.1.2. 软件可能产生什么问题
-
3.4.1.3. 我们在应对这些威胁的时候发挥得如何
4. 威胁建模
4.1. 哪里有可能出错?
- 4.1.1. 在安全领域,这个问题毫无反讽的意味,它简洁地表达了威胁建模的出发点
4.2. 威胁建模的基本流程
-
4.2.1. 从系统模型出发,确保我们对整个系统范畴内的要求全都进行了考量
-
4.2.2. 判断系统中需要加以保护的资产(包括有价值的数据和资源)
-
4.2.3. 逐个组件地搜索系统模型,寻找潜在的威胁,判断攻击面(即攻击的起源地)、信任边界(连接系统可靠组件和不可靠组件的接口)以及不同类型的威胁
-
4.2.4. 按照确定性从高到低的顺序,依次分析这些潜在的威胁
-
4.2.5. 按照严重性从高到低的顺序,依次对威胁进行评分
-
4.2.6. 对最严重的威胁提出降低风险的缓解措施
-
4.2.7. 从效果最好、实施最简单的方法开始,不断增加缓解措施,直至看到实际的效果
-
4.2.8. 从最关键的威胁开始,逐个测试缓解措施的效果
4.3. 在实践当中,第一次威胁建模只应该关注那些针对高价值资产的重大、高风险威胁
4.4. 人们在日常生活中都会根据本能,做一些类似于威胁建模的工作,采取符合一般常识的预防性措施
- 4.4.1. 在别人能听到的地方说话就是攻击面,用无声的输入法代替语言表达就是一种行之有效的缓解措施
4.5. 虽然我们在现实生活中会自然而然地采取这样的行动,但是把这些方法应用到我们本能还不能覆盖的复杂软件系统上则需要制定更多的规则
4.6. 威胁建模会使用数据流图(Data Flow Diagram,DFD)或者统一建模语言(Unified Modeling Language,UML)
- 4.6.1. 更加正式的方法往往更加严格,输出的结果也更加准确
4.7. 比较理想的软件可以把工作中最重要的内容全部自动化
- 4.7.1. 解释输出的结果并且进行风险评估还是需要人工介入
4.8. 按照Goldilocks原则选择合适的粒度
-
4.8.1. 不要掺杂过多的细节(否则工作永远也做不完)
-
4.8.2. 不要太过提纲挈领(否则我们会忽略很多重要的细节)
-
4.8.3. 如果这项工作很快就完成了,但我们还是没有获得针对目标系统的太多信息,这就很可能表示粒度不够
-
4.8.4. 如果我们埋头苦干了几个小时之后,发现距离成果还有十万八千里,就说明粒度有些过高了
5. 判断资产
5.1. 资产是指系统中我们必须加以保护的实体
-
5.1.1. 大多数资产都是数据,但是资产也有可能包含硬件、通信带宽、计算资源和其他物理资源(如电能等)等
-
5.1.2. 威胁建模的初学者一般都希望对所有资产统统加以保护
-
5.1.3. 在实际工作中,我们需要对自己的资产进行优先级排序
-
5.1.4. 应该始终确保内部系统日志是保密的
-
5.1.4.1. 让管理员成为唯一可以读取日志内容的账户
-
5.1.5. 应该保证将有限的精力优先投入到需要加以保护的资产上
5.2. 建议不要试着去做复杂的风险评估计算
5.3. 对资产进行优先级排序其实有一种简单而且非常高效的方式,那就是通过“衬衫尺码”来对它们进行排序
-
5.3.1. 需要进行优先级排序的资产应该包含客户资源、个人信息、企业文档、操作日志和软件代码等,当然这些只是一部分高优先级资产
-
5.3.2. 不同数据泄露、篡改和破坏带来的负面影响也大不相同
5.4. 如果我们把资产集中起来,就可以大幅简化我们的分析工作,不过我们也要注意在这个过程中不能错失重要的信息
5.5. 一定要从各方的角度分别考虑资产的价值
-
5.5.1. 资产中每一项的价值都取决于你在这家公司扮演的角色是CEO、广告人员、客户,还是希望从网络攻击中攫取经济利益或者政治回报的黑客
-
5.5.2. 即使是客户,不同的客户对通信中的数据隐私和数据价值也有不同的理解
-
5.5.3. 优秀的数据管理原则规定,对客户和合作伙伴的数据所提供的保护应该超过对自己企业数据提供的保护
6. 判断攻击面
6.1. 我们应该考虑尽一切可能缩小攻击面,因为这样可以彻底消弭潜在的攻击源
6.2. 很多攻击都有可能在整个系统中散布出去,所以尽早防御这类攻击才是最好的防御方式
- 6.2.1. 安全的政府大楼都会把配备金属探测器的检验装置放在大楼唯一的公共入口处
6.3. 软件设计往往比设计一栋物理大楼要复杂得多,所以判断整个攻击面也殊非易事
6.4. 应该把内部网络看成一个风险比较小的攻击面
6.5. 攻击面不局限于数字世界内部
- 6.5.1. 攻击者甚至可以执行侧信道攻击(side-channel attack),即通过检测系统发出的电磁信号、热量、功耗甚至键盘的声音等信息,来推测系统内部的状态
7. 判断信任边界
7.1. 信任和权限总是相互关联的,所以我们也可以把信任边界理解为权限边界
- 7.1.1. 信任和权限是高度相关的:只要信任度够高,权限往往也就很高,反之亦然
7.2. 如果在人类社会中给信任边界进行类比,那么信任边界可以类比公司经理(了解更多内部信息)和员工之间的信息差,或者你家的大门(你可以选择谁能够进入你的家门)
7.3. 操作系统的内核-用户界面就是信任边界的经典示例
-
7.3.1. 系统会启动内核
-
7.3.2. 内核会把应用隔离在不同的用户处理实例当中
-
7.3.2.1. 不同的用户处理实例对应不同的用户账户
-
7.3.3. 这样就可以避免各个用户相互干扰,或者整个系统彻底崩溃
7.4. 安全外壳(Secure Shell,SSH)守护进程(sshd)是一个理想的、采用了信任边界的安全设计示例
-
7.4.1. 为了能够接收SSH登录请求,守护进程必须为通信创建一条安全的信道(在这条信道中传输的数据不会被嗅探或者篡改),然后才能处理和验证敏感的证书
-
7.4.2. 需要调用大量代码、使用最高的优先级运行程序(这样才能为所有用户账户创建进程)
-
7.4.3. 入站的请求可以来自互联网的任何一个角落,而请求是否是攻击行为一开始根本无从判断,所以我们很难找到更有吸引力的目标
7.5. 在一个操作系统中,超级用户固然应该拥有最高级别的信任,其他管理员用户也可以考虑获得类似级别的权限
- 7.5.1. 访客账户的信任级别一般都是最低的,而且我们可能更需要针对这类用户来保护我们的系统,而不是去保护这类用户的资源
7.6. Web服务器需要有能力抵御恶意客户端用户,因此Web前端系统往往会对入站的流量进行验证,并且只转发那些合法的请求,这就有效地建立起了与互联网之间的信任边界
7.7. 在信任边界的两边,一定要通过定义明确的接口和协议来提供转换和过渡
7.8. 最大的风险往往隐藏在从低信任区域向高信任区域过渡的地方
-
7.8.1. 不代表我们就可以忽视那些从高信任区域向低信任区域过渡的场景
-
7.8.2. 只要我们的系统还在向那些信任度比较低的组件发送数据,我们就应该考虑信息泄露的可能性和出现其他问题的可能性
-
7.8.3. 不要用敏感的信息来命名计算机,这样会让攻击者得到重要的提示,从而有机会在系统上运行代码来对系统实施入侵
-
7.8.4. 只要高信任服务会代表低信任请求来运作,这个系统就有遭到DoS攻击的风险
-
7.8.4.1. 用户请求方可能会通过这种方式让系统内核过载
8. 判断威胁
8.1. 威胁往往会集中在重要资产和信任边界周围,但是也有可能出现在系统的任何一个地方
8.2. 系统的主要威胁会集中于我们的资产和系统的信任边界
8.3. 即使很小的威胁也有可能需要我们采取措施进行缓解
- 8.3.1. 是否需要采取措施取决于这些威胁有多大的可能性会发展成严重的问题,以及它所针对的资产有多高的价值
8.4. STRIDE关注的重点是判断威胁的过程
- 8.4.1. STRIDE只是对软件面临的威胁进行了分类
8.5. STRIDE中有一半的威胁属于对基础信息安全的直接威胁
-
8.5.1. 信息泄露针对的是机密性
-
8.5.2. 篡改针对的是完整性
-
8.5.3. 拒绝服务则旨在破坏信息的可用性
8.6. STRIDE的另一半针对的是黄金标准
-
8.6.1. 欺骗是通过建立虚假的身份来破坏真实性
-
8.6.2. 权限提升破坏的是正确的授权
-
8.6.3. 抵赖则是对审计的威胁
-
8.6.3.1. 避免抵赖这种威胁的方法是让管理员和用户明白,他们都要对自己的行为负责
-
8.6.3.2. 他们都知道系统中有一份准确的审计记录
9. 缓解威胁
9.1. 通过重新设计或者增加防御的方式来缓解威胁,从而减少威胁的发生,或者把威胁造成的伤害降到可以接受的程度
9.2. 如果受到威胁的资产不是必要的信息,就直接把这些信息删除
- 9.2.1. 如果不可能删除这些信息,就努力降低这些信息暴露的可能性,或者对可能增加威胁性的可选特性进行限制
9.3. 把一些责任交给第三方,从而转移风险
9.4. 在充分理解风险之后,接受风险的存在,承认风险的发生自有其合理性
9.5. 部分缓解措施
-
9.5.1. 降低伤害发生的概率:让攻击只可能在一小段时间内发生
-
9.5.2. 降低伤害的严重程度:让攻击只能破坏一小部分数据
-
9.5.3. 设法逆转伤害:确保我们可以从备份文件中轻松恢复所有丢失的数据
-
9.5.4. 设计伤害正在发生的信号:使用防篡改的包装,当产品遭到篡改的时候能够轻松发现篡改已经发生,从而对消费者提供保护
-
9.5.4.1. 在软件领域,良好的日志记录可以起到这样的效果
10. 隐私方面
10.1. 针对隐私的威胁手段与其他安全威胁手段一样真实
-
10.1.1. 在对系统面临的威胁进行完整的评估时,这些针对隐私构成威胁的方式需要进行单独的分析
-
10.1.2. 这类威胁在信息泄露的风险中增加了人为因素
10.2. 我们应该把自己看成个人信息的管家,努力从用户的角度进行考虑,包括认真考虑用户关注的隐私安全问题,并且为了避免用户隐私泄露,怎么关注都不为过
10.3. 随着人们的生活日趋数字化,移动计算设备已经无处不在,隐私也越来越依赖这些代码,未来如何发展殊难预料
- 10.3.1. 最明智的做法是保持高度警惕,并且始终站在时代浪潮的潮头
10.4. 降低隐私威胁的一般解决方案
-
10.4.1. 对真实的用户案例进行建模,然后对隐私进行评估,而不要仅思考抽象的内容
-
10.4.2. 学习所应用的隐私策略或法规,然后严格遵守这些条款
-
10.4.3. 通过限制手段,保证只有在必要的情况下才收集数据
-
10.4.4. 对看起来后果非常严重的威胁保持警惕
-
10.4.5. 不要在没有清晰使用意图的情况下收集或者保存隐私信息
-
10.4.6. 当收集到的信息不需要继续使用的时候,要主动删除这些信息
-
10.4.7. 减少敏感信息的泄露
-
10.4.7.1. 在理想情况下,敏感信息只能分享给需要知道的人
-
10.4.8. 减少与第三方共享的信息
-
10.4.8.1. 如果存在这类信息,应该用文档进行详细的记录
-
10.4.9. 要保持透明,让终端用户理解自己的数据保护措施