CVE-2026-4329

Blackhole for Bad Bots <= 3.8 - Unauthenticated Stored Cross-Site Scripting via User-Agent HTTP Header

highImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
7.2
CVSS Score
7.2
CVSS Score
high
Severity
3.8.1
Patched in
1d
Time to patch

Description

The Blackhole for Bad Bots plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the User-Agent HTTP header in all versions up to and including 3.8. This is due to insufficient input sanitization and output escaping. The plugin uses sanitize_text_field() when capturing bot data (which strips HTML tags but does not escape HTML entities like double quotes), then stores the data via update_option(). When an administrator views the Bad Bots log page, the stored data is output directly into HTML input value attributes (lines 75-83) without esc_attr() and into HTML span content without esc_html(). This makes it possible for unauthenticated attackers to inject arbitrary web scripts that execute when an administrator views the Blackhole Bad Bots admin page.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=3.8
PublishedMarch 25, 2026
Last updatedMarch 26, 2026
Affected pluginblackhole-bad-bots

What Changed in the Fix

Changes introduced in v3.8.1

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Research Plan: Blackhole for Bad Bots Stored XSS ## 1. Vulnerability Summary The **Blackhole for Bad Bots** plugin (<= 3.8) is vulnerable to **Unauthenticated Stored Cross-Site Scripting (XSS)** via the `User-Agent` HTTP header. The plugin is designed to trap "bad bots" that ignore `robots.txt`…

Show full research plan

Research Plan: Blackhole for Bad Bots Stored XSS

1. Vulnerability Summary

The Blackhole for Bad Bots plugin (<= 3.8) is vulnerable to Unauthenticated Stored Cross-Site Scripting (XSS) via the User-Agent HTTP header.

The plugin is designed to trap "bad bots" that ignore robots.txt rules and visit a hidden "blackhole" link. When a bot hits this link, the plugin captures its request metadata (IP, User-Agent, etc.) and stores it in the WordPress database (within the bbb_badbots option).

The vulnerability exists because:

  1. Insufficient Sanitization: Captured User-Agent data is processed using sanitize_text_field(). While this function strips HTML tags, it does not escape quotes or HTML entities.
  2. Lack of Output Escaping: When an administrator views the "Bad Bots" log in the WordPress dashboard (wp-admin/admin.php?page=blackhole_badbots), the stored User-Agent is output directly into HTML value attributes of input tags (used for form persistence) and into <span> content without using esc_attr() or esc_html().

This allows an unauthenticated attacker to inject attributes into the input tags (e.g., onfocus) to execute arbitrary JavaScript when the administrator views the log.

2. Attack Vector Analysis

  • Endpoint: Any public-facing page (to find the trap) and the "blackhole" trap URL itself.
  • Trap URL: Typically /?blackhole or a randomized path added to robots.txt.
  • HTTP Parameter: User-Agent HTTP header.
  • Authentication: Unauthenticated (the bot is by definition an external visitor).
  • Preconditions:
    • The plugin must be active.
    • The attacker must identify the forbidden "blackhole" link path.

3. Code Flow

  1. Entry Point: blackhole_scanner() (registered in blackhole.php line 98) runs on the init hook for every request.
  2. Detection: blackhole_scanner checks if the current request URI matches the "blackhole" trigger link.
  3. Data Capture: If matched, the plugin gathers $_SERVER['HTTP_USER_AGENT'].
  4. Storage: The plugin updates the bbb_badbots option.
  5. Sanitization Sink: The register_setting for bbb_badbots (defined in inc/badbots-register.php line 9) specifies blackhole_validate_badbots as the validation callback.
  6. Validation: blackhole_validate_badbots (line 21) runs sanitize_text_field() on the user_agent field.
    • Input: EvilBot " onfocus="alert(1)" autofocus="
    • Output: EvilBot " onfocus="alert(1)" autofocus=" (Quotes are preserved).
  7. Output Sink: An administrator navigates to the Bad Bots page. The function blackhole_callback_blocked_bots() (line 64) is called.
    • Line 85: echo '<input name="bbb_badbots['. $key .'][user_agent]" type="hidden" value="'. $user_agent .'" /> ';
    • The injected quote breaks out of the value attribute, and autofocus + onfocus executes the script.

