Over the weekend I spent time working on the Cloud Village CTF put on this year at Defcon 33. While you needed to be present physically to be eligible for any prizing if you were to place in the top 3, the challenges (outside of one) were available to complete online. The below are the challenges I managed to complete myself in the 48-hour window they were open. My team managed to place 19th out of the 312 registered teams.

HEX-FORM
You are a threat intelligence analyst who has intercepted a .zip archive containing Terraform configurations for "Hexform"—a covert C2 platform.
Your mission is to locate and reconstruct a 7-part deactivation key without ever running terraform plan or terraform apply.
The key is hidden in seven fragments across these blueprints using obfuscation, logic traps, and indirect references. Each part must be found in order and assembled like a digital archeologist would reassemble an ancient cipher.
Good luck, Agent.
Key Format: FLAG-{P1P2P3P4P5P6P7}
This challenge provides you with an archive named hex-form-assets.zip
, which when unzipped has the following structure within:
└── assets
├── data
│ └── geo_nodes.json
├── locals.tf
├── main.tf
├── modules
│ ├── 1_network
│ │ ├── main.tf
│ │ └── variables.tf
│ ├── 2_identity
│ │ ├── main.tf
│ │ └── outputs.tf
│ ├── 3_storage
│ │ ├── locals.tf
│ │ └── main.tf
│ └── 4_decoy_compute
│ ├── main.tf
│ └── variables.tf
└── variables.tf
Folder structure of provided files
We can see that the main Terraform configuration files are in /assets
, and the modules are in the aptly-named /modules
folder. The challenge description leads us to believe that we need to look through the files provided, look for out-of-place string fragments within, and combine them together in order to create the flag.
Looking within each of the files, they are not too terrible on their own and this challenge appears to be finding out how the flags are hidden, not necessarily complex string and pattern matching. Additionally we are provided within the document which fragment number is associated with each string, simplifying the eventual concatenation.

