DOS through IP spoofing – (Limit Login Attempts Reloaded <= 2.25.5)

Affected pluginLimit Login Attempts Reloaded
Active installs2+ million
Vulnerable version<= 2.25.5
Audited version2.25.5
Fully patched version
Recommended remediationRemoval of the plugin

Description


The plugin uses the current IP address to rate-limit requests to the wp-login endpoint. However, the implementation is vulnerable to IP spoofing, if anything but the default configuration is used to detect IP addresses.

An attacker can exploit this to ban legitimate users or the site’s reverse proxy from requesting the wp-login endpoint, preventing anybody from logging in.

Proof of concept


The plugin uses the “Limit_Login_Attempts::get_address” method everywhere it needs access to the current request’s IP address.

This method contains the following part which makes it vulnerable to IP spoofing.

foreach ($trusted_ip_origins as $origin) {
			
			if (isset($_SERVER[$origin]) && ! empty($_SERVER[$origin])) {
				
				if (strpos($_SERVER[$origin], ',') !== false) {
					
					$origin_ips = explode(',', $_SERVER[$origin]);
					$origin_ips = array_map('trim', $origin_ips);
					
					if ($origin_ips) {
						foreach ($origin_ips as $check_ip) {
							if ($this->is_ip_valid($check_ip)) {
								$ip = $check_ip;
								break 2;
							}
						}
					}
				}
				
				if ($this->is_ip_valid($_SERVER[$origin])) {
					$ip = $_SERVER[$origin];
					break;
				}
			}
		}}

“$trusted_ip_origins” is an array of header names that the plugin will use to determine the current IP address. By default, it only contains REMOTE_ADDR. However, the plugin allows users to configure this in the configuration UI.

This method will always return the leftmost IP address if one of the configured IP-source headers contains multiple IP addresses.

A fronted reverse proxy will, as per the HTTP spec always append headers to the right of already existing values.

Assuming an attacker’s real IP address is 176.202.20.197.

A normal request forwarded by the reverse proxy to the web server will have the following X-Forwarded-For header:

X-Forwarded-For: 176.202.20.197

However, if an attacker sends a request with an already spoofed header to the reverse proxy, the header will now look like this for the web server (and the plugin):

X-Forwarded-For: <spoofed-ip> 176.202.20.197

The plugin will now use “<spoofed-ip>” to rate-limit requests since it always uses the leftmost IP address that is valid.

$origin_ips = explode(',', $_SERVER[$origin]);
$origin_ips = array_map('trim', $origin_ips);

if ($origin_ips) {
    foreach ($origin_ips as $check_ip) {
        if ($this->is_ip_valid($check_ip)) {
            $ip = $check_ip;
            break 2;
        }
    }
}

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 11, 2022
First Response
Fully patched at
Publicly disclosedApril 24, 2023

Miscellaneous


Leave a Reply

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