4. Nonce Acquisition Strategy

No nonce is required.
The vulnerability is triggered by an unauthenticated GET request to the "blackhole" trap URL. The blackhole_scanner() logic on init does not perform nonce verification because its purpose is to catch automated bots which would not possess a WordPress nonce.

5. Exploitation Strategy

  1. Identify the Trap:
    • Navigate to the site's homepage using browser_navigate.
    • Check the HTML source for a hidden link (usually in the footer, as per blackhole.php lines 96-97).
    • Alternatively, check /robots.txt for a Disallow rule pointing to a blackhole path.
  2. Submit Payload:
    • Send an HTTP GET request to the identified trap URL using http_request.
    • Set the User-Agent header to a payload that breaks out of an HTML attribute.
  3. Payload Design:
    • Since sanitize_text_field is used, we cannot use <script> tags.
    • We must use attribute breakout: EvilBot " onfocus="alert(1)" autofocus="
    • Note: Avoid the strings "googlebot", "bingbot", "chrome", or "wordpress" as they are whitelisted (see blackhole.php line 182).
  4. Trigger Execution:
    • Log in to the WordPress admin panel.
    • Navigate to http://localhost:8080/wp-admin/admin.php?page=blackhole_badbots.
    • The input tag will render as: <input ... value="EvilBot " onfocus="alert(1)" autofocus="" />.
    • The autofocus attribute will trigger onfocus immediately upon page load in modern browsers.

6. Test Data Setup

  1. Plugin Installation: Ensure "Blackhole for Bad Bots" version 3.8 is installed and activated.
  2. Configuration: The plugin defaults are usually sufficient.
  3. Admin User: A standard administrator account to view the logs.
  4. Shortcode/Page: No specific shortcode is needed to trigger the vulnerability, as the trap is typically appended to the footer of all pages by blackhole_trigger on wp_footer.

7. Expected Results

  • The malicious request is logged by the plugin.
  • The bbb_badbots option in the wp_options table contains the payload string with quotes intact.
  • Upon visiting the Bad Bots admin page, a JavaScript alert(1) (or equivalent) executes in the context of the administrator session.

8. Verification Steps

  1. Check Database via WP-CLI:
    wp option get bbb_badbots --format=json
    
    Verify that the user_agent field for one of the entries contains the raw payload string.
  2. Check HTML Output via HTTP Request (Admin Session):
    • Perform an authenticated request to /wp-admin/admin.php?page=blackhole_badbots.
    • Inspect the response body for the unescaped payload:
    <input name="bbb_badbots[1][user_agent]" type="hidden" value="EvilBot " onfocus="alert(1)" autofocus="" />
    

9. Alternative Approaches

If autofocus is filtered or blocked by the browser, use a payload that creates a larger invisible overlay to trigger on mouse movement:

  • Payload: EvilBot " style="position:fixed;top:0;left:0;width:100%;height:100%;display:block;" onmouseover="alert(1)
  • If sanitize_text_field interferes with the style attribute, try onmouseenter or onclick on the hidden input field, though onfocus with autofocus is the most reliable for hidden inputs.
  • Note that even though the input is type="hidden", most browsers will still honor autofocus and onfocus if they are manually injected into the DOM as attributes. If type="hidden" prevents focus, we can attempt to inject type="text" to overwrite the earlier type attribute (if the browser allows attribute precedence for the first occurrence).
Research Findings
Static analysis — not yet PoC-verified

Summary

The Blackhole for Bad Bots plugin for WordPress is vulnerable to Unauthenticated Stored Cross-Site Scripting (XSS) due to insufficient sanitization and output escaping of the User-Agent HTTP header. Attackers can trigger the vulnerability by visiting the site's hidden "blackhole" trap URL with a crafted User-Agent string, which then executes malicious JavaScript in the browser of an administrator viewing the Bad Bots log page.

Vulnerable Code