I'm not going to bore with the process for finding each flag, but will give the fragments, their location, and a brief summary of what it took to extract.
- P1 =
he1ios
File:assets/modules/1_network/variables.tf
(after#P1
)
Hidden via zero‑width characters (U+2062/2063/200B/200C). Decoding INVISIBLE TIMES/SEPARATOR to bits and reading as UTF‑16 yieldshe1ios
(leetspeak “helios”). - P2 =
I5
File:assets/modules/2_identity/main.tf
⇒ concatenates to I5.
labels.obfuscated = join("", ["I","5"]) //P2 - P3 =
A53cre7
File:assets/modules/3_storage/locals.tf
is base64 of gzipped data.
gammabase64decode(gamma)
-> gunzip -> JSON{"key":"A53cre7"}
⇒ A53cre7. - P4 =
Msg
Files:assets/main.tf
+assets/data/geo_nodes.json
and
selection_map = { lower(k) => v } //P4primary_node = "eu-west-1"
->node_password = selection_map["eu-west-1"]
= Msg. - P5 =
FlagF0r
File:assets/modules/4_decoy_compute/main.tf
with
legacy = "zz-FlagF0r-FROM-zz"slice = substr(legacy, 3, 7)
-> FlagF0r. - P6 =
Ter4
File:assets/locals.tf
and
final_segment = "${var.env_mode == "prod" ? (true ? "Ter4" : "DEBUG") : "DEV"}" // P6env_mode
default is"prod"
-> Ter4. - P7 =
Pzl
File:assets/modules/2_identity/outputs.tf
-> Pzl (left as-is; no further transform indicated in-repo).
output "clue_final" { value = "Pzl" // P7 }
Combining the fragments in the order provided in the description gives us the correct flag: FLAG-{he1iosI5A53cre7MsgFlagF0rTer4Pzl}
BruteForceLogin
You are Agent Shadow, a cybersecurity operative working for the Digital Defense Initiative (DDI). Intelligence has revealed that HexNova, a notorious threat actor known for their sophisticated cyber-espionage campaigns, has compromised multiple Azure environments under the codename "Shadow Protocol."
HexNova's modus operandi involves creating seemingly legitimate cloud infrastructure that serves as a front for their intelligence-gathering operations. They've hidden critical data within their Azure services, but accessing it requires navigating through layers of security and exploiting their own misconfigurations.
Your mission is clear: infiltrate their cloud fortress, bypass their security measures, and extract the classified intelligence hidden within their misconfigured services. The fate of countless organizations depends on your success.
The challenge provides you with a link to a webpage that looks like a simple login page.

Nothing super interesting I could find when looking in the source, but when you click View Intelligence Data button it downloads a 3mb~ image that looks noteworthy.

Running zsteg
on the image provides the following information:

zsteg bfl-data.png
Okay so we have a username for the above login page and a hint for the password. I ended up searching through the rockyou wordlist for anything matching astronaut and there was a few passwords to attempt on the login page, but the credentials ended up being simply agent007:astronaut
.

We are brought to a dashboard after authenticating. We can see on the right (after expanding it) that there are some Azure CLI commands that it wants us to look for. Additionally there is a hint to check specifically the function app configuration. At this point the next logical step is to look through this dashboard for azure authentication credentials. Nothing else on this website is clickable, but when checking the console for the website in developer mode in the browser we find their hints:

After this I look within the localStorage in my browser and there are Azure credentials stored in a key-value pair.

From here we need to authenticate in Azure via CLI:
APPID="VALUE"
SECRET="VALUE"
TENANT="VALUE"
SUBS="VALUE"
RG="BruteForceLogin"
FUNC="bflportal"
Load info in env variables
az login --service-principal -u "$APPID" -p "$SECRET" --tenant "$TENANT"
Authenticate to Azure
RG="BruteForceLogin"
SITE="bflportal"
RESID="/subscriptions/$SUBS/resourceGroups/$RG/providers/Microsoft.Web/sites/$SITE"
Find the function app
az rest --method POST \
--uri "https://management.azure.com$RESID/config/appsettings/list?api-version=2023-01-01" \
--query "properties" -o json | tee appsettings.json
Request the function app properties

After that we have our flag in the field at the bottom: FLAG-{m4st3r0f8ruteforceD5D7DC5E86cc00}
Objective: MCP
You're lurking in the shadows, as Hex Nova, running the daily-crawls, and you find something interesting, a server endpoint. This endpoint is part of Development environment, you wished it was staging, well who knows.

To begin was have a Claude Desktop Configuration snippet, I've never used Claude Desktop so this was a lot of documentation digging for me. I started with the most obvious step – downloading the application and finding where I needed to plug this JSON in. I found my claude_desktop_config.json
file and inserted the above in it, but this resulted in an error in my Claude Desktop application that made me explore just sending requests as curl
.

After some fiddling we can initialize the MCP connection using curl
:
curl -i -u ctfuser:ctfpass123 \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'X-MCP-Client: ctf' \
-H 'MCP-Version: 2024-11-05' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","clientInfo":{"name":"ctf","version":"1.0"},"capabilities":{}}}' \
http://13.91.85.21:8080/mcp
Authenticating to the MCP

Now that we're authenticated and the azure_identity_status
is showing us validated, we want to enumerate what tools or other secrets are present. We can see in the above response we have capabilities resources
and tools
. With a bit of fiddling, I find the correct syntax to request the tooling list:
curl -s -u ctfuser:ctfpass123 \
-H 'Content-Type: application/json' -H 'Accept: application/json' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
http://13.91.85.21:8080/mcp | jq .
Enumerating Tools
We receive a JSON response with some details, JSONPathing ($[].tools[].name
) it down for a clearer answer we have the following tools available:
[
"get_secret",
"list_secrets",
"test_vault_access",
"discover_resources",
"get_secret_from_vault",
"get_command_history"
]
Lets start by exploring discover_resources
:
curl -s -u ctfuser:ctfpass123 \
-H 'Content-Type: application/json' -H 'Accept: application/json' \
-d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"discover_resources"}}' \
http://13.91.85.21:8080/mcp | jq .
Call to explore discover_resources

