$ cat writeup.md…
$ cat writeup.md…
metactf
Task: a protocol challenge uses attacker-provided symmetric encryption parameters inside an Alice-to-Bob request, and the live service accepts a slightly different string than the PDF shows. Solution: query Alice to recover the exact accepted text, choose a known key and nonce, send them directly to Bob, then decrypt the returned ciphertext with the utility endpoint.
The protocol manual describes an Alice-to-Bob request for sending the flag encrypted under a symmetric key and nonce.
English summary: the challenge provides a protocol PDF and a live service. The important twist is that the real accepted Bob request is not exactly the visible PDF wording, so we first need to recover Alice's actual message from the service and then abuse Bob's trust in attacker-chosen key material.
This challenge is a broken secure-channel design. Alice's first message includes a symmetric key and nonce, and Bob blindly trusts those values when encrypting the flag. By querying /alice with any syntactically valid placeholder such as t:hi, we learn the exact string Bob will accept, including the typo symetric. We can then talk to Bob directly, choose our own known key and nonce, receive the ciphertext, and decrypt it with /util/sym_decrypt.
The shared manual explains the protocol shape, but the live service is stricter than the visible PDF text: the accepted string must match Alice's real output exactly.
Sending any syntactically valid placeholder message to /alice, for example t:hi, causes Alice to reveal her actual first message:
t:Hello|n:bob|t:this is|n:alice|t:send me the flag encrypted under this symetric key and nonce|k:<key>|d:<nonce>
The critical detail is the typo: the accepted request contains symetric, not symmetric. The text also has to be exactly send me the flag encrypted under this symetric key and nonce.
Bob uses key and nonce values taken directly from the incoming message. There is no authentication, no trusted key exchange, and no restriction that those parameters must come from a legitimate Alice-controlled process. That means the attacker can choose a known symmetric key and nonce, send the full forged Alice request directly to Bob, and force Bob to encrypt the flag under attacker-controlled parameters.
This turns the protocol into a chosen-key / chosen-nonce attack: confidentiality fails because the recipient encrypts sensitive data with values fully controlled by the attacker.
I used the following known values:
00 repeated 32 bytes11 repeated 12 bytesThat gives this working Bob request:
t:Hello|n:bob|t:this is|n:alice|t:send me the flag encrypted under this symetric key and nonce|k:0000000000000000000000000000000000000000000000000000000000000000|d:111111111111111111111111
Bob responds in the format:
t:here it is|d:<ciphertext>
Finally, submit that ciphertext to /util/sym_decrypt using the same chosen key and nonce. Because we selected the parameters ourselves, the utility cleanly decrypts the ciphertext and returns the flag.
Working solver:
#!/usr/bin/env python3 import requests BASE = "https://protocols.live" KEY = "00" * 32 NONCE = "11" * 12 def main(): session = requests.Session() create = session.post(f"{BASE}/model/4") create.raise_for_status() conn_id = create.json()["conn_id"] alice = session.post( f"{BASE}/alice", json={"conn_id": conn_id, "content": "t:hi"}, ) alice.raise_for_status() print("Alice says:", alice.json()["content"]) forged = ( "t:Hello|n:bob|t:this is|n:alice|" "t:send me the flag encrypted under this symetric key and nonce|" f"k:{KEY}|d:{NONCE}" ) bob = session.post( f"{BASE}/bob", json={"conn_id": conn_id, "content": forged}, ) bob.raise_for_status() bob_content = bob.json()["content"] print("Bob says:", bob_content) ciphertext = bob_content.split("|d:", 1)[1] dec = session.post( f"{BASE}/util/sym_decrypt", json={"conn_id": conn_id, "content": f"k:{KEY}|d:{NONCE}|d:{ciphertext}"}, ) dec.raise_for_status() print(dec.json()) if __name__ == "__main__": main()
$ cat /etc/motd
Liked this one?
Pro unlocks every writeup, every flag, and API access. $9/mo.
$ cat pricing.md$ grep --similar