Protocol Analysis 5: Is This Real?
Protocol Analysis 5: Is This Real?
Platform: Metactf | Category: Cryptography | Type: Challenge | Difficulty: Easy | OS: NA | Author: D3v0o0Nu11 | Date: 2026-04-10 | Status: Solved Techniques: asymmetric_decryption_oracle, chosen_key_attack, direct_bob_trigger, no_identity_binding, public_key_substitution
Summary
Task: a DawgCTF protocol analysis challenge where Alice is supposed to tell Bob to encrypt the flag under her public key, but Bob's recv template accepts any public key alongside Alice's name. Solution: generate our own asymmetric key pair, send Bob the hello message with our public key substituted for Alice's, then decrypt the returned ciphertext with our private key using the service's asym_decrypt helper.
Recon
Port scan
bash read-only • copy allowednmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
metactf| ID:20260410_metactf_protocol_analysis_5_is_this_real - Tags: rsa, asymmetric_encryption, mitm, protocol_analysis, alice_and_bob, dawgctf, public_key_substitution, unauthenticated_key
- Indicators: the protocol sends a public key inline with no certificate or signature, the receiver's recv template uses pub_X where X is any name, the service exposes /util/gen_asym_key_pair and /util/asym_decrypt oracles, the accepted request text contains the typo asymetric, Bob encrypts the flag with the key he was handed
- Source:
20260410_metactf_protocol_analysis_5_is_this_real.md
Foothold
Vulnerability / Misconfiguration
- asymmetric_decryption_oracle
- chosen_key_attack
- direct_bob_trigger
- no_identity_binding
- public_key_substitution
bash read-only • copy allowed<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
bash read-only • copy allowedsudo -l find / -perm -4000 2>/dev/null getcap -r / 2>/dev/null cat /etc/crontab ps aux
Exploitation
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
bash read-only • copy allowed<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- asymmetric_decryption_oracle
- chosen_key_attack
- direct_bob_trigger
- no_identity_binding
- public_key_substitution
- Tags: rsa, asymmetric_encryption, mitm, protocol_analysis, alice_and_bob, dawgctf, public_key_substitution, unauthenticated_key
Original Writeup
<details><summary>Click to expand original content</summary>Protocol Analysis 5: Is This Real? — metactf
Description
Alice and Bob are using this cool new thing called asymmetric cryptography! Alice isn't gonna leak her private key, so how could anyone intercept the flag?
Source: https://github.com/UMBCCyberDawgs/dawgctf-sp26/blob/main/Protocol%20Analysis%20(1-9)/Protocol_Analysis_chals.pdf
Challenge 5 of the DawgCTF Protocol Analysis series. All Protocol Analysis tasks share the same PDF manual and live server at https://protocols.live.
Protocol
From the manual, Challenge 5 is specified as:
text read-only • copy allowedProtocol key: A = alice B = bob X = any name Alice Bob send: "Hello",B,"this is",A,"send the flag encrypted with this asymmetric key",pubA recv: "Hello",B,"this is",A,"send the flag encrypted with this asymmetric key",pubX send: "here it is",{[FLAG]}pubX recv: "here it is",{[FLAG]}pubA
The critical notation detail is X = any name in the protocol key. Bob's recv template uses pub_X where the X subscript means "any public key is accepted". Bob will then use that same key in his send to encrypt the flag, so whoever supplies the key also gets to decrypt what Bob sends back.
Service primer
- All actions are HTTP POST requests to
https://protocols.live. POST /model/5creates a fresh instance and returns{"conn_id": ...}.POST /aliceandPOST /bobtake{"conn_id": ..., "content": ...}and advance the corresponding participant's script.- Utility endpoints under
/util/ignoreconn_idand provide cryptographic helpers:/util/gen_asym_key_pairreturns a fresh public and private key./util/asym_decrypttakesk:<priv>|d:<ciphertext>and returns plaintext.
- The
contentfield is a pipe-separated list of typed itemstype:value. Types aret(text),n(name),k(key hex),d(data / hex blob). - If a request ever errors, the instance is destroyed and you must create a new one.
Vulnerability
There is no binding between Alice's name and her public key. The protocol transmits pubA inline, with no certificate, no signature, and no prior trusted distribution. Bob's recv template only checks:
- The leading text blob:
Hello - Bob's own name:
bob - The connector text:
this is - The claimed sender name:
alice - The request text:
send the flag encrypted under this asymetric key(note the server's typo — singlem) - A public key field — any public key is accepted
Nothing ties the key to Alice's identity, and Bob will encrypt the flag under whatever key was provided. This is the classic "public key substitution" weakness that certificates (cert_E) solve in later challenges of the series.
Because step 6 accepts any key, we can act as the network adversary, supply our own public key, and let Bob encrypt the flag under a key we control. Alice's private key is never touched — the challenge text about Alice "not leaking her private key" is a red herring.
Exploitation
Steps:
- Call
/util/gen_asym_key_pairto produce(pubX, privX). - Create a fresh instance with
POST /model/5. - Craft the hello message with
pubXsubstituted forpubA:t:Hello|n:bob|t:this is|n:alice|t:send the flag encrypted under this asymetric key|k:<pubX> POST /bobwith that content. Bob replies witht:here it is|d:<{FLAG}pubX>.- Feed the ciphertext and
privXto/util/asym_decryptto recover the flag plaintext.
No interaction with /alice is required at all — we know Bob's exact expected input from the protocol definition, and we never need Alice's key material.
Working solver
python read-only • copy allowed#!/usr/bin/env python3 """Solve DawgCTF Protocol Analysis 5: Is This Real?""" import json import sys import requests BASE = "https://protocols.live" def post(path, conn_id, content): r = requests.post( f"{BASE}{path}", headers={"Content-Type": "application/json"}, data=json.dumps({"conn_id": conn_id, "content": content}), ) if r.status_code != 200: sys.exit(f"Request failed: {r.status_code} {r.text}") return r.json()["content"] def util(name, content): return post(f"/util/{name}", 0, content) def parse_content(content): return [tuple(item.split(":", 1)) for item in content.split("|")] def main(): # 1. Generate our own RSA keypair (attacker-controlled) kp = parse_content(util("gen_asym_key_pair", "")) pub_x = kp[1][1] # first k: priv_x = kp[3][1] # second k: # 2. Fresh protocol instance conn = requests.post(f"{BASE}/model/5").json()["conn_id"] # 3. Craft Bob's expected hello with OUR public key in place of Alice's mitm = ( "t:Hello|n:bob|t:this is|n:alice|" "t:send the flag encrypted under this asymetric key|" f"k:{pub_x}" ) # 4. Bob encrypts the flag under pub_x and returns it bob_resp = post("/bob", conn, mitm) enc_flag = dict(parse_content(bob_resp))["d"] # 5. Decrypt with our private key via the utility oracle dec = util("asym_decrypt", f"k:{priv_x}|d:{enc_flag}") print("FLAG =", dict(parse_content(dec))["t"]) if __name__ == "__main__": main()
Example run:
</details>text read-only • copy allowed[+] pubX = 30818902818100c18c2ee92eb31ad31e788244edf53e108c99c8d998d473... [+] privX = 30820276020100300d06092a864886f70d0101010500048202603082025c... [+] conn_id = 14441278294909 [+] MITM -> Bob [+] Bob -> t:here it is|d:6facd7a293974343775351b345826b5c8a59bb052913e52f56a8336cb00cfefdd... [+] FLAG = DawgCTF{REDACTED}
signed by XESXOR