← Back to Feed CACHED · 2026-10-30 21:04:57 · CACHE_KEY CVE-2026-103039
CVE-2026-103039 · CWE-502 · Disclosed 2026-10-30

Gravity Forms Post Custom Field PHP Object Injection via unrestricted maybe_unserialize

ASSESSED — NOISGATE
Vendor
—
—
—
Reassessed
—
—
—
Verdict: —
Do you agree?
01 · The Real Story

A form field that treats a serialized attacker string like a signed contract with no signature check

CVE-2026-103039 affects Gravity Forms for WordPress up to and including 3.1.2. When a form contains a Post Custom Field with an input type of List and a content template enabled, GFFormsModel::create_post() passes the submitted value to WordPress' unrestricted maybe_unserialize() instead of the plugin's own GFCommon::maybe_unserialize(allowed_classes => false). That means an unauthenticated attacker can inject a PHP object into post meta.

The vendor labels this CRITICAL at 9.8, but the plugin itself ships no PHP object injection gadget chain. A concrete RCE path exists only when the field is mapped to the protected _wp_page_template meta key, the host has a readable PEAR install, and register_argc_argv is enabled—or when a separate plugin/theme supplies a gadget chain. Those preconditions are real but narrow, so the practical severity is HIGH rather than CRITICAL.

"It's not the plugin's own gadget chain—it's the WordPress core chain on a misconfigured host that makes this unauthenticated object injection worth patching now."
02 · The Attack Path

5 steps from start to impact.

STEP 01

Identify a vulnerable Gravity Forms form

The attacker enumerates public WordPress pages for Gravity Forms forms and inspects form markup or source for a Post Custom Field configured with input type List and a content template. The form must be published and reachable without authentication.
Conditions required:
  • Gravity Forms ≤ 3.1.2 installed
  • Form includes a Post Custom Field with List input type
  • Content template enabled on that field
  • Form is publicly accessible
Where this breaks in practice:
  • Most Gravity Forms deployments do not use this exact field configuration
  • Forms may have CAPTCHA, honeypot, or anti-spam protections
  • Automated scanning for form configuration is not straightforward
Detection/coverage: Wordfence/Patchstack can identify plugin version but not the vulnerable form config; WAF may flag serialized payloads in submissions.
STEP 02

Craft a serialized PHP object payload

The attacker selects a usable gadget chain. For the WordPress core chain, the payload manipulates validate_file() accepting non-scalars and WP_Theme::__toString() resolving an attacker-controlled path. Alternatively, the attacker uses a POP chain from an installed plugin or theme.
Conditions required:
  • Knowledge of target stack and installed plugins/themes
  • A gadget chain available either in WordPress core (with specific host config) or in a third-party component
Where this breaks in practice:
  • Gravity Forms has no native gadget chain
  • The WordPress core chain requires a specific admin mapping to _wp_page_template plus PEAR and register_argc_argv
  • Third-party gadget chains vary widely and may not be present
Detection/coverage: Manual code review of installed plugins/themes for unsafe unserialize or __wakeup/__toString methods; public PoC repositories may not have a working payload for this CVE.
STEP 03

Submit malicious serialized value as unauthenticated user

The attacker submits the form with the serialized PHP object in the vulnerable List field. Gravity Forms processes the submission and passes the value to the unsafe maybe_unserialize() call inside create_post().
Conditions required:
  • Form accepts public submissions
  • No authentication requirement on the form
  • Payload passes any client-side validation
Where this breaks in practice:
  • WAF rules for PHP object injection may block O: or a: serialized strings
  • Gravity Forms honeypot/spam filtering can reject automated submissions
  • Rate limiting or IP reputation may slow large-scale exploitation
Detection/coverage: WAF logs for serialized payloads, server access logs showing POSTs to form endpoints, Gravity Forms entry logs if enabled.
STEP 04

Trigger object instantiation via unsafe unserialize

