CVE-2026-6711

Website LLMs.txt <= 8.2.6 - Reflected Cross-Site Scripting

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

Description

The Website LLMs.txt plugin for WordPress is vulnerable to Reflected Cross-Site Scripting via the 'tab' parameter in all versions up to, and including, 8.2.6. This is due to the use of filter_input() without a sanitization filter and insufficient output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that execute if they can successfully trick an administrator into performing an action such as clicking on a link.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
Required
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=8.2.6
PublishedApril 20, 2026
Last updatedApril 21, 2026
Affected pluginwebsite-llms-txt

What Changed in the Fix

Changes introduced in v8.2.7

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-6711 - Reflected XSS in Website LLMs.txt ## 1. Vulnerability Summary The **Website LLMs.txt** plugin for WordPress (versions <= 8.2.6) contains a reflected Cross-Site Scripting (XSS) vulnerability in its administrative settings page. The vulnerability exists b…

Show full research plan

Exploitation Research Plan: CVE-2026-6711 - Reflected XSS in Website LLMs.txt

1. Vulnerability Summary

The Website LLMs.txt plugin for WordPress (versions <= 8.2.6) contains a reflected Cross-Site Scripting (XSS) vulnerability in its administrative settings page. The vulnerability exists because the plugin retrieves the tab GET parameter using filter_input(INPUT_GET, 'tab') without specifying a sanitization filter (defaulting to FILTER_DEFAULT, which does not strip HTML tags or attributes) and subsequently echoes this value back into the HTML document without appropriate output escaping (like esc_attr() or esc_html()).

2. Attack Vector Analysis

  • Target Page: The plugin's settings page in the WordPress admin dashboard.
  • Endpoint: /wp-admin/options-general.php or /wp-admin/tools.php (depending on which menu the plugin registers under).
  • Vulnerable Parameter: tab (GET parameter).
  • Authentication Requirement: The victim must be a logged-in Administrator (or a user with the manage_options capability) because the vulnerable code resides in the admin area.
  • Preconditions: The attacker must trick the administrator into clicking a malicious link (Reflected XSS).

3. Code Flow

  1. Entry Point: A user (administrator) navigates to wp-admin/options-general.php?page=website-llms-txt&tab=[PAYLOAD].
  2. Input Retrieval: In admin/admin-page.php (or the core class loading it), the plugin likely performs a check like:
    $active_tab = filter_input(INPUT_GET, 'tab'); 
    
    Note: In PHP, filter_input with two arguments defaults to the raw string if no filter is provided.
  3. Vulnerable Sink: Later in the same file, the $active_tab variable is used to construct navigation tabs or hidden inputs without escaping:
    // Likely vulnerable pattern (inferred from description)
    echo '<a href="?page=website-llms-txt&tab=' . $active_tab . '">Tab Name</a>';
    
  4. Execution: The browser renders the malicious HTML, executing the payload in the context of the administrator's session.

4. Nonce Acquisition Strategy

Reflected XSS vulnerabilities in GET parameters typically do not require nonces for the initial reflection to occur, as the tab parameter is usually used for UI state rather than state-changing actions. The page itself is accessed via a standard menu link.

  • Nonce Check: The provided admin/admin-page.php code shows nonces are checked for cache_cleared and settings-updated actions, but there is no nonce check required just to render the settings page or process the tab parameter.
  • Bypass: None needed for reflection.

5. Exploitation Strategy

The goal is to demonstrate script execution in the admin dashboard.

  1. Identify Admin Slug: Confirm the exact URL for the settings page. Based on README.txt, it is under "Settings". The slug is likely website-llms-txt.
  2. Payload Construction: Use a payload that breaks out of an HTML attribute (most likely an href or class attribute in a tab list).
    • Payload: "><script>alert(document.domain)</script>
  3. HTTP Request:
    • Tool: http_request (as an administrator).
    • Method: GET
    • URL: http://localhost:8080/wp-admin/options-general.php?page=website-llms-txt&tab=%22%3E%3Cscript%3Ealert(document.domain)%3C%2Fscript%3E
  4. Alternative Payload (if inside a value attribute):
    • Payload: " autofocus onfocus="alert(1)

6. Test Data Setup

  1. Install and Activate: Ensure the "Website LLMs.txt" plugin version 8.2.6 is installed and activated.
  2. User: Create or use an existing Administrator user account.
  3. Session: Log in as the administrator to obtain a valid session cookie for the http_request tool.

