Skip to content
TheoSecLABS
All labs
InsaneWeb Exploitation (OSWE)

Vantage

Vantage is an on-prem infrastructure monitoring and automation console — hosts, metrics, alerts, and a remote-diagnostics API — that has been extended and patched by many hands over the years. Plenty of its endpoints look dangerous; most of them are perfectly safe. Finding the handful that aren't, s

Description

Vantage is an on-prem infrastructure monitoring and automation console — hosts, metrics, alerts, and a remote-diagnostics API — that has been extended and patched by many hands over the years. Plenty of its endpoints look dangerous; most of them are perfectly safe. Finding the handful that aren’t, seeing through the filters guarding them, and chaining them into a single exploit is the whole game.

Difficulty vs OSWE: Above the OSWE exam. Vantage is deliberately harder than the exam — built for people who want to be over-prepared. Every stage sits behind a filter you have to defeat a different way, surrounded by convincing dead ends, and the real path only becomes clear once you’ve ruled the decoys out.

Setup

  1. Download the Vantage OVA from Theosec Labs.
  2. Import into VirtualBox — File → Import Appliance, select the .ova, and import.
  3. Start the VM. At the console login, sign in and find its IP:
    • Username: dev · Password: vantage (this account has full sudo)
    • Run ifconfig (or ip a) and note the address (e.g. 192.168.56.x).
  4. Open the in-browser debugger. Browse to http://<vm-ip>:8080 (code-server). Password: vantage — there is no username.
    • Open Folder → choose the folder named vantage.
    • Click the Run and Debug icon in the left sidebar.
    • Click the green ▶ Start Debugging button to launch a debug instance of the app.
    • Step-through needs Xdebug on the box; you have full sudo, so sudo apt install php-xdebug if it isn’t already there. Reading the source is the main event.
  5. Read the source to find the vulnerabilities.
  6. Write your exploit. The target app is at http://<vm-ip>/ (port 80). Write a script (Python recommended) that automates the whole chain — unauthenticated → admin → remote code execution on the host. It is a long chain with several links and plenty of convincing dead ends; ruling them out is the exercise. This box is about application privilege escalation and RCE.

Solution

Finished the box?

Check your exploit against the reference solution, or read how the box is found and exploited end to end. Both contain full spoilers — they stay hidden until you open them.

Download solution
Reveal walkthroughSpoilers — how the box is found and exploited
# From → To Class Location
1 unauth → recon IDOR / user enumeration public/api/user.php
2 recon → service acct Auth bypass via PHP type juggling (magic hash) public/api/token.php
3 service → admin Second-order blind SQLi write public/api/views.php, sink public/api/views.php (GET)
4 admin → API key Path traversal (recursive ../ strip + decode-after-filter) public/admin/report.php
5 admin → internal reach SSRF to a loopback-only service public/admin/probe.php
6 internal → RCE Filtered OS command injection internal/diag.php (127.0.0.1:9077)

Stage 1 — IDOR user enumeration

/api/user.php?id= returns any user’s card by numeric id with no authorisation. The service account svc_monitor is deliberately not listed on /team.php (which shows humans only), so enumerating ids 1…N on this API is the only way to learn it exists (role: service). The decoy /api/profile.php looks similar but is correctly scoped to the caller’s own session (no id parameter to tamper with).

Stage 2 — magic-hash auth bypass

token.php compares md5($pass) to the stored password_md5 with loose ==. svc_monitor’s seeded password 240610708 has md5 0e462097… (all-digit “0e” form). Any password whose md5 is also 0e-form — e.g. QNKCDZO → 0e830400… — makes PHP compare both as float 0, so == is true. Verified the spray lands on only svc_monitor; admin and the decoy svc_backup (ordinary hash) reject it.

Stage 3 — second-order blind SQLi → admin session token

POST /api/views.php stores a saved view (name, severity) through a prepared statement — the write path is safe, and this is where students expect (and fail to find) the injection. GET /api/views.php then reads each stored view back and folds the stored severity straight into a count query:

$sql = "SELECT COUNT(*) c FROM alerts WHERE severity = '" . $v['severity'] . "'";

So the injection is second order: stored safely, fired later in a different code path. The count is a blind boolean oracle (PDO is SILENT — a broken query yields 0). Store a payload, then read it back:

severity = zzz' OR (SELECT substr(token,i,1) FROM sessions WHERE user_id=1)='c

zzz matches no alerts, so the count is 8 (all rows) when the char matches and 0 otherwise. Extract the admin session token — now 64 hex chars — a character at a time, set cookie vs=<token> → admin. The old first-order sink /api/alerts.php is now a prepared decoy (the dashboard still points students at it).

Stage 4 — path traversal → .env → API key

report.php strips ../ recursively (so ....// no longer survives), then runs its own urldecode() on the result. Encode the traversal twice: ?f=..%252f.env → the app decodes once to ..%2f.env (no literal ../ for the filter to catch) → its urldecode() turns %2f into / → ../.env, read from app/reports/ up into app/.env, yielding VANTAGE_API_KEY=….

Stage 5 — SSRF → the loopback diagnostics collector

The v2 diagnostics collector is not on the public web server. It is an internal service bound to 127.0.0.1:9077 (shown in the admin console’s Internal services registry). admin/probe.php fetches an arbitrary URL server-side (“probe a host’s health endpoint”), so point it at the collector:

url = http://127.0.0.1:9077/diag.php?key=<VANTAGE_API_KEY>&tool=ping&target=<inject>

The collector takes the API key as a query param (it is loopback-only), so the GET-based SSRF carries it.

Stage 6 — filtered OS command injection → RCE

internal/diag.php runs shell_exec("$tool $target 2>&1"). diag_filter rejects space, ; & | ` $( ${ ( ) { } < > \ " ' ! * ? ~ # and the words cat tac nc ncat bash zsh exec eval /bin /etc /dev wget curl python perl ruby php base64 xxd env proc. Bypass with a newline (%0a) as the command separator and a tab (%09) for spaces, using a non-blocked reader:

target = 127.0.0.1%0ahead%09/var/www/local.txt

local.txt (/var/www/local.txt, www-data-readable) comes back in the JSON output field. The filter deliberately blocks every common reverse-shell primitive (nc, bash, python, /bin, redirection, pipes, $, backticks), so the intended proof of host RCE is arbitrary command execution + reading local.txt, not a connect-back shell.

More labs

Super easyLinux · Python

Simmer

Simmer is a small community recipe box where home cooks publish recipes, leave comments, and keep a profile. It looks friendly and unremarkable — which is exactly the point.

Web Exploitation (OSWE)Open lab
EasyLinux · PHP

Craftly

Craftly is a boutique online marketplace for handmade goods — ceramics, textiles and woodwork sold by independent makers. It has customer accounts, product listings, reviews and an admin catalogue, and at a glance it all looks perfectly ordinary. The security is not as tidy as the storefront. Get in

Web Exploitation (OSWE)Open lab
MediumLinux · NodeJS

Currents

Currents is a self-hosted feed reader: add your blogs and news feeds and it pulls the articles into one clean stream. It has a personal dashboard, an admin area, and a small internal service it talks to behind the scenes. Neither getting in nor getting code execution is a one-shot affair here — each

Web Exploitation (OSWE)Open lab

→Next step

Break something on purpose.

Every lab ships as a VM you run on your own hardware — download it, read the source, and work the chain end to end. No account, no scoreboard, no catch.