CVE-2026-1293

Yoast SEO <= 26.8 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'yoast-schema' Block Attribute

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

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 `yoast-schema` block attribute in all versions up to, and including, 26.8 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<=26.8
PublishedFebruary 5, 2026
Last updatedFebruary 6, 2026
Affected pluginwordpress-seo

What Changed in the Fix

Changes introduced in v26.9

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan - CVE-2026-1293 ## 1. Vulnerability Summary The **Yoast SEO** plugin (versions <= 26.8) is vulnerable to **Authenticated (Contributor+) Stored Cross-Site Scripting**. The vulnerability exists because the plugin fails to sanitize or escape the `yoast-schema` attribute us…

Show full research plan

Exploitation Research Plan - CVE-2026-1293

1. Vulnerability Summary

The Yoast SEO plugin (versions <= 26.8) is vulnerable to Authenticated (Contributor+) Stored Cross-Site Scripting. The vulnerability exists because the plugin fails to sanitize or escape the yoast-schema attribute used within its Gutenberg structured data blocks (such as the FAQ block). While this attribute is primarily used for generating JSON-LD structured data, it is improperly handled during the server-side rendering or client-side display of the block, allowing a user with at least Contributor-level permissions to inject arbitrary scripts that execute in the context of any user (including Administrators) viewing the affected page.

2. Attack Vector Analysis

  • Vulnerable Attribute: yoast-schema (associated with blocks like yoast/faq-block).
  • Endpoint: WordPress REST API POST /wp-json/wp/v2/posts or the classic AJAX-based POST /wp-admin/post.php.
  • Authentication Required: Contributor level or higher (authenticated).
  • Preconditions: The Yoast SEO plugin must be active. The attacker needs the ability to create or edit posts (Contributors have this by default).

3. Code Flow

  1. Input: A Contributor sends a request to save a post containing a Yoast block (e.g., yoast/faq-block).
  2. Storage: The WordPress Block Editor (Gutenberg) serializes block attributes into the post_content in the database. The payload is stored within the HTML comments that define the block: <!-- wp:yoast/faq-block {"yoast-schema":"<script>alert(1)</script>"} /-->.
  3. Rendering (Sink): When the post is viewed, the plugin's block rendering logic (likely a render_callback registered in the block's PHP registration) retrieves the attributes from the block comments.
  4. Vulnerability: The logic fails to apply esc_attr(), esc_html(), or wp_kses() to the yoast-schema value before outputting it into the page source (either as part of a hidden input, a data attribute, or within a <script type="application/ld+json"> block).

4. Nonce Acquisition Strategy

To save a post via the REST API (the most reliable method for an automated agent), a wp_rest nonce is required.

  1. Create Content: The agent should first ensure a post exists where the plugin's assets are loaded, or simply use the standard admin dashboard.
  2. Navigation: Use browser_navigate to go to wp-admin/post-new.php as the Contributor user.
  3. Extraction: Execute browser_eval to extract the REST nonce from the wpApiSettings object provided by WordPress.
    • JS Script: window.wpApiSettings.nonce
  4. Header: Include this nonce in the X-WP-Nonce header of subsequent REST API requests.

5. Exploitation Strategy

Step 1: Authentication and Nonce Extraction

  • Action: Log in as a Contributor user.
  • Action: Navigate to wp-admin/index.php.
  • Extraction: nonce = browser_eval("window.wpApiSettings.nonce").

Step 2: Inject Malicious Block

  • Method: POST
  • URL: /wp-json/wp/v2/posts
  • Headers:
    • Content-Type: application/json
    • X-WP-Nonce: [EXTRACTED_NONCE]
  • Body (JSON):
{
  "title": "SEO FAQ Test",
  "content": "<!-- wp:yoast/faq-block {\"yoast-schema\":\"<script>alert(document.domain)</script>\"} -->\n<div class=\"schema-faq wp-block-yoast-faq-block\"></div>\n<!-- /wp:yoast/faq-block -->",
  "status": "publish"
}

Note: If the yoast-schema is rendered inside a script tag, use: </script><script>alert(1)</script>.

Step 3: Trigger the XSS

  • Action: Navigate to the URL of the newly created post (found in the REST API response as link).
  • Observation: Verify if the JavaScript executes.

6. Test Data Setup

  1. User: Create a user with the contributor role.
  2. Plugin: Install and activate Yoast SEO version 26.8.
  3. Settings: Default settings are sufficient.

