Resetting An Account's Password Using an SQL Injection Vulnerability

Reading time: 10 minutes

Introduction

In this article we are going to explore how an SQL injection vulnerability can be leveraged to reset an account’s password by extracting the reset token from the database. We will discover how an attacker with access to the source code can identify the steps needed to bypass the authentication mechanism.

The focus of this article is to see how an SQLi can be exploited to gain unauthorized access to a web application. Therefore, we jump straight to the exploitation part, skipping the vulnerability discovery process.

We will observe how an SQLi can be exploited on the 12th version of the open-source ERPNext platform [1]. In this demonstration, we employ white box testing techniques to understand how our malicious requests interact with the database.

Setting Up the White Box Testing Environment

We would like to have a clear overview of how our interaction with the web application looks like in the back end. Therefore, we will set up query logging for MariaDB. We start by modifying the /etc/mysql/my.cnf file and we uncomment general_log_file and general_log:

We then tail the log file to see the queries performed against the database:

Vulnerable Function

ERPNext’s web_search function partially sanitizes user-controlled input:


On lines 484 and 485, the text is escaped before being appended to the mariadb_conditions and postgres_conditions variables. However, on lines 478, 488 and 489 the user-controlled input is not escaped. This theoretically means that we could inject SQL using the scope, limit and start variables.

We validate this assumption by sending a malicious request to the function:

Then, we look into the logs and we grep for the keyword hacked’:

Just as assumed, the scope parameter is not escaped. We can also see that the second hacked’ is indeed escaped, as expected. We will now abuse this vulnerability to gain unauthorized access to the application.

Exploitation

In a black box assessment, we would use the “ORDER BY x #” technique to figure out how many columns the query returns. We need this information in order to craft an UNION query that returns the data that we want. However, we already know from the logs that we are selecting values from five columns:

We use this knowledge to rebuild our request to find out the database version:

And it indeed returns the version of the database:

We can thus access information from the database through this SQL injection. As password reset is our exploitation vector, we will now try to identify users who have an account. To do that, we need to:

1. Enter an invalid log-in ([email protected])

2. Check the logs by grepping for the invalid log-in

3. Identify, by looking at the query, which tables show up in the logs

We attempt an invalid log-in to track where the log-in values end up in the database:

And checking the logs:

The table where users information is stored is called tabUser and the e-mail address is saved in the column name. We modify our payload so that we can extract all users from the tabUser table:

We receive an error:

A collation defines the patterns of bits that represent each character (e.g. the character “a”) in a dataset [2]. In this case, we need to figure out what collation is used for the column name from the tabUser table. To do this, we will query the information_schema database:

And the server response provides the needed collation:

Now we can force the collation directly in our query:

And we receive a response with the registered users:

We can see that the [email protected] account has an associated e-mail address. Thus, we know that we can reset its password as a reset link will be sent to that e-mail. We will use the SQL injection vulnerability to extract the token directly from the database.

Before resetting the password, we need to figure out where the reset token is stored in the database. We will firstly enumerate the database to find where the password reset token is stored. We will query the information_schema database once again. We will update our payload accordingly:

Looking at the server response, we come across the reset_password_key column:

Next, we request a password reset for [email protected]:

We then extract the reset token from the database:

And the result:

It is important to know how the reset link looks like. To do that, we will search for reset_password_key in the source code and we will rebuild the link:

The reset link would look like:

http://erpnext:8000/update-password?key=[TOKEN]&password_expired=true

Where [TOKEN] is the token we extracted using the SQL injection from the database. We access the link via the browser:

We are prompted to change the password. Once we do that, we gain access to the application:

Conclusions

We have seen, using white box techniques, how an SQL injection vulnerability can be used to reset an account's password. From an attacker's perspective, it is now possible to develop an automated way to exploit this vulnerability.

This exploitation chain is possible because a whitelisted function (in this case a function that can be called without any authentication) partially sanitizes user-controllable input. This vulnerability allows an unauthenticated user to arbitrarily reset passwords. It is therefore crucial to sanitize all parameters that are user-controllable. 

References

[1] - https://erpnext.com/

[2] - https://learn.microsoft.com/en-us/sql/relational-databases/collations/collation-and-unicode-support?view=sql-server-ver16