// inc/badbots-register.php:21
function blackhole_validate_badbots($bots) {
	
	foreach ($bots as $key => $val) {
		
		$bots[$key]['ip_address']   = isset($bots[$key]['ip_address'])   ? sanitize_text_field($bots[$key]['ip_address'])   : null;
		$bots[$key]['request_uri']  = isset($bots[$key]['request_uri'])  ? sanitize_text_field($bots[$key]['request_uri'])  : null;
		$bots[$key]['remote_host']  = isset($bots[$key]['remote_host'])  ? sanitize_text_field($bots[$key]['remote_host'])  : null;
		$bots[$key]['query_string'] = isset($bots[$key]['query_string']) ? sanitize_text_field($bots[$key]['query_string']) : null;
		$bots[$key]['user_agent']   = isset($bots[$key]['user_agent'])   ? sanitize_text_field($bots[$key]['user_agent'])   : null;
		$bots[$key]['referrer']     = isset($bots[$key]['referrer'])     ? sanitize_text_field($bots[$key]['referrer'])     : null;
		$bots[$key]['protocol']     = isset($bots[$key]['protocol'])     ? sanitize_text_field($bots[$key]['protocol'])     : null;
		$bots[$key]['method']       = isset($bots[$key]['method'])       ? sanitize_text_field($bots[$key]['method'])       : null;
		$bots[$key]['date']         = isset($bots[$key]['date'])         ? sanitize_text_field($bots[$key]['date'])         : null;
		
	}
	
	return $bots;
}

---

