$ cat writeup.md…
$ cat writeup.md…
avitoctf
Task: Recover an intercepted session protected by static P-256 ECDH, AES-CBC, and a secret-prefix SHA-256 tag. Solution: Replay the public key, length-extend authenticated packets, build a response-size padding oracle, and reuse the recovered credentials.
$ cat /etc/rate-limit
Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.
A bee-themed MI6 service uses P-256 ECDH, AES-CBC, and a secret-prefix SHA-256 tag. A supplied PCAP contains an intercepted encrypted session.
The goal was to recover the intercepted correspondence and then access the protected file service. The challenge supplied the client, server, and packet capture, making the protocol implementation and one complete session available for analysis.
After P-256 ECDH, both peers derive a 32-byte session key:
key = SHA256(ECDH shared secret) payload = IV || AES-CBC(key, PKCS#7(plaintext)) tag = SHA256(key || payload) packet = payload || tag
The critical code is in beesix/server.py: key derivation is at lines 150-152, tag creation at lines 155-169, and verification followed by CBC decryption at lines 172-190.
The server loads one P-256 private key globally at lines 57-70 and reuses it in every handshake at lines 282-288. Replaying the captured client's DER public key therefore makes the server derive exactly the session key used in the PCAP.
This does not reveal the key to the attacker. Replaying the captured login and password packets reaches the authenticated state, but every prompt and response remains encrypted under the unknown old key. The only captured command is a terminating command, so simple replay cannot construct a useful list or read packet and cannot decrypt responses with fresh IVs.
The tag is not HMAC. Because SHA-256 is Merkle-Damgård, knowing
SHA256(key || payload) permits computation of a valid digest for:
key || payload || SHA256_glue_padding || attacker_suffix
...
$ grep --similar