ReportVerse

webssrfpdfreconnaissance

A “professional report builder” that proudly advertises it will fetch any URL you give it, server-side, and embed the result in a PDF. It is not wrong about that. The internal dashboard it can reach is the problem.

Spoiler-light notes — no flags, credentials or copy-paste exploits. Solve it yourself first.

Recon

One web port, one vhost handed to you by a redirect, and a Node application behind it.

The landing page is a report generator with a few template choices and one field that stands out: an “external data endpoint”. The marketing copy next to it says, in as many words, that the URL is fetched from the server, that this “works with internal staging APIs, authenticated endpoints behind your firewall, or any URL not publicly accessible”, and that the response is embedded directly into the generated PDF.

An application describing its own SSRF vector is not a subtle bug hunt — but “the app fetches URLs” is the start of a process, not the answer. What you do with that primitive is the box.

Building the primitive

file:// and other schemes are rejected — only HTTP and HTTPS are accepted. That’s a real limit, and it removes the easiest escalations, so the useful question becomes: what is reachable over HTTP from inside this host?

The generator renders the fetched content into a PDF and hands you the file. That’s the detail that makes this box pleasant to work: the SSRF is full-read, not blind. A blind SSRF only lets you infer (“something answered”); here the fetched page is rendered and returned to you, so anything the internal service will render to a browser comes back as document text. The renderer is acting as your exfiltration channel, and it works even against services that would never let you talk to them directly.

The PDF is also the reason file:// matters less than it looks. A blocked file scheme costs you direct filesystem reads, but every HTTP-accessible service inside the host remains in scope — and those are where credentials and internal dashboards actually live.

That rendered file also gives a side channel: the size of the response body differs depending on what answered. Sweeping common localhost ports and comparing output sizes turns a blind fetch into a port oracle — a port that isn’t listening produces an error or a timeout, and a port that is listening produces a PDF whose size grows with how much content it rendered. One port came back markedly larger than the rest, which is enough to know something substantive lives there.

Reading a PDF that won’t give up its text

The PDFs were not extractable with the usual command-line tool — it returned nothing at all, which reads like an empty document but isn’t. The renderer embeds CID-mapped fonts: glyphs are referenced by font-internal identifiers rather than Unicode codepoints, so a naive extractor cannot map them back to characters without the font’s ToUnicode tables.

A more thorough PDF parser reads those CMap streams and reassembles the text correctly. The lesson is not to trust a blank extraction result: “no text found” and “text I can’t decode” look identical from the outside, and the difference matters enormously when the entire objective is text sitting inside the PDF.

What was behind the port

The internal service is a metrics dashboard bound to loopback — and it has no authentication at all. Its own text explains why: “Authentication is disabled — network isolation is assumed.” That is the entire bug in one sentence. The developers substituted the network boundary for an authorisation check, and the SSRF removed the network boundary.

The dashboard hands over the internal service topology (including which services have auth and which don’t) alongside the credential the box is asking for. That topology leak is worth more than the flag: it’s a map of what else is reachable, which listeners are bound where, and — crucially — which ones have no authentication. An SSRF that returns that map is an SSRF you can chain, and the “beyond the flag” question (is there a cache, a database, another app on this network?) is answered for you.

Worth noting what the write-up didn’t need: nothing was brute-forced, no credential was cracked, and no code was executed. Two design decisions — fetch any URL server-side, and protect an internal service with network isolation instead of authentication — were sufficient on their own.

Takeaways

  • Server-side fetch features are SSRF by construction — especially in PDF/screenshot/render pipelines, where the response is returned to you as a file.
  • Response size is a usable oracle. When you can’t read the content back, differential sizes still enumerate ports and identify interesting services.
  • A blank text extraction is not an empty document. If a PDF resists extraction, suspect CID-mapped fonts and reach for a parser that honours ToUnicode maps.
  • “Network isolation is assumed” is not authentication. Any SSRF, misrouted proxy or request-smuggling bug converts an assumed boundary into an actual exposure.
  • Enumerate every scheme the fetcher allows — a blocked file:// narrows the attack, it doesn’t close it.