opencart / opencart

A free shopping cart system. OpenCart is an open source PHP-based online e-commerce solution.

Home Page:https://www.opencart.com/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

IP Spoofing through X-Forwarded-For header

achkar69 opened this issue · comments

What version of OpenCart are you reporting this for?
4.0.2.3

Describe the bug
The web application supports X-Forwarded-For header using which, a user can spoof source address and one cannot verify that the user has logged in from the IP address that is in their account's log. Another risk is that this header is passed on to the proxied application. The application may do any kind of verification, logging, blocking or rate limiting based on the IP address, and this IP address can be overridden by anybody that want to.

As highlighted by @ADDCreative, the X-Forwarded-For or Client-IP headers can also be used in order to bypass the IP based restriction present in APIs.

What section does it affect?
-> Logs
-> IP Section for users in admin panel
-> API

To Reproduce
Steps to reproduce the behavior:

  1. Go to login page
  2. Fill in username & password
  3. Intercept the request in intercepting proxy and append X-Forwarded-For header with any custom value
  4. Login as admin, go to the user you logged in as prior and click on IP section, you'll be able to see the custom value in IP row.

Expected behavior
Ideally the application should not trust these headers as they can be used to spoof source IP address.

Screenshots / Screen recordings
Will share the PoC, if you validate this as a bug or if you guys are actually willing to fix it.

this has to do with ur ssl settings and web server settings.

if i start messing with ip and forcing one ip people that are on large company networks and have multiple ips can lose the session.

share the poc any way i will look at it

  1. It doesn't have anything to do with SSL settings and web server settings, the code is present in file /upload/system/startup.php

// Line Number 48
// Check IP if forwarded IP
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
} elseif (!empty($_SERVER['HTTP_CLIENT_IP'])) {
$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_CLIENT_IP'];
}

  1. In order to fix this issue, you don't have to do much around IP, just disable the X-FORWARDED-FOR header.

if u use cloudflare u wont get the original IP

fine

The bottom line is that OpenCart shouldn't rely on the request's IP-address for any functionality. There are too many uncertainties with IP-addresses, e.g. usage of proxies, VPN, CloudFlare, CGNAT, etc. As long as OpenCart is securely implemented, this shouldn't be an issue.

  1. This is the default behaviour of OpenCart, so the bug exists by default, meaning the integrity of logs cannot be trusted by default. Uncertainty shouldn't be your concern until or unless a functionality entirely depends on the source (proxy, VPN, CloudFlare etc) and specially should not be an excuse, to not fix the bug.

  2. Saying as long as OpenCart product is securely implemented, this shouldn't be an issue is a very naive statement. Almost all the products have securely implement section, but bugs anyway gets discovered and people do get compromised. Doesn't mean you'll ship a vulnerable product and let users fix it.

  3. Honestly as a developer you really can't be like "we'll ship vulnerable by default, it's up to the user if he/she is securely implementing it". Ideally you should be shipping the product as bug free as possible.

  4. Just to mention, i have seen how you guys behave with people reporting bugs, so if you don't wanna fix it, we can end the discussion here. Closing an issue before even letting the author clarify / answer your questions, is indicative enough.

You have yet to demonstrate why this is a security risk. You said that one cannot verify that the user has logged in from the IP address that is in their account's log . A user should be able to log into his account from any network, regardless of which IP-address it uses. So can you please clarify why you think this is an issue?

One question, in case of an incident, how will you trace back where the request originated from ? How will you ensure the integrity of your website logs ?

{ kindly don't joke around saying a hacker will anyway use proxy and stuff, because it's less common than you think. }

not sure how u would intercept the information unless u were the ISP

or in control of the network

we also have a 2 fact auth system recently added in the admin

@achkar69 : You still haven't answered my simple question: A user should be able to log into his account from any network, regardless of which IP-address it uses. So why do you this is an issue?

The only exception would be for an admin login which I usually restrict for certain IP-addresses only via the 'admin/.htaccess'. This of course require the admin to use a proper static IP-address for his office, and not rely on CGNAT, VPN, or CloudFlare etc.

@danielkerr
Question 1 : not sure how u would intercept the information unless u were the ISP
Answer : As a user i can intercept the traffic between my browser and the website, and change anything & everything in the request. We aren't talking about man-in-the-middle scenario here. { Attacker as a website user is the case here }

