浅谈云上攻防——对象存储服务访问策略评估机制研究

前言
近些年来,越来越多的 IT 产业正在向云原生的开发和部署模式转变,这些模式的转变也带来了一些全新的安全挑战。
对象存储作为云原生的一项重要功能,同样面临着一些列安全挑战。但在对象存储所导致的安全问题中,绝大部分是由于用户使用此功能时错误的配置导致的。据统计,由于缺乏经验或人为错误导致的存储桶错误配置所造成的安全问题占所有云安全漏洞的 16%。
以 2017 美国国防部承包商数据泄露为例:此次数据泄露事件是由于 Booz Allen Hamilton 公司(提供情报与防御顾问服务)在使用亚马逊 S3 服务器存储政府的敏感数据时,使用了错误的配置,从而导致了政府保密信息可被公开访问。经安全研究人员发现,公开访问的 S3 存储桶中包含 47 个文件和文件夹,其中三个文件可供下载,其中包含了大量“绝密”(TOP SECRET)以及“外籍禁阅”(NOFORN)文件。
与此相似的案例有很多,例如 Verizon 数据泄露事件、道琼斯客户数据泄露事件。如何正确的使用以及配置存储桶,成为了云上安全的一个重要环节。
存储桶的访问控制包含多个级别,而每个级别都有其独特的错误配置风险。在本文中,我们将深入探讨什么是存储桶、什么是存储桶 ACL、什么是存储桶 Policy 以及平台是如何处理访问权限,并对错误配置存储桶权限导致的安全问题进行阐述。通过本文的阅读,可以很好的帮助理解存储桶的鉴权方式以及鉴权流程,避免在开发过程中产生由存储桶错误配置导致的安全问题。
首先,我们来看简单的对对象存储的概念进行了解。
对象存储
对象存储是一种存储海量文件的分布式存储服务,用户可通过网络随时存储和查看数据。对象存储使所有用户都能使用具备高扩展性、低成本、可靠和安全的数据存储服务。
对象存储可以通过控制台、API、SDK 和工具等多样化方式简单、快速地接入,实现了海量数据存储和管理。通过对象存储可以进行任意格式文件的上传、下载和管理。
在了解对象存储之后,我们来梳理下 ACL、Policy、存储桶的鉴权方式以及鉴权流程以及使用过程中容易产生的配置错误。
存储桶访问权限(ACL)
访问控制列表(ACL)使用 XML 语言描述,是与资源关联的一个指定被授权者和授予权限的列表,每个存储桶和对象都有与之关联的 ACL,支持向匿名用户或其他主账号授予基本的读写权限。ACL 属性见下表:

表格 1 ACL 属性表
从控制台上来看,存储桶访问权限分为公共权限与用户权限,见下图:

图 1 存储桶访问权限配置项
从上图的选项来看,公共权限和用户权限配置共同组成了存储桶访问权限。公共权限包括私有读写、公有读私有写和公有读写这几个选项可以选择,且为单选:

图 2 公共权限选项
用户权限可以通过添加用户进行配置,通过填写账号 ID 并为其配置数据读取、数据写入、权限读取、权限写入以及完全控制五个选项。

图 3 用户权限选项
除完全控制选项,其他几个选项都可以灵活的搭配;而勾选完全控制选项后,则会将前四个选项一同勾选上。控制台中存储桶公共权限以及用户权限可配置项如下:

表格 2 存储桶访问权限
但是公共权限与用户权限有什么区别与关联呢?二者又是如何作用于访问控制列表(ACL)呢?
这些问题,单从控制台上功能上来看是并不能完全理解的,我们需要通过修改控制台中不同的公共权限与用户权限组合,对比 ACL 中内容的变化来分析控制台上这些配置项的真实作用。
首先我们通过在控制台中勾选的选项来测试一下公共权限是如何作用于 ACL 的。
公共权限
公共权限包括:私有读写、公有读私有写和公有读写,我们将依次测试一下在控制台中勾选后 ACL 中实际的配置情况。
私有读写
只有该存储桶的创建者及有授权的账号才对该存储桶中的对象有读写权限,其他任何人对该存储桶中的对象都没有读写权限。存储桶访问权限默认为私有读写。
我们将公共权限设置为私有读写,见下图:

