Introduction
MD2PDF is a TryHackMe web challenge that demonstrates how a seemingly harmless Markdown-to-PDF feature can introduce serious security flaws when server-side rendering and access controls are improperly enforced.
Room Link: https://tryhackme.com/room/md2pdf
Initial Reconnaissance
I started where it always makes sense to start: identifying the exposed services. A basic nmap scan was enough to get a clear picture of what the machine was offering.
nmap -sV -sC -Pn <IP>
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
80/tcp open rtsp
5000/tcp open rtsp
The scan returned three open ports:
- Port 22 running SSH
- Port 80 serving HTTP
- Port 5000 running an unidentified service
Nothing exotic at this stage, but enough surface area to begin exploring.
Web Enumeration
With HTTP exposed, I moved straight to the browser and opened http://<IP>. The application was extremely minimal. It presented a simple Markdown input field with a submit button, designed to convert Markdown content into a downloadable PDF.
I spent some time interacting with the functionality, testing different inputs and basic injections, but nothing meaningful surfaced from this interface directly. With no immediate feedback or errors to pivot on, the next logical step was directory enumeration.
I ran dirsearch against the web root to look for hidden or restricted paths.
dirsearch -u http://<IP>
_|. _ _ _ _ _ _|_ v0.4.3
(_||| _) (/_(_|| (_| )
Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25 | Wordlist size: 11460
Output File: /home/death/reports/http_10.48.156.225/__26-01-01_20-02-27.txt
Target: http://10.48.156.225/
[20:02:27] Starting:
[20:02:35] 403 - 166B - /admin
Task Completed
The scan revealed a restricted endpoint: /admin.
Exploitation via Localhost Restriction Bypass
I navigated to the /admin directory directly from the browser to see what it exposed.
The response was immediate and explicit:
Forbidden This page can only be seen internally (localhost:5000)
That single line said a lot. The endpoint clearly existed, but access was restricted based on request origin. Anything not coming from 127.0.0.1:5000 was blocked outright. This wasn’t a dead end, just a locked door.
Given the application’s purpose, the next thought was natural. User-supplied Markdown is being processed server-side and converted into a PDF. That processing context mattered.
Markdown Meets HTML
On the surface, the Markdown parser appeared constrained. Plain text rendered as expected, and typical Markdown features behaved normally. The interesting part emerged when I tested raw HTML support.
I submitted the following payload into the Markdown input field:
<iframe src="http://localhost:5000/admin"></iframe>
The request completed without errors, and the application generated the PDF as usual. When I opened the downloaded file, the result was immediate.
The contents of the internally restricted /admin page were embedded directly inside the PDF, including the flag.
The restriction wasn’t enforced at the rendering layer. The server itself fetched the internal resource and embedded it into the output, effectively bypassing the access control by trusting its own request context.
Flag.txt
flag{1f4a2b6ffeaf4707c43885d704eaee4b}
Conclusion
This room was a clean reminder that “internal only” is not a security boundary when user-controlled content is rendered server-side. Markdown processors, especially those that allow raw HTML, can quietly become a bridge into places that were never meant to be exposed.
Thanks for reading this WriteUp.