读零信任网络:在不可信网络中构建安全系统13运行时安全
1. 建立分发系统的信任
1.1. 分发是选择交付给下游使用者的工件的过程,构建系统会产生许多工件,其中一部分需要交付给下游使用者
1.2. 工件发布
-
1.2.1. 在不改变工件内容的情况下指定一个工件作为权威发布版本的操作被称为工件发布
-
1.2.2. 工件发布操作自身也需要具备不变性:一旦版本被分配和发布,就不能改变
-
1.2.3. 新工件的生产和发布必须基于更高的版本号
-
1.2.4. 如何在不改变构建工件的情况下添加版本信息呢?
-
1.2.4.1. 比较初级的方法是在发布过程中巧妙地改变工件,比如,将版本号存储在构建工件中一个可简单修改的位置,但不提倡采用这种方法
-
1.2.4.2. 另一种方法是,发布工程师明确区分公开发布的版本号和构建编号,将构建编号作为发布信息的额外组成部分
> 1.2.4.2.1. 产生的构建工件具有相同的发行版本号,但每个构建都有唯一对应的构建编号
-
1.2.5. 工件发布应确保其可验证性,需要提供机制确保使用者能够验证其获取的工件是正式发布版本而不是过渡版本,甚至是有缺陷的版本
-
1.2.5.1. 使用发布专用密钥为发布工件签名,向使用者证明他们拥有的版本是正式发布版
-
1.2.5.2. 发布一个签名的清单,列出发布的版本和对应的加密散列
> 1.2.5.2.1. 许多流行的包分发系统(如APT)采用这种方法来验证从分发系统获得的版本
1.3. 分发安全
-
1.3.1. 软件分发与电力输送类似,由一个集中的源头产生电力,经由配电网络传送给广大的使用者群体
-
1.3.2. 与电力输送不同的是,分发系统必须保护软件的完整性,并且允许使用者独立地验证软件完整性
-
1.3.3. 许多广泛应用的包分发和管理系统,都在分发过程中实现了必要的保护,并允许使用者验证包的真实性
1.4. 完整性和真实性
-
1.4.1. 在软件分发系统中,主要采用散列和签名两种主要机制来维护完整性和真实性
-
1.4.2. 通过验证散列值,使用者可以验证二进制文件在离开开发人员后是否被篡改
-
1.4.3. 需要设计者采用私钥加密发布版本的散列值,使用者通过解密可验证软件是否由权威机构发布
-
1.4.4. APT存储库包含3种类型的文件
-
1.4.4.1. 发布文件
> 1.4.4.1.1. 发布文件的用途正是防止散列修改
> 1.4.4.1.2. 包含关于版本库的元数据
> 1.4.4.1.2.1. 包括版本库名称和其使用的分发操作系统版本
> 1.4.4.1.3. 还包括包文件的校验和,这样使用者可先验证索引的完整性,随后验证下载包的完整性
> 1.4.4.1.4. 还必须引入加密签名
> 1.4.4.1.4.1. 签名机制不仅为已签名文件提供完整性保证(散列包含在签名中),同时还提供了真实性保证,这是由于对签名的成功解密可以证明生成方确实持有私钥
> 1.4.4.1.4.2. 软件版本库的维护人员使用私钥对发布文件进行签名,其中有一个与私钥相对应的、公开分布的公钥
> 1.4.4.1.4.3. 如果不能对软件发布进行签名,那么就必须回到标准的安全实践中
1.4.4.1.4.3.1. 需要保证所有的通信都是双向验证的,这意味着分发系统存储库间的出、入以及相互通信的流量都要双向验证
1.4.4.1.4.3.2. 需要确保存储库使用的存储服务是足够安全的
- 1.4.4.2. 包文件
> 1.4.4.2.1. 包文件是存储库中所有包的索引
> 1.4.4.2.2. 包文件中存储了库中包含的每个包的元数据索引
- 1.4.4.3. 包本身
1.5. 建立分发网络的信任
-
1.5.1. 当为大量的或地理位置分散的消费群体分发软件时,通常将软件复制到多个位置或存储库,以满足伸缩性、可用性或性能上的挑战,这些副本被称为镜像
-
1.5.2. 软件作者需要对软件版本库进行验证
-
1.5.3. 对于访问的每一个镜像都可以检查发布签名,以验证镜像是原始版本的真实副本
-
1.5.3.1. 可能被强制降级到较老的(已签名)版本,因为镜像服务器上的工件仍然是合法的
-
1.5.4. 为了保护客户端,要确保其总是连接到一个可信的发布镜像
-
1.5.5. 软件分发的另一个弱点是缺乏受TLS保护的存储库
-
1.5.5.1. 解决这个问题的一种方法是使用TLS保护包分发网络
-
1.5.5.2. 通过添加TLS,客户端可以确保其连接的镜像服务可信,并且通信不会被篡改
2. 人工参与
2.1. 在确保构建了安全的流水线之后,就可以仔细斟酌人工应在什么位置参与
2.2. 通过将人工参与限制在几个关键点,即可确保在发布流水线安全的情况下攻击者不能利用流水线的自动化功能交付恶意软件
2.3. 将代码提交到版本控制系统是一个明确需要人工参与的关键点
- 2.3.1. 根据项目敏感性,要求审核人员只对已签名的提交进行检查以确信提交的真实性
2.4. 选择最终发布工件的过程最好有人工参与,可以通过多种机制实现
- 2.4.1. 只要机制是安全的,如何人工验证可发布的二进制文件的机制就没有那么重要了
2.5. 建立安全系统并采取措施来避免任何可能的威胁,这确实很有诱惑力,但应该平衡潜在的风险和人工负担
-
2.5.1. 在软件发布量很大的情况下,应该妥善保管签名密钥,因为轮转一个泄露的密钥代价极大
-
2.5.2. 对于仅内部使用的软件,轮转一个密钥的工作量相对较少,适当放宽安全操作是合理的
2.6. 使用“代码签名仪式”,签名密钥被存储在硬件安全模块(HSM)中,并且有多个参与方的授权才能解锁私钥,这种机制可以减少此高敏感密钥的盗窃风险
2.7. 在安全系统中人的注意力是稀缺但重要的资源,随着软件发布率的不断提高,必须仔细斟酌人员参与的最好时机
3. 信任实例
3.1. 在设计零信任网络时,了解基础设施中运行的内容很重要
3.2. 单向升级策略
-
3.2.1. 软件版本号是判断其版本和评估其新旧的重要因素。针对正在运行的软件版本,可以根据版本号判断软件可能包含哪些漏洞
-
3.2.2. 诱导用户进行版本降级,可以将软件暴露于一个已知的漏洞,这种攻击很有效,因为被降级的软件仍然是被授权和信任的,只是版本较老
-
3.2.3. 如果软件是为内部分发而构建的,那么分发系统可能只提供最新版本
-
3.2.3.1. 这样可以防止被破坏或错误配置的系统降级回滚到一个可能包含已知漏洞的旧版本
-
3.2.4. 苹果iOS使用硬件安全芯片来验证软件更新,并确保只有在当前安装的软件加载之后才能构建签名软件
3.3. 授权实例
-
3.3.1. 相对于简单地理解已经部署的最新版本,知道正在运行的是什么版本更为重要
-
3.3.2. 运行实例必须单独授权
-
3.3.3. 选择以应用为中心的方式实现对运行实例的授权——应用秘密
-
3.3.4. 大多数运行的应用程序都需要某种秘密才能完成工作
-
3.3.4.1. API密钥、X.509证书,甚至是消息队列的凭证
-
3.3.5. 应用程序必须获取秘密才能运行,并且这个秘密必须有效
-
3.3.5.1. 一个秘密的有效性(显而易见)是为运行中的应用程序实例授权的关键
-
3.3.6. 为应用秘密附加一个生命周期,对于限制其滥用非常有效
-
3.3.6.1. 允许秘密过期机制,通过确保它们不会无限期地运行,从而减轻了“捣蛋”实例的影响
-
3.3.7. 必须有某个系统负责在运行时生成和注入这些秘密,这是一项重要的职责
-
3.3.7.1. 承载这个职责的系统最终也是对实例进行授权
-
3.3.7.2. 将这一职责赋予软件部署系统合情合理,因为它已经承担了类似的职责
-
3.3.8. 与其让部署系统直接访问秘密,不如利用一个受信任的第三方,允许部署系统分配策略决定了运行实例可以访问哪些秘密
-
3.3.8.1. 部署服务通知秘密管理服务即将发生的变更,并授权新的应用程序实例
-
3.3.9. 那些能够创造(潜在的)和检索秘密的系统的权力很大
-
3.3.9.1. 权力越大,责任越大
-
3.3.9.2. 允许一个自治系统产生和分发秘密对组织来说风险太大,在这一环节中可以考虑人员参与
-
3.3.9.3. 可以在部署环节引入人工审批,需提供TOTP或其他身份验证码,并将其用于授权部署系统对秘密进行创建/检索
3.4. 对秘密进行管理通常是一项艰巨的任务,因为更改凭证通常是非常烦琐的
- 3.4.1. 通过平滑的凭证开通流程能够提供频繁轮换凭证的机会,凭证管理流程可以确保只有授权的应用才能在生产环境中继续运行
4. 运行时安全
4.1. 一旦应用程序被构建,在生产环境中确保它们持续执行的过程就会发生变化
- 4.1.1. 随着漏洞被发现,可信的应用程序可能在将来会变得不可信
4.2. 需确保应用程序实例的全生命周期安全、可靠地运行
4.3. 应用程序在整个生命周期中是否始终是经过授权的、值得信任的部署还是不得而知
4.4. 许多攻击向量都可以破坏已经授权的应用程序实例,并且这些攻击向量被大量利用
4.5. 安全编码实践
-
4.5.1. 学习安全的编码实践,在隔离的环境中部署应用程序,然后主动监视它们,这是值得信赖的生产环境的最后一环
-
4.5.2. 大多数(甚至是所有的)应用级漏洞都是从一个潜在的“缺陷”(Bug)开始的,攻击者可以利用Bug来控制受信任的应用程序进行不当操作
-
4.5.3. 要真正降低这种风险,需要应用开发人员的思维模式发生转变,以确保编码实践的安全
-
4.5.4. 应用程序在处理之前没有对用户输入数据进行适当的验证
-
4.5.5. 通过几个层面的防御机制来应对,应用程序库将精心构造API,不会轻易信任用户提交的数据
-
4.5.6. 清晰的API还可以支持应用软件的自动扫描
-
4.5.6.1. 应用程序逻辑也可以被跟踪分析,识别出那些遗失的必要检查项
-
4.5.7. 主动识别已知漏洞是很有用的,但是一些漏洞隐藏过深,难以准确地检测到,因此,另一种可采用的技术是模糊测试
-
4.5.7.1. 将随机数据发送到运行的应用程序,以检测意外的错误,这些错误一旦暴露,往往就会被攻击者利用,使其在系统中获得立足点
-
4.5.7.2. 模糊测试可以作为功能测试套件的一部分执行,甚至可以持续地对线上基础设施进行此类测试
-
4.5.8. 程序员应该熟悉这些安全实践,以便提高应用程序的安全性
-
4.5.9. 让安全咨询团队来检查他们的应用程序和开发实践,以识别此类问题
4.6. 隔离
-
4.6.1. 在零信任网络中,通过限制可访问的资源集来隔离已部署的应用程序很重要
-
4.6.1.1. 传统上,应用程序是在共享环境中执行的,用户的应用程序在执行环境中运行时,对其如何交互的限制很少
-
4.6.1.2. 如果应用程序遭到破坏,则共享的环境会产生大量风险,这类挑战和基于边界防护模型的安全挑战类似
-
4.6.2. 隔离将限制操作系统提供的功能和资源
-
4.6.2.1. CPU时间(CPU Time)
-
4.6.2.2. 存储访问(Memory Access)
-
4.6.2.3. 网络访问(Network Access)
-
4.6.2.4. 文件系统访问(FileSystem Access)
-
4.6.2.5. 系统调用(System Call)
-
4.6.3. 在最好的情况下,每个应用程序都获得了完成其工作所必需的最小访问权限,一个受到良好约束的应用即便被攻破,也无法在整个系统中造成更大的破坏
-
4.6.3.1. 通过隔离可以大大降低受损应用程序的潜在损害
-
4.6.3.2. 攻击者横向移动的企图难以得逞
-
4.6.4. 不同的技术
-
4.6.4.1. SELinux、AppArmor
-
4.6.4.2. BSD Jail
-
4.6.4.3. 虚拟化/容器化
-
4.6.4.4. 苹果公司的应用沙箱
-
4.6.4.5. Windows的隔离应用
-
4.6.5. 隔离通常可以分为两种类型
-
4.6.5.1. 虚拟化
> 4.6.5.1.1. 虚拟化通常被认为更安全,因为应用程序运行于虚拟硬件环境中,此环境通过VM执行环境之外的虚拟机管理程序(Hypervisor)来提供服务
> 4.6.5.1.2. 在虚拟机管理程序和虚拟机之间有一个清晰的边界,其交互界面足够小
> 4.6.5.1.3. 通过提供对底层硬件的直接访问能力,虚拟化环境的安全特性正逐步接近共享内核环境
- 4.6.5.2. 共享内核环境
> 4.6.5.2.1. 共享内核环境(类似于容器化或应用程序策略系统)同样提供了一些隔离保障,但它与完全虚拟化的系统不一样
> 4.6.5.2.2. 共享内核执行环境使用更少的资源来运行同一组应用程序,因此其在注重成本的组织中获得了青睐
4.7. 主动监控
-
4.7.1. 与所有生产系统一样,详细的监测和日志记录是至关重要的
-
4.7.2. 传统的安全模型将注意力集中在外部攻击向量上,零信任网络鼓励对内部活动保证同样的警觉性
-
4.7.3. 尽早检测出攻击可以采取防护措施,而不至于系统被完全攻破
-
4.7.4. 可以对整个基础设施安全事件的常规日志记录进行监控
-
4.7.5. 在发布流水线的早期或许并无足够的时间和耐心让模糊扫描发挥效力,但主动监控策略主张系统上线后也需要持续对其进行扫描
-
4.7.6. 如果安全扫描会破坏系统的稳定,那么可能意味着一个更大的潜在问题,甚至是一个漏洞本身
-
4.7.6.1. 不要刻意规避在生产环境中进行扫描的潜在的“风险”,而应思考风险从何而来,并努力排除导致这些风险产生的因素,从而确保扫描能够安全地运行
-
4.7.6.2. 自动扫描可以成为确保系统行为一致的有效工具
-
4.7.6.3. 并不是所有的扫描都能产生如此清晰的行动建议,比如对已安装的软件进行扫描,识别网络所面临(或可能面临)的威胁,并据此决定软件升级的优先级
-
4.7.7. 扫描器
-
4.7.7.1. 模糊测试(如AFL-Fuzz)
-
4.7.7.2. 注入扫描(如SQLMap)
-
4.7.7.3. 网络端口扫描(如Nmap)
-
4.7.7.4. 通用漏洞扫描(如Nessus)
-
4.7.8. 一般来说,可疑的(但不关键的)事件仅记录在案,并定期对其进行审查,这种做法是无效的,因为它可能导致报告疲劳,而在很长一段时间内被忽略
-
4.7.8.1. 对于重要的事件可以让相关人员进行积极的调查,这些事件有足够强烈的信号来唤醒一个人,在大多数情况下,这是一个强大的防线
4.8. 应用程序相互监视
-
4.8.1. 在应用程序安全监控领域,一种颇为创新的观点是,参与单个集群或服务的应用程序可以积极地监控其他应用的健康状况,并与他人达成共识
-
4.8.2. 通过允许应用程序相互监视,将获得高信噪比的检测结果,同时在整个基础设施中分担责任
-
4.8.2.1. 这种方法有效地防止了侧信道攻击或者通过多租户启用的攻击,因为这些向量不太可能在整个集群中共
-
4.8.3. “某些东西是错误的”信号可以触发基础设施中的自动操作,这可能意味着撤销属于可疑实例的密钥,将其踢出集群,甚至向数据中心管理软件发出通报,由其将此实例下线并隔离,以便后续取证
-
4.8.4. 当使用主动响应时,可能会快速造成很大的损害
-
4.8.4.1. 引入拒绝服务攻击,或者更有可能的是,由于操作员的失误而关闭了整个服务
-
4.8.4.2. 在设计主动响应系统时,要放置一些容错机制
> 4.8.4.2.1. 如果集群的节点数处于危险的低水平,那么不应该再触发将主机踢出集群的主动响应