WordPress maybe_unserialize() instantiates attacker-controlled classes and invokes magic methods. In the WordPress core chain, the eventual path depends on WP_Theme::__toString() and validate_file() handling non-scalars, which only matters when the meta key is _wp_page_template.
Conditions required:
  • Custom field mapped to _wp_page_template (admin-configured reserved meta key)
  • Serialized object matches the gadget chain
  • PHP configuration allows object instantiation (default)
Where this breaks in practice:
  • Mapping a field to _wp_page_template is an uncommon admin misconfiguration
  • Without that mapping, the core chain does not fire; third-party chain becomes the only path
Detection/coverage: No direct detection for object instantiation; server-side PHP error logs may show class not found or fatal errors if payload is malformed.
STEP 05

Achieve remote code execution on the web server

On hosts with a readable PEAR installation and register_argc_argv enabled, the resolved path from WP_Theme::__toString() leads to code execution under the web server user. Alternatively, a third-party gadget chain may lead straight to RCE.
Conditions required:
  • PEAR library readable by the PHP process
  • PHP setting register_argc_argv = On
  • Web server user has permissions to execute the resulting payload
Where this breaks in practice:
  • Many PHP-FPM production configs disable register_argc_argv
  • PEAR is often absent on minimal or hardened WordPress hosts
  • Shared hosting may sandbox PHP and prevent cross-site impact
Detection/coverage: EDR/HIDS may catch PHP process spawning unexpected child processes; file integrity monitoring may detect drop files; WAF can sometimes see command injection follow-up.
03 · Compensating Control

1
HIGH 7.2→IGNORE 0.0
SEVERITY REDUCED
Upgrade Gravity Forms to 3.1.3 or newer — The vendor patch replaces the unsafe maybe_unserialize() call with GFCommon::maybe_unserialize(allowed_classes => false), eliminating object instantiation. Apply within 30 days per noisgate mitigation SLA for HIGH; final remediation deadline is 180 days.
2
HIGH 7.2→LOW 2.0
SEVERITY REDUCED
Temporarily disable content templates on Post Custom Field List inputs — If you cannot patch immediately, turn off content templates on all Post Custom Field fields using List input. This breaks the code path that passes the submitted value to the unsafe unserialize call. Deploy as an emergency mitigation within 3 days if active exploitation appears, otherwise within 30 days.
3
HIGH 7.2→MEDIUM 4.0
SEVERITY REDUCED
Deploy WAF rule to block serialized PHP objects in form submissions — Use a WAF signature to detect and block O: or a: serialized strings in Gravity Forms submission fields. This reduces exposure while patch testing. Deploy within 30 days per HIGH mitigation SLA.
4
HIGH 7.2→HIGH 7.0
Audit Gravity Forms forms for _wp_page_template Post Custom Field mappings — Check all forms for a custom field named _wp_page_template with List input and content template enabled. Remove or rename these fields. This directly blocks the WordPress core gadget chain.
What doesn't work
  • Disabling register_argc_argv alone: stops the WordPress core chain but does not prevent third-party gadget chains.
  • Updating WordPress core: the vulnerable call is in Gravity Forms, not WordPress itself.
  • Restricting PEAR file permissions: blocks one core chain pathway but leaves third-party gadget exploitation intact.
04 · Intelligence Metadata

The supporting signals.

In-the-wild statusNo known active exploitation; not listed in CISA KEV as of this assessment.
Proof-of-concept availabilityNo public PoC repo named after CVE-2026-103039 found on GitHub; conceptual payloads for the WordPress core chain are discussed in WordPress security research. Check pocindex.io before acting.
EPSS score0.00508 (0.508% predicted exploitation probability in next 30 days) — very low predictive signal, but EPSS may lag unreported plugin vulnerabilities.
KEV statusNot listed in Cisco/CISA KEV catalog.
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H = 9.8; assumes a gadget chain is always available, which is not true for the plugin itself.
Affected versionsGravity Forms <= 3.1.2.
Fixed versions3.1.3 or later (first release after 3.1.2; verify against vendor changelog).
Scanning / exposure dataShodan/Censys cannot fingerprint Gravity Forms version or form configuration; public forms are exposed, but the vulnerable field setup is not externally detectable.
Disclosure date2026-10-30
Reporting researcher/orgNot specified in the official CVE record; likely coordinated through Patchstack or Wordfence.