图 4 设置存储桶私有读写访问权限
通过访问 API 接口,获取此时存储桶 ACL 规则:

如上所示,ACL 描述了存储桶拥有者(Owner)为(用户 UIN:10001xxx),且此用户拥有存储桶的完全控制权限(FULL_CONTROL)。
值得注意的是,此处 XML 中权限配置,并不是因为我们勾选了公共权限配置中的私有读写而来,而是控制台中用户权限里默认配置中当前账号的权限策略,见下图红框处:

图 5 默认配置的当前账号权限策略
因此,在公共权限里勾选私有读写,相当于在 ACL 中不额外写入任何配置内容。
公有读私有写
任何人(包括匿名访问者)都对该存储桶中的对象有读权限,但只有存储桶创建者及有授权的账号才对该存储桶中的对象有写权限。我们将公共权限设置为公有读私有写,见下图:

图 6 配置存储桶公有读私有写访问权限
通过访问 API 接口,获取此时存储桶访问权限(ACL)

从 XML 内容可见,通过勾选公有读私有写,ACL 中新增了如下配置条目:

此条配置授予了 AllUsers 用户组的 READ 的权限,按权限分类来说,属于“匿名用户公有读”权限,示意图如下:

图 7 公有读私有写权限配置示意图
公有读写
任何人(包括匿名访问者)都对该存储桶中的对象有读权限和写权限。

图 8 配置存储桶公有读写访问权限
通过访问 API 接口,获取此时存储桶 ACL。

如上所示,通过勾选公有读写,ACL 中新增了如下配置条目。

与上文的公有读私有写权限相比,新增了 AllUsers 用户组 WRITE 的权限,即“匿名用户公有读写”权限,示意图如下:

图 9 公有读写权限配置示意图
从上述实验结果来看:公共权限配置的选项“私有读写“、”公有读私有写“和公有读写”本质上是在 ACL 中添加 AllUsers 用户组的 READ 与 WRITE 权限。公共权限配置选项的总结如下:
私有读写:不在 ACL 中添加任何额外的权限配置条目
公有读私有写:在 ACL 中添加 AllUsers 用户组 READ 权限项
公有读写:在 ACL 中添加 AllUsers 用户组 READ 权限项、AllUsers 用户组 WRITE 权限项
在分析完公共权限之后,我们来分析一下用户权限。
用户权限
用户权限和公共权限有什么区别呢?其实都是修改 ACL 策略,没有本质的区别,只是公共权限在勾选时,生成 ACL 中 ALLUSers 的三个权限,而通过用户权限配置的,在 ACL 中精准到用户,并且权限策略也扩充为 5 个可选项。

图 10 用户权限配置可选项
我们先保持公共权限的默认设置——私有读写,并在控制台编辑用户权限,添加一个 ID 为 123456 的账号。
数据读取-数据写入
我们为此账号设置数据读取、数据写入的权限,见下图:

图 11 配置用户数据读取写入权限
通过访问 API 接口,获取此时存储桶 ACL。

从 XML 内容可见,在控制台新增一个拥有数据读取、数据写入权限的账号后, ACL 中新增了如下配置:

ACL 中增加了一个 uin 为 123456 的用户的 READ 与 WRITE 权限,示意图如下:

图 12 数据读取写入权限配置示意图
权限读取-权限写入
接下来我们保持公共权限为默认的私有读写不变,并在用户权限处添加一个 ID 为 123456 的账号,我们为此账号设置权限读取、权限写入的权限,见下图:

图 13 配置用户权限读取写入权限
通过访问 API 接口,获取此时存储桶 ACL。

如上所示,在控制台新增一个拥有权限读取、权限写入的账号后, ACL 中新增了如下配置:

ACL 中增加了一个 uin 为 123456 的用户的 READ_ACP 与 WRITE_ACP 权限,此时 123456 用户可以对 ACL 进行读取以及更新操作,示意图如下:

