If I was a highly sophisticated threat actor into pwning cloud stuff, I’d want to gather as many account IDs (and metadata about their owners) as possible. This hypothetical doesn’t seem that unique, even for the good guys, like super security researcher Nick Frichette.
Nick made me promise to stress that this was just a hypothetical and he totally doesn’t have world ending 0day in AWS. I for one definitely believe you Nick.
If bad actors and honest security researchers have a desire to collect account IDs, it stands to reason that keeping them secret has some utility. That’s our positionanyway. There are folks who make spirited but reasonable arguments against that thinking.
Who is correct? Let’s hope it’s Andrew because today we’re finding ways to populate that list Nick claims he has no reason to make.
By the way AWS itself states account IDs, “are not considered secret, sensitive, or confidential information”. If you subscribe to that view then there’s nothing being leaked and you can stop reading here. Enjoy the time saved, perhaps with a refreshing glass of Milo? ⏱️
Validating AWS account IDs
An AWS account ID is 12 digits. Twelve digit numbers don’t seem that common so one way to make a list of them might be to search the internet for them and assume they are all AWS account IDs. That could work, but how would you know for sure?
Before we start looking for them, we need a way to validate that a twelve digit number is indeed a valid AWS account ID. Given that AWS doesn’t recognize them as secrets, it makes sense that there would be some methods to do that validation.
The classic way
The AWS policy engine is very powerful and very helpful. Try to attach a malformed policy to a resource or identity and it will tell you, “no, this is not valid you incompetent fool”. Besides validating well formed JSON, the policy engine also validates ARNs, ensuring they exist before allowing them to be used. Since every account has a “root” principal, which is sort of just short hand for ‘the whole account’, it’s possible use the policy engine to check if an account exists.
Here’s an example of doing that using the validate principals tool from aws_pwn. There are other tools such as Quiet Riot that scale this approach so it doesn’t take as long as my kids do to brush their damn teeth. Oof.
% ./validate_iam_principals.py \
-i root.txt \
-a 123456789012
{
"principalName": "root",
"accountId": "123456789012",
"arn": "arn:aws:iam::123456789012:root",
"exists": true,
"error": null
}
The new way
Not everyone wants to do it the way old people (me, I’m the old people) do it though. That’s alright, we welcome hackers of all ages here.
Recently we identified and reported to AWS that some API calls perform account validation server-side when supplied certain parameters. For example, SecretsManager:DescribeSecret will helpfully explain that there was “No account found for the given parameters” when passed a SecretID ARN in an account that doesn’t exist.
% aws secretsmanager describe-secret --secret-id arn:aws:secretsmanager:us-east-2:111122223333:secret:MySecret-abcdef
An error occurred (AccessDeniedException) when calling the DescribeSecret operation: Access to account 111122223333 not allowed: No account found for the given parameters
Yet, it will complain that the user “is not authorized to perform: secretsmanager:DescribeSecret” when given a SecretID ARN in a real account.
% aws secretsmanager describe-secret --secret-id arn:aws:secretsmanager:us-east-1:REALACCOUNTID:secret:MySecret-abcdef
An error occurred (AccessDeniedException) when calling the DescribeSecret operation: User: REDACTED is not authorized to perform: secretsmanager:DescribeSecret on resource: arn:aws:secretsmanager:us-east-1:REALACCOUNTID:secret:MySecret-abcdef because no resource-based policy allows the secretsmanager:DescribeSecret action
The actual SecretID value itself is completely irrelevant and can be totally imaginary, like the “Full” in Tesla FSD.
A similar behavior exists in Amazon CloudWatch Logs when calling logs:GetLogGroupFields with a log group identifier ARN.
% aws logs get-log-group-fields --log-group-identifier arn:aws:logs:ap-southeast-2:REALACCOUNTID:log-group:/blah
An error occurred (AccessDeniedException) when calling the GetLogGroupFields operation: User with accountId: REDACTED is not authorized to perform GetLogGroupFields on resources /blah.
% aws logs get-log-group-fields --log-group-identifier arn:aws:logs:ap-southeast-2:111122223333:log-group:/blah
An error occurred (UnrecognizedClientException) when calling the GetLogGroupFields operation: No account found for the given parameters
We weren’t able to comprehensively identify all the API operations with this quirk because the errors are generated server side and don’t appear to be in any public SDKs. However, we asked a magic 8-ball whether there were other such instances and it said “it is decidedly so”.
Poking AWS public applications
The internet is overflowing AWS related domains and web apps. Finding quirky ones that reveal account IDs is an interesting challenge. Here’s what we’ve seen so far.
Serverless Application Repository
AWS has many registries and repositories, some of them public, some of them not. They all appear to be built by different teams in slightly different ways, which leads to interesting behaviors.
The Serverless Application Repository (SAR) is a public repository for you guessed it, serverless applications. It lets folks store and share reusable applications with the world. Besides being a fun way to distribute correct-looking-but-dangerous malicious code (like every other code registry in the world), it’s a great way to save time with re-usable components.
Anyone with an AWS account can publish a serverless application to the SAR. Today there are approximately 1800 published applications. Each application has a bunch of metadata included with it, such as its code repo URL, author’s name, a license, permissions required etc. Fortunately for us, that list also includes the full ARN with owner account ID, of the app. This makes sense because in order to use a lambda layer for example, you have to specify its ARN.
It’s trivial to pull all the account IDs and other metadata of these apps using the below handy paginated API. No authentication required.
% curl "https://shr32taah3.execute-api.us-east-1.amazonaws.com/Prod/applications/browse?pageSize=100&pageNumber=1" | jq .
{
"approximateResultCount": 1052,
"applications": [
{
"id": "arn:aws:serverlessrepo:us-east-1:145266761615:applications/image-magick-lambda-layer",
"name": "image-magick-lambda-layer",
"description": "Static build of ImageMagick for Amazon Linux 2, packaged as a Lambda layer. Bundles ImageMagick 7.0.8-45.\nIncluding convert, mogrify and identify tools and support for jpeg, gif, png, tiff and webm formats.\n",
"publisherAlias": "Gojko Adzic",
"homePageUrl": "https://github.com/serverlesspub/imagemagick-aws-lambda-2",
"deploymentCount": 11126,
"isVerifiedAuthor": true,
"verifiedAuthorUrl": "https://github.com/gojko",
"labels": [
"image",
"lambda",
"layer",
"imagemagick"
],
"requiredCapabilitiesForLatestVersion": []
},
...
Our recommendation for customers is to publish resources like lambda layers, applications, and integrations from separate purpose-built AWS accounts. Avoid doing so from production accounts.
Marketplace reviews
The AWS Marketplace is awesome. When you want to buy a piece of software that runs on top of AWS, instead of mucking around with external contracts, bank transfers and deployment tools, you just do it all natively inside the marketplace. You can buy Plerion’s cloud security platform through the AWS Marketplace.
The finding here is not what you’d expect. It’s a little more confronting. The Marketplace doesn’t appear to show the account IDs of the software vendors, but it does show account IDs of the customers who provide a review, along with their review. Not only does that reveal the owners of accounts but also the software they are using, which can be valuable to an attacker attempting to map a customer’s attack surface.
The API in question is not authenticated but does require some annoying cookies. First you’ll need to do a search, which will hit the discovery API to get a full list of software.
https://aws.amazon.com/marketplace/search
https://aws.amazon.com/marketplace/api/awsmpdiscovery
Within that list will be a review URL and review identifier (e.g. B01LXMNGHB) for each piece of software. From there it’s a no fuss get request to retrieve all of the reviews with their metadata.
% curl "https://aws.amazon.com/marketplace/community/ajax/reviews/B01LXMNGHB?page=1&size=100" | jq .
{
"AverageCustomerRating": 5,
"CustomerRatingCounts": {
"Star1": 0,
"Star2": 0,
"Star3": 0,
"Star4": 9,
"Star5": 17
},
"CustomerRatingPercent": {
"Star1": 0.00000,
"Star2": 0.00000,
"Star3": 0.00000,
"Star4": 0.34615,
"Star5": 0.65385
},
"CustomerReviews": [
{
"Id": "6c5509fe-857c-4af1-8afd-26d25679c483",
"ExternalReviewId": null,
"Rating": 5,
"CustomerAccountId": "978771243266",
"CustomerName": "shixuesong",
"Title": "Thank you!",
"Body": "erified purchase review from AWS Marketplace<br/>We are satisfied , the vendor support was professional and the product itself was a huge<br/>step forward to our business.",
"Date": "2023-06-27T02:23:08.000Z",
"Comments": [],
"ExternalUrl": "N/A",
"Datasource": "AWSMP",
"Verified": true,
"TotalComments": 0,
"HelpfulCount": 0,
"IsErrorResponse": false
},
...
For now, our recommendation is to avoid writing reviews on the AWS Marketplace unless there’s a strong incentive to do so. If possible use a pseudonym that can’t be traced to your organisation and perhaps even use a separate account just for reviews.
That advice is probably super annoying and over-the-top. In fact, AWS has requested that we not discourage customers from writing reviews because their view is account IDs are not secrets. I guess it all depends on which side of this nerdy argument you land. 🤷
Registry of Open Data
If you want to access open source data sets, most cloud providers have a service that hosts and shares them. Google has BigQuery Public Datasets. Microsoft has Azure Open Datasets. And Amazon has the Registry of Open Data.
Each dataset hosted in the registry comes with a resource ARN pointing to where the data lives. Most of those resources are S3 buckets which are in a global namespace and don’t contain ARNs, but some are not buckets and include full ARNs with account IDs. Those account IDs end up in a JavaScript file.
% curl "https://registry.opendata.aws/js/index.js"
...
}, {
"Description": "Notifications for the Level II archival bucket",
"ARN": "arn:aws:sns:us-east-1:811054952067:NewNEXRADLevel2Archive",
"Region": "us-east-1",
"Type": "SNS Topic"
}, {
...
Parsing the JavaScript file might be overkill since contributing to the registry requires a YAML file to be submitted to this public Github repository. It contains more details and can be cloned at your leisure.
Searching searchable sources
Undoubtedly the best place to find account IDs is Github. The issue is not if you’ll find account IDs, but what is the fastest and most complete way to search.
The queries
Account IDs pop up in a number of contexts. Without being exhaustive, here are three regular expressions that will find them.
1. Console sign-in URLs – Amazon allows you to auto-populate the account ID field of a management console login screen by including it in a URL.
/\d{12}\.signin.aws.amazon.com/
2. Amazon Resource Names (ARNs) – ARNs with account IDs are required by many AWS API operations so they end up everywhere in code.
/arn:aws:iam::[0-9]{12}:.+/
3. Variable assignment – It’s common practice to store an account ID inside a variable and then reference it elsewhere by name.
/account[_-]id[\s](:|=)[\s][\'\"]\d{12}/
You could also just as easily search for s3 buckets and other identifiers that can have their account IDs enumerated.
The tools
There are more tools to search Github than Time the Toolman Taylor would know what to do with.
- Github Code Search – Github offers a native search interface within its user interface. It’s a great way to test theories about search queries to see what works.
- Sourcegraph – Sourcegraph allows developers to rapidly search the biggest open source repositories hosted on Github, no authentication required.
- Github public events feed – Searching the totallity of Github is fun but once you’ve done it, you probably will want to keep up to date without searching the whole thing again. Github offers a streaming API of all public repo events that you can plug into.
- GH Archive – Finally, there’s a project that records the public GitHub event timeline and archive it in S3. It’s maybe the most reliable and scalable way to search all of Github’s public repos.
Asking the AWS CLI nicely
From the first stirrings of life beneath water, to the great beasts of the stone age, account IDs have been available to CLI users who ask nicely for them. This generally applies to older services that allow resources to be shared globally using full ARNs as identifiers. Below are some well known examples.
EC2 Images:
% aws ec2 describe-images --region [REGION] | jq '.Images[] | {OwnerId, Description}'
{
"OwnerId": "979382823631",
"Description": "This image may not be the latest version available and might include security vulnerabilities. Please check the latest, up-to-date, available version at https://bitnami.com/stacks."
}
{
"OwnerId": "466383067502",
"Description": "Cloud9 Cloud9Ubuntu AMI"
}
...
EBS snapshots:
% aws ec2 describe-snapshots --region [REGION] | jq '.Snapshots[] | {OwnerId, Description}'
{
"OwnerId": "099720109477",
"Description": "hvm-ssd/ubuntu-precise-amd64-server-20150930"
}
{
"OwnerId": "099720109477",
"Description": "hvm/ubuntu-trusty-amd64-server-20150805"
}
...
FPGA Images (AFIs) – I had to ask AI to understand this one. An AFI is a binary file that contains the compiled hardware design that you can load onto an FPGA. In the AWS context, AFIs are what you deploy to AWS F1 instances (a type of EC2 instance equipped with FPGAs) to run your custom hardware accelerations.
% aws ec2 describe-fpga-images --region [REGION] | jq '.FpgaImages[] | {OwnerId, OwnerAlias, Description}'
{
"OwnerId": "562705319195",
"OwnerAlias": null,
"Description": "rsa_hls_krnl.hw.xilinx_aws-vu9p-f1-04261818_dynamic_5_0"
}
{
"OwnerId": "095707098027",
"OwnerAlias": "amazon",
"Description": null
}
...
RDS cluster snapshots:
% aws rds describe-db-cluster-snapshots --include-public --region [REGION] | jq '.DBClusterSnapshots[] | {DBClusterSnapshotArn}'
{
"DBClusterSnapshotArn": "arn:aws:rds:ap-southeast-2:409321507311:cluster-snapshot:cluster-snapshot-yjh5xzmk0779kce7-rdm7agoz1xrlfjdjmysdokmc5"
}
{
"DBClusterSnapshotArn": "arn:aws:rds:ap-southeast-2:923256115193:cluster-snapshot:pb2keventcanary-palisade-pb2k-rds-clust-rdscluster-vo8nwohrbl1i-snapshot-0df1a559-d43e-415c-8a44-9b6785032e73"
}
...
RDS snapshots:
% aws rds describe-db-snapshots --include-public --region [REGION] | jq '.DBSnapshots[] | {DBSnapshotArn}'
{
"DBSnapshotArn": "arn:aws:rds:ap-southeast-2:740749281307:snapshot:np1vy92yswr68ff-publicmagnolia"
}
{
"DBSnapshotArn": "arn:aws:rds:ap-southeast-2:430571171076:snapshot:oracle-db-snapshot"
}
...
Amateur tip: Don’t forget to cycle through all of the regions as in general these resources are region bound. Describe calls will produce different lists in different regions.
Our recommendation for customers is to publish resources like images and snapshots from separate purpose-built AWS accounts. Avoid doing so from production accounts.
Resolving access keys to ARNs
Did you know that there are account IDs hidden inside AWS unique identifiers? Between Aidan Steele and his posts on AWS Access Key ID formats and Reversing AWS IAM unique IDs, and Tal Be’ery and his article titled A short note on AWS KEY ID, we now have an understanding of how to reliably extract account IDs from these identifiers:
We didn’t go looking for these ourselves but AWS users who publish these should be aware that they are also effectively publishing their account IDs.
Enumerating account IDs from public resources
It’s not just unique identifiers that can be convinced to spill their beans. Some public AWS resources that are addressed in global namespaces can also have their account IDs enumerated. This wonderful blog post by Ben Bridts describes a way to abuse IAM policy condition evaluation logic to enumerate account IDs of s3 buckets character by character.
As a result of Ben’s work, finding a publicly accessible S3 bucket also means you’ve found the ID of the account it is hosted in. The assignment then becomes finding public S3 buckets. The folks at Grayhat Warfare have completed that assignment for us by indexing over 300,000 buckets and putting them in a searchable database.
There’s just no good advice for not revealing account IDs through public S3 buckets. Sometimes your use case just requires a web blob store 🤷.
Manual labor and random ideas
There are people who have completed some of these missions before and kindly shared their results. For example, fwd:cloudsec maintains a list of well known accounts and their owners. Duo Labs maintains one too. Quiet Riot comes bundled with tens of thousands of valid account IDs.
If you browse around AWS properties long enough with a “\d{12}” regex hoovering up data in the background, you will find a lot of Amazon’s own account IDs.
- The ECR public registry has a javascript file with guest role ARNs related to CloudWatch RUM.
- SkillBuilder has guest role ARN related to CloudWatch RUM.
- AppStream publishes fleet image ARNs inside Javascript files.
- There’s even a global static file imported by the Management Console that includes 160 accounts.
- And so on…
Okay, but where is the list?
There is no list. It’s your conference presentation and your 0day Nick. You’ll just have to do the hard work yourself! Maybe you can share the list after you deliver the catchy conference talk?