7. Expected Results

  • The HTTP response body will contain the literal string "><script>alert(document.domain)</script> inside an HTML attribute or tag, unescaped.
  • When viewed in a browser via the PoC agent, a JavaScript alert dialog showing the site domain will appear.

8. Verification Steps

  1. Source Inspection: Use the http_request tool to fetch the settings page with a unique canary in the tab parameter.
    • Request: GET /wp-admin/options-general.php?page=website-llms-txt&tab=canary_xss_test
    • Verification: Search the response for canary_xss_test. Check if it is wrapped in esc_attr() (e.g., value="canary_xss_test") or if it's raw.
  2. Browser Confirmation: Use browser_navigate to the malicious URL and check for the execution of JavaScript (e.g., checking for a global variable set by the script).

9. Alternative Approaches

  • Lesser Privileges: Check if the page is accessible to "Editors" or "Authors". If so, the XSS impact remains but the target pool is larger.
  • Admin Takeover Payload: If alert() works, replace the payload with a script that uses the administrator's _wpnonce_create-user (extracted from /wp-admin/user-new.php) to create a new administrator account.
  • Payload Path: If options-general.php returns 404 for that page, try tools.php?page=llms-file-manager or admin.php?page=website-llms-txt. (The redirect in includes/class-llms-generator.php to tools.php strongly suggests checking the Tools menu).
Research Findings
Static analysis — not yet PoC-verified

Summary

The Website LLMs.txt plugin for WordPress is vulnerable to Reflected Cross-Site Scripting (XSS) via the 'tab' parameter in versions up to and including 8.2.6. This occurs because the plugin uses filter_input() without a sanitization filter and outputs the resulting value directly into an HTML class attribute without proper escaping.

Vulnerable Code

// admin/admin-page.php

<?php
    $tab = filter_input(INPUT_GET,'tab');
?>
<div class="card-column">
    <div class="card <?php echo $tab; ?>">

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/website-llms-txt/8.2.6/admin/admin-page.php /home/deploy/wp-safety.org/data/plugin-versions/website-llms-txt/8.2.7/admin/admin-page.php
--- /home/deploy/wp-safety.org/data/plugin-versions/website-llms-txt/8.2.6/admin/admin-page.php	2026-02-11 11:25:06.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/website-llms-txt/8.2.7/admin/admin-page.php	2026-03-13 18:44:40.000000000 +0000
@@ -75,7 +75,7 @@
                     <div id="llms-post-types-sortable" class="sortable-list">
                         <?php
                         $post_types = get_post_types(array('public' => true), 'objects');
-                        $ordered_types = array_flip($settings['post_types']); // Create lookup array
+                        $ordered_types = array_flip($settings['post_types'] ?? []); // Create lookup array
                         $unordered_types = array(); // For types not in the current order
 
                         // Separate ordered and unordered post types
@@ -99,7 +99,7 @@
                                 <div class="sortable-item active" data-post-type="<?php echo esc_attr($post_type->name); ?>">
                                     <label>
                                         <input type="checkbox" name="llms_generator_settings[post_types][]" value="<?php echo esc_attr($post_type->name); ?>" checked>
-                                        <input type="text" name="llms_generator_settings[post_name][<?php echo esc_html($post_type->labels->name); ?>]" value="<?php echo $settings['post_name'][esc_html($post_type->labels->name)] ?? ''  ?>"/>
+                                        <input type="text" name="llms_generator_settings[post_name][<?php echo esc_attr($post_type->labels->name); ?>]" value="<?php echo esc_attr($settings['post_name'][$post_type->labels->name] ?? '')  ?>"/>
                                         <span class="dashicons dashicons-menu"></span>
                                         <?php echo esc_html($post_type->labels->name); ?>
                                         <small style="opacity: 0.7;">(<?php echo intval($indexed_count) . ' indexed of ' . intval($all_count); ?>)</small>
@@ -117,7 +117,7 @@
                             <div class="sortable-item" data-post-type="<?php echo esc_attr($post_type->name); ?>">
                                 <label>
                                     <input type="checkbox" name="llms_generator_settings[post_types][]" value="<?php echo esc_attr($post_type->name); ?>"/>
