Skip to content

Matomo can be tricked to record spoofed X-Forwarded-For IPs #17202

@timdream

Description

@timdream

Problem

Considering the current implementation:

matomo/core/IP.php

Lines 99 to 125 in a31fd86

/**
* Returns the last IP address in a comma separated list, subject to an optional exclusion list.
*
* @param string $csv Comma separated list of elements.
* @param array $excludedIps Optional list of excluded IP addresses (or IP address ranges).
* @return string Last (non-excluded) IP address in the list or an empty string if all given IPs are excluded.
*/
public static function getFirstIpFromList($csv, $excludedIps = null)
{
$p = strrpos($csv, ',');
if ($p !== false) {
$elements = explode(',', $csv);
foreach ($elements as $ipString) {
$element = trim(Common::sanitizeInputValue($ipString));
if(empty($element)) {
continue;
}
$ip = \Matomo\Network\IP::fromStringIP(IPUtils::sanitizeIp($element));
if (empty($excludedIps) || (!in_array($element, $excludedIps) && !$ip->isInRanges($excludedIps))) {
return $element;
}
}
return '';
}
return trim(Common::sanitizeInputValue($csv));
}

With the following setup:

[client] -> [reverse proxy 1] -> [reverse proxy 2] -> [matomo Nginx]

Since each proxy appends the IP of the incoming connection according to RFC7239, X-Forwarded-For will be this by the time the connection reaches Nginx (1):

X-Forwarded-For: (client), (proxy 1)

Matomo currently extracts the first IP from the list, since #10404. This is correct until you realized the "client" can pretended to be a proxy as well, by including its own X-Forwarded-For header, like this:

curl -i -H"X-Forwarded-For: 8.8.8.8" "http://example.com/matomo/matomo.php?..."

When this happens, proxy 1 will be faithfully preserve the header passed in and append the client IP after it (2):

X-Forwarded-For: 8.8.8.8, (client), (proxy 1)

And Matomo will extract 8.8.8.8 as the client IP.

Solutions

Solution 1: Trust the "transparent proxies"

If Matomo decided the first "client" IP can always be trusted, we can still extract the first IP address, and this issue can be closed as "works as intended." But it will in risk of having the analytics data being polluted. Brutal force login detection can be tricked too.

Solution 2: Extract the last IP address

This would involve reverting #10404 and go back to extract the last IP address. Since the original proxy_ips[] configuration is still intact, when facing a header like (2), above, a correctly configured Matomo will be able to exclude the "proxy 1" IP address.

Notes

Thanks!

Metadata

Metadata

Assignees

No one assigned

    Labels

    c: SecurityFor issues that make Matomo more secure. Please report issues through HackerOne and not in Github.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions