$ cat writeup.md…
$ cat writeup.md…
avitoctf
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.
$ cat /etc/rate-limit
Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.
МедХантер — портал вакансий. Генерация 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.
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.
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.
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"
...
$ grep --similar