AWS Account ID Enumeration Through Root User MFA
June 27, 2025 by Michael Magyar
Updated on July 9, 2025
Expected reading time: 14 minutes
***UPDATE***
It appears that AWS has fixed the account ID leakage issue, sometime in the first week of July 2025.
The simple script is still valid for enumerating MFA, so I will leave it available: https://github.com/cyqual-sec/aws-mfa-enum.
Regardless of the fix, I still recommend adding some complexity to your AWS root account email addresses, such as through plus-addressing. It is still fairly trivial to guess email addresses used for AWS accounts and validate them through the MFA endpoint if they have a registered method. Why make it easy for threat actors to know which emails are connected to your AWS accounts?
The original article (with an updated timeline) follows for posterity.
TL;DR
It is trivial to list an AWS root user’s registered MFA methods with a simple HTTP POST request (no password or captcha required).
More importantly, if the AWS root user has registered a single U2F key (e.g., a passkey), the response includes the registered method’s arn, including the AWS account ID.
|
|
AWS does not consider this to be a vulnerability. You should defend against this by using complex email addresses for your root user accounts to complicate enumeration (plus-addressing FTW!), registering at least 2 passkeys, and/or by disabling the root user account altogether for member accounts in an organization.
Background
Why AWS Account IDs
Each AWS account is identified by a 12-digit ID. The account ID is not inherently public, but AWS does not consider it to be sensitive information. While knowing the account ID is not enough for an attacker to breach your account, it can still be useful for attackers for several reasons.
Social Engineering
The most obvious reason is social engineering. Because most organizations do not expect anyone to know their AWS account ID, an attacker who knows the account ID could incorporate it in their lure to gain credibility. A fake notice that your account is being closed or that your payment method expired is much more convincing when it includes your account ID.
Finding Public Resources
The account ID is also useful because it is usually part of resource identifiers, called ARNs. Some resources can be publicly enumerated via a list command without supplying an account ID, such as public AMIs and public EBS snapshots, but knowing the account ID can point an attacker at that specific target’s resources.
There are also other resource types that are not easily listable but that can still be made public through an overly permissive resource policy. Knowing the account ID could help an attacker construct potential ARNs for such resources that can be checked for access. For example, targeting a dynamoDB table requires the following ARN structure:
- arn:aws:dynamodb:<REGION>:<ACCOUNT>:table/<TABLENAME>
If an attacker were to know the account ID and guess at the regions that are likely in use (us-east-1 for U.S. based companies, etc.), then the only missing value is the table name. That considerably limits the number of permutations. An attacker could then leverage wordlists to iterate over millions of potential ARNs to see if they exist and/or can be accessed publicly.
There are several notable services that support resource policies and that might be misconfigured to allow anonymous access. Possible examples include the following:
- DynamoDB: arn:aws:dynamodb:<Region>:<AccountId>:table/<TableName>
- Simple Queue Service (SQS): arn:aws:sqs:<Region>:<AccountId>:<QueueName>
- Simple Notification Service (SNS): arn:aws:sns:<Region>:<AccountId>:<TopicName>
- Cloud Watch Log Groups: arn:aws:logs:<Region>:<AccountId>:log-group:<LogGroupName>
- Lambda: arn:aws:lambda:<Region>:<AccountId>:function:<FunctionName>
- Kinesis Data Streams: arn:aws:kinesis:<Region>:<AccountId>:stream/<StreamName>
- Systems Manager Parameter Store: arn:aws:ssm:<Region>:<AccountId>:parameter/<ParameterName>
Note that Secrets Manager was omitted from this list because it appends six random characters to the end of the name, which significantly complicates such attacks.
S3 was also omitted because bucket ARNs do not include the account ID (S3 predates IAM!), which is why bucket names must be globally unique inside a partition.
Bruteforce IAM Users
Because IAM Users are only unique for each account, logging in requires an AWS account ID. Although an attacker could simply spray username and password combinations at all account IDs, the length of the account ID makes this impractical, and it does not allow for targeted attacks.
However, if an attacker were able to associate an AWS account ID with its target, the attacker would have much less entropy to deal with.
Enumerating AWS Account IDs
Researchers have found some very innovative ways to identify a resource’s AWS account ID. Some examples:
- Ben Bridts used IAM condition keys to find the Account ID of any public S3 bucket
- Sam Cox expanded on this approach and used VPC Endpoints policies to find the AWS Account ID of any S3 Bucket
Those approaches focused on starting with a known resource and attempting to associate it with an account ID. The approach I will present is a little different and instead connects an account’s root email to its account ID (when specific conditions are present).
Root User Authentication Flow
When signing into the console as a root user at https://console.aws.amazon.com, your browser interacts with the following endpoints:
- https://signin.aws.amazon.com/metrics/fingerprint
- https://signin.aws.amazon.com/signin
- https://signin.aws.amazon.com/mfa
In addition to validating your password and MFA, this auth flow performs browser fingerprinting, adds CSRF values, and can include a captcha challenge.
MFA Before or After Passwords?
Multi-factor authentication provides additional security guarantees, but it also poses some challenges. If an identity provider supports multiple types of MFA methods (e.g., SMS, email, authenticator app, etc.), then the application may struggle to know which MFA methods to present to the user unless it queries the user record. This presents a chicken or the egg problem.
MFA Options Before Password
Some authentication systems will intentionally NOT validate a user’s password before presenting the user’s MFA options. While this seems backwards, the intention is to block attackers from bruteforcing user passwords. If attackers do not receive authentication success/failure feedback until the full authentication is complete, then they do not know if the failure was because of an incorrect password or a failed MFA challenge.
The tradeoff with this approach is that the authentication flow will have to present the available MFA options before validating the password. In most instances, this allows unauthenticated attackers to enumerate a user’s MFA methods.
Password Before MFA Options
On the other hand, some authentication systems check the user’s password before querying the user record for the MFA methods. This blocks unauthenticated attackers from enumerating a user’s MFA methods, but it provides success/failure feedback prior to the MFA check, allowing an attacker to try passwords and determine whether they are correct.
AWS’s Approach
The login experience for root users through https://console.aws.amazon.com protects against bruteforcing passwords by presenting the MFA challenge before validating the authentication attempt.
This allows for unauthenticated enumeration of the root user’s MFA options. To see this in action, enter your root account email address and a false password. If you have MFA options enabled, you will see a customized list of the onboarded types presented to you even with the incorrect password.
This is not inherently good or bad - it just chooses to protect user passwords over user MFA methods.
Enumerating MFA Methods Programmatically
Looking under the hood of AWS’s root user authentication flow, the https://signin.aws.amazon.com/mfa endpoint caught my eye. The browser sends a POST request with the root user’s email address in the form field email=testing@domain.com. The purpose of this request is to determine whether the account is protected by MFA and to facilitate the MFA challenge process for U2F keys (e.g., passkeys).
This POST request includes sessionId and csrf parameters; however, a simple curl proves that they are not required:
|
|
This is the response I received for an account protected by both a software authenticator app (“SW”) and a YubiKey (“U2F”). The frontend code takes this response and uses it to determine which options to display to the user.
While playing with other accounts and MFA combinations, I encountered the following scenarios:
- Root account doesn’t exist:
{"mfaType":"NONE"} - Root account isn’t protected by MFA:
{"mfaType":"NONE"} - Root account protected by a software application:
{"mfaType":"SW"} - Root account protected by a YubiKey:
{"mfaType":"U2F"} - Root account protected by multiple keys:
{"mfaType":"MULTI","mfaTypeList":[...]}
When a user selects an option, the system will send another POST query to the /mfa endpoint with an additional parameter called selectedMfaOption. In testing, I didn’t see this actually having an effect for SW. However, things get interesting with U2F.
|
|
And this makes sense. AWS needs to provide the challenge string as part of the U2F flow.
However, what was really interesting to me was when I queried an account that only had a single passkey registered (with or without the selectedMfaOption parameter):
|
|
Unlike with multiple passkeys registered, we received the mfaSerial of our key, and this includes the full arn, including the account ID!
Again, this did not require a password, passing a captcha, or any other information than a simple curl query.
Weaknesses
Missing Captcha Protection
The /mfa endpoint is not protected by captcha (or at least it does not deny the request without those fields), so it is easy to mass enumerate.
This could be intentional, but since the web console authentication flow includes a captcha challenge, it seems off that the /mfa endpoint is not gated behind it.
In my opinion, AWS should consider either extending the captcha challenge to the /mfa endpoint or removing it if it is not actually considered a security protection.
MFA Serial Number Information Disclosure
More importantly, the /mfa endpoint provides an overly verbose response that includes the MFA device’s arn, which includes the account ID.
This also appears to be a mistake to me because the mfaSerial field is only present when there is a single U2F key. When multiple U2F keys are registered, the mfaSerial field is absent, so it must not be required for the authentication flow.
My guess is that there is a slightly different code path for constructing an array of U2F devices than for when there is only one record to return. This may also be missed in testing if the test cases do not include the edge case of a single U2F key. It is also possible that the code attempts to add mfaSerial multiple times but errors out when there are multiple keys, but that seems unlikely.
AWS should consider reworking the /mfa endpoint’s response to strip out the mfaSerial when there is a single U2F key configured. There is no reason to disclose a full arn with an account ID on an unauthenticated endpoint if it is not utilized. If I am mistaken and the MFA device ID matters in some cases, I still fail to see why the full arn is required rather than simply the MFA device ID itself.
Testing
OSINT
This behavior can be somewhat useful on an unauthenticated cloud penetration test.
If a tester can enumerate the target organization’s email addresses, the tester can send each of them to the /mfa endpoint. If the email address has been used to sign up for an AWS account and has an MFA method configured, the endpoint with happily provide a list of registered methods, validating that it is an active account.
In addition to simply enumerating existing AWS accounts, if the root user associated with the email address has registered exactly one U2F key, the /mfa endpoint will also return the mfaSerial and therefore the account ID associated with that root email address.
With an account ID, a tester could attempt to enumerate public resources for that target organization or attempt social engineering attacks.
Automated Tool
The /mfa endpoint is relatively trivial to query, so I wrote a quick tool to automate the process. It accepts either a single email or a file with one email per line, queries the /mfa endpoint, and returns formatted results.
Here is the GitHub repository: https://github.com/cyqual-sec/aws-mfa-enum
As with all offensive operations, you should consider the legal ramifications of your actions and only employ tools and techniques with consent of all parties involved.
Mitigations
How you should respond to this information likely depends on several factors, including your threat model. Most organizations will not be adversely affected by having their account ID known. However, some organizations may still wish to at least complicate such enumeration.
Obfuscate AWS Root Email
Some organizations create emails with long, randomized strings for the root (email) users of their AWS accounts (e.g., “vmlwqrjv394rtgfhxakx@domain.com”). Such an approach greatly complicates AWS account enumeration if an attacker is unable to enumerate those email address. However, if the attacker is able to determine that those email addresses exist, such an approach has limited benefit. It can also be hard to manage a fleet of randomized email addresses!
Alternatively, I recommend using plus-addressing, which is supported by both Exchange Online and Google Workspace. This works by simply adding a “+” immediately before the @ symbol of your email address followed by any number of characters. Most email servers will remove the plus and subsequent characters before the @ symbol to determine the recipient mailbox. In effect, you can create an (almost) unlimited number of dynamic aliases, and most service providers, including AWS, will treat them as different addresses for account creation purposes.
One common approach is to create a mailbox or distribution list for AWS accounts such as aws@your.domain and then use plus addressing to differentiate each account. One can even add additional random characters after the account name to complicate enumeration. For example, you could assign the following email addresses to your AWS accounts:
- aws+root-gwktsx@your.domain
- aws+security-zlsuhp@your.domain
- aws+logging-wqmcit@your.domain
- aws+dev-ylmcrp@your.domain
- aws+prod-cecyrl@your.domain
Each of these will simply route all of their email to aws@your.domain, creating a centralized location for AWS email while simultaneously leveraging complex addresses to thwart easy account ID enumeration.
You can change the email address associated with an AWS account by following these instructions, though you have to do that for each account individually if you have an organization. (Maybe some day that will be added to centralized root access as a privileged action!)
Multiple U2F Keys
Another, simpler approach to blocking account ID enumeration is to ensure that you have more or less than exactly one U2F key protecting each of your root accounts (e.g., 0, 2, 3, etc.).
U2F keys are strong, phishing-resistant authentication methods, so it would be better to add a second key to block transmission of the mfaSerial value than to remove these keys altogether.
Removing Root Credentials
Even better, if your account is part of an organization, consider removing credentials from all member accounts by enabling Centralized root access for member accounts and Deleting access keys for the root user.
If you remove the root credentials, your Cloud Security Posture Management (CSPM) tools may complain that your root accounts are not protected by MFA, but this is even arguably more secure, especially if you then use Service Control Policies (SCPs) to Block service access for the root user. You could likely then categorize those findings as mitigated another way. Keep in mind that SCPs only apply to child accounts and cannot affect the root user of the management account.
Vulnerability Reporting
I reported this, but AWS determined that this was working as expected and that account IDs “are not considered secret, sensitive, or confidential information”. Here is a rough timeline:
- 2025-03-13: I reported to AWS through HackerOne
- 2025-03-13: HackerOne triage confirmed receipt
- 2025-03-14: I clarified that this leaks account IDs
- 2025-04-01: AWS confirmed it is functioning as designed
- 2025-06-20: I notified AWS of intent to post and provided draft
- 2025-06-27: I posted after no response from AWS
- 2025-07-??: AWS remediated the issue ***UPDATED***