discover_resources
responseWe have a keyvault available to us, I'm interested in trying the test_vault_access
method to see if we can get access.
curl -s -u ctfuser:ctfpass123 -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"test_vault_access","arguments":{"vault_url":"https://kv-ctf-prod-uqrl85n4.vault.azure.net/"}}}' \
http://13.91.85.21:8080/mcp | jq .
Passing the vault_url
to the server gives us a response we were hoping for:

With this, we can submit the secrets in the response above to the get_secret_from_vault
method we identified earlier:
curl -s -u ctfuser:ctfpass123 \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":21,"method":"tools/call","params":{"name":"get_secret_from_vault","arguments":{"vault_url":"https://kv-ctf-prod-uqrl85n4.vault.azure.net/","secret_name":"ctf-flag"}}}' \
http://13.91.85.21:8080/mcp | jq .

This gives us our flag of FLAG-{OwldcqhaOtCFcpS0u4khfg23hassw90q}
for the challenge.
Dumpster Dive Layer 0
HEX-NOVA developer is a highly advanced threat actor with access to modern CPU architectures. Deep dive into containers and identify the hidden secret.

We are given a container in an Elastic Container Registry within AWS to begin this challenge. We start by pulling the manifest for the container with the following command:
docker manifest inspect public.ecr.aws/r7v3d6d9/ctf-dumpsterdive-layer0:latest
Returning the following JSON:
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.index.v1+json",
"manifests": [
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 1232,
"digest": "sha256:8461b4b7dd54dc11a3ccc3cad385a3c78e38ff6deffe8bb6ceefbc2ef6185215",
"platform": {
"architecture": "ppc64le",
"os": "linux"
}
},
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 566,
"digest": "sha256:80523639205c1780e686e5c63bc3f20fa5f0ce7569a21c0ad0511bfdef1ab3fc",
"platform": {
"architecture": "unknown",
"os": "unknown"
}
}
]
}
The important part for our investigation is the platform architecture of the container, linux/ppc64le
. This needs to be specified to actually pull the container image:
docker pull --platform linux/ppc64le public.ecr.aws/r7v3d6d9/ctf-dumpsterdive-layer0:latest
From here I wanted to unpack the container's layers with some tarball creation and a bunch of enumeration, but what I was finding was a bunch of junk. After spending a bit of time on this I went to the Docker desktop application and inspected the layers of the container to see if I could find anything interesting.

