You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: src/pentesting-web/deserialization/php-deserialization-+-autoload-classes.md
+32-5Lines changed: 32 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -12,9 +12,12 @@ Steps:
12
12
13
13
- You have found a **deserialization** and there **isn’t any gadget** in the current app code
14
14
- You can abuse a **`spl_autoload_register`** function like the following to **load any local file with `.php` extension**
15
-
- For that you use a deserialization where the name of the class is going to be inside **`$name`**. You **cannot use "/" or "."** in a class name in a serialized object, but the **code** is **replacing** the **underscores** ("\_") **for slashes** ("/"). So a class name such as `tmp_passwd` will be transformed into `/tmp/passwd.php` and the code will try to load it.\
15
+
- For that you use a deserialization where the name of the class is going to be inside **`$name`**. You **cannot use "/" or "."** in a class name in a serialized object, but the **code** is **replacing** the **underscores** ("_") **for slashes** ("/"). So a class name such as `tmp_passwd` will be transformed into `/tmp/passwd.php` and the code will try to load it.\
16
16
A **gadget example** will be: **`O:10:"tmp_passwd":0:{}`**
- Now, we can **create and write a file**, however, the user **couldn’t write in any folder inside the web server**. So, as you can see in the payload, PHP calling **`system`** with some **base64** is created in **`/tmp/a.php`**. Then, we can **reuse the first type of payload** that we used to as LFI to load the composer loader of the other webapp t**o load the generated `/tmp/a.php`** file. Just add it to the deserialization gadget:
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.
69
74
75
+
### Recent phpggc goodies (2025)
76
+
77
+
- The **phpggc master branch keeps adding chains**: OpenCart/RCE2, Drupal/FD1/SQLI1/XXE1, WordPress/YoastSEO/FW1 and others landed in 2025 — useful when the target app shares vendor code with those projects. A quick way to search is `phpggc -l | grep -E "OpenCart|Drupal|Yoast"` (update your clone first).
78
+
- When mixing gadgets across apps via autoloading, remember **private properties in gadget definitions may be dropped** when classes are re-declared differently in the target; edit the gadget’s `chain.php` to make properties `public` if the payload arrives with empty values (same trick shown above).
`phpunit` before **8.5.52 / 9.6.34 / 10.5.63 / 11.5.50 / 12.5.8** (CVE-2026-24765) unserialized arbitrary PHP objects from `.coverage` files produced by the **PHPT runner**. In CI pipelines where untrusted contributors can push tests, dropping a crafted `.coverage` file triggers deserialization as soon as the suite runs — no web access needed.
83
+
84
+
**Attack flow**
85
+
86
+
1. Place a malicious `.coverage` file in the repo (or artifact) containing a serialized gadget that exists in the test dependencies (e.g., a Monolog or Guzzle chain from phpggc).
87
+
2. Submit a PR; when CI executes `phpunit --configuration phpunit.xml`, the PHPT runner reads the coverage file and deserializes the gadget, giving **RCE inside the runner container**.
88
+
3. This is especially nasty when tests mount CI secrets (cloud creds, deployment keys).
89
+
90
+
**Minimal malicious coverage stub** (drop alongside a PHPT test):
91
+
```php
92
+
<?php
93
+
$payload = file_get_contents('php://stdin'); // serialized gadget from phpggc
94
+
file_put_contents('exploit.coverage', $payload);
95
+
```
96
+
Run the PHPT so phpunit consumes `exploit.coverage`.
97
+
70
98
## TCPDF `__destruct` POP chain for arbitrary file deletion
71
99
72
100
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:
@@ -100,8 +128,7 @@ The call to `file_exists()` deserializes the metadata, instantiates TCPDF, and i
100
128
## References
101
129
102
130
-[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/)
0 commit comments