CVE-2026-32521

WP Custom Admin Interface <= 7.42 - Authenticated (Subscriber+) Stored Cross-Site Scripting

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.4
CVSS Score
6.4
CVSS Score
medium
Severity
7.43
Patched in
7d
Time to patch

Description

The WP Custom Admin Interface plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 7.42 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with subscriber-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=7.42
PublishedMarch 20, 2026
Last updatedMarch 26, 2026

What Changed in the Fix

Changes introduced in v7.43

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This plan outlines the research and exploitation process for CVE-2026-32521, a Stored Cross-Site Scripting (XSS) vulnerability in the "WP Custom Admin Interface" plugin. ### 1. Vulnerability Summary The **WP Custom Admin Interface** plugin (<= 7.42) is vulnerable to Stored XSS because it fails to p…

Show full research plan

This plan outlines the research and exploitation process for CVE-2026-32521, a Stored Cross-Site Scripting (XSS) vulnerability in the "WP Custom Admin Interface" plugin.

1. Vulnerability Summary

The WP Custom Admin Interface plugin (<= 7.42) is vulnerable to Stored XSS because it fails to perform capability checks on its AJAX settings-saving functionality and does not adequately sanitize or escape administrative settings that are rendered in the WordPress dashboard. Specifically, a low-privileged user (Subscriber) can trigger an AJAX action intended for administrators to update plugin settings, such as the "Custom Footer" text. This text is then rendered unescaped in the footer of every admin page, allowing for execution of arbitrary JavaScript in the context of an Administrator.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • AJAX Action: wp_custom_admin_interface_save_settings (inferred from plugin patterns)
  • Vulnerable Parameter: data (specifically the sub-field wp_custom_admin_interface_custom_footer)
  • Authentication Level: Subscriber or higher (requires access to /wp-admin/profile.php to obtain a nonce).
  • Preconditions: The plugin must be active. No specific configuration is required, as the "Custom Footer" feature is a core part of the plugin.

3. Code Flow

  1. Registration: In wp-custom-admin-interface.php, the plugin hooks into admin_init which calls wp_custom_admin_interface_settings_init (defined in inc/options/options-output.php).
  2. AJAX Handler: The plugin registers a handler (e.g., wp_ajax_wp_custom_admin_interface_save_settings). This handler verifies a nonce but lacks a current_user_can('manage_options') check.
  3. Storage: The handler receives the data parameter (often a serialized string of form inputs) and uses update_option() to save the wp_custom_admin_interface_settings_GeneralSettings option.
  4. Sink: The value of wp_custom_admin_interface_custom_footer within that option is retrieved and echoed in the admin footer via the admin_footer_text or update_footer filter, without using esc_html() or wp_kses().

4. Nonce Acquisition Strategy

The plugin enqueues its administrative scripts and localizes a nonce for AJAX operations. Even Subscribers can access the WordPress dashboard (specifically profile.php), where these scripts are loaded.

  1. Navigate: Use browser_navigate to go to http://localhost:8080/wp-admin/profile.php as a Subscriber.
  2. Extract Nonce: The nonce is stored in a localized JavaScript object. Use browser_eval to retrieve it:
    // Inferred localized object and key name based on plugin structure
    window.wp_custom_admin_interface_ajax_obj?.nonce
    
    Note: If the variable name differs, inspect the page source for wp_localize_script output or search for "nonce" in the global window object.

5. Exploitation Strategy

  1. Preparation: Log in as a Subscriber user.
  2. Nonce Retrieval: Obtain the security nonce using the strategy in Section 4.
  3. Injection Request: Send a POST request to admin-ajax.php to update the general settings.
    • Tool: http_request
    • URL: http://localhost:8080/wp-admin/admin-ajax.php
    • Method: POST
    • Headers: Content-Type: application/x-www-form-urlencoded
    • Body Parameters:
      • action: wp_custom_admin_interface_save_settings
      • security: [EXTRACTED_NONCE]
      • data: wp_custom_admin_interface_settings_GeneralSettings%5Bwp_custom_admin_interface_custom_footer%5D%3D%3Cscript%3Ealert%28document.domain%29%3C%2Fscript%3E
        (Note: The 'data' parameter is often a URL-encoded string representing the serialized form.)

6. Test Data Setup

  1. Install Plugin: Ensure WP Custom Admin Interface v7.42 is installed and active.
  2. Create User: Create a Subscriber user.
    wp user create attacker attacker@example.com --role=subscriber --user_pass=password
    
  3. Target Page: No specific page creation is needed, as the footer injection affects all admin pages.

7. Expected Results

  • The AJAX request should return a success status (likely 1 or a JSON success message).
  • The WordPress option wp_custom_admin_interface_settings_GeneralSettings will now contain the <script> payload.
  • When any user (including an Administrator) navigates to any page in the /wp-admin/ area, an alert box showing the document domain will appear.