-                                    <input type="text" name="llms_generator_settings[post_name][<?php echo esc_html($post_type->labels->name); ?>]" value="<?php echo $settings['post_name'][esc_html($post_type->labels->name)] ?? ''  ?>"/>
+                                    <input type="text" name="llms_generator_settings[post_name][<?php echo esc_attr($post_type->labels->name); ?>]" value="<?php echo esc_attr($settings['post_name'][$post_type->labels->name] ?? '')  ?>"/>
                                     <span class="dashicons dashicons-menu"></span>
                                     <?php echo esc_html($post_type->labels->name); ?>
                                     <small style="opacity: 0.7;">(<?php echo intval($indexed_count) . ' indexed of ' . intval($all_count); ?>)</small>
@@ -197,10 +197,10 @@
                             <?php if(in_array($key, ['post_types', 'max_posts', 'max_words', 'include_meta', 'include_excerpts', 'detailed_content', 'include_taxonomies', 'gform_include'])) continue ?>
                             <?php if(is_array($value)):
                                 foreach($value as $second_key => $second_value):
-                                    <input type="hidden" name="llms_generator_settings[<?= $key ?>][]" value="<?= $second_value ?>"/>
+                                    <input type="hidden" name="llms_generator_settings[<?= esc_attr($key) ?>][]" value="<?= esc_attr($second_value) ?>"/>
                                 <?php endforeach
                             else:
-                                <input type="hidden" name="llms_generator_settings[<?= $key ?>]" value="<?= $value ?>"/>
+                                <input type="hidden" name="llms_generator_settings[<?= esc_attr($key) ?>]" value="<?= esc_attr($value) ?>"/>
                             <?php endif ?>
                         <?php endforeach ?>
                     <?php endif ?>
@@ -256,10 +256,10 @@
                             <?php if(in_array($key, ['include_md_file', 'noindex_header', 'llms_allow_indexing', 'update_frequency'])) continue ?>
                             <?php if(is_array($value)):
                                 foreach($value as $second_key => $second_value):
-                                    <input type="hidden" name="llms_generator_settings[<?= $key ?>][]" value="<?= $second_value ?>"/>
+                                    <input type="hidden" name="llms_generator_settings[<?= esc_attr($key) ?>][]" value="<?= esc_attr($second_value) ?>"/>
                                 <?php endforeach
                             else:
-                                <input type="hidden" name="llms_generator_settings[<?= $key ?>]" value="<?= $value ?>"/>
+                                <input type="hidden" name="llms_generator_settings[<?= esc_attr($key) ?>]" value="<?= esc_attr($value) ?>"/>
                             <?php endif ?>
                         <?php endforeach ?>
                     <?php endif ?>
@@ -314,28 +314,28 @@
                         <label>
                             <b><?php esc_html_e('LLMS.txt Title', 'website-llms-txt'); ?></b>
                         </label><br/>
-                        <textarea name="llms_generator_settings[llms_txt_title]" style="width: 100%;height: 40px;"><?php echo (isset($settings['llms_txt_title']) ? $settings['llms_txt_title'] : '') ?></textarea>
+                        <textarea name="llms_generator_settings[llms_txt_title]" style="width: 100%;height: 40px;"><?php echo esc_textarea($settings['llms_txt_title'] ?? '') ?></textarea>
                         <i><?php esc_html_e('Set a custom title for your LLMs.txt file. This will appear at the top of the generated file before any listed URLs.', 'website-llms-txt'); ?></i>
                     </p>
                     <p>
                         <label>
                             <b><?php esc_html_e('LLMS.txt Description', 'website-llms-txt'); ?></b>
                         </label><br/>
-                        <textarea name="llms_generator_settings[llms_txt_description]" style="width: 100%;height: 80px;"><?php echo (isset($settings['llms_txt_description']) ? $settings['llms_txt_description'] : '') ?></textarea>
+                        <textarea name="llms_generator_settings[llms_txt_description]" style="width: 100%;height: 80px;"><?php echo esc_textarea($settings['llms_txt_description'] ?? '') ?></textarea>
                         <i><?php esc_html_e('Optional introduction text added before the list of URLs. Use this to explain the purpose or structure of your LLMs.txt file.', 'website-llms-txt'); ?></i>
                     </p>
                     <p>
                         <label>
                             <b><?php esc_html_e('LLMS.txt After Description', 'website-llms-txt'); ?></b>
                         </label><br/>