// inc/badbots-register.php:75
		echo '<input name="bbb_badbots['. $key .'][ip_address]"   type="hidden" value="'. $ip_address .'" /> ';
		echo '<input name="bbb_badbots['. $key .'][request_uri]"  type="hidden" value="'. $request_uri .'" /> ';
		echo '<input name="bbb_badbots['. $key .'][remote_host]"  type="hidden" value="'. $remote_host .'" /> ';
		echo '<input name="bbb_badbots['. $key .'][query_string]" type="hidden" value="'. $query_string .'" /> ';
		echo '<input name="bbb_badbots['. $key .'][user_agent]"   type="hidden" value="'. $user_agent .'" /> ';
		echo '<input name="bbb_badbots['. $key .'][referrer]"     type="hidden" value="'. $referrer .'" /> ';
		echo '<input name="bbb_badbots['. $key .'][protocol]"     type="hidden" value="'. $protocol .'" /> ';
		echo '<input name="bbb_badbots['. $key .'][method]"       type="hidden" value="'. $method .'" /> ';
		echo '<input name="bbb_badbots['. $key .'][date]"         type="hidden" value="'. $date .'" /> ';
		
		$data = '<strong>'. $date .'</strong> - '. $ip_address .' - '. $protocol .' - <span class="bbb-user-agent">'. $user_agent . '</span>';

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/blackhole-bad-bots/3.8/blackhole.php /home/deploy/wp-safety.org/data/plugin-versions/blackhole-bad-bots/3.8.1/blackhole.php
--- /home/deploy/wp-safety.org/data/plugin-versions/blackhole-bad-bots/3.8/blackhole.php	2026-02-06 22:29:52.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/blackhole-bad-bots/3.8.1/blackhole.php	2026-03-20 16:43:10.000000000 +0000
@@ -114,7 +114,7 @@
 		
 		private function constants() {
 			if (!defined('BBB_REQUIRE')) define('BBB_REQUIRE', '4.7');
-			if (!defined('BBB_VERSION')) define('BBB_VERSION', '3.8');
+			if (!defined('BBB_VERSION')) define('BBB_VERSION', '3.8.1');
 			if (!defined('BBB_NAME'))    define('BBB_NAME',    'Blackhole for Bad Bots');
 			if (!defined('BBB_AUTHOR'))  define('BBB_AUTHOR',  'Jeff Starr');
 			if (!defined('BBB_HOME'))    define('BBB_HOME',    'https://perishablepress.com/blackhole-bad-bots/');
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/blackhole-bad-bots/3.8/inc/badbots-register.php /home/deploy/wp-safety.org/data/plugin-versions/blackhole-bad-bots/3.8.1/inc/badbots-register.php
--- /home/deploy/wp-safety.org/data/plugin-versions/blackhole-bad-bots/3.8/inc/badbots-register.php	2023-03-07 00:20:46.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/blackhole-bad-bots/3.8.1/inc/badbots-register.php	2026-03-20 16:43:10.000000000 +0000
@@ -72,17 +72,17 @@
 		$method       = isset($val['method'])       ? $val['method']       : '';
 		$date         = isset($val['date'])         ? $val['date']         : '';
 		
-		echo '<input name="bbb_badbots['. $key .'][ip_address]"   type="hidden" value="'. $ip_address .'" /> ';
-		echo '<input name="bbb_badbots['. $key .'][request_uri]"  type="hidden" value="'. $request_uri .'" /> ';
-		echo '<input name="bbb_badbots['. $key .'][remote_host]"  type="hidden" value="'. $remote_host .'" /> ';
-		echo '<input name="bbb_badbots['. $key .'][query_string]" type="hidden" value="'. $query_string .'" /> ';
-		echo '<input name="bbb_badbots['. $key .'][user_agent]"   type="hidden" value="'. $user_agent .'" /> ';
-		echo '<input name="bbb_badbots['. $key .'][referrer]"     type="hidden" value="'. $referrer .'" /> ';
-		echo '<input name="bbb_badbots['. $key .'][protocol]"     type="hidden" value="'. $protocol .'" /> ';
-		echo '<input name="bbb_badbots['. $key .'][method]"       type="hidden" value="'. $method .'" /> ';
-		echo '<input name="bbb_badbots['. $key .'][date]"         type="hidden" value="'. $date .'" /> ';
+		echo '<input name="bbb_badbots['. $key .'][ip_address]"   type="hidden" value="'. esc_attr($ip_address) .'" /> ';
+		echo '<input name="bbb_badbots['. $key .'][request_uri]"  type="hidden" value="'. esc_attr($request_uri) .'" /> ';
+		echo '<input name="bbb_badbots['. $key .'][remote_host]"  type="hidden" value="'. esc_attr($remote_host) .'" /> ';
+		echo '<input name="bbb_badbots['. $key .'][query_string]" type="hidden" value="'. esc_attr($query_string) .'" /> ';
+		echo '<input name="bbb_badbots['. $key .'][user_agent]"   type="hidden" value="'. esc_attr($user_agent) .'" /> ';
+		echo '<input name="bbb_badbots['. $key .'][referrer]"     type="hidden" value="'. esc_attr($referrer) .'" /> ';
+		echo '<input name="bbb_badbots['. $key .'][protocol]"     type="hidden" value="'. esc_attr($protocol) .'" /> ';
+		echo '<input name="bbb_badbots['. $key .'][method]"       type="hidden" value="'. esc_attr($method) .'" /> ';
+		echo '<input name="bbb_badbots['. $key .'][date]"         type="hidden" value="'. esc_attr($date) .'" /> ';
 		
-		$data = '<strong>'. $date .'</strong> - '. $ip_address .' - '. $protocol .' - <span class="bbb-user-agent">'. $user_agent . '</span>';
+		$data = '<strong>'. esc_html($date) .'</strong> - '. esc_html($ip_address) .' - '. esc_html($protocol) .' - <span class="bbb-user-agent">'. esc_html($user_agent) . '</span>';

Exploit Outline

1. Identify the hidden "blackhole" trap URL on the target WordPress site (found either in the robots.txt file or as a hidden link in the page source). 2. Send an unauthenticated GET request to the trap URL. 3. Include a malicious User-Agent header in the request (e.g., `" onfocus="alert(1)" autofocus="`). 4. The plugin captures this header, passes it through `sanitize_text_field` (which preserves quotes), and saves it to the `bbb_badbots` WordPress option. 5. Wait for an administrator to view the "Bad Bots" log page in the WordPress admin dashboard (`/wp-admin/admin.php?page=blackhole_badbots`). 6. The payload breaks out of the HTML `value` attribute of a hidden input tag, injecting the `onfocus` and `autofocus` attributes, which execute the JavaScript immediately upon page load.

Check if your site is affected.

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