严正声明:本文仅限技术讨论,严禁用于其他用途。
简介
Bucket上传策略是一种直接从客户端向Bucket(存储空间)上传数据的便捷方式。通过上传策略中的规则以及与访问某些文件的相关逻辑,我们将展示如何拿到完整的Bucket对象列表,同时能够修改或删除Bucket中的现有文件。
什么是Bucket策略
(如果你早已经知道了什么是Bucket策略和URL签名,那么你完全可以直接跳转到下面的“利用”部分)
Bucket策略是一种将内容直接上传到基于云端的大型存储区(如Google云端存储或AWS S3)的安全方式。我们的想法是创建一个定义有检验是否允许文件上传的策略,随后使用密钥对策略进行签名,并将策略和签名提交给客户端。
然后,客户端可以直接将文件上传到Bucket,Bucket存储会验证上传的内容和策略是否匹配。如果匹配,则上传文件。
上传策略与URL预签名
在开始之前,我们需要明确指出有多种方法可以访问Bucket中的对象。使用POST请求访问Bucket时,POST策略(AWS)和POST对象 (谷歌云存储)方式只允许上传内容。
另一种称为URL预签名(AWS)或URL签名(Google云端存储)的方式就不仅仅是可以修改对象。我们是否可以PUT、DELETE或GET 默认的私有对象,这取决于预签名逻辑定义的HTTP方式。
在定义内容类型(Content-Type)、访问控制和文件上传时,URL预签名与POST策略相比会相对宽松。使用错误的自定义逻辑也会更频繁地执行URL签名,如下所示。
这里有很多允许某人访问上传内容的方法,其中一个是AssumeRoleWithWebIdentity ,类似于POST策略,区别在于你可以获得由预定义的IAM Role(身份和访问管理角色)创建的临时安全凭证(ASIA *)。
如何发现上传策略或URL签名
这是使用POST方法的上传请求,如下所示:
该策略使用的是ba64编码的JSON,如下所示:
{
"expiration":"2018-07-31T13:55:50Z",
"conditions": [
{"bucket": "bucket-name"},
["starts-with", "$key", "acc123"],
{"acl": "public-read"},
{"success_action_redirect":"https://dashboard.example.com/"},
["starts-with", "$Content-Type", ""],
["content-length-range", 0, 524288]
]
}
在AWS S3上的类似于下面的URL签名:
https://bucket-name.s3.amazonaws.com/?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA...
就像谷歌云存储一样:
HTTPS ://storage.googleapis.com/uploads/images/test.png?Expires=1515198382&GoogleAccessId=example%40example.iam.gserviceaccount.com&Signature=dlMA---
上传策略的利用
如果我们想要发现策略中的错误,并对其进行利用,那么我们需要定义一些不同的属性:
l Access = Yes-在上传后,我们是否可以以某种方式访问该文件。在策略中ACL是否被定义为public-read或者能够接收上传文件的URL预签名。在策略中上传但未定义ACL的对象默认为私有。
l Inline=Yes-如果你能够修改content-disposition文件,那么我们可以在内联中提供内容。如果策略中根本没有定义,那么文件则以内联方式提供。
1. starts-with $key是空的
例:
["starts-with", "$key",""]
可以上传文件到Bucket中的任何位置,覆盖任何对象。你可以将key属性设置为任何内容,并且接受该策略。
注意:在某些情况下,这种利用很困难。例如,只有一个Bucket用于上传从未公开的或以后会使用的名为UUID(通用唯一标识符)的对象。在这种情况下,我们不知道要覆盖哪些文件,并且无法知道Bucket中其他对象的名称。
2. starts-with $key不包含路径分隔符或为所有用户都用相同的路径
例:
["starts-with", "$key","acc_1322342m3423"]
如果策略的 $key部分包含一个已定义的部分,但是没有路径分隔符,我们可以将内容直接放在Bucket的根目录中。如果 Access=Yes和 Inline=Yes,并取决于content-type的类型(参见#3和#4),我们则可以通过安装AppCache-manifest来窃取其他用户上传的URL(AppCache中的相关漏洞是由我和 @avlidienbrunn以及@filedescriptor分别发现的)。
如果上传对象的路径对所有用户都是相同的,那这个问题也一样适用。
3. starts-with $Content-Type为空
例:
["starts-with","$Content-Type", ""]
如果Access=Yes 和Inline=Yes,我们就可以在Bucket域上传text/html并提供此服务,如#2所示,我们可以使用它来运行javascript或在此路径上安装AppCache-manifest,这意味着在此路径下访问的所有文件都将泄露给攻击者。
4.使用starts-with $Content-Type定义内容类型
例:
["starts-with","$Content-Type", "image/jpeg"]
这个和#3一样,我们可以添加一些内容来使第一个内容类型成为一个未知的mime类型,随后追加text/html,文件将被认作为text/html类型:
Content-type: image/jpegz;text/html
此外,如果S3-Bucket托管在公司的子域中,通过利用上述策略,我们还可以通过上传HTML文件在域上运行javascript。
最有意思的部分是在沙盒域上通过上传内容来利用网站。
使用自定义逻辑利用URL签名
URL签名是在服务器端签名并提交给客户端,以允许它们上传、修改或访问内容。最常见的问题是网站构建自定义逻辑来检索它们。
首先,要了解怎么利用已签名的URL,重要的是要知道在默认情况下,如何获取Bucket根目录下已签名的且可以显示Bucket的文件列表的GET-URL。这和使用公开列表Bucket的情况基本相同,不同之处在于此Bucket肯定包含其他用户的私有数据。
请记住,当我们知道Bucket中的其它文件时,我们也可以为它们请求URL签名,这就让我们拥有了访问私密文件的权限。
因此,我们目标始终是尝试获根目录或已知的另一个文件。
错误的自定义逻辑的示例
以下是一些示例,其中逻辑通过发出已签名的GET-URL实际暴露了Bucket的根路径。
1.使用get-image这个端点对Bucket进行完全可读访问
有以下要求:
https://freehand.example.com/api/get-image?key=abc&document=xyz
提供以下URL签名:
https://prodapp.s3.amazonaws.com/documents/648475/images/abc?X-Amz-Algorithm=AWS4-HMAC-SHA256...
但是,端点在签名之前对URL进行了规范化,因此通过遍历路径,我们实际上可以指向Bucket的根目录:
https://freehand.example.com/api/get-image?key=../../../&document=xyz
结果:
https://prodapp.s3.amazonaws.com/?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA...
这个URL提供了Bucket中全部文件的列表。
2.因正则表达式解析URL签名请求,导致可完全获取读权限
这是另外一个示例,以下请求是在网站上的端点上获取所需的对象的URL签名:
POST /api/file_service/file_upload_policies/s3_url_signature.jsonHTTP/1.1
Host: sectest.example.com
{"url":"https://example-bucket.s3.amazonaws.com/dir/file.png"}
它会解析URL并将其部分附加到URL签名,你将会得到这个:
{"signedUrl":"https://s3.amazonaws.com/example-bucket/dir/file.png?X-Amz-Algorithm=AWS4-HMAC..."}
可以使用s3.amazonaws.com上的子域和路径访问S3-bucket。在这种情况下,服务器端逻辑规则会将URL更改为基于路径的Bucket URL。
通过欺骗URL extraction,你可以发送如下内容:
{ “url” :“https://.x./example-bucket”}
它会返回一个URL签名,如下所示:
{"signedURL":"https://s3.amazonaws.com//example-beta?X-Amz-Algorithm=AWS4-HMAC..."}
此URL将显示Bucket的完整文件列表。
3.滥用临时的URL签名链接
这个示例来自两年前,是我发现的第一个和URL签名有关的问题。
在网站上,当你上传文件时,你首先在 secure.example.com下创建了一个随机密钥:
POST /api/s3_file/ HTTP/1.1
Host: secure.example.com
{"id":null,"random_key":"abc-123-def-456-ghi-789","s3_key":"/file.jpg","uploader_id":71957,"employee_id":null}
然后,你会返回:
HTTP/1.1 201 CREATED
{"employee_id":null,"s3_key": "/file.jpg", "uploader_id": 71957,"random_key":"abc-123-def-456-ghi-789", "id": null}
这意味着,以下的URL:
然后会重定向到:
Location:https://example.s3.amazonaws.com/file.jpg?Signature=i0YZ...
然后,可以发送以下的s3_key内容:
"random_key":"xx1234","s3_key":"/"
之后,会有以下URL:
重新定向到:
Location:https://example.s3.amazonaws.com/?Signature=i0YZ...
至此,我现在就拥有了他们Bucket的文件列表。该网站使用一个Bucket来存储他们的所有数据,包含他们拥有的每一个文档和文件。当我尝试提取文件列表时,发现Bucket非常庞大,文件数量可以用百万来计算。因此我直接将这个漏洞通报给了该公司,以下是他们的回复:
建议
应根据每个文件上传请求或至少对每个用户生成一个对应的上传策略。
l $key应该有完整的定义:有一个唯一的、随机的名称以及随机的路径。
l 最好将content-disposition定义为attachment。
l acl 应该优先选择 private 或者不要定义。
l content-type应该明确设置(不使用 starts-with)或者不要设置。
另外,永远不要基于用户的请求参数创建URL签名,否则就会出现如上所示的情况。
我见过的最糟的情况是:
你确实给了它你要签名的请求,并且它也回复了你所要求的签名:
0zfAa9zIBlXH76rTitXXXuhEyJI =
这可以用来制作获取URL签名的请求:
curl -H "Authorization: AWSAKIAJAXXPZR2XXX7ZXXX:0zfAa9zIBlXH76rTitXXXuhEyJI=" -H "x-amz-date:Fri, 09 Mar 2018 00:11:28 GMT" https://s3.amazonaws.com/bucket-name/
相同的签名方法不仅仅适用于S3,它使得你能够将你想要的每一个请求签署到AWS-key被允许使用的任何AWS服务。
*参考来源:detectify,Hydralab 编译,转载请注明来自 FreeBuf.COM