## https://sploitus.com/exploit?id=BD4609B1-6C96-584B-81CB-38A8D42B8A2F
# Forminator PoC validation lab
This repository packages an isolated, loopback-only Docker lab for validating five Forminator security paths over real HTTP. The default target is the exact vulnerable combination used for the complete suite:
- WordPress `6.8.3`
- PHP `7.4`
- Forminator `1.56.1`
- MariaDB `10.11`
The lab downloads WordPress and Forminator only from their official distribution hosts and verifies their SHA-256 digests during the image build. Docker publishes WordPress on `127.0.0.1` only; the database has no host port.
## What is proven
| Test | Access and preconditions | Marker-verified result | Scope boundary |
|---|---|---|---|
| `afu` | Unauthenticated; published form with Upload + Select fields | A raw marker-bearing `.php` file is written beneath the Forminator upload directory | File write is the primary proof. PHP execution depends on upload-directory server configuration. This is the published CVE-2026-15748 path. |
| `poll-ip` | Unauthenticated; published poll using the default IP-based vote limit; no trusted proxy sanitizing headers | Two rows from one client connection store two attacker-selected `Client-IP` values; a repeated value is rejected | Vote-integrity issue, not account compromise or code execution. |
| `role-bypass` | Authenticated delegated form manager with `manage_forminator_modules` + `create_users`, without `promote_users`; public registration form; WordPress user registration enabled | A negative control with explicit fixed-role mode is rejected, while omission of the selector is accepted and a later unauthenticated submission creates an administrator | Configuration/delegation gated. The attacker who submits the final registration request is unauthenticated; the form mutation requires the delegated account. |
| `oi-rce` | Unauthenticated abandoned-form write; abandonment enabled; later administrator entry read; uploads execute PHP | Raw serialized `form_uid` reaches `maybe_unserialize`, bundled `FileCookieJar` writes a marker file, and one bounded `printf` command returns its marker over HTTP | Second-order and trigger-dependent. The lab performs marker-scoped DB cleanup and the file self-removes. |
| `xmlrpc-oi` | Authenticated Forminator manager/administrator; XML-RPC enabled | A NUL-free public-property lab canary executes `__wakeup` and returns a self-removing marker over HTTP | This proves deserialization reachability only. It does **not** prove a production POP gadget or RCE for this transport. |
The four legacy-path tests are pinned to Forminator `1.56.1`. The distinct XML-RPC path is pinned to `1.57.1`, where the newly added `wp_unslash` fallback makes the attacker-supplied serialized value reach `maybe_unserialize`. The launcher can also build `1.55.1` and `1.57.0` for manual vulnerable/fixed-boundary comparison. Forminator `1.57.1` contains the source fixes for the abandoned-form `form_uid` chain and registration-role omission while introducing the separate XML-RPC deserialization path.
## Quick start
Requirements: Docker Engine/Desktop with Compose v2, `bash`, `curl`, and Python 3.
```bash
./lab start
./lab test all
./lab reset
./lab start 1.57.1
./lab test xmlrpc-oi
./lab status
./lab reset
```
The expected final line from each test begins with `RS_OK`. The test is failed if its marker or negative control is missing, even when an HTTP request returned `200`. Exact 1.56.1 is also the negative version-boundary control for `xmlrpc-oi`: it accepts the XML-RPC request but does not instantiate the canary because the serialized string remains escaped. The unsafe fallback first appears in 1.57.1.
To run one case:
```bash
./lab test afu
./lab test poll-ip
./lab test role-bypass
./lab test oi-rce
./lab test xmlrpc-oi
```
To inspect another exact Forminator release:
```bash
./lab reset
./lab start 1.57.1
./lab test xmlrpc-oi
```
The local WordPress UI is at with lab-only credentials `labadmin` / `labpass`. Override the loopback port with `FORMINATOR_LAB_PORT`, using the same value for subsequent commands.
## Validation design
The tests deliberately separate setup from proof:
1. WP-CLI creates the minimum form, poll, role, or user state inside the isolated container.
2. The security-relevant action is sent over HTTP through WordPress and Forminator.
3. The test asserts the filesystem, database, user-role, or canary side effect.
4. A negative control is used where it materially distinguishes a working guard from a bypass.
5. A trap removes marker-scoped state. `./lab reset` removes the lab containers and volumes.
The object-injection RCE test invokes only `printf `. Although the underlying bundled gadget can write an attacker-selected PHP file, the validation path neither persists a shell nor runs an arbitrary operator command.
## Exact source identities
| Artifact | Official URL | SHA-256 |
|---|---|---|
| WordPress 6.8.3 | `https://wordpress.org/wordpress-6.8.3.tar.gz` | `92da34c9960e64d1258652c1ef73c517f7e46ac6dfd2dfc75436d3855af46b0c` |
| Forminator 1.55.1 | `https://downloads.wordpress.org/plugin/forminator.1.55.1.zip` | `4104e2a611a427a2a2c4fa9fab4ad6af71742b27adb1a22d857f40401cf1a0c2` |
| Forminator 1.56.1 | `https://downloads.wordpress.org/plugin/forminator.1.56.1.zip` | `9fc3ec887bba4bcd90be28a82717b04be25dc67bad63222ca35781cf382a603d` |
| Forminator 1.57.0 | `https://downloads.wordpress.org/plugin/forminator.1.57.0.zip` | `fe6a7f36323cfc62cf5fd3beba7b6b75298c2f030a07522d2e98b62c7e8d2913` |
| Forminator 1.57.1 | `https://downloads.wordpress.org/plugin/forminator.1.57.1.zip` | `496bf211d146ce5c6f0c287118c48850ace0abfbfba488a8d00a557fdc0afa22` |
The Docker base images are also pinned by manifest digest in `Dockerfile` and `docker-compose.yml`.
## Important PoC review notes
The older standalone scripts remain for evidence continuity, but the Docker test suite is the supported public path:
- Several original shell scripts contain paths, ports, or nonce keys from the research workstation and are not portable.
- `poc_afu_live_target.sh` cannot prove a write when directory listing is blocked; it now exits `2` and reports the result as unverified instead of treating request construction as proof.
- The legacy shell gadget PoC can leave an executable file if interrupted before cleanup. `./lab test oi-rce` uses the Python driver, a loopback guard, a marker-only command, self-unlink, DB assertions, and a cleanup trap.
## Safety
Run this repository only on systems you own or are explicitly authorized to test. The default launcher refuses unsupported versions, the Python drivers refuse non-loopback targets unless an explicit override is supplied, and the Compose port is bound to `127.0.0.1`.
See [SECURITY.md](SECURITY.md) for disclosure handling. This repository does not include production targets, customer data, access tokens, or evidence from real systems.