From looking here, it looks like the flag is built around layer 4 and then deleted around layer 8. If we can build the container up until that point then grep for the flag format we should be able to finish the challenge. Now when I was working on this I was not aware of Dive, which can be used exactly for this purpose. I instead asked ChatGPT to build me a bash script to do exactly that and spit out possible flag matches with the following:
bash <<'BASH'
set -euo pipefail
IMG="public.ecr.aws/r7v3d6d9/ctf-dumpsterdive-layer0:latest"
TMP="$(mktemp -d)"
ROOT="$TMP/rootfs"; mkdir -p "$ROOT"
MANIFEST="$TMP/manifest.json"
LAYERS_TXT="$TMP/layers.txt"
echo "[*] Pulling image (linux/ppc64le)…"
docker pull --platform linux/ppc64le "$IMG" >/dev/null
echo "[*] Saving and unpacking image…"
docker save -o "$TMP/img.tar" "$IMG"
tar -xf "$TMP/img.tar" -C "$TMP"
# --- Get ordered layer list into a file (jq if present, else python3) ---
if command -v jq >/dev/null 2>&1; then
jq -r '.[0].Layers[]' "$MANIFEST" > "$LAYERS_TXT"
else
python3 - <<'PY' "$MANIFEST" > "$LAYERS_TXT"
import json,sys
m=json.load(open(sys.argv[1]))
for L in m[0]["Layers"]:
print(L)
PY
fi
# --- Find add/delete indices for /tmp/flag.txt ---
ADD_IDX=-1
DEL_IDX=-1
i=0
while IFS= read -r REL; do
L="$TMP/$REL"
if tar -tf "$L" | grep -qx 'tmp/flag.txt'; then ADD_IDX="$i"; fi
if tar -tf "$L" | grep -qx 'tmp/.wh.flag.txt'; then DEL_IDX="$i"; fi
i=$((i+1))
done < "$LAYERS_TXT"
if [ "$ADD_IDX" -lt 0 ]; then
echo "[!] Could not find tmp/flag.txt in any layer." >&2
exit 1
fi
# --- Determine end index (layer before deletion, or last layer if never deleted) ---
if [ "$DEL_IDX" -ge 0 ]; then
END_IDX=$((DEL_IDX - 1))
echo "[*] Flag added at idx $ADD_IDX; deleted at idx $DEL_IDX."
echo "[*] Rebuilding up to idx $END_IDX (before delete)…"
else
END_IDX=$(wc -l < "$LAYERS_TXT"); END_IDX=$((END_IDX - 1))
echo "[*] Flag added at idx $ADD_IDX; not deleted later. Rebuilding through idx $END_IDX…"
fi
# --- Reconstruct filesystem up to END_IDX ---
i=0
while IFS= read -r REL; do
[ "$i" -le "$END_IDX" ] || break
tar -xf "$TMP/$REL" -C "$ROOT"
i=$((i+1))
done < "$LAYERS_TXT"
FLAG="$ROOT/tmp/flag.txt"
echo
echo "=== Rebuilt rootfs: $ROOT ==="
if [ -f "$FLAG" ]; then
echo "----- RAW -----"
cat "$FLAG" || true
echo
echo "----- strings | grep FLAG -----"
if command -v strings >/dev/null 2>&1; then
strings "$FLAG" | grep -aoE 'FLAG-\{[^}]+\}' || true
else
echo "(no 'strings' command available)"
fi
echo "----- base64 decode try -----"
(base64 -d < "$FLAG" 2>/dev/null | strings 2>/dev/null | grep -aoE 'FLAG-\{[^}]+\}' || true)
else
echo "[!] /tmp/flag.txt not present after reconstruction (unexpected)."
fi
echo
echo "[*] Scratch dir: $TMP"
BASH
Executing that the output was the following, extracting our flag:
[*] Pulling image (linux/ppc64le)…
[*] Saving and unpacking image…
[*] Flag added at idx 1; deleted at idx 3.
[*] Rebuilding up to idx 2 (before delete)…
=== Rebuilt rootfs: /var/folders/c4/yhncs3_97fn8t3kh9hs125x80000gp/T/tmp.6ktOmLgens/rootfs ===
----- RAW -----
FLAG-{732C496A8CCe46B024FCc7CA1ADa14Cb}
----- strings | grep FLAG -----
FLAG-{732C496A8CCe46B024FCc7CA1ADa14Cb}
----- base64 decode try -----
Spectre Heist
HEX-NOVA slipped. A troubleshooting.log left behind on their Azure Static Website may contain the key to uncovering their secrets.
Your mission:
Uncover the clues within the log and retrieve flag.txt before the opportunity vanishes.
This challenges provides us with a web page hosted in Azure shown below:

Clicking the Troubleshooting page brings us to the following:

It looks like they want us to navigate to /troubleshooting.log
.
curl -sS https://spectreheisa034e4979.z5.web.core.windows.net/troubleshooting.log -o troubleshooting.log
We curl
to download the log file to our machine, then run simple search for likely string matches:
grep -Ein 'flag|secret|token|key|\.txt|\.zip|\.log|http' troubleshooting.log
We're provided with the following SES link:
https://spectreheisa034e4979.blob.core.windows.net/flag-container/flag.txt?sv=2018-11-09&sr=c&st=2025-08-03T01:25:55Z&se=2025-08-10T01:25:55Z&sp=r&spr=https&sig=Uus84OW582%2F%2FBOzaoKR70i7qhn3qxsbQPm7VAelOoT8%3D
Accessing this temporary link provided the flag, which is no longer valid.
Classified Registry
The system was wiped, scrubbed clean down to the metal. Yet one container kept running, untouched, like a whisper in a forgotten corner. Some believe H3X N0V4 was testing more than defenses—maybe they were testing curiosity.
This challenge provided us with a webpage with a simple pattern recognition game involving arrow keys, once you get the pattern right you are provided with a container image to download.

After pulling the container and inspecting the layers in Docker Desktop, we can see the flag being added to the environment variables, finishing the challenge.
