Сопромёд: метро
Сопромёд: метро
Platform: Avitoctf | Category: Forensics | Type: Challenge | Difficulty: Easy | OS: NA | Author: D3v0o0Nu11 | Date: 2026-07-22 | Status: Solved Techniques: http_traffic_extraction, column_wise_majority_voting, credential_replay
Summary
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.
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:
avitoctf| ID:20260722_avitoctf_sopromed_metro - Tags: pcap, json, http, plaintext_credentials, typing_errors
- Indicators: 96 equal-length password attempts, plaintext POST /api/auth/login JSON, all captured login responses are HTTP 401, no Cookie or Set-Cookie fields in the capture
- Source:
20260722_avitoctf_sopromed_metro.md
Foothold
Vulnerability / Misconfiguration
- http_traffic_extraction
- column_wise_majority_voting
- credential_replay
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
- http_traffic_extraction
- column_wise_majority_voting
- credential_replay
- Tags: pcap, json, http, plaintext_credentials, typing_errors
Original Writeup
<details><summary>Click to expand original content</summary>Description
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.
PCAP Triage
First, preserve the artifact identity and inspect its basic structure:
bash read-only • copy allowedshasum -a 256 sopromed_capture.pcap capinfos -c sopromed_capture.pcap tshark -r sopromed_capture.pcap -q -z io,phs
The SHA-256 is:
text read-only • copy allowed46b6970704f7082559b4242a7d0ecb9bba71c2b4464285d8ef03ea2bc8d43432
The capture has 960 Ethernet packets and contains HTTP carrying JSON. Filtering on the login route isolates the relevant requests:
bash read-only • copy allowedtshark -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:
bash read-only • copy allowedtshark -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.
Analysis
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.
Solution
The following script extracts login bodies directly with tshark, validates the shared structure, and computes the per-position mode:
python read-only • copy allowed#!/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:
bash read-only • copy allowedpython3 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:
bash read-only • copy allowedBASE_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.
Eliminated Approaches
- Session-cookie replay: Every captured login received HTTP 401, and neither
CookienorSet-Cookiefields occur in the capture. No authenticated session was available to replay. - Encrypted credential recovery: The username and noisy password samples are already visible as plaintext JSON, so cryptanalysis or TLS key recovery is irrelevant.
- OTP bypass or session forgery: Replaying the statistically reconstructed credential through the normal login flow creates a teacher session that can read the dashboard directly.
signed by XESXOR