$ cat writeup.md…
$ cat writeup.md…
avitoctf
Task: Analyze plaintext HTTP login attempts in a PCAP and recover a professor's password from repeated typing errors. Solution: Extract JSON bodies with tshark, take the per-position mode across equal-length attempts, replay the credential, and read the teacher dashboard.
A student captured the traffic of Professor Barsukov repeatedly attempting to log into the university LMS over open metro Wi-Fi while carriage movement caused typing errors. Given PCAP
sopromed_capture.pcapand live LMS instance, recover access to the professor account.
The capture contains many failed login attempts made over plaintext HTTP. The goal is to reconstruct the intended password from the professor's independent typing errors, authenticate normally, and inspect the teacher dashboard.
First, preserve the artifact identity and inspect its basic structure:
shasum -a 256 sopromed_capture.pcap capinfos -c sopromed_capture.pcap tshark -r sopromed_capture.pcap -q -z io,phs
The SHA-256 is:
46b6970704f7082559b4242a7d0ecb9bba71c2b4464285d8ef03ea2bc8d43432
The capture has 960 Ethernet packets and contains HTTP carrying JSON. Filtering on the login route isolates the relevant requests:
tshark -r sopromed_capture.pcap \ -Y 'http.request.method == "POST" && http.request.uri == "/api/auth/login"' \ -T fields -e http.file_data > login_bodies.txt
There are 96 request bodies. Every body names prof_barsukov, and every supplied password is exactly 19 characters long. Wireshark versions commonly render the byte-valued http.file_data field as hexadecimal, so the solver below handles both hexadecimal and directly rendered JSON.
The corresponding evidence rules out session replay:
tshark -r sopromed_capture.pcap -Y 'http.response.code == 401' \ -T fields -e frame.number -e http.response.code tshark -r sopromed_capture.pcap -Y 'http.cookie || http.set_cookie' \ -T fields -e http.cookie -e http.set_cookie
All 96 captured login responses have status 401, while the cookie filter returns no requests or responses. There is therefore no authenticated session in the PCAP to reuse.
The narrative says carriage movement caused typing errors. This suggests that each observed 19-character password is a noisy version of one fixed intended password. Because all attempts have equal length, align them by character position.
For each of the 19 columns, count the observed characters and choose the statistical mode. Under the intended model—independent substitutions around a fixed string—the correct character appears more often than any individual erroneous character. The resulting modes are unambiguous; fixed underscores and leetspeak positions appear in all 96 attempts, while the leading count at each noisy position ranges from 40 to 61 out of 96.
This reconstructs the password b4dger_st34ls_h0ney. Together with the invariant username, the recovered credential is prof_barsukov:b4dger_st34ls_h0ney.
No decryption is involved: the request bodies are plaintext JSON. Likewise, an OTP bypass or forged session is unnecessary because ordinary credential login grants a valid teacher session and the dashboard is directly accessible.
The following script extracts login bodies directly with tshark, validates the shared structure, and computes the per-position mode:
#!/usr/bin/env python3 import json import subprocess import sys from collections import Counter pcap = sys.argv[1] if len(sys.argv) > 1 else "sopromed_capture.pcap" result = subprocess.run( [ "tshark", "-r", pcap, "-Y", 'http.request.method == "POST" && ' 'http.request.uri == "/api/auth/login"', "-T", "fields", "-e", "http.file_data", ], check=True, text=True, capture_output=True, ) def decode_body(value): value = value.strip() try: # http.file_data is an FT_BYTES field in common tshark builds. return json.loads(bytes.fromhex(value.replace(":", "")).decode()) except (ValueError, UnicodeDecodeError, json.JSONDecodeError): return json.loads(value) bodies = [decode_body(line) for line in result.stdout.splitlines() if line.strip()] assert len(bodies) == 96 usernames = {body["username"] for body in bodies} passwords = [body["password"] for body in bodies] lengths = {len(password) for password in passwords} assert usernames == {"prof_barsukov"} assert lengths == {19} recovered = [] for position, column in enumerate(zip(*passwords)): ranking = Counter(column).most_common() assert len(ranking) == 1 or ranking[0][1] > ranking[1][1], position recovered.append(ranking[0][0]) username = usernames.pop() password = "".join(recovered) print(f"username={username}") print(f"password={password}")
Run it locally:
python3 solve.py sopromed_capture.pcap
The reconstructed credential can then be submitted to the supplied challenge instance. Keep the resulting cookie in a local jar rather than printing it:
BASE_URL='https://<SUPPLIED_INSTANCE>' curl -sS -c cookies.txt \ -H 'Content-Type: application/json' \ --data '{"username":"prof_barsukov","password":"b4dger_st34ls_h0ney"}' \ "$BASE_URL/api/auth/login" > login_response.json curl -sS -b cookies.txt \ "$BASE_URL/api/teacher/dashboard" > teacher_dashboard.json python3 - <<'PY' import json import re data = json.load(open("teacher_dashboard.json", encoding="utf-8")) value = data["flag_note"] assert re.fullmatch(r"avito\{[^}]*\}", value) print("dashboard flag_note matches the expected flag format") PY
The login response identifies the account as a teacher. The authenticated dashboard response contains a flag_note, and its value matches the required format. The commands intentionally do not display the session cookie or the recovered flag.
Cookie nor Set-Cookie fields occur in the capture. No authenticated session was available to replay.$ cat /etc/motd
Liked this one?
Pro unlocks every complete writeup and expanded API access. $9/mo.
$ cat pricing.md$ grep --similar