【漏洞分析】【CTF】Wiz CTF - The Big IAM Challenge: Test Your Cloud Security Skills - Writeup
入口
https://thebigiamchallenge.com/
Challenge 1 - Buckets of Fun
IAM Policy如下:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::thebigiamchallenge-storage-9979f4b/*"
},
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::thebigiamchallenge-storage-9979f4b",
"Condition": {
"StringLike": {
"s3:prefix": "files/*"
}
}
}
]
}
从IAM策略可以看到,允许未授权对S3桶thebigiamchallenge-storage-9979f4b
进行对象遍历,且允许读取对象的内容,因此很简单:
先通过ListBucket方式,找到flag文件所在路径:
然后下载S3桶里的flag文件到本地,然后读取文件得到flag:
Challenge 2 - Google Analytics
IAM Policy如下:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": [
"sqs:SendMessage",
"sqs:ReceiveMessage"
],
"Resource": "arn:aws:sqs:us-east-1:092297851374:wiz-tbic-analytics-sqs-queue-ca7a1b2"
}
]
}
从IAM策略可以看到,允许未授权往sqs消息队列发送消息,也可以未授权从sqs消息队列读取消息。队列名称 wiz-tbic-analytics-sqs-queue-ca7a1b2
可能包含flag。
因此,通过aws命令帮助,结合GPT的帮助,可以执行以下命令来读取指定消息队列的消息:
aws sqs receive-message --queue-url https://sqs.us-east-1/092297851374/wiz-tbic-analytics-sqs-queue-ca7a1b2
返回的消息内容中有一个s3桶的访问url,点击访问,返回了flag:
Challenge 3 - Enable Push Notifications
IAM Policy如下:
{
"Version": "2008-10-17",
"Id": "Statement1",
"Statement": [
{
"Sid": "Statement1",
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": "SNS:Subscribe",
"Resource": "arn:aws:sns:us-east-1:092297851374:TBICWizPushNotifications",
"Condition": {
"StringLike": {
"sns:Endpoint": "*@tbic.wiz.io"
}
}
}
]
}
解读下该IAM Policy,就是说,允许所有AWS用户订阅指定的SNS主题(即TBICWizPushNotifications
),但有个条件,就是仅允许后缀为@tbic.wiz.io
的端点进行订阅。
一开始我被GPT的解读把思维给固定了,GPT的解读是仅允许域名为tbic.wiz.io
的邮箱地址进行订阅。搞得我立马以为只能用邮箱去订阅。利用aws
命令发送订阅请求后,SNS会发送一个确认链接到你的邮箱,可问题是,你并没有@tbic.wiz.io
这样的邮箱,所以你如果指定这样的邮箱去订阅的话,你是无法获得确认链接的,也就解不了这道题。
好在后来想到,订阅时,可能不止支持email去接收。于是问GPT:
既然支持http/https协议,那就好办了,指定--protocol
为http
, --notification-endpoint
为http://<IP>/?test@tbic.wiz.io
,这样就能符合 *@tbic.wiz.io
的条件了:
在自己的服务器上开启监听,接收到消息中包含了确认订阅的链接SubscribeURL:
接着访问该SubscribeURL:
访问确认订阅链接SubscribeURL后,继续在自己的服务器上监听,就可以获取到来自TBICWizPushNotifications
的消息推送,从中便可得到flag:
Challenge 4 - Admin only?
IAM Policy如下:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::thebigiamchallenge-admin-storage-abf1321/*"
},
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::thebigiamchallenge-admin-storage-abf1321",
"Condition": {
"StringLike": {
"s3:prefix": "files/*"
},
"ForAllValues:StringLike": {
"aws:PrincipalArn": "arn:aws:iam::133713371337:user/admin"
}
}
}
]
}
该题目是在前面第一道题的基础上,添加了个安全策略,只有指定的管理员用户才能列出指定S3桶files/
路径下的文件对象。这道题目需要绕过这个策略。
解题方式1:
根据AWS官方文档对IAM Policy中ForAllValues
这个条件限定符的解释可以知道,当aws请求中上下文中没带有限定条件中指定的键或者这个键的值是空时(比如这道题指定的键是aws:PrincipalArn
),ForAllValues
条件限定符也会返回true,即符合限定条件。
而在命令行使用aws
工具时,默认都会带上当前用户为AWS CLI 上所配置的凭证信息。
通过aws help
帮助命令知道可以通过--no-sign-request
来指定aws请求不要带上凭证信息:
加上--no-sign-request
:
知道flag文件的路径后,直接下载即可:
或者直接回显到终端:
解题方式2:
如果用浏览器直接访问S3桶,就是没有带凭据信息的,所以还有一种更简单的方式,就是在url后面加上参数prefix=files/
,如下:
知道flag文件的路径后,拼接url即可查看:
PS:
s3桶的url,可以是:
https://thebigiamchallenge-admin-storage-abf1321.s3.amazonaws.com/
, 或:
https://s3.amazonaws.com/thebigiamchallenge-admin-storage-abf1321/
Challenge 5 - Do I know you?
IAM Policy如下:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"mobileanalytics:PutEvents",
"cognito-sync:*"
],
"Resource": "*"
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::wiz-privatefiles",
"arn:aws:s3:::wiz-privatefiles/*"
]
}
]
}
AWS Cognito是AWS提供的一项用于身份认证和授权相关的服务。
其实一开始加载第五道题的页面时,出现图片加载延迟,然后显示"图片"来自一个S3桶.
打开图片链接一看,一看这个桶名,不就是题目IAM Policy提到的桶么:
而且从这个图片的url来看,是带有签名信息的,所以有可能这个签名信息是通过前端js的aws sdk生成的,于是打开F12,看下,果然跟猜想一样,另外还惊喜的发现,AWS Identity Pool Id硬编码在前端代码里:
用这个身份池ID,通过aws cognito-identity get-id --identity-pool-id <idpid>
,获取身份ID(identity-id):
用得到的identity-id换取Cognito临时凭证:
但是用aws configure
命令在Wiz提供的环境里,总是报错,总之无法输入凭证到上下文.
干脆换个方法,前面提到,题目页面那个AWS Cognito的Logo图片,也是wiz-privatefiles
这个桶里的,而且前端已经写好了现成的js sdk生成包含签名信息的下载url,可以参考使用这段代码,让GPT稍微改造下,只在nodejs控制台跑就行了,生成该桶的根路径的含签名信息的url:
// 安装 AWS SDK: npm install aws-sdk
const AWS = require('aws-sdk');
// 配置 AWS 区域和凭证
AWS.config.update({
region: 'us-east-1',
credentials: new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:b73cb2d2-0d00-4e77-8e80-f99d9c13da3b'
})
});
// 创建 S3 服务对象
const s3 = new AWS.S3();
const listAndSignObjects = async () => {
const params = {
Bucket: 'wiz-privatefiles'
};
try {
const data = await s3.listObjectsV2(params).promise();
const objects = data.Contents;
objects.forEach(obj => {
const urlParams = {
Bucket: 'wiz-privatefiles',
Key: obj.Key,
Expires: 60 * 60 // 1 hour
};
const signedUrl = s3.getSignedUrl('getObject', urlParams);
console.log('File:', obj.Key, 'URL:', signedUrl);
});
} catch (err) {
console.log('Error:', err);
}
};
listAndSignObjects();
代码逻辑是先文件遍历,然后把里面的文件的带签名信息url打印出来,运行结果如下:
访问flag1.txt的url,得到flag:
PS:因为这个身份池ID是跟具体的AWS用户关联的,所以即使你用自己的AWS账号创建了Identity pool,然后用自己的identity pool id,然后换取临时凭证,结果肯定也是没有权限访问的题目IAM策略里的S3桶的。
Challenge 6 - One final push
IAM Policy如下:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "cognito-identity.amazonaws.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": "us-east-1:b73cb2d2-0d00-4e77-8e80-f99d9c13da3b"
}
}
}
]
}
获取identity id:
aws cognito-identity get-id --identity-pool-id
获取OpenID token:
aws cognito-identity get-open-id-token --identity-id
通过OpenID token,换取AWS临时凭证:
aws sts assume-role-with-web-identity
(跟前面一样,Wiz提供的那个环境无法设置AWS凭证)
设置AWS凭证到当前环境变量:
查看当前AWS用户的所有s3桶列表:
最终在wiz-privatefiles-x1000
这个桶里发现了flag文件:
小结
学到了很多,其实云安全是个很有意思的领域,涉及新知识非常多,很有新鲜感.
另外,学习思路是一样的,你的知识面越广,对研究目标越熟悉,你能看到的攻击面越大。
通关后拿了个证书,当个纪念:
https://bigiamchallenge.com/finisher/He5QJi9d