Question 2: We also have a 2 fact auth system recently added in the admin
Answer : Knew this was coming, you are just playing with me, instead of fixing the actual thing, you are throwing other defenses in place, i have one question for you "Which famous tech you know that had 2FA enabled and was not compromised ever ?". Even google provides 2FA, yet majority of the people don't use it. Does that mean they don't fix things saying "oh we have 2FA, so that's never gonnna happen". Also enumerating directories is not as difficult as you might think, just in case if you are gonna say that next.

@mhcwebdesign bro, are you even reading what i am typing, and if yes, are you even thinking ? Lemme try to clarify more.

Question : A user should be able to log into his account from any network, regardless of which IP-address it uses. So why do you this is an issue ?
Answer : Yes you are correct any user should be able to login into any network regardless of which IP-address it uses. But along with it, the website also relies on this particular IP address as a way of keeping track of "where the request came from", one of the primary reason why we keep logs of user accessing websites, is so that we have visibility over what happened, in case something gets compromised. It's the IP that you are left with eventually.

Sorry, but logging requests including the IP-address is not a security risk. It's a common practice most web servers do. And it can be useful to have this info included. And the logs contain more than just the IP-address. So where do you think is the security risk for OpenCart?

Man, it's not just about "logging IP-address" , either you are playing around and don't wanna fix it or else you genuinely are confused. Here's a reference, it explains what i am explaining.

https://owasp.org/www-community/pages/attacks/ip_spoofing_via_http_headers

@achkar69 : You are still not answering my simple question. Your link says this, which isn't relevant for OpenCart:

Significance of Client IP Addresses

Client IP addresses often serve as crucial identifiers in web applications, influencing access controls and rate limits.

Where does OpenCart rely on the client's IP-address for security purposes?

The IP address is not used for access control, if that's what you are asking. and i have answered your question thrice now, let's have a look at what you just wrote

"but logging requests including the IP-address is not a security risk. It's a common practice most web servers do. And it can be useful to have this info included."

What if that info is not trust worthy ? What if the integrity of that "info" is breached ? Meaning it's not the legit info, but info manipulated by some remote attacker, would it be reliable then ?

@achkar69 : Where does OpenCart rely on the IP-address for security purposes? Who says the logging info, e.g. IP-address, is trustworthy? How would this compromise the OpenCart security? We have spambots, bruteforce attackers, etc, and there are ways to deal with these scenarios, but yours is not an issue to deal with.

"but yours is not an issue to deal with."
You should've started with this, instead of wasting our time, just to mention, i did not advertise it as a 'security risk', it's about data integrity. Which clearly is not a matter of concern for you. Also just to mention if your scope of security issues is limited to "spambots, bruteforce attackers, credit card fraudsters" then you should really start studying more about security or have a person in place who knows more about security than you do. You guys are nothing more than what i expected.

Anyway, have a good day !

@achkar69 : Calm down.

Expected behavior
Ideally the application should not trust these headers as they can be used to spoof source IP address.

OpenCart does not rely on the IP-address for security purposes, simple as that. I won't have sleepless nights over this.

Lmao, if changing 2-3 lines of code can give you sleepless nights, are you even a developer ? Here's a little gift for you.

if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_CLIENT_IP'];
}

Ctrl-C + Ctrl-V

Adios amigo :)

the last issue was reported for an installer which is something that gets deleted after install

Stop bullshitting around man, the /upload/system/startup.php this file is not a part of installer, it's a file used when a user logs in, i have studied the code more in 2 days than you have idk for what time. Don't worry, many CVEs coming your way now ( including an RCE ). They'll be disclosed straight to the public internet. I have seen how good you people are at resolving bugs.

Stop bullshitting around man, the /upload/system/startup.php this file is not a part of installer, it's a file used when a user logs in, i have studied the code more in 2 days than you have idk for what time. Don't worry, many CVEs coming your way now ( including an RCE ). They'll be disclosed straight to the public internet. I have seen how good you people are at resolving bugs.

ur full of shit!

At least you fixed it dickhead, also better be fucking nice to people who help you make your project more secure. There's no need to be such a douchebag.

Lmao, if changing 2-3 lines of code can give you sleepless nights, are you even a developer ? Here's a little gift for you.

if (!empty($_SERVER['HTTP_CLIENT_IP'])) { $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_CLIENT_IP']; }

Ctrl-C + Ctrl-V

Adios amigo :)

