Security Risk: Minor: Could be used for targeted attacks, but requires specific configuration and knowledge to exploit.
DREAD Score: 5.6
Vulnerability: Stored XSS
Patched Version: 12.6.7
The WordPress plugin WP Statistics, which has an active installation base of 500k users, has an unauthenticated stored XSS vulnerability on versions prior to 12.6.7.
This vulnerability can only be exploited under certain configurations—the default settings are not vulnerable.
- 2019/06/26 – Initial contact to the developer.
- 2019/06/27 – Response from the developer, disclosure of the vulnerability.
- 2019/06/30 – Patch proposed for review.
- 2019/07/01 – Version 12.6.7 released, patching the vulnerability.
Stored XSS via IP Manipulation
Under certain configurations of the plugin, the website can use a header to find the visitor’s IP address.
This is frequently the case when using a firewall, as otherwise all visitors would have the firewall IP instead of their own.
Why Using a Firewall Requires the Use of a Header
By default, a website can easily find the IP address of a user accessing it. It must be the IP performing the request, as shown in the following diagram:
Things get a bit more tricky when we use a website firewall, either to optimize our performance or protect ourselves from attacks.
Since users contact a firewall before contacting the website, the website can’t utilize the connecting address to find out who is actually performing the request between the user or the firewall.
To remedy this case, the firewall typically adds a header containing the original user IP.
This allows the website to correctly identify the original user and his respective IP address.
When the attacker himself emits a forwarded IP (even though one previously didn’t exist), it is possible to confuse the server about the original IP. This also happens when there are multiple layers of firewall, since each one will add the previous IP address.
This depends entirely on how the firewall is configured, and how it handles the existing forwarded values.
Most firewalls will add their own custom header with the user ip, such as X_SUCURI_CLIENTIP.
This header will always have the value of the user’s IP, as it should never exist outside of the context of the firewall. It will therefore be overwritten if it already exists.
In a scenario where you have multiple proxies, you might want to get the client IP, and not the last connected IP. In this case, you can use the common X-forwarded-for header which can contain multiple IP addresses.
If this header already has a value before reaching your firewall, one of the following will be performed:
- Append the user’s IP to any existing ones.
- Leave the value as-is; do not add or replace its value.
- Clear the header; remove all values.
- Overwrite the header with the user’s IP.
These results may vary depending on the firewall being used and its configuration, and allows a multi-level firewall to correctly send the user information to the server.
The plugin’s vulnerability is based on the scenario where it doesn’t sanitize or validate the user’s IP.
The vulnerability can only be exploited when the plugin uses a header to identify the IP address of the visitor (e.g. not REMOTE_ADDR):
Either one of the following two conditions must also be met for the exploit:
- The firewall must be bypassable.*
*This means the website must be configured to accept connections from everyone, and not only the ones forwarded by the firewall.
If you are using the Sucuri Firewall, you can find the details on implementing bypass prevention here: https://kb.sucuri.net/firewall/Configuration/preventing-bypass
- The firewall must leave the header as-is, if it exists.
The common component of these two settings is the forwarded value being fully controlled by the attacker.
Using a WAF does not make you vulnerable unless it can be bypassed (read here for instructions on how to prevent firewall bypass) or it has been configured to leave the IP as-is.
If you previously used a WAF but stopped without updating the plugin’s settings, you might be vulnerable.
Since the header can contain multiple addresses depending on the amount of firewalls and their configuration, the plugin will first set the user IP to the complete header value, then replace it with the rightmost valid address if there are multiple ones provided.
The plugin uses the get_IP method on the class-wp-statistics.php file.
The default value of the IP variable is the header provided in the settings, with REMOTE_ADDR as the default. If there are multiple IP addresses delimited with a comma, the plugin will use the last valid one as the IP.
Since the default value of the IP addresses is the header value and it isn’t sanitized or validated with the FILTER_VALIDATE_IP method, it will be stored as-is if there are not multiple IP addresses in the header.
It will then be part of the page output whenever the visitor IP is used—such as in the top visitors, online users, and recent visitors modules, which are part of the plugin overview page.
Certain types of information might seem safe, such as the visitor’s IP address, but in reality aren’t always what you expect. Due to certain assumptions from the developers, it is possible for visitors to inject malicious code on administrative pages, leading to a full website takeover.
To protect against this vulnerability, we strongly encourage users to update the plugin to version 12.6.7 as soon as possible. Users that are unable to update immediately can leverage the Sucuri Firewall or equivalent technology to virtually patch the vulnerability.