Gravity Forms CVE-2026-84434 – Unauthenticated File Upload to RCE

Threat Overview

CVE-2026-84434 is a critical arbitrary file-upload vulnerability in the Gravity Forms WordPress plugin. A hidden File Upload field can bypass extension validation, while rejected upload state can later reach upload_file() without equivalent re-validation. On a publicly accessible form configured with a hidden file-upload field, an unauthenticated attacker can upload files that may be executable and potentially achieve remote code execution.

References

Impacted Systems

Gravity Forms through version 3.1.0.4 when a publicly accessible form contains a File Upload field with Visibility set to Hidden. Patched in 3.1.1. WordPress/PHP deployment is required.

Why this matters

The flaw provides an unauthenticated path to dangerous file upload on Internet-facing WordPress sites and can become code execution where uploaded files are executable by the web stack.

Exploitation Status

The vulnerability is publicly disclosed and technically exploitable. Current reporting includes a VulnCheck exploitation claim, but it is not in CISA KEV as of this run, so active exploitation is not represented as independently confirmed by CISA.

What this hunt looks for

Gravity Forms upload activity, executable PHP creation under WordPress paths, requests to uploaded PHP, web-server child processes, new outbound destinations, and persistence behavior.

Required logs

WAF or reverse-proxy data in CommonSecurityLog, Defender XDR DeviceFileEvents, DeviceProcessEvents and DeviceNetworkEvents, and Syslog where available.

First Pass – Gravity Forms Upload Requests

CommonSecurityLog
| where TimeGenerated >= ago(30d)
| where RequestURL has_any ("/wp-admin/admin-ajax.php","/wp-json/","/wp-content/uploads/")
| where Message has_any ("gravityforms","gform","upload")
| project TimeGenerated,SourceIP,DestinationIP,RequestMethod,RequestURL,DeviceAction,Message
| order by TimeGenerated desc

PHP Uploads Under WordPress Content

DeviceFileEvents
| where Timestamp >= ago(30d)
| where FolderPath has_any ("wp-content","uploads")
| where FileName endswith ".php" or FileName endswith ".phtml" or FileName endswith ".phar"
| where ActionType in~ ("FileCreated","FileModified")
| project Timestamp,DeviceName,ActionType,FileName,FolderPath,SHA256,InitiatingProcessFileName,InitiatingProcessCommandLine
| order by Timestamp desc

Web Server Spawning Shells

DeviceProcessEvents
| where Timestamp >= ago(30d)
| where InitiatingProcessFileName in~ ("httpd","apache2","nginx","php","php-fpm")
| where FileName in~ ("sh","bash","dash","curl","wget","python","python3","powershell.exe","cmd.exe")
| project Timestamp,DeviceName,AccountName,InitiatingProcessFileName,FileName,ProcessCommandLine,SHA256
| order by Timestamp desc

Web Server Outbound Connections

DeviceNetworkEvents
| where Timestamp >= ago(30d)
| where InitiatingProcessFileName in~ ("httpd","apache2","nginx","php","php-fpm")
| project Timestamp,DeviceName,InitiatingProcessFileName,InitiatingProcessCommandLine,RemoteUrl,RemoteIP,RemotePort
| order by Timestamp desc

New Destinations From Web Server Processes

let Known=DeviceNetworkEvents
| where Timestamp between (ago(30d)..ago(2d))
| where InitiatingProcessFileName in~ ("httpd","apache2","nginx","php","php-fpm")
| summarize by DeviceId,RemoteIP,RemoteUrl;
DeviceNetworkEvents
| where Timestamp >= ago(2d)
| where InitiatingProcessFileName in~ ("httpd","apache2","nginx","php","php-fpm")
| join kind=leftanti Known on DeviceId,RemoteIP,RemoteUrl
| project Timestamp,DeviceName,InitiatingProcessFileName,RemoteUrl,RemoteIP,RemotePort
| order by Timestamp desc

Requests to Uploaded PHP

CommonSecurityLog
| where TimeGenerated >= ago(30d)
| where RequestURL has "wp-content/uploads"
| where RequestURL matches regex @"(?i)\.(php|phtml|phar)(\?|$)"
| project TimeGenerated,SourceIP,DestinationIP,RequestMethod,RequestURL,DeviceAction,Message
| order by TimeGenerated desc

WordPress File Modification Burst

DeviceFileEvents
| where Timestamp >= ago(30d)
| where FolderPath has_any ("wp-content","wp-admin","wp-includes")
| where ActionType in~ ("FileCreated","FileModified")
| summarize FileChanges=count(),Files=make_set(FileName,50) by DeviceName,bin(Timestamp,10m),InitiatingProcessFileName
| where FileChanges >= 10
| order by Timestamp desc

Syslog Webshell Fallback

Syslog
| where TimeGenerated >= ago(30d)
| where ProcessName in~ ("apache2","httpd","nginx","php-fpm","php")
| where SyslogMessage has_any ("POST ","wp-content/uploads",".php","curl ","wget ","bash","sh -c")
| project TimeGenerated,Computer,HostName,ProcessName,SeverityLevel,SyslogMessage
| order by TimeGenerated desc

Detection Notes

Highest signal is executable PHP creation under upload/content paths followed by requests or web-server execution. CommonSecurityLog fields vary by parser. The hidden upload field is a configuration prerequisite, so inventory and form configuration validation are essential.

Leave a comment