← Back to Writeups
CUSTOMEasyForensics

Сопромёд: метро

XESXOR9/10/20264 min read
#forensics#custom#easy#http_traffic_extraction#column_wise_majority_voting#credential_replay#linux

Сопромёд: метро

Platform: avitoctf | Category: Forensics | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | 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.

Sopromed: Metro — avitoctf

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.pcap and 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
shasum -a 256 sopromed_capture.pcap
capinfos -c sopromed_capture.pcap
tshark -r sopromed_capture.pcap -q -z io,phs

The SHA-256 is:

text
46b6970704f7082559b4242a7d0ecb9bba71c2b4464285d8ef03ea2bc8d43432

The capture has 960 Ethernet packets and contains HTTP carrying JSON. Filtering on the login route isolates the relevant requests:

bash
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:

bash
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.

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
#!/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
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:

bash
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.

Eliminated Approaches

  • Session-cookie replay: Every captured login received HTTP 401, and neither Cookie nor Set-Cookie fields 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.