@achkar69 That change wouldn't fix the issue. The Client-IP header can be just as easily spoofed as X-Forwarded-For. Both need removing.

@achkar69 :

the /upload/system/startup.php this file is not a part of installer

Wrong, the installer uses it in install/index.php in line 39:

require_once(DIR_SYSTEM . 'startup.php');

Your so-called issue was never a security issue for OpenCart. For anyone who wants to know more about this, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For

@ADDCreative i feel sorry for you, you should've at least tried. Here's a Poc

CASE 1 : Before the commit 6efe939

Code : Startup.php

// Line Number 48
// Check IP if forwarded IP
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
} elseif (!empty($_SERVER['HTTP_CLIENT_IP'])) {
$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_CLIENT_IP'];
}

Sample Request When Logging in as a user or admin :

POST /opencart/admin/index.php?route=common/login.login&login_token=0009f5535ad69f8bd615ad978cda9e00 HTTP/1.1
Host: 127.0.0.1
Content-Length: 38
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/x-www-form-urlencoded
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: OCSESSID=<session-ID>; currency=GBP
Connection: close
X-Forwarded-For: <SPOOFED-IP-HERE>

username=admin&password=<password>

Screenshot :

1

CASE 2 : After the commit 6efe939

Code : Startup.php

// Line number 48
// Check IP
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
	$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_CLIENT_IP'];
}

Sample Request When Logging in as a user or admin :

POST /opencart/admin/index.php?route=common/login.login&login_token=0009f5535ad69f8bd615ad978cda9e00 HTTP/1.1
Host: 127.0.0.1
Content-Length: 38
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/x-www-form-urlencoded
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: OCSESSID=<session-ID>; currency=GBP
Connection: close
X-Forwarded-For: <SPOOFED-IP-HERE>

username=admin&password=<password>

Screenshot :

2

@achkar69 :

the /upload/system/startup.php this file is not a part of installer

Wrong, the installer uses it in install/index.php in line 39:

require_once(DIR_SYSTEM . 'startup.php');

Your so-called issue was never a security issue for OpenCart. For anyone who wants to know more about this, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For

As @danielkerr mentioned "the last issue was reported for an installer which is something that gets deleted after install". My bad, i should've mentioned it's not only used during installation, but it is also used post installation, even after you remove the install directory.

I NEVER SAID, IT'S A SECURITY ISSUE, I HIGHLIGHTED IT'S A DATA INTEGRITY ISSUE.

This is what MDN Docs say "But if there's a risk the client or any proxy is malicious or misconfigured, then it's possible any part (or the entirety) of the header may have been spoofed (and may not be a list or contain IP addresses at all). ". Hence, it should not be trusted.

@ADDCreative i feel sorry for you, you should've at least tried. Here's a Poc

@achkar69 Please do not be rude. I was agreeing and trying to help you. Of course I tested before I posted. X-Forwarded-For is not the only header that can be spoofed. Try just substituting Client-IP in place of X-Forwarded-For in you POST headers and test again.

POST /opencart/admin/index.php?route=common/login.login&login_token=0009f5535ad69f8bd615ad978cda9e00 HTTP/1.1
Host: 127.0.0.1
Content-Length: 38
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/x-www-form-urlencoded
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: OCSESSID=<session-ID>; currency=GBP
Connection: close
Client-IP: <SPOOFED-IP-HERE>

username=admin&password=<password>

There is also another random header used as an IP address in the footer controller that will need removing.

if (isset($this->request->server['HTTP_X_REAL_IP'])) {
$ip = $this->request->server['HTTP_X_REAL_IP'];
} elseif (isset($this->request->server['REMOTE_ADDR'])) {
$ip = $this->request->server['REMOTE_ADDR'];
} else {
$ip = '';
}

@ADDCreative sorry buddy, yeah you are right, multiple places would need this modification. Some of them are

