Affected plugin | Limit Login Attempts Reloaded |
Active installs | 2+ million |
Vulnerable version | <= 2.25.5 |
Audited version | 2.25.5 |
Fully patched version | – |
Recommended remediation | Removal 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 contacted | September 11, 2022 |
First Response | – |
Fully patched at | – |
Publicly disclosed | April 24, 2023 |
Leave a Reply