Website LLMs.txt <= 8.2.6 - Reflected Cross-Site Scripting
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:NTechnical Details
<=8.2.6What Changed in the Fix
Changes introduced in v8.2.7
Source Code
WordPress.org SVN# 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.phpor/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_optionscapability) 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
- Entry Point: A user (administrator) navigates to
wp-admin/options-general.php?page=website-llms-txt&tab=[PAYLOAD]. - Input Retrieval: In
admin/admin-page.php(or the core class loading it), the plugin likely performs a check like:
Note: In PHP,$active_tab = filter_input(INPUT_GET, 'tab');filter_inputwith two arguments defaults to the raw string if no filter is provided. - Vulnerable Sink: Later in the same file, the
$active_tabvariable 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>'; - 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.phpcode shows nonces are checked forcache_clearedandsettings-updatedactions, but there is no nonce check required just to render the settings page or process thetabparameter. - Bypass: None needed for reflection.
5. Exploitation Strategy
The goal is to demonstrate script execution in the admin dashboard.
- Identify Admin Slug: Confirm the exact URL for the settings page. Based on
README.txt, it is under "Settings". The slug is likelywebsite-llms-txt. - Payload Construction: Use a payload that breaks out of an HTML attribute (most likely an
hreforclassattribute in a tab list).- Payload:
"><script>alert(document.domain)</script>
- Payload:
- 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
- Tool:
- Alternative Payload (if inside a
valueattribute):- Payload:
" autofocus onfocus="alert(1)
- Payload:
6. Test Data Setup
- Install and Activate: Ensure the "Website LLMs.txt" plugin version 8.2.6 is installed and activated.
- User: Create or use an existing Administrator user account.
- Session: Log in as the administrator to obtain a valid session cookie for the
http_requesttool.
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
- Source Inspection: Use the
http_requesttool to fetch the settings page with a unique canary in thetabparameter.- 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 inesc_attr()(e.g.,value="canary_xss_test") or if it's raw.
- Request:
- Browser Confirmation: Use
browser_navigateto 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.phpreturns 404 for that page, trytools.php?page=llms-file-manageroradmin.php?page=website-llms-txt. (The redirect inincludes/class-llms-generator.phptotools.phpstrongly suggests checking the Tools menu).
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
@@ -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.