White Label CMS <= 2.7.12 - Authenticated (Administrator+) Stored Cross-Site Scripting via Import Settings
Description
The White Label CMS plugin for WordPress is vulnerable to Stored Cross-Site Scripting via admin settings in all versions up to, and including, 2.7.12 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with administrator-level permissions and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page. This only affects multi-site installations and installations where unfiltered_html has been disabled.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:C/C:L/I:L/A:NTechnical Details
<=2.7.12What Changed in the Fix
Changes introduced in v2.7.13
Source Code
WordPress.org SVN# Exploitation Research Plan - CVE-2026-11898 ## 1. Vulnerability Summary The **White Label CMS** plugin for WordPress (versions <= 2.7.12) is vulnerable to **Stored Cross-Site Scripting (XSS)** via its "Import Settings" functionality. The vulnerability exists because the `WLCMS_Settings::import()`…
Show full research plan
Exploitation Research Plan - CVE-2026-11898
1. Vulnerability Summary
The White Label CMS plugin for WordPress (versions <= 2.7.12) is vulnerable to Stored Cross-Site Scripting (XSS) via its "Import Settings" functionality. The vulnerability exists because the WLCMS_Settings::import() method directly decodes JSON input from an uploaded file and saves it to the WordPress options table using update_option() without any sanitization or validation of the setting values. While the standard settings save mechanism (WLCMS_Settings::store()) applies wlcms_kses() to inputs, the import mechanism bypasses this protection. This allows an Administrator (or Super Admin in Multisite) to inject arbitrary scripts that execute when the settings are rendered, particularly affecting environments where unfiltered_html is disabled.
2. Attack Vector Analysis
- Endpoint:
admin-inithook via the plugin's settings page. - Action: Form submission to
options-general.php?page=wlcms-plugin.php. - Vulnerable Parameter:
import_file(file upload containing JSON). - Required Authentication: Administrator or Super Admin.
- Preconditions:
- Plugin version <= 2.7.12.
- Multisite installation OR a single-site installation where the
unfiltered_htmlcapability has been disabled for administrators (e.g., via a security plugin orwp-config.php).
- Target Setting: The
dashboard_titlesetting is an ideal target because it is rendered inside a JavaScript string viawlcms_add_js(), allowing for JS string breakout.
3. Code Flow
- Entry Point:
includes/classes/Settings.php:WLCMS_Settings::init()is hooked toadmin_init. - Handler:
init()calls$this->import(). - Authentication/Nonce Check:
import()checksis_wlcms_super_admin().- It verifies the nonce:
wp_verify_nonce($_POST['wlcms-settings_nonce'], 'wlcms-settings-action').
- File Processing:
- It retrieves the uploaded file:
$temp_file_raw = $_FILES['import_file']['tmp_name']. - It reads and decodes the JSON:
$settings = (array)json_decode(file_get_contents($temp_file), true);.
- It retrieves the uploaded file:
- Vulnerable Sink:
- It calls
$this->setAll($settings)and then$this->save(). save()executesupdate_option("wlcms_options", $this->settings). No sanitization occurs here.
- It calls
- Rendering (XSS):
includes/classes/Admin_Dashboard.php:dashboard_title()retrieves the setting:$title = $this->get_settings('dashboard_title').- It constructs a string:
$dashboard_title .= '<span id=\"wlcms_dashboard_title\">' . esc_attr($title) . '</span>';. - It injects this into an inline script:
wlcms_add_js('jQuery(".index-php #wpbody-content .wrap h1:eq(0)").html("' . $dashboard_title . '")');.
4. Nonce Acquisition Strategy
The nonce wlcms-settings_nonce is generated in the parts/import-settings view and rendered on the White Label CMS settings page.
- Navigate: Use
browser_navigatetowp-admin/options-general.php?page=wlcms-plugin.php. - Extract: The nonce is stored in a hidden input field within the import form.
- JavaScript Extraction:
// Extract the nonce value from the form document.querySelector('input[name="wlcms-settings_nonce"]').value
5. Exploitation Strategy
The goal is to provide a JSON file that breaks out of the JavaScript string context in dashboard_title().
Payload Preparation
Create a file named exploit.json:
{
"dashboard_title": "\");alert(document.domain);(\""
}
Note: When esc_attr processes this, it remains ");alert(document.domain);(". When placed into the wlcms_add_js call, it results in: jQuery(...).html("<span ...>");alert(document.domain);("</span>").
Step-by-Step Execution
- Login: Authenticate as an Administrator.
- Get Nonce: Navigate to
wp-admin/options-general.php?page=wlcms-plugin.phpand extract the value ofwlcms-settings_nonce. - Upload Exploit:
- Use
http_requestto send aPOSTrequest towp-admin/options-general.php?page=wlcms-plugin.php. - Content-Type:
multipart/form-data. - Parameters:
wlcms-settings_nonce: [EXTRACTED_NONCE]import_file: [exploit.json content]
- Use
- Trigger: Navigate to the WordPress Dashboard (
wp-admin/index.php).
6. Test Data Setup
- Install Plugin:
wp plugin install white-label-cms --version=2.7.12 --activate. - Disable unfiltered_html (Simulate restricted Admin):
wp eval "role = get_role('administrator'); role->remove_cap('unfiltered_html');" - User: Ensure an administrator user exists.
7. Expected Results
- The
import()function will process the JSON and update thewlcms_optionsoption. - Upon visiting the Dashboard, the
dashboard_title()function will generate a script block. - The browser will execute the injected JavaScript, resulting in an alert box displaying the document domain.
8. Verification Steps
- Check Database:
wp option get wlcms_options --format=json | grep "alert" - Inspect Page Source: Navigate to the Dashboard and search for the injected string in the HTML source. It will appear within a
<script>block managed by the plugin.
9. Alternative Approaches
If the dashboard_title injection is mitigated by the way wlcms_add_js escapes quotes, target the Custom RSS Feed or Custom Welcome Panel settings.
RSS Feed Target:
Inject the payload into the rss_title setting. If the plugin renders the RSS title on the dashboard using echo without esc_html(), a standard tag-based payload will work:
{
"rss_title": "<img src=x onerror=alert(1)>"
}
Welcome Panel Target:
Settings like welcome_panel often store HTML templates. Injecting a script tag directly into the panel content key might be successful if the output function assumes the content was already sanitized during the (bypassed) store() phase.
Summary
The White Label CMS plugin for WordPress is vulnerable to Stored Cross-Site Scripting (XSS) via its 'Import Settings' feature in versions up to and including 2.7.12. This vulnerability allows authenticated administrators to bypass standard input sanitization by uploading a malicious JSON configuration file, leading to the execution of arbitrary scripts when the affected settings are rendered on the dashboard.
Vulnerable Code
// includes/classes/Settings.php (approx. lines 215-226) $settings = (array)json_decode( file_get_contents($temp_file), true ); unlink($temp_file); if (!$settings) { WLCMS_Queue('Nothing to import, please check your json file format.', 'error'); wp_redirect(wlcms()->admin_url()); exit; } $this->setAll($settings); $this->save(); --- // includes/classes/Admin_Dashboard.php (approx. lines 426-431) $introduction = $this->get_settings('rss_introduction'); $url = $this->get_settings('rss_feed_address'); if ($introduction) { echo '<p>' . $introduction . '</p>'; }
Security Fix
@@ -427,13 +427,13 @@ $url = $this->get_settings('rss_feed_address'); if ($introduction) { - echo '<p>' . $introduction . '</p>'; + echo '<p>' . wp_kses_post($introduction) . '</p>'; } $rss = fetch_feed($url); if ($error = is_wp_error($rss)) { - echo '<div class="warning-text">' . $rss->get_error_message() . '</div>'; + echo '<div class="warning-text">' . esc_html($rss->get_error_message()) . '</div>'; wlcms_set_css( '.index-php .warning-text', @@ -462,7 +462,7 @@ ); if ($show_post_content) : - $rss_list .= preg_replace('/<img[^>]+./', '', $item->get_content()); + $rss_list .= wp_kses_post($item->get_content()); endif; $rss_list .= '</li>'; @@ -225,6 +225,15 @@ exit; } + // Validate the JSON schema: only accept keys the plugin actually + // recognises so arbitrary options cannot be injected via import. + $allowed_keys = array_merge($this->keys(), array('version')); + $settings = array_intersect_key($settings, array_flip($allowed_keys)); + + // Sanitize imported values the same way the regular save path does + // (see store()), so untrusted JSON cannot smuggle in unescaped markup. + $settings = wlcms_kses($settings); + $this->setAll($settings); $this->save();
Exploit Outline
To exploit this vulnerability, an authenticated Administrator or Super Admin must first obtain a valid security nonce (`wlcms-settings_nonce`) from the plugin's settings page at `wp-admin/options-general.php?page=wlcms-plugin.php`. The attacker then prepares a JSON file containing a payload where one or more setting keys (e.g., 'rss_introduction' or 'dashboard_title') are assigned malicious JavaScript or HTML. By submitting a POST request to the same settings page with the `multipart/form-data` content type, providing the extracted nonce, and uploading the JSON file via the `import_file` parameter, the malicious settings are saved to the database. The XSS payload executes when a user navigates to the WordPress Dashboard, where the plugin renders the affected settings.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.