Sources.

  1. CVE.org Record
  2. NVD Detail
  3. Gravity Forms Changelog
  4. Wordfence Threat Intelligence
  5. Patchstack Database
  6. PoC Index
  7. OWASP PHP Object Injection
  8. CISA KEV Catalog
05 · The Call

Final Verdict
↓ DOWNGRADED to HIGH (7.2/10)

Why this verdict

  • Unadjusted CVSS: vendor scores 9.8 because the attack is unauthenticated, remote, and can lead to RCE, but that assumes a gadget chain is permanently present in the vulnerable component.
  • Gadget dependency: Gravity Forms itself ships no POP chain; real damage requires either a third-party plugin/theme gadget or the WordPress core chain, which only fires under specific conditions.
  • WordPress core chain friction: the concrete RCE path requires _wp_page_template mapping, a readable PEAR install, and register_argc_argv enabled. Each precondition is uncommon, and all three together are rare.
  • Role multiplier: WordPress/Gravity Forms runs in the exposed web tier. If RCE lands on a commerce or membership site, the blast radius includes customer PII and site takeover, but the affected component is not canonically a high-value role like IdP, hypervisor, or DC. No CRITICAL floor applies from deployment role alone.
  • Exposure: only forms with Post Custom Field List + content template are reachable; a small subset of Gravity Forms installs meet this configuration.
  • Detection friction: serialized PHP object payloads are noisy; WAFs and Gravity Forms honeypots can block many automated attempts.

Why not higher?

We do not keep CRITICAL because the plugin itself lacks a gadget chain, and the only in-core RCE path requires a misconfigured reserved meta key plus two host-level conditions. The attack is not fire-and-forget against a default Gravity Forms install; it requires environment-specific reconnaissance and a matching gadget.

Why not lower?

We do not drop to MEDIUM because the injection is unauthenticated, Gravity Forms is widely exposed on public sites, and the WordPress core chain gives a concrete RCE outcome on some real hosts. Compromise of a web server hosting customer data or e-commerce flows warrants HIGH even without a universal exploit.

06 · Verification

Crowdsourced verification payload.

Run on the WordPress web host as the web user or a user with read access to wp-content/plugins/gravityforms/. Invoke as bash check_cve-2026-103039.sh /path/to/wordpress with the WordPress root path. Requires read access to plugin files.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/bin/bash
# Check for CVE-2026-103039 in Gravity Forms plugin
WP_ROOT="${1:-.}"
if [ ! -f "$WP_ROOT/wp-content/plugins/gravityforms/gravityforms.php" ]; then
  echo "UNKNOWN: Gravity Forms plugin not found at $WP_ROOT"
  exit 3
fi
GF_VERSION=$(grep -m1 'Version:' "$WP_ROOT/wp-content/plugins/gravityforms/gravityforms.php" | awk '{print $2}' | tr -d '\r')
if [ -z "$GF_VERSION" ]; then
  echo "UNKNOWN: could not determine Gravity Forms version"
  exit 3
fi
# Compare version to 3.1.3
MAJOR=$(echo "$GF_VERSION" | cut -d. -f1)
MINOR=$(echo "$GF_VERSION" | cut -d. -f2)
PATCH=$(echo "$GF_VERSION" | cut -d. -f3)
if [ "$MAJOR" -lt 3 ] || { [ "$MAJOR" -eq 3 ] && [ "$MINOR" -lt 1 ]; } || { [ "$MAJOR" -eq 3 ] && [ "$MINOR" -eq 1 ] && [ "$PATCH" -le 2 ]; }; then
  echo "VULNERABLE: Gravity Forms version $GF_VERSION <= 3.1.2"
  exit 1
else
  echo "PATCHED: Gravity Forms version $GF_VERSION >= 3.1.3"
  exit 0
fi