图 14 权限读取写入权限配置示意图
公有读写-数据读取-数据写入
在这环节中,我们将实验一下公共权限与用户权限的关系,我们将公共权限设置为公有读写,并在用户权限处添加一个 ID 为 123456 的账号,我们为此账号设置权限读取、权限写入的权限,见下图:

图 15 配置公有读写-数据读写权限
通过访问 API 接口,获取此时存储桶 ACL

通过对比公共权限章节中公有读写与用户权限章节中数据读取-数据写入部分的内容可以发现, 在控制台中配置的公共权限与用户权限是各自作用于 ACL 中,在 ACL 中并不互相影响,配置的公有读写将会在 ACL 中添加一个 AllUsers 用户组的 WRITE 与 READ 权限,而用户权限中添加的 123456 账号的数据读取、数据写入将在 ACL 中加入了一个 123456 账号的 READ 与 WRITE 权限。
但是细心的读者可能会发现一个有意思的问题,在配置用户权限时,ACL 中默认的 Owner 的 FULL_CONTROL 权限不见了
消失的 Owner 权限
对比一下公共权限章节中私有读写部分的 ACL,我们发现了一个问题,见下图:

图 16 公有权限相同、用户权限不同时 ACL 差异性
虽然我们仅仅是在用户权限处增加了一个新用户,并没有删除也没有办法删除控制台中默认的主账号的完全控制权,但是 ACL 中默认的拥有完全控制权的主账号条目不见了,见上图红框处。
这样会不会导致主账号失去了存储桶的控制权呢?经过测试发现,主账号依然拥有存储桶的完全控制权,这是问什么呢?
通过查阅官方文档,我们发现了答案:

资源的拥有者,即 Owner 始终对资源具备完全控制权,无论 ACL 中是否存在此项。
存储桶策略(Bucket Policy)
在分析完 ACL 之后,我们来看看 Policy。存储桶策略(Bucket Policy)使用 JSON 语言描述,支持向匿名身份或任何 CAM 账户授予对存储桶、存储桶操作、对象或对象操作的权限,在对象存储中存储桶策略可以用于管理该存储桶内的几乎所有操作。Policy 属性见下图:

表格 3 Bucket Policy 属性表
我们可以通过在控制台中添加策略的方式来设置 Policy 权限。

图 17 通过控制台添加 Policy
我们添加一个新策略,该策略允许所有用户对我们的存储桶进行所有操作,见下图:

图 18 添加新策略
通过访问 API 接口,获取权限策略。

可以发现,Policy 中以共有四个主要的属性:Action、Effect、Principal、Resource,分别对应了控制台中填写的操作、效力、用户、资源路径。与 ACL 仅可以配置的用户与权限选项相比,控制的颗粒更细。
接下来,我们添加一个允许账号 ID 为 123456 的账号对 cos-aclxxx/policy_test 路径的读操作。

图 19 配置账号指定资源操作权限
通过访问 API 接口,获取权限策略。

在这个 Policy 中,我们可以看到更细腻的 Action 与 Resource 配置。
对象访问权限
在对象存储中,每一个对象同样存在着可配置的访问权限,默认继承存储桶的 ACL。

图 20 对象访问权限控制台界面
我们将此对象设置为公有读私有写权限,见下图:

图 21 配置对象公有读私有写权限
通过查询 GetObjectAcl API 接口,获取其 ACL。

从 ACL 可见,与存储桶的 ACL 配置项完全一样,只不过这里的 ACL 作用于目标对象而存储桶 ACL 作用于存储桶。
但是对象存储是如何通过 ACL 与 Policy 共同协调控制存储桶权限的呢?
我们接下来看一下对象存储的访问策略评估流程。
访问策略评估机制
在开始介绍对象存储访问策略评估流程之前,我们先介绍一下几个流程中涉及到的重要概念:显示拒绝、显示允许、隐式拒绝以及三者之间的联系:
01 显式拒绝
在用户策略、用户组策略、存储桶 Policy 中针对特定用户有明确的 Deny 策略。
02 显式允许
在用户策略、用户组策略、存储桶 Policy、存储桶 ACL 中通过 grant-\*明确指定特定用户针对特定用户有明确的 Allow 策略。
03 隐式拒绝
在默认情况下(未经配置的情况下),所有请求都被隐式拒绝(deny)。
显示拒绝、显式允许、隐式拒绝之间的关系如下:
如果在用户组策略、用户策略、存储桶策略或者存储桶/对象访问控制列表中存在显式允许时,将覆盖此默认值。任何策略中的显式拒绝将覆盖任何允许。
在计算访问策略时,应取基于身份的策略(用户组策略、用户策略)和基于资源的策略(存储桶策略或者存储桶/对象访问控制列表)中策略条目的并集,根据显示拒绝、显式允许、隐式拒绝之间的关系计算出此时的权限策略。

