# CVE-2026-103039 — Gravity Forms PHP Object Injection **Unauthenticated PHP object injection leading to remote code execution (RCE)** > **Author:** NØXCRYPT · [@noxcrypt-x](https://github.com/noxcrypt-x) > > **Disclaimer:** This repository is provided for security research and authorized testing only. Do not use it against systems you do not own or have explicit permission to test. The author is not responsible for any misuse. > > **Repository contents:** PoC script and documentation only. The optional PHP payload generator (`gen_payload.php`) is intentionally not included. | | | |---|---| | Product | Gravity Forms (WordPress plugin) | | Affected versions | All versions up to 3.1.2 | | Fixed in | 3.1.3 | | Class | CWE-502 — Deserialization of Untrusted Data | | CVSS 3.1 | 9.8 Critical — `AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H` | | Identifiers | CVE-2026-103039 | | Vector | Post Custom Field (List input type) with content template enabled | --- ## 1. Summary `GFFormsModel::create_post()` passes the submitted value of a Post Custom Field (List input type, content template enabled) to WordPress' unrestricted global `maybe_unserialize()` instead of Gravity Forms' hardened `GFCommon::maybe_unserialize()`, which sets `allowed_classes => false`. This allows an unauthenticated attacker to inject PHP objects into post meta. No gadget chain ships in the plugin itself. RCE requires one of the following: - A POP chain provided by another plugin or theme installed on the target, or - The `_wp_page_template` meta-key vector on a WordPress version where `validate_file()` accepts non-scalars (pre-fix), combined with a readable PEAR installation and `register_argc_argv` enabled. --- ## 2. Root Cause Gravity Forms uses two different deserialization helpers: - `GFCommon::maybe_unserialize()` — hardened, with `allowed_classes => false` - WordPress global `maybe_unserialize()` — unrestricted For the affected field type, the submitted value reaches the unrestricted helper. A serialized PHP object submitted as a List field value is therefore stored and later deserialized as a live object. The vulnerable configuration is: - A Gravity Forms form containing a **Post Custom Field** - Input type set to **List** - **Create Content Template** enabled - The field mapped to a meta key such as `_wp_page_template` for the page-template vector --- ## 3. PoC File: `CVE-2026-103039.py` (Python 3, requires only `requests`). ```bash pip install -r requirements.txt ``` The script includes a built-in terminal banner and author credit: ```text CVE-2026-103039 — Gravity Forms PHP Object Injection Unauthenticated RCE via Post Custom Field (List type) Programmer : NØXCRYPT Handle : noxcrypt-x ``` ### Version detection ```bash python CVE-2026-103039.py -u https://target --detect ``` ### Discover forms and fields ```bash python CVE-2026-103039.py -u https://target --list-forms python CVE-2026-103039.py -u https://target --auto-detect ``` ### Submit a payload ```bash python CVE-2026-103039.py -u https://target \ --form-id 1 --field-id 2 \ --payload "O:13:\"WP_HTML_Token\":..." \ --form-page https://target/contact/ ``` ### Scan all fields of a form ```bash python CVE-2026-103039.py -u https://target --form-id 1 --scan-fields ``` ### Mass scan ```bash python CVE-2026-103039.py -l targets.txt --detect --threads 20 -o results.json ``` ### Main options | Option | Description | |---|---| | `-u, --url` | Single target URL | | `-l, --list` | File with target URLs | | `--detect` | Version detection only | | `--form-id` | Gravity Forms form ID | | `--field-id` | Post Custom Field field ID (List type) | | `--form-page` | URL of the page containing the form | | `--payload` | Serialized PHP payload string | | `--payload-file` | Read payload from file | | `--cmd` | Command for the `wp_html_token` payload | | `--oob` | Out-of-band callback value | | `--cookie` | Authenticated cookie | | `--insecure` | Disable TLS verification | | `--timeout` | HTTP timeout in seconds | | `--output, -o` | Save results to JSON | | `--threads` | Concurrent workers for target lists | | `--auto-detect` | Auto-detect form and field IDs | | `--list-forms` | Discover and print forms/fields only | | `--scan-fields` | Try the payload against every field | | `--verify` / `--no-verify` | Enable or disable RCE verification | | `--trigger-url` | URL to fetch to trigger the gadget chain | ### Payload generation Payload generation requires a PHP binary and WordPress source. The helper `gen_payload.php` is **not included** in this repository. Supply a serialized payload using `--payload` or `--payload-file`. If you have your own compatible generator, point the script to it with `--gen-payload --gen-script /path/to/gen_payload.php`. --- ## 4. Verification Status The script was reviewed against its own source and the documented vulnerability. It supports version detection, form/field discovery, submission, field scanning, multi-target threading, and post-submission RCE verification. End-to-end RCE depends on a usable gadget chain from the target's plugins or theme, or on the `_wp_page_template` vector on a vulnerable WordPress version. No live target was used for validation in this repository. --- ## 5. Remediation 1. **Update Gravity Forms to 3.1.3 or later.** 2. Avoid Post Custom Fields with the List input type and content templates enabled unless strictly required. 3. Audit existing forms for Post Custom Fields mapped to sensitive meta keys such as `_wp_page_template`. 4. Keep WordPress core, plugins, and themes updated to reduce available gadget chains. 5. Restrict object deserialization in custom code by using `allowed_classes => false` where appropriate. --- ## 6. References - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-103039 - Gravity Forms: https://www.gravityforms.com/ - WordPress `maybe_unserialize()`: https://developer.wordpress.org/reference/functions/maybe_unserialize/