DOS through IP spoofing – (SecuPress <= 2.2.2)

| in


Affected pluginSecuPress
Active installs30.000+
Vulnerable version<= 2.2.2
Audited version2.2.2
Fully patched version2.2.3
Recommended remediationUpgrade to version 2.2.3 or higher

Description


The plugin uses the current IP address to rate-limit and/or ban users based on their IP address. However, the implementation is vulnerable to IP spoofing, so an attacker can ban arbitrary IP addresses. This can be exploited by banning search engine crawlers, the site’s reverse proxy, or legitimate users.

Proof of concept


The plugin uses the “secupress_get_ip” method to determine the client IP.

It looks like this:

function secupress_get_ip( $priority = null ) {
   // Find the best order.
   $keys = [
      'HTTP_CF_CONNECTING_IP', // CF = CloudFlare.
      'HTTP_CLIENT_IP',
      'HTTP_X_FORWARDED_FOR',
      'HTTP_X_FORWARDED',
      'HTTP_X_CLUSTER_CLIENT_IP',
      'HTTP_X_REAL_IP',
      'HTTP_FORWARDED_FOR',
      'HTTP_FORWARDED',
      'REMOTE_ADDR',
   ];
   if ( ! is_null( $priority ) ) {
      array_unshift( $keys, $priority );
   }
   $ip = '';
   foreach ( $keys as $key ) {
      if ( array_key_exists( $key, $_SERVER ) ) {
         $ip = explode( ',', $_SERVER[ $key ], 2 );
         $ip = reset( $ip );
         if ( false !== secupress_ip_is_valid( $ip ) ) {
            /**
             * Filter the valid IP address.
             *
             * @since 1.0
             *
             * @param (string) $ip The IP address.
             */
            return apply_filters( 'secupress.ip.get_ip', $ip );
         }
      }
   }
   /**
    * Filter the default IP address.
    *
    * @since 2.0 $ip + $priority params
    * @since 1.0
    *
    * @param (string) The fake IP address.
    * @param (string) $ip The original IP address.
    * @param (string) $priority
    */
   return apply_filters( 'secupress.ip.default_ip', '0.0.0.0', $ip, $priority );
}

This function is wide open to IP spoofing if it is called without “REMOTE_ADDR” being the method argument (which is almost always the case).

The following scenarios are possible:

1. The method is called like so:

$ip = secuperss_get_ip('REMOTE_ADDR');

==> This can never be spoofed.

2. The site is using Cloudflare as a reverse proxy:

$ip = secuprss_get_ip();

==> This can never be spoofed IF the server uses authenticated origin pulls to verify that only Cloudflare can connect directly to the server.
The only reason is that CloudFlare’s “CF-Connecting-Ip” header is defined first on lines 4-13 above.

3. In any other scenario, the plugin is vulnerable to IP spoofing.

To prove this, install the below must-use plugin on a local site. It emulates the behavior of a website with a fronted reverse proxy.

<?php
declare(strict_types=1);

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

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

/**
 * 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('plugins_loaded', function () {
    echo secupress_get_ip();
    echo "\n";
    die();
});

Now, run the following curl commands. (Remember that the “real” IP of the user is hardcoded in the must-use plugin so that )

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

Now:

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

The plugin now thinks we are GoogleBot.

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 09, 2022
First ResponseSeptember 12, 2022
Fully patched atSeptember 14, 2022
Publicly disclosedApril 24, 2023

Miscellaneous


  • The vendor has shown exceptional cooperation and implemented our proposed patches within less than four business days.
  • The vendor was the only one that had a clear security policy and a bug-bounty program.
  • The vendor took this security issue seriously and granted the highest bug bounty in his security policy.

Leave a Reply

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