-                        <textarea name="llms_generator_settings[llms_after_txt_description]" style="width: 100%;height: 80px;"><?php echo (isset($settings['llms_after_txt_description']) ? $settings['llms_after_txt_description'] : '') ?></textarea>
+                        <textarea name="llms_generator_settings[llms_after_txt_description]" style="width: 100%;height: 80px;"><?php echo esc_textarea($settings['llms_after_txt_description'] ?? '') ?></textarea>
                         <i><?php esc_html_e('Optional text inserted right before the list of links or content entries. You can use it to add additional notes, context, or data usage information before the URLs begin.', 'website-llms-txt'); ?></i>
                     </p>
                     <p>
                         <label>
                             <b><?php esc_html_e('LLMS.txt End File Description', 'website-llms-txt'); ?></b>
                         </label><br/>
-                        <textarea name="llms_generator_settings[llms_end_file_description]" style="width: 100%;height: 80px;"><?php echo (isset($settings['llms_end_file_description']) ? $settings['llms_end_file_description'] : '') ?></textarea>
+                        <textarea name="llms_generator_settings[llms_end_file_description]" style="width: 100%;height: 80px;"><?php echo esc_textarea($settings['llms_end_file_description'] ?? '') ?></textarea>
                         <i><?php esc_html_e('Final text appended at the bottom of the LLMs.txt file (e.g. footer, contact, or disclaimer information).', 'website-llms-txt'); ?></i>
                     </p>
                     <?php if(!empty($settings)):
@@ -343,10 +343,10 @@
                             <?php if(in_array($key, ['llms_txt_title', 'llms_txt_description', 'llms_after_txt_description', 'llms_end_file_description'])) continue ?>
                             <?php if(is_array($value)):
                                 foreach($value as $second_key => $second_value):
-                                    <input type="hidden" name="llms_generator_settings[<?= $key ?>][]" value="<?= $second_value ?>"/>
+                                    <input type="hidden" name="llms_generator_settings[<?= esc_attr($key) ?>][]" value="<?= esc_attr($second_value) ?>"/>
                                 <?php endforeach
                             else:
-                                <input type="hidden" name="llms_generator_settings[<?= $key ?>]" value="<?= $value ?>"/>
+                                <input type="hidden" name="llms_generator_settings[<?= esc_attr($key) ?>]" value="<?= esc_attr($value) ?>"/>
                             <?php endif ?>
                         <?php endforeach ?>
                     <?php endif ?>
@@ -354,11 +354,9 @@
                 </form>
             </div>
         </div>
-        <?php
-            $tab = filter_input(INPUT_GET,'tab');
-        ?>
+        <?php $tab = sanitize_key(filter_input(INPUT_GET, 'tab')); ?>
         <div class="card-column">
-            <div class="card <?php echo $tab; ?>">
+            <div class="card <?php echo esc_attr(sanitize_key($tab)); ?>">
                 <form method="post" action="options.php" id="llms-settings-crawler-form">
                     <?php settings_fields('llms_generator_settings'); ?>
                     <h2><?php esc_html_e('AI Crawler Detection','website-llms-txt') ?></h2>
@@ -383,10 +381,10 @@
                             <?php if(in_array($key, ['llms_local_log_enabled'])) continue ?>
                             <?php if(is_array($value)):
                                 foreach($value as $second_key => $second_value):
-                                    <input type="hidden" name="llms_generator_settings[<?= $key ?>][]" value="<?= $second_value ?>"/>
+                                    <input type="hidden" name="llms_generator_settings[<?= esc_attr($key) ?>][]" value="<?= esc_attr($second_value) ?>"/>
                                 <?php endforeach
                             else:
-                                <input type="hidden" name="llms_generator_settings[<?= $key ?>]" value="<?= $value ?>"/>
+                                <input type="hidden" name="llms_generator_settings[<?= esc_attr($key) ?>]" value="<?= esc_attr($value) ?>"/>
                             <?php endif ?>
                         <?php endforeach ?>
                     <?php endif ?>

Exploit Outline

1. An attacker identifies the plugin's settings page URL, typically `wp-admin/options-general.php?page=website-llms-txt` (though sometimes registered under Tools). 2. The attacker constructs a malicious URL including the `tab` GET parameter containing a script payload, for example: `?page=website-llms-txt&tab="><script>alert(document.domain)</script>`. 3. The attacker crafts a delivery mechanism (e.g., a phishing email or a malicious site) to trick an authenticated WordPress Administrator into clicking this link. 4. When the Administrator visits the link, the plugin retrieves the `tab` value via `filter_input(INPUT_GET, 'tab')` and reflects it into the HTML class attribute of a `div` element without escaping. 5. The payload executes in the context of the Administrator's session, potentially allowing for session hijacking, unauthorized configuration changes, or the creation of new administrative accounts.

Check if your site is affected.

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