7. Expected Results

  • The REST API should return 201 Created.
  • When viewing the post on the frontend, the page source should contain the unescaped payload.
  • The browser should execute the alert(document.domain) script.

8. Verification Steps

  1. Database Check: Use wp-cli to inspect the post content.
    • wp post get [POST_ID] --field=post_content
    • Confirm the yoast-schema attribute contains the raw payload.
  2. Frontend Inspection: Use http_request to fetch the post content and grep for the payload.
    • http_request(url_of_post)
    • Search for <script>alert(document.domain)</script> in the response body.

9. Alternative Approaches

If the yoast-schema attribute is not rendered directly but is instead used as an HTML attribute:

  • Attribute Breakout Payload: \" onmouseover=\"alert(1)\" data-x=\"
  • JSON-LD Breakout Payload: \"};alert(1);var x={\"a\":\" (if inside a JS variable)

If the REST API is restricted, use the bulk editor AJAX endpoint found in admin/ajax.php:

  • Action: wpseo_save_title or wpseo_save_metadesc.
  • Note: These handlers in ajax.php use sanitize_text_field, which might strip tags. Focus on the Gutenberg block attribute path as described in the CVE title.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Yoast SEO plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'yoast-schema' block attribute in versions up to and including 26.8. This allows authenticated attackers with Contributor-level access or higher to inject arbitrary web scripts into Gutenberg structured data blocks, which are then executed in the context of any user viewing the affected page due to missing output escaping.

Vulnerable Code

// From blocks/structured-data-blocks/faq/block.json (line 20-30)
// The plugin processes block attributes like 'yoast-schema' which are not properly sanitized before rendering.

"attributes": {
  "questions": {
    "type": "array"
  },
  "additionalListCssClasses": {
    "type": "string"
  }
},
"example": {
  "attributes": {
    "steps": [
      {
        "id": "faq-question-1",
        "question": [ ],
        "answer": [ ]
      }

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wordpress-seo/26.8/admin/class-gutenberg-compatibility.php /home/deploy/wp-safety.org/data/plugin-versions/wordpress-seo/26.9/admin/class-gutenberg-compatibility.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wordpress-seo/26.8/admin/class-gutenberg-compatibility.php	2026-01-07 11:32:08.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wordpress-seo/26.9/admin/class-gutenberg-compatibility.php	2026-01-29 13:25:00.000000000 +0000
@@ -15,14 +15,14 @@
 	 *
 	 * @var string
 	 */
-	public const CURRENT_RELEASE = '22.3.0';
+	public const CURRENT_RELEASE = '22.4.2';
 
 	/**
 	 * The minimally supported version of Gutenberg by the plugin.
 	 *
 	 * @var string
 	 */
-	public const MINIMUM_SUPPORTED = '22.3.0';
+	public const MINIMUM_SUPPORTED = '22.4.2';
 
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wordpress-seo/26.8/blocks/structured-data-blocks/faq/block.json /home/deploy/wp-safety.org/data/plugin-versions/wordpress-seo/26.9/blocks/structured-data-blocks/faq/block.json
--- /home/deploy/wp-safety.org/data/plugin-versions/wordpress-seo/26.8/blocks/structured-data-blocks/faq/block.json	2024-05-23 15:08:52.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wordpress-seo/26.9/blocks/structured-data-blocks/faq/block.json	2026-01-20 12:23:48.000000000 +0000
@@ -25,7 +25,7 @@
   },
   "example": {
     "attributes": {
-      "steps": [
+      "questions": [

Exploit Outline

1. Authenticate as a user with at least Contributor-level permissions (the ability to create or edit posts). 2. Obtain a valid REST API nonce by navigating to the WordPress admin dashboard and extracting 'window.wpApiSettings.nonce'. 3. Send a POST request to '/wp-json/wp/v2/posts' to create a new post. 4. In the request body, include a Yoast FAQ block within the 'content' field, specifically targeting the 'yoast-schema' attribute with a malicious payload: <!-- wp:yoast/faq-block {"yoast-schema":"<script>alert(1)</script>"} /-->. 5. View the published or previewed post on the frontend. The plugin's rendering logic will output the unescaped 'yoast-schema' value, causing the JavaScript to execute in the victim's browser.

Check if your site is affected.

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