图 22 存储桶鉴权流程
通过上图,我们可以很清楚的理解存储桶的鉴权流程。
访错误配置导致的安全问题
错误使用公有读写权限
在所有错误配置导致的存储桶安全问题中,最常见的一种便是错误的使用了公有读写权限导致的安全问题。

图 23 配置存储桶公有读写访问权限
通过上文的分析可知,公有读权限可以通过匿名身份直接读取用户存储桶中的数据,存在着严重的安全隐患。
但是有些用户为了避免使用繁杂且细粒度的权限配置,会错误的将其存储桶设置为公有读写,这将导致了其存储桶中的内容被攻击者窃取与篡改。正如本文前言中所描述的 2017 美国国防部承包商数据泄露案例。即便是美国国防部承包商,在使用存储桶进行对象存储时,也会犯下这样的常见错误。
因此,为了保障存储桶安全,建议用户为存储桶配置私有读写权限。
存储桶、对象访问权限差异性问题
存储桶权限与对象权限的差异性,往往会为对象资源来安全性问题。
在实际操作中,为了存储桶的安全起见,存储桶的公共权限往往会被设置为私有读写,这也是存储桶的默认公共权限配置,见下图:

图 24 配置存储桶私有读写权限
存储桶的私有权限表明,只有该存储桶的创建者及有授权的账号才对该存储桶中的对象有读写权限,其他任何人对该存储桶中的对象都没有读写权限。
但是将存储桶的公共权限设置为私有读写可以完全保护存储桶中的中的对象资源不被读取吗?
在我们测试的这个存储桶中,并未设置 Policy 策略,并且存在着一个名为 p2.png 的对象。

图 25 p2.png 对象
而从上文可知,存储桶中的对象也有着其对应的对象权限。
在这里我们将对象 p2.png 的 ACL 权限设置为公有读私有写,见下图:

图 26 为 p2.png 对象配置公有读私有写
通过访问 p2.png 资源 url 可以发现,此时 p2.png 对象可以被访问,见下图:

图 27 成功访问 p2.png 对象
测试表明,当存储桶公共权限设置为私有读写时,当存储桶中的对象公共权限为公有读私有写时,此对象依然是可以被读取的。
实际原理很简单,我们为对象 p2.png 设置了公有读私有写 ACL 策略,此时对象资源 p2.png 的 ACL 如下:

根据上文访问策略评估机制一章可知,对象 p2.png 设置了 AllUsers 用户组的显性允许 READ 权限,因此当匿名用户访问 p2.png 时,即使存储桶设置了私有读写权限,依然可以访问此对象,原理图见下图:

图 28 访问 p2.png 时的鉴权流程
因此,单单依靠存储桶的访问权限,并不能保护其中资源的未授权访问情况。为存储桶中资源配置对应的访问权限,才可以保证对象的安全性。
错误授予的操作 ACL 权限
在 Policy 权限设置中,如果授权用户操作存储桶以及对象 ACL 的权限(GET、PUT)见下图:

图 29 授予用户操作 ACL 权限
即使 Policy 中没有授权该用户读取存储桶、写入存储桶、读取对象、写入对象的权限,这个操作依然是及其危险的,因为该用户可以通过修改存储桶以及对象的 ACL 进行越权。
我们在 coscmd 中配置授权用户的密钥信息后,通过 coscmd list 列出存储桶中内容。

