Flipping bits in an AES-CBC IV to rewrite a decrypted filename, then brute-forcing a 2-byte HMAC gap to read an arbitrary file off the server.
AceBear{fl1pp1ng_and_fake_HMAC_SECRET__for_a_warm_Up_Challenge___^_^}We arrive at a landing page that lets us submit a secret.
Submitting the word "test" gets us a page with a link to our secret.
Clicking through shows what looks like an MD5 hash of our secret.
Viewing the source of the landing page and scrolling to the bottom turns up something interesting - a potential link.
Visiting that secret URL and viewing its source is where things get useful - bingo. The full snapshot of the code is included below; it's clearly a Python backend.
A quick read through the source turns up a helpful comment telling us exactly where the flag lives - in a file called flag that we need to somehow retrieve.
This is the function responsible for retrieving files, driven by an AES-encrypted string. There has to be a flaw somewhere in here that gets us to the flag.
Here's the raw request used to create a secret. Doing so gets us back a URL containing a file and a sign parameter.
Reading through this:
random library. Not something we can realistically attack.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.md5_, giving us a filename like md5_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.| and then an HMAC of the filename itself.So what we actually need is for the decrypted payload to read flag|<HMAC of flag>. Worth checking next how the AES encryption itself is implemented - standard library, or something custom?
Bingo - if we control the IV, we can manipulate the decrypted plaintext directly. Let me explain how.
This is the standard diagram for how AES-CBC works - here's the plain-English version. AES is a block cipher: it splits your data into fixed-size blocks and encrypts (or decrypts) block by block. We care about decryption here specifically.
The first block gets decrypted using the key, producing what looks like garbage - that garbage is then XORed with the IV to produce the actual plaintext. Critically, regardless of the key, the plaintext is directly tied to the IV.
We already know the first 4 characters of the plaintext are going to be md5_. If we modify the first 4 bytes of the IV, we can predictably change what those 4 characters decrypt to - enough to turn md5_ into flag, deterministically. We also know the next character should be a pipe (|), followed by the HMAC.
And here's the actual logic flaw - see if you can spot it before I say it. The server splits the decrypted string on the | character: the first part becomes the filename, and the second part is the secret used to generate the HMAC.
So what happens with flag||? The filename becomes flag, and the HMAC secret becomes an empty string. We can compute the HMAC of flag with a blank secret ourselves, since the HMAC generation function is right there in the source we already have - trivial.
The catch: we only control the first 4 characters of the plaintext through IV manipulation. The next 32 characters (the MD5-derived filename portion) are effectively random hex - 0-9a-f, 16 possible values per character. We only need to brute-force 2 of those characters to line things up: 16 × 16 = 256 possibilities. Very reasonable.
Since only the first block is affected by IV manipulation, most of that 32-character hash segment stays exactly as originally generated - I already know its value from the file parameter returned when the secret was created. The only genuinely unknown piece is the small slice landing right at the point where I need the pipe to appear, which is where the 2-character brute force comes in.
I calculated the HMAC for flag with a blank secret ahead of time - it came out to f7101d3ad5cb2622672fb15e079d8db3, using the same Python HMAC logic pulled straight from the leaked source.
I wrote a quick Node.js script to generate all 256 possible payloads, then loaded Burp, set the payload position on the file parameter, and fed in our known HMAC. Hit go and hoped for the best.
It worked through the 256 guesses, and attempt number 92 looked different from the rest. Bingo - that's the flag.
Really enjoyed this one - genuinely satisfying to solve.
Need help with security testing?
CONTACT US →