//Quick Mistake
>TL;DR
The attacker abused an admin telemetry endpoint to make the server leak a TLS/QUIC key log (via encrypted telemetry datagrams). With that key log, we can decrypt the HTTP/3 (QUIC) traffic in the PCAP, download an internal /source bundle that contains an .env file with AES_FLAG_KEY, and then decrypt the /flag response to get flag part 2.
Final flag:
nite{192.0.2.66_2457ce19cb87e0eb_qu1c_d4t4gr4m_pwn3d}
>Files provided
Inside the handout:
Chal.pcap— incident packet capture
Tools used:
tshark(Wireshark CLI)- Python 3 +
cryptography
>1) Quick PCAP recon (what protocols are present?)
First, confirm what we’re dealing with:
tshark -r handout/Chal.pcap -q -z io,phs
Key takeaways from the protocol hierarchy:
- There is HTTP over TCP (a few frames)
- There is QUIC traffic (UDP) — this strongly suggests HTTP/3
Then list the heaviest endpoints:
tshark -r handout/Chal.pcap -q -z endpoints,ip
You’ll see a small set of internal-ish IPs plus one external server:
203.0.113.100— QUIC server (UDP/4433)192.0.2.66— suspicious client that later pulls/sourceand/flag
To confirm the “interesting” HTTP requests:
tshark -r handout/Chal.pcap -Y http.request \\
-T fields -E header=y -E separator=$'\\t' \\
-e frame.time_relative -e ip.src -e ip.dst -e http.host -e http.request.method -e http.request.uri
This shows a standout request:
192.0.2.66 -> 203.0.113.100GET /telemetry(classic “admin telemetry” smell)
>2) Identify the telemetry leak mechanism
The PCAP contains plaintext JSON sent over UDP that literally references sslkeylog and includes encrypted chunks.
Quick way to spot it:
strings -a handout/Chal.pcap | grep -E 'handshake_init|telemetry_sslkeylog|sslkeylog'
You should find JSON objects like:
{"type": "handshake_init", "seed": "…", "salt": "telemetry", "info": "sslkeylog"}{"type": "telemetry_sslkeylog", "seq": 0|1, "nonce_b64": "…", "ct_b64": "…", "tag_b64": "…"}
Locate the exact frames:
tshark -r handout/Chal.pcap -Y 'frame contains "handshake_init"' -T fields -e frame.number
tshark -r handout/Chal.pcap -Y 'frame contains "telemetry_sslkeylog"' -T fields -e frame.number
In this capture, those are:
handshake_init: frames179/180(duplicate)telemetry_sslkeylog: frames182–185(duplicates)
Extract one copy of each JSON payload as hex:
# handshake_init (pick one)
tshark -r handout/Chal.pcap -Y 'frame.number==179' -T fields -e udp.payload
# chunk 0 (pick one)
tshark -r handout/Chal.pcap -Y 'frame.number==182' -T fields -e udp.payload
# chunk 1 (pick one)
tshark -r handout/Chal.pcap -Y 'frame.number==184' -T fields -e udp.payload
>3) Decrypt the telemetry “sslkeylog” chunks
What’s going on cryptographically?
- The
handshake_initprovides a seed plus HKDF parameters:salt = "telemetry"info = "sslkeylog"
- A key is derived via HKDF-SHA256 (32 bytes)
- The chunks are encrypted with AES-GCM using base64-encoded
nonce,ct, andtag
Decryption script
Run this Python snippet to:
- Pull the UDP JSON blobs directly from the PCAP
- Derive the AES-GCM key using HKDF
- Decrypt chunk 0 + chunk 1
- Write the output as a standard
SSLKEYLOGFILE
python3 - <<'PY'
import base64, json, subprocess
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
PCAP = 'handout/Chal.pcap'
# Frame numbers in this capture (one copy of each)
HANDSHAKE_FRAME = 179
CHUNK0_FRAME = 182
CHUNK1_FRAME = 184
cmd = [
'tshark','-r',PCAP,
'-Y', f'frame.number=={HANDSHAKE_FRAME} || frame.number=={CHUNK0_FRAME} || frame.number=={CHUNK1_FRAME}',
'-T','fields','-E','separator=\\t','-e','frame.number','-e','udp.payload'
]
raw = subprocess.check_output(cmd, text=True).strip().splitlines()
by_frame = {}
for line in raw:
num, payload_hex = line.split('\\t', 1)
by_frame[int(num)] = bytes.fromhex(payload_hex.strip())
handshake = json.loads(by_frame[HANDSHAKE_FRAME].decode('utf-8'))
chunk0 = json.loads(by_frame[CHUNK0_FRAME].decode('utf-8'))
chunk1 = json.loads(by_frame[CHUNK1_FRAME].decode('utf-8'))
seed = bytes.fromhex(handshake['seed'])
salt = handshake['salt'].encode('utf-8')
info = handshake['info'].encode('utf-8')
key = HKDF(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
info=info,
).derive(seed)
aesgcm = AESGCM(key)
def decrypt_chunk(chunk):
nonce = base64.b64decode(chunk['nonce_b64'])
ct = base64.b64decode(chunk['ct_b64'])
tag = base64.b64decode(chunk['tag_b64'])
return aesgcm.decrypt(nonce, ct + tag, None)
plaintext = decrypt_chunk(chunk0) + decrypt_chunk(chunk1)
out_path = 'handout/sslkeylog.txt'
with open(out_path, 'wb') as f:
f.write(plaintext)
print('Wrote', out_path)
print(plaintext.decode('utf-8', errors='replace'))
PY
At the end you should have handout/sslkeylog.txt containing entries like:
CLIENT_TRAFFIC_SECRET_0 …SERVER_TRAFFIC_SECRET_0 …
That’s exactly what Wireshark/tshark needs to decrypt QUIC.
>4) Decrypt QUIC/HTTP3 and extract the stolen internal data
Now that we have a keylog file, use it to decrypt HTTP/3:
tshark -r handout/Chal.pcap \\
-o tls.keylog_file:handout/sslkeylog.txt \\
-Y 'http3.headers.method' \\
-T fields -E header=y -E separator=$'\\t' \\
-e frame.number -e ip.src -e ip.dst \\
-e http3.headers.method -e http3.headers.authority -e http3.headers.path -e http3.headers.status
You should see the attacker (192.0.2.66) request:
GET <https://localhost/source>GET <https://localhost/flag>
Identify the attacker CID (Connection ID)
For the /source and /flag transaction, correlate the QUIC connection ID:
tshark -r handout/Chal.pcap \\
-o tls.keylog_file:handout/sslkeylog.txt \\
-Y 'frame.number>=360 && frame.number<=390 && quic' \\
-T fields -E header=y -E separator=$'\\t' \\
-e frame.number -e ip.src -e ip.dst \\
-e quic.dcid -e quic.scid \\
-e http3.headers.path -e http3.headers.method -e http3.headers.status
In this capture, the server-to-attacker traffic uses the destination CID:
2457ce19cb87e0eb
That’s the CID expected in the flag format.
>5) Extract /source (it’s a tar.gz served over HTTP/3)
The /source response body is shown by tshark as a long hex string (it starts with gzip magic 1f8b08).
Extract the decrypted payload from the /source response frame (in this capture: frame 373):
# Save the HTTP/3 data field (hex)
tshark -r handout/Chal.pcap \\
-o tls.keylog_file:handout/sslkeylog.txt \\
-Y 'frame.number==373' \\
-T fields -e http3.data > handout/source_hex.txt
# Convert hex -> gzip bytes
python3 - <<'PY'
from pathlib import Path
hx = Path('handout/source_hex.txt').read_text().strip().replace('\\n','')
Path('handout/source.gz').write_bytes(bytes.fromhex(hx))
print('wrote handout/source.gz')
PY
# Decompress gzip -> tar
python3 - <<'PY'
import gzip
from pathlib import Path
Path('handout/source.tar').write_bytes(gzip.decompress(Path('handout/source.gz').read_bytes()))
print('wrote handout/source.tar')
PY
# Extract tar
mkdir -p handout/source_extract
tar -xf handout/source.tar -C handout/source_extract
# Inspect
ls -la handout/source_extract
cat handout/source_extract/.env
The .env contains the key we need:
AES_FLAG_KEY=wEN64tLF1PtOglz3Oorl7su8_GQzmlU2jbFP70cFz7c=
This is a valid Fernet key (URL-safe base64), despite the variable name saying AES.
>6) Extract /flag and decrypt it (flag part 2)
From the decrypted HTTP/3 output, the /flag response body is a Fernet token:
gAAAAABpNXDCHUJ4YqH0Md2p6tzE303L8z5kPpPPWwYYrXUdiyW89eCaWWL1dbYU2JYj7SUvdwySW_egZDRF0fyFGxPua2KoFmd8upKP7cZv55jVp_SzItA=
Decrypt it using the leaked AES_FLAG_KEY:
python3 - <<'PY'
from cryptography.fernet import Fernet
key = b'wEN64tLF1PtOglz3Oorl7su8_GQzmlU2jbFP70cFz7c='
token = 'gAAAAABpNXDCHUJ4YqH0Md2p6tzE303L8z5kPpPPWwYYrXUdiyW89eCaWWL1dbYU2JYj7SUvdwySW_egZDRF0fyFGxPua2KoFmd8upKP7cZv55jVp_SzItA='
print(Fernet(key).decrypt(token.encode()).decode())
PY
Output:
qu1c_d4t4gr4m_pwn3d}
That’s flag part 2.
>7) Assemble the final flag
The challenge wants:
nite{attacker_ip_attacker_CID_flag_part_2}
From the decrypted HTTP/3 requests:
- attacker IP:
192.0.2.66(the client requesting/sourceand/flag) - attacker CID:
2457ce19cb87e0eb - flag part 2:
qu1c_d4t4gr4m_pwn3d
So the final flag is:
nite{192.0.2.66_2457ce19cb87e0eb_qu1c_d4t4gr4m_pwn3d}