WP Custom Admin Interface <= 7.42 - Authenticated (Subscriber+) Stored Cross-Site Scripting
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:NTechnical Details
<=7.42What Changed in the Fix
Changes introduced in v7.43
Source Code
WordPress.org SVNThis 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-fieldwp_custom_admin_interface_custom_footer) - Authentication Level: Subscriber or higher (requires access to
/wp-admin/profile.phpto 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
- Registration: In
wp-custom-admin-interface.php, the plugin hooks intoadmin_initwhich callswp_custom_admin_interface_settings_init(defined ininc/options/options-output.php). - AJAX Handler: The plugin registers a handler (e.g.,
wp_ajax_wp_custom_admin_interface_save_settings). This handler verifies a nonce but lacks acurrent_user_can('manage_options')check. - Storage: The handler receives the
dataparameter (often a serialized string of form inputs) and usesupdate_option()to save thewp_custom_admin_interface_settings_GeneralSettingsoption. - Sink: The value of
wp_custom_admin_interface_custom_footerwithin that option is retrieved and echoed in the admin footer via theadmin_footer_textorupdate_footerfilter, without usingesc_html()orwp_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.
- Navigate: Use
browser_navigateto go tohttp://localhost:8080/wp-admin/profile.phpas a Subscriber. - Extract Nonce: The nonce is stored in a localized JavaScript object. Use
browser_evalto retrieve it:
Note: If the variable name differs, inspect the page source for// Inferred localized object and key name based on plugin structure window.wp_custom_admin_interface_ajax_obj?.noncewp_localize_scriptoutput or search for "nonce" in the global window object.
5. Exploitation Strategy
- Preparation: Log in as a Subscriber user.
- Nonce Retrieval: Obtain the
securitynonce using the strategy in Section 4. - Injection Request: Send a POST request to
admin-ajax.phpto 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_settingssecurity:[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.)
- Tool:
6. Test Data Setup
- Install Plugin: Ensure WP Custom Admin Interface v7.42 is installed and active.
- Create User: Create a Subscriber user.
wp user create attacker attacker@example.com --role=subscriber --user_pass=password - 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
1or a JSON success message). - The WordPress option
wp_custom_admin_interface_settings_GeneralSettingswill 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
- Check Database: Verify the option was updated using WP-CLI:
Confirm that thewp option get wp_custom_admin_interface_settings_GeneralSettingswp_custom_admin_interface_custom_footerkey contains the<script>tag. - 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:
- Search the source code for
add_action( 'wp_ajax_to identify the specific save handler. - 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)
- If the
dataparameter 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>
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
@@ -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 @@ -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.