Press enter or click to view image in full size
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
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 rtspThe scan returned three open ports:
Nothing exotic at this stage, but enough surface area to begin exploring.
With HTTP exposed, I moved straight to the browser. 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.
Press enter or click to view image in full size
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.
Join Medium for free to get updates from this writer.
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: 11460Output File: /home/death/reports/http_10.48.156.225/__26-01-01_20-02-27.txtTarget: http://10.48.156.225/[20:02:27] Starting:
[20:02:35] 403 - 166B - /adminTask Completed
The scan revealed a restricted endpoint: /admin.
I navigated to the /admin directory directly from the browser to see what it exposed.
Press enter or click to view image in full size
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.
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>Press enter or click to view image in full size
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.
Press enter or click to view image in full size
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{1f4a2b6ffeaf4707c43885d704eaee4b}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.
Press enter or click to view image in full size
Thanks for reading this Writeup.