DEV Community

Yogeshwar Peela
Yogeshwar Peela

Posted on Originally published at exploitnotes.hashnode.dev

TryHackMe: Corridor - Writeup

Overview

Corridor is an easy TryHackMe box built around a single Flask/Werkzeug web app. The homepage presents an image of a corridor with thirteen clickable doors, each linking to a URL that is an MD5 hash of a number. Every visible door leads to an identical, empty placeholder page. The actual flag sits behind a door the app supports but never links to on the page - found only by reasoning about the pattern behind the visible URLs and testing the one number missing from it.

Recon

Standard nmap scan against the target:

nmap -A -Pn  -o nmap
Enter fullscreen mode Exit fullscreen mode
PORT   STATE SERVICE VERSION
80/tcp open  http    Werkzeug httpd 2.0.3 (Python 3.10.2)
|_http-title: Corridor
|_http-server-header: Werkzeug/2.0.3 Python/3.10.2
Enter fullscreen mode Exit fullscreen mode

Only port 80 is open, running a Flask dev server (Werkzeug). The title "Corridor" matched the box name, so the web app was the entire attack surface.

Enumerating the Corridor

Pulled the homepage:

curl http:///
Enter fullscreen mode Exit fullscreen mode

The page is a single image (corridor.png) with an HTML overlay containing thirteen clickable elements. Each one links to a 32-character hex string:

 ... href="c4ca4238a0b923820dcc509a6f75849b" ...>
 ... href="c81e728d9d4c2f636f067f89cc14862c" ...>
 ... href="eccbc87e4b5ce2fe28308fd9f2a7baf3" ...>
...
 ... href="c51ce410c124a10e0db5e4b97fc2af39" ...>
Enter fullscreen mode Exit fullscreen mode

These are 32-character hex strings - the right length and format for MD5 hashes. Recognizing a few of them from memory (c4ca4238a0b923820dcc509a6f75849b is the well-known MD5 of "1"), it was clear each door's URL is simply md5(n) for n = 1 through 13, one per door on the corridor image.

Requested the first door directly:

curl http:///c4ca4238a0b923820dcc509a6f75849b -v
Enter fullscreen mode Exit fullscreen mode
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 632
Server: Werkzeug/2.0.3 Python/3.10.2
Enter fullscreen mode Exit fullscreen mode

Returned a generic "empty room" page:


Enter fullscreen mode Exit fullscreen mode

Looped through all thirteen door hashes the same way:

for h in c4ca4238a0b923820dcc509a6f75849b c81e728d9d4c2f636f067f89cc14862c \
         eccbc87e4b5ce2fe28308fd9f2a7baf3 a87ff679a2f3e71d9181a67b7542122c \
         e4da3b7fbbce2345d7772b0674a318d5 1679091c5a880faf6fb5e6087eb1b2dc \
         8f14e45fceea167a5a36dedd4bea2543 c9f0f895fb98ab9159f51fd0297e236d \
         45c48cce2e2d7fbdea1afc51c7c6ad26 d3d9446802a44259755d38e6d163e820 \
         6512bd43d9caa6e02c990b0a82652dca c20ad4d76fe97759aa27a0c99bff6710 \
         c51ce410c124a10e0db5e4b97fc2af39; do
  echo "=== $h ==="
  curl -s http:///$h
  echo
done
Enter fullscreen mode Exit fullscreen mode

Every single one returned the identical empty-room page. All thirteen visible doors are dead ends by design - none of them hold the flag.

Checking for a Debug Console

With no result from the doors, checked whether the Werkzeug dev server exposed its interactive debugger:

curl -sI http:///
curl -s http:///console
Enter fullscreen mode Exit fullscreen mode
HTTP/1.0 200 OK
404 Not Found

Not Found

Enter fullscreen mode Exit fullscreen mode

Clean 404, no debug PIN prompt. Debug mode is off - that path was a dead end too.

Finding the Hidden Door

Since the app clearly derives each door's route from md5(n), and the corridor image only exposes doors 1 through 13, the natural next question was: does the app support n = 0, even though no door links to it?

Directory brute-forcing was also run in parallel to rule out unrelated hidden endpoints:

gobuster dir -u http:/// -w /usr/share/wordlists/dirb/common.txt -x php,txt,json
Enter fullscreen mode Exit fullscreen mode

This didn't turn up anything relevant before being stopped early - the real answer wasn't a generic wordlist guess, it was reasoning about the app's own hashing pattern. Computed md5("0"):

cfcd208495d565ef66e7dff9f98764da
Enter fullscreen mode Exit fullscreen mode

Requested it directly:

curl -s http:///cfcd208495d565ef66e7dff9f98764da
Enter fullscreen mode Exit fullscreen mode

Unlike every numbered door, this one rendered differently - a different background image and a flag banner:


flag{REDACTED}

Enter fullscreen mode Exit fullscreen mode

Flag

flag{REDACTED}
Enter fullscreen mode Exit fullscreen mode

Takeaways

  • When a set of URLs all follow an obvious derivation pattern (here, md5(door_number) for doors 1-13), it's worth testing values outside the visible range before reaching for brute-force wordlists - the boundary case (0) was the intended answer, not a hidden path.

  • A generic dirb/gobuster wordlist won't find hash-derived routes; those only fall out of understanding the app's own logic, not guessing common path names.

  • Always check the Werkzeug/Flask dev server for an exposed debug console (/console) early - it's a free RCE path when present, and ruling it out quickly avoids wasted effort chasing it later.

  • Identical response bodies across every visible endpoint is itself a signal: it means the real content lives somewhere the UI doesn't expose, not that the challenge is broken.

Top comments (0)