8. Verification Steps

  1. Check Database: Verify the option was updated using WP-CLI:
    wp option get wp_custom_admin_interface_settings_GeneralSettings
    
    Confirm that the wp_custom_admin_interface_custom_footer key contains the <script> tag.
  2. Verify Rendering: Navigate to the dashboard as an Administrator and check the HTML source for the footer:
    # Using browser_navigate as Admin then checking source
    # Look for: <script>alert(document.domain)</script>
    

9. Alternative Approaches

If wp_custom_admin_interface_save_settings is not the correct action name:

  1. Search the source code for add_action( 'wp_ajax_ to identify the specific save handler.
  2. Try injecting into other fields registered in inc/options/options-output.php, such as:
    • wp_custom_admin_interface_custom_css_code (Custom Code section)
    • wp_custom_admin_interface_maintenance_text (Maintenance section)
  3. If the data parameter isn't a serialized string, try sending the settings as direct POST parameters:
    action=wp_custom_admin_interface_save_settings&security=[NONCE]&wp_custom_admin_interface_custom_footer=<script>alert(1)</script>
Research Findings
Static analysis — not yet PoC-verified

Summary

The WP Custom Admin Interface plugin for WordPress is vulnerable to Stored Cross-Site Scripting (XSS) due to a lack of authorization checks on its AJAX settings-saving functionality and insufficient escaping on administrative settings. Authenticated attackers with subscriber-level access can inject arbitrary scripts into plugin options, such as the custom footer or user-related fields, which then execute in the context of higher-privileged users browsing the admin dashboard.

Vulnerable Code

// inc/options/options-output.php line 1982
                echo '<li class="user-item"><div><i class="fa fa-eye-slash remove-user-item" title="Hide user" aria-hidden="true"></i><span id="user-name" style="font-weight: bold;" data="'.$userId.'">'.$userDisplayName.' <em style="font-weight: normal;">('.$userRole.')</em></span></div></li>';

---

// wp-custom-admin-interface.php line 1880
        $outputOfUsersAndRolesSelection .= 'User: '.$userDisplayName.'('.$userId.'),';

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wp-custom-admin-interface/7.42/inc/options/options-output.php /home/deploy/wp-safety.org/data/plugin-versions/wp-custom-admin-interface/7.43/inc/options/options-output.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wp-custom-admin-interface/7.42/inc/options/options-output.php	2026-01-19 21:25:46.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-custom-admin-interface/7.43/inc/options/options-output.php	2026-02-10 22:23:20.000000000 +0000
@@ -1979,7 +1979,7 @@
                 
                 
                 
-                echo '<li class="user-item"><div><i class="fa fa-eye-slash remove-user-item" title="Hide user" aria-hidden="true"></i><span id="user-name" style="font-weight: bold;" data="'.$userId.'">'.$userDisplayName.' <em style="font-weight: normal;">('.$userRole.')</em></span></div></li>';  
+                echo '<li class="user-item"><div><i class="fa fa-eye-slash remove-user-item" title="Hide user" aria-hidden="true"></i><span id="user-name" style="font-weight: bold;" data="'.esc_attr($userId).'">'.esc_html($userDisplayName).' <em style="font-weight: normal;">('.esc_html($userRole).')</em></span></div></li>';  
 
             }
             //end container
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wp-custom-admin-interface/7.42/wp-custom-admin-interface.php /home/deploy/wp-safety.org/data/plugin-versions/wp-custom-admin-interface/7.43/wp-custom-admin-interface.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wp-custom-admin-interface/7.42/wp-custom-admin-interface.php	2026-01-19 21:25:46.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-custom-admin-interface/7.43/wp-custom-admin-interface.php	2026-02-10 22:23:20.000000000 +0000
@@ -1877,8 +1877,7 @@
             $userDisplayName = $userFirstName.' '.$userLastName;
         }
 
-
-        $outputOfUsersAndRolesSelection .= 'User: '.$userDisplayName.'('.$userId.'),';
+        $outputOfUsersAndRolesSelection .= 'User: '.esc_html($userDisplayName).'('.intval($userId).'),';

Exploit Outline

The exploit involves two main steps: acquiring a valid nonce and then performing a CSRF-like AJAX request to update settings. 1. Log in to the WordPress site as a Subscriber-level user. 2. Navigate to `/wp-admin/profile.php` and extract the AJAX nonce from the localized script object (e.g., `wp_custom_admin_interface_ajax_obj.nonce`). 3. Construct a POST request to `/wp-admin/admin-ajax.php` using the action `wp_custom_admin_interface_save_settings`. 4. In the `data` parameter, provide a payload that targets settings like `wp_custom_admin_interface_custom_footer` containing a malicious `<script>` tag. 5. Because the plugin lacks a capability check (`current_user_can('manage_options')`) in its save handler, the settings are updated. 6. The payload will execute whenever an administrator logs in and views any page in the WordPress dashboard where the custom footer or other modified settings are rendered.

Check if your site is affected.

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