Many popular WordPress security plugins vulnerable to IP spoofing.

A WordPress user who was facing a small brute-force attack asked us for help. He was using a popular security plugin but, this time, his plugin was literally unable to protect the blog.
Having full root access to the server, I made a quick capture of the incoming TCP traffic on port 80 using the tcpdump utility to see what was going on:

There was a brute-force attack on WordPress wp-login.php page and the HTTP request included an X-Forwarded-For header with an IP. But the most interesting part was that each consecutive POST request sent by the attacker had a different X-Forwarded-For IP. In other words, those IPs were spoofed.

I reviewed the security plugin and found this piece of code:

function brute_get_ip() {
   if ( isset( $this->user_ip ) ) {
      return $this->user_ip;
   }

   $server_headers = array(
      'HTTP_CLIENT_IP',
      'HTTP_CF_CONNECTING_IP',
      'HTTP_X_FORWARDED_FOR',
      'HTTP_X_FORWARDED',
      'HTTP_X_CLUSTER_CLIENT_IP',
      'HTTP_FORWARDED_FOR',
      'HTTP_FORWARDED',
      'REMOTE_ADDR'
   );

   if ( function_exists( 'filter_var' ) ) :
      foreach ( $server_headers as $key ) :
         if ( array_key_exists( $key, $_SERVER ) === true ) :
            foreach ( explode( ',', $_SERVER[ $key ] ) as $ip ) :
               $ip = trim( $ip ); // just to be safe
               //if the IP is private, return REMOTE_ADDR to help prevent spoofing
               if ( $ip == '127.0.0.1' || $ip == '::1' || $this->ip_is_private( $ip ) ) {
                  $this->user_ip = $_SERVER[ 'REMOTE_ADDR' ];
                  return $_SERVER[ 'REMOTE_ADDR' ];
               }
               if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) !== false ) :
                  $this->user_ip = $ip;
                  return $this->user_ip;
               endif;
            endforeach;
         endif;
      endforeach;
   else : // PHP filter extension isn't available

Instead of retrieving the user IP from the safe REMOTE_ADDR variable, the plugin will first attempt to get it from 7 totally unreliable sources: HTTP_CLIENT_IPHTTP_CF_CONNECTING_IPHTTP_X_FORWARDED_FORHTTP_X_FORWARDEDHTTP_X_CLUSTER_CLIENT_IPHTTP_FORWARDED_FOR and HTTP_FORWARDED.

Because those headers are sent by the client (i.e., the attacker) to the server, they cannot be trusted. That is the reason why the security plugin was unable to help during the attack: it relied on the spoofed IPs.

Just the tip of the iceberg ?

I downloaded and tested the 15 most popular security plugins dealing with visitors IP, either recommended in the WordPress Codex (Hardening WordPress and Brute Force Attacks articles) articles, or found via the plugin directory search feature (searching for securityattackfirewall keywords).

Out of the 15 plugins, 9 were vulnerable to IP spoofing.
I sorted them into 3 categories, based on the impact of the security flaw:

  • Low: can display and/or log a spoofed IP.
  • Medium: can blacklist and/or whitelist a spoofed IP.
  • Critical: one or more security features of the plugin can be bypassed by using a spoofed IP.

All In One WP Security & Firewall:

Version: 3.8.3
Impact: critical

  • get_user_ip_address() function in wp-security-utility-ip-address.php:

  • phpwhois_getclientip() function in whois.ip.lib.php:


BruteProtect:

Version: 2.2.6
Impact: critical

  • brute_get_ip() function in bruteprotect.php:


BulletProof Security:

Version: .50.8
Impact: medium

  • bpsPro_get_real_ip_address() function in db-backup-security.php:


Centrora Security (formerly OSE Firewall Security):

Version: 3.8.4
Impact: critical

  • getRealIP() function in RemoteLogin.php:

  • getRealIP() function in ipmanager.php:


iThemes Security (formerly Better WP Security):

Version: 4.4.13
Impact: critical

  • get_ip() function in class-itsec-lib.php:


Mute Screamer:

Version: 1.0.7
Impact: critical

  • ip_address() function in Utils.php:


Security-protection:

Version: 2.1
Impact: low

  • secprot_log() function in security-protection.php:


WordPress Simple Firewall:

Version: 3.5.3
Impact: critical

  • GetVisitorIpAddress() function in icwp-data-processor.php:


Wordfence:

Version: 5.2.4
Impact: critical

  • defaultGetIP() function in wfUtils.php:

  • phpwhois_getclientip() function in whois.ip.lib.php:


The 6 plugins I tested that were not subject to IP spoofing:

  • Apocalypse Meow
  • Botnet Attack Blocker
  • Login LockDown
  • WP fail2ban
  • WP Login Security 2

Preventing IP spoofing

If you are a PHP developer, preventing IP spoofing is easy:

$ip = $_SERVER['REMOTE_ADDR'];

Unless you are seriously looking for troubles, there is absolutely no alternative.
You can add an option to your plugin so that users can change it if their website is behind a reverse proxy, a load-balancer or a CDN, but it should remain optional only and should never be enabled by default.