图 30 存储桶 list 操作失败
从返回结果来看,该用户并没有读取存储桶列表的权限
经过测试,用户同样也没有下载 p2.png 对象的权限,见下图:

图 31 下载对象操作失败
但是我们却可以查询存储桶中对象的 ACL,见下图:

图 32 成功查看对象 ACL
由于该用户拥有修改存储桶中对象 ACL 的权限,因此可以通过如下指令授予该用户读取 p2.png 的权限,见下图:

图 33 授予用户 p2.png 读权限
在修改过 p2.png 权限之后,可以顺利的将此对象下载到本地。

图 34 成功下载 p2.png 对象
资源超范围限定
在使用存储桶进行对象读取或写入操作时,如果没有合理的或者错误的在 Policy 中配置用户允许访问的资源路径(resource),则会出现越权访问,导致用户数据被恶意上传覆盖或被其他用户下载等安全问题。
在 Web 应用开发中,经常会发生此类问题。设想以下场景:在一个 Web 应用使用对象存储来存储用户头像,且通过前端直传的方式将用户上传的头像传至存储桶中,并希望在存储桶/avatar/路径中存储桶用户的头像,由于后端开发时为了方便而进行了不规范的存储桶 Policy 配置,在生成用户用以上传头像的临时密钥时直接将此临时密钥允许访问的 resource 指定为 qcs::cos:<Region>:uid/<APPID>:<BucketName-APPID>/avatar/*路径。
这样以来,系统为每个用户所生成的用以上传以及浏览头像的临时密钥虽然不尽相同,但是这个临时密钥都拥有 qcs::cos:<Region>:uid/<APPID>:<BucketName-APPID>/avatar/*路径中的所有资源的读写权限。
这一错误的配置导致了很多严重的安全问题,由于在此场景下,Web 应用程序使用前端直传的方式访问存储桶,因此后台生成的临时密钥将会发送给前台,任意用户通过网络抓包等手段获取到的临时凭据,可参见下图流量中响应包内容。

图 35 从流量中获取临时凭据
在获取了临时密钥之后,攻击者凭借此凭据读写 qcs::cos:<Region>:uid/<APPID>:<BucketName-APPID>/avatar/*路径中的任意对象。
攻击者可以通过此方式覆盖目录中其他用户资源,见下图:

图 36 覆盖其他用户资源
上图攻击者通过 test.txt 文件覆盖了 16.png。当然,攻击者也可以轻易的读取此目录中其他用户的文件。
针对此问题的修复方式如下:可以通过每个用户的用户标识来为每一个用户设置一个独用的路径,例如可以在为用户生成临时密钥时,将 policy 中 resource 指定为 qcs::cos:<Region>:uid/<APPID>:<BucketName-APPID>/avatar/<Username>/*来满足规范要求;此外,resource 字段支持以数组的形式传入多个值。因此,也可以显式指定多个 resource 值来完全限定用户有权限访问的最终资源路径。
写在后面
对象存储服务作为一项重要的云上服务,承担了存储用户数据的重要功能。从上文分析可见,对象存储服务提供了细粒度的访问权限控制功能,以保证用户数据的安全性。
但是由于用户使用对象存储服务时安全意识不足或对访问权限以及访问策略评估机制错误的理解,将会导致数据被非法访问或篡改。这些错误的配置包括用户错误的使用公有读写权限、错误授予操作 ACL 权限、配置资源超过范围限定以及对存储桶权限机制错误理解等,这些错误的配置将会造成严重的安全问题。
因此,深入了解对象存储服务所提供的访问权限以及访问策略评估机制,并始终遵循最小权限原则,将会为存储桶中存储的数据安全构筑立体防护体系的一道坚固的门锁,与此同时,也可以通过检查存储桶日志以及文件时间戳来排查存储桶是否被侵害,确保云上资产的安全。
参考文献
https://blog.lightspin.io/how-to-access-aws-s3-buckets
https://blog.lightspin.io/risks-of-misconfigured-s3-buckets
https://cloud.tencent.com/document/product/436/40265
https://main.qcloudimg.com/raw/document/intl/product/pdf/436_9511_zh.pdf
评论