译者 | 陈峻
审校 | 重楼
2019 年,美国第一资本投资国际集团(Capital One)曾遭受了数据泄露安全事件,并导致了超过上亿名客户的个人数据被泄露。据调查,此次攻击并未涉及到复杂的社会工程或多种黑客工具的使用。攻击者只是从配置错误的 Web 应用防火墙 (WAF) 开始,逐步访问到了 Amazon S3 的内部实例。那么,其背后的原因是什么呢?IAM 角色设置得过于宽松,没有严格遵守需知(need-to-know)的访问权限,使得攻击者能够将其权限从相对较小的访问点,升级到主要的数据资产处。
无独有偶,2023 年,丰田也披露了一起涉及到客户的个人信息数据库与数据资源泄露的事件。究其根本问题,在限制较少的非生产环境中,IAM 策略授予了过于广泛的访问权限。而这些权限并未得到检查,以至于让敏感的资源被暴露在公众面前。这些宽松的策略背后有着统一的基本逻辑:“他们认为非生产环境的风险较低”。
可见,当数据泄露和那些与权限相关的安全事件不断发生时,其背后往往与没能正确处理访问控制相关联。因此,我们有必要重新审视那些违规行为,并重点考虑过于宽松的身份和访问管理(IAM)策略所带来的后果,即使在那些看似不太可能受到攻击的环境中,也是如此。
阻力最小的途径
业界著名的DevOps 工程师 Mat Duggan曾说:“由于云端的 IAM 系统在设计上相当复杂,因此安全性设置对于大多数用户来说,是一场艰苦的战斗。越来越多的攻击者会利用带有高风险的错误配置,作为最容易实施的攻击手段。而由于设置严格的 IAM 权限既耗时、又具有挑战性,因此云端服务风险环生,权限攻击频繁地发生”。
假设我们在 AWS 中有一个云应用,其中包含了多项需要独特的权限,才能访问到不同的 AWS 资源服务,例如:用于存储的 S3、用于数据库的 DynamoDB 、以及用于消息收发的 SQS。那么,值得推荐的一种方法是:创建针对每一项服务所量身定制的自定义 IAM 角色,并确保个人用户、应用程序或系统仅具有执行其任务所需的基本访问权限,从而通过以下方式降低安全风险:
- 默认最小访问权限:仅为每个用户角色或应用程序授予基本的权限。据此,即便是在非生产环境中,也能避免过于宽泛的访问权限。
- 动态权限:随着角色和要求的变化,定期审查和调整权限,以保持角色仅通过最低、必要的权限访问相应的资源。
- 撤销访问权限:在不再需要访问权限时删除权限,以防止随着时间的推移,出现“权限蠕变(permission creep)”的现象。
可见,在发生数据泄露时,授予最低访问权限的最大好处,便是降低了泄露的严重性。在几乎没有任何权限的情况下,即使攻击者可以在函数层面上,找到某种方法让 Lambda 调用代码,那么执行代码的能力所能造成的损害也是非常有限的,而且还能最大限度地减少违规所带来的影响。
让我们来看看较为复杂的实践层面。由于实施最低权限要求按需提供应用程序的需求规范,以及每个互连资源背后的层次结构和上下文的详细信息,因此开发人员很少能确切地知道每个服务具体需要哪些权限。例如,要在 S3 存储桶上执行读取时,我们还需要列出 S3 存储桶内容的相关权限。
要弄清楚这一切,往往需要反复试验、检查日志、更新角色、以及在每次发生缺少权限错误时,按需进行重新部署。此外,某些服务可能需要间接的权限。例如,如果服务 A 与服务 B 进行交互,那么服务 A 就可能需要访问服务 B 所依赖的资源相关权限。因此,在快速交付的压力下,阻力最小的一种途径便是授予广泛访问权限,并注明会在稍后的某个时候,调整或收紧访问权限。不过,在很多时候,实际情况并非如此。这可能会导致整个“链条”中的权限范围变得更广,从而也就更难以“纯净”地隔离访问了。
仅仅被动是不够的
其实,上面介绍的是一些被动的权限管理理论。在实践中,我们往往需要通过工具来扫描应用中的各种不合理配置。例如,AWS的 IAM 访问分析器和 Google Cloud 的 IAM 推荐器等工具,对于识别存在风险的权限、以及潜在越权行为等方面非常实用。不过,如果我们仅仅依赖此类工具作为主要防线,则可能会产生一种虚假的安全感。
目前,大多数权限检查工具旨在分析某个时间点的权限态势,也就是说,它们通常是在权限已经分配到位后,再进行标记和追溯。这种被动的方法意味着,不合理的配置只有在引起了问题之后才会得到解决,如果扫描的频率不够频繁,那么中间的空档期就留给了攻击足够的时间。
而且,此类工具只标记了那些显著的、过于广泛的权限,但是缺乏了评估更细微的配置、以及确定所需的绝对最低访问级别的上下文。例如,IAM 访问分析器可以标记 S3 存储桶中那些可以被公开访问等问题。虽然将发现的问题标记为待整改的能力无可厚非,但是该工具缺乏了对已授予的权限,是否真的符合应用程序的实际使用需求的深入理解。也就是说,在上述例子中,如果我们能够提供正确的上下文,那么我们有了如下能力:
- 将更具敏感性的操作(如PutObject 或DeleteObject )限制到特定的用户或角色上。
- 限制针对那些与可信源相匹配的特定 IP 段的访问。
- 通过在存储桶策略中设置条件,仅允许在特定时间访问,从而适当地限制了对外暴露的时段。
默认最低权限
根据上述思路,我们可以重新思考实现方式。让我们来看下面两段非常简单的代码,它们都公开了一个 API,其中包含一个用于从 Cloud Storage 存储桶返回预签名 URL的路由。
左图的例子使用的是 FastAPI 框架,而右边的例子使用的是 Nitric 框架。这两个函数是等效的,它们将返回一个预签名的 URL 来下载文件。其主要区别在于 Nitric 示例包含了一个附加声明,指示函数将如何使用存储桶:.allow(“read”)。该声明的意图是在两个资源之间生成关系层级所需的上下文。没有它,处理程序将无权访问到存储桶。
虽然该方法非常简单,但是它代表了我们在访问控制思路上的转变。通过允许开发人员在声明部分直接指定他们对于资源的预期用途,便可以清楚地表明其希望应用能够利用基础架构做些什么。这种与声明的融合简化了上下文的管理,毕竟开发人员无需在脑海中映射整个系统。相反,在部署时,我们已有了每个应用所需权限的准确记录。
更进一步,我们还可以生成JSON,以及可视化的此类关系图。
最后,如果你想查看实际的效果,请参阅 Nitric 的快速入门指南链接--https://nitric.io/docs/get-started/quickstart。该指南将引导你设置项目、创建新的技术栈,并生成基础架构即代码(如 Pulumi 或 Terraform),并在默认情况下为你的应用授予最低权限。