./catalog/controller/common/footer.php:                 if (isset($this->request->server['HTTP_X_REAL_IP'])) {
./catalog/controller/common/footer.php:                         $ip = $this->request->server['HTTP_X_REAL_IP'];
./catalog/controller/api/order.php:                     if (!empty($this->request->server['HTTP_X_FORWARDED_FOR'])) {
./catalog/controller/api/order.php:                             $order_data['forwarded_ip'] = $this->request->server['HTTP_X_FORWARDED_FOR'];
./catalog/controller/api/order.php:                     if (!empty($this->request->server['HTTP_X_FORWARDED_FOR'])) {
./catalog/controller/api/order.php:                             $order_data['forwarded_ip'] = $this->request->server['HTTP_X_FORWARDED_FOR'];
./catalog/controller/checkout/confirm.php:                      if (!empty($this->request->server['HTTP_X_FORWARDED_FOR'])) {
./catalog/controller/checkout/confirm.php:                              $order_data['forwarded_ip'] = $this->request->server['HTTP_X_FORWARDED_FOR'];
./system/startup.php://if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
./system/startup.php:// $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];

OpenCart does not rely on the IP-address for security purposes, simple as that. I won't have sleepless nights over this.

@mhcwebdesign This is a security issue. First the IP address is used for security in the API. Being able to spoof it would make the check pointless.

public function getApiByToken(string $token): array {
$query = $this->db->query("SELECT DISTINCT * FROM `" . DB_PREFIX . "api` `a` LEFT JOIN `" . DB_PREFIX . "api_session` `as` ON (`a`.`api_id` = `as`.`api_id`) LEFT JOIN `" . DB_PREFIX . "api_ip` `ai` ON (`a`.`api_id` = `ai`.`api_id`) WHERE `a`.`status` = '1' AND `as`.`session_id` = '" . $this->db->escape($token) . "' AND `ai`.`ip` = '" . $this->db->escape($this->request->server['REMOTE_ADDR']) . "'");

// Make sure the IP is allowed
$api_info = $this->model_setting_api->getApiByToken($this->request->get['api_token']);
if ($api_info) {

The second issue is the the IP address is used in some on the mail templates. So it's possible for an attacker to set X-Forwarded-For or Client-IP headers to anything (it does not need to be an IP address). They would then use the forgotten password forms (and maybe others) to send messages that included whatever they set the header to, to any registered user or customer.

@ADDCreative : While I agree that the X-Forwarded-For or Client-IP headers could be manipulated, how exactly would the attacker be able to send messages to registered customers or admin users?

And, when using the X-Forwarded-For, Client-IP etc, a validation should always be carried out first, using something like this:

    private function isValidIp($ip) {
        if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)
            && !filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 | FILTER_FLAG_NO_PRIV_RANGE)
        ) {
            return false;
        }

        return true;
    }

For the online reports, e.g. the Who's online stats, merely using the REMOTE_ADDR isn't good enough. While $_SERVER['REMOTE_ADDR'] is the only reliable way to get users ip address, it can show erroneous results if behind a proxy server. From our experience the usefulness for online reports of having X-Forwarded-For or Client-IP outweighs the potential risk of the latter being spoofed.

@ADDCreative : While I agree that the X-Forwarded-For or Client-IP headers could be manipulated, how exactly would the attacker be able to send messages to registered customers or admin users?

And, when using the X-Forwarded-For, Client-IP etc, a validation should always be carried out first, using something like this:

    private function isValidIp($ip) {
        if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)
            && !filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 | FILTER_FLAG_NO_PRIV_RANGE)
        ) {
            return false;
        }

        return true;
    }

For the online reports, e.g. the Who's online stats, merely using the REMOTE_ADDR isn't good enough. While $_SERVER['REMOTE_ADDR'] is the only reliable way to get users ip address, it can show erroneous results if behind a proxy server. From our experience the usefulness for online reports of having X-Forwarded-For or Client-IP outweighs the potential risk of the latter being spoofed.

Talk about APIs too, why are you ignoring that ?

@ADDCreative : While I agree that the X-Forwarded-For or Client-IP headers could be manipulated, how exactly would the attacker be able to send messages to registered customers or admin users?

POST to forgotten password form with one of the headers set to the text you want to appear in the email. The email will be sent out and instead of where the IP address should be you will see the test set in one of the headers.

Probably what is more important than validating the IP address is to only use one of the headers as the IP address if the store has been configured that way.

Probably have something in system/config/default.php and only check the headers if the store owner has configured it.

@mhcwebdesign I have updated the initial description for the issue, can you change the state to open ?

i already fixed it

there is no security issue. just means some extensions requiring to check forward ip vs remote ip wont work. no issue because ip is only for logging.