Nine 0-days in HTML-to-PDF libraries: ReportLab RCE, ReLaXed RCE, WeasyPrint SSRF/LFR, mPDF, Dompdf and TCPDF flaws
Researcher Pedro Cruz (Hakai Security / The Hacking Club) disclosed nine vulnerabilities in widely used server-side PDF generation libraries, with a combined footprint of over 90 million monthly installs. The cases range from RCE (ReportLab, ReLaXed) through arbitrary file read and SSRF (WeasyPrint, Browsershot) to DoS and file/directory existence oracles (mPDF, Dompdf, TCPDF). The common cause is user input concatenated into templates or HTML and passed to a renderer that runs on the backend with filesystem, internal network and possibly cloud metadata access. The article also gives a six-step methodology for testing any "Export PDF" feature. No in-the-wild exploitation is reported.
Detalhes técnicos
- ReportLab (<4.4.8, 50.9M downloads/month):
decode_labelinreportlab/lib/utils.pycallspickle.loads(). It is reachable through the<onDraw name="_indexAdd" label="..."/>tag inside a Paragraph when the app usesSimpleIndex(getCanvasMaker()) orPageAccumulator, giving RCE from user-controlled text. Fixed in 4.4.8 (2026-01-15). - ReLaXed: user input concatenated into the Pug template source lets an attacker open a
- ...code block, becauserequireis exposed in thepug.render()context. This gives RCE as the ReLaXed process. - WeasyPrint
<link rel="attachment" href="file://...">gives local file read (CVE-2024-28184, credited to @nullie, not the author's discovery). - WeasyPrint <68.0 (CVE-2025-68616, CVSS 7.5):
default_url_fetcherusesurllib.request.urlopen, which follows 301/302/307 redirects without re-entering a customurl_fetcher. This bypasses SSRF blocklists and exposed AWS STS credentials via 169.254.169.254. Advisory GHSA-983w-rhvv-gwmv. - Browsershot (spatie): the
$unsafeProtocolsblocklist misses UNC paths, so Chromium resolves\\localhost/etc/passwdas a local file (LFI). - mPDF: the manual SVG
<!ENTITY>expander built onpreg_replaceallows a Billion Laughs DoS (482 bytes of input, ~1 GB RAM). A second issue is directory enumeration viaLocalContentLoader::loadwarnings. - Dompdf (CVE-2026-55555, CVSS 6.3, CWE-770):
@font-face src:url(file://...)causes OOM only when the file exists, creating a file existence oracle that also bypasses chroot. Fixed in 3.1.6+. - TCPDF:
Image()error messages leak the full path whengetimagesize()fails, so one request can test thousands of paths. - Methodology and bypasses: HTML injection test, JS execution test, tag-by-tag attacks, encoding, alternative protocols and tags,
<base href>, redirects, IPv6 forms of the metadata IP (6to4, NAT64), and side-channels.
Remediação
- Update ReportLab to ≥4.4.8, WeasyPrint to ≥68.0 and Dompdf to ≥3.1.6.
- Never concatenate input into template source; pass it as locals and strip
requirefrom the context. - Use allowlists (protocol, host, path) rather than blocklists, and revalidate on every redirect hop.
- Classify IP addresses by class, including tunneled prefixes, not by string matching.
- Disable JS in headless engines, isolate the worker's network, and avoid loading real session cookies.
- Return generic error messages to close the oracles.
- Avoid unsafe parsers on user data (
pickle.loads,eval,unserialize,preg_replacein loops).
Linha do tempo
-
Nine 0-days in HTML-to-PDF libraries: ReportLab RCE, ReLaXed RCE, WeasyPrint SSRF/LFR, mPDF, Dompdf and TCPDF flaws
ler a análise desta fonte ocultar
Researcher Pedro Cruz (Hakai Security / The Hacking Club) disclosed nine vulnerabilities in widely used server-side PDF generation libraries, with a combined footprint of over 90 million monthly installs. The cases range from RCE (ReportLab, ReLaXed) through arbitrary file read and SSRF (WeasyPrint, Browsershot) to DoS and file/directory existence oracles (mPDF, Dompdf, TCPDF). The common cause is user input concatenated into templates or HTML and passed to a renderer that runs on the backend with filesystem, internal network and possibly cloud metadata access. The article also gives a six-step methodology for testing any "Export PDF" feature. No in-the-wild exploitation is reported.
Technical Details
- ReportLab (<4.4.8, 50.9M downloads/month):
decode_labelinreportlab/lib/utils.pycallspickle.loads(). It is reachable through the<onDraw name="_indexAdd" label="..."/>tag inside a Paragraph when the app usesSimpleIndex(getCanvasMaker()) orPageAccumulator, giving RCE from user-controlled text. Fixed in 4.4.8 (2026-01-15). - ReLaXed: user input concatenated into the Pug template source lets an attacker open a
- ...code block, becauserequireis exposed in thepug.render()context. This gives RCE as the ReLaXed process. - WeasyPrint
<link rel="attachment" href="file://...">gives local file read (CVE-2024-28184, credited to @nullie, not the author's discovery). - WeasyPrint <68.0 (CVE-2025-68616, CVSS 7.5):
default_url_fetcherusesurllib.request.urlopen, which follows 301/302/307 redirects without re-entering a customurl_fetcher. This bypasses SSRF blocklists and exposed AWS STS credentials via 169.254.169.254. Advisory GHSA-983w-rhvv-gwmv. - Browsershot (spatie): the
$unsafeProtocolsblocklist misses UNC paths, so Chromium resolves\\localhost/etc/passwdas a local file (LFI). - mPDF: the manual SVG
<!ENTITY>expander built onpreg_replaceallows a Billion Laughs DoS (482 bytes of input, ~1 GB RAM). A second issue is directory enumeration viaLocalContentLoader::loadwarnings. - Dompdf (CVE-2026-55555, CVSS 6.3, CWE-770):
@font-face src:url(file://...)causes OOM only when the file exists, creating a file existence oracle that also bypasses chroot. Fixed in 3.1.6+. - TCPDF:
Image()error messages leak the full path whengetimagesize()fails, so one request can test thousands of paths. - Methodology and bypasses: HTML injection test, JS execution test, tag-by-tag attacks, encoding, alternative protocols and tags,
<base href>, redirects, IPv6 forms of the metadata IP (6to4, NAT64), and side-channels.
Remediation
- Update ReportLab to ≥4.4.8, WeasyPrint to ≥68.0 and Dompdf to ≥3.1.6.
- Never concatenate input into template source; pass it as locals and strip
requirefrom the context. - Use allowlists (protocol, host, path) rather than blocklists, and revalidate on every redirect hop.
- Classify IP addresses by class, including tunneled prefixes, not by string matching.
- Disable JS in headless engines, isolate the worker's network, and avoid loading real session cookies.
- Return generic error messages to close the oracles.
- Avoid unsafe parsers on user data (
pickle.loads,eval,unserialize,preg_replacein loops).
- ReportLab (<4.4.8, 50.9M downloads/month):