Pentesting AWS Enviroments

Attacking AWS Accounts from a black box perspective

I spend alot of time reviewing the configuration of AWS accounts from a white box perspective (hence snotra and snotra.cloud which affords you complete visibility of all resources in the account. However, this post will focus on attacking AWS from a black box perspective, where you might only have a static S3 hosted website, a bucket name or an account ID for example. I will endeavour to keep this page updated as new black box techniques are discovered.

S3 Static Website Hosting

All S3 hosted websites are aliased in DNS to an AWS subdomain

Resolve the IP address of the websites hostname

$ host flaws.cloud
flaws.cloud has address 52.92.242.131
flaws.cloud has address 52.92.187.51
flaws.cloud has address 52.218.216.226
flaws.cloud has address 52.92.165.179
flaws.cloud has address 52.92.178.3
flaws.cloud has address 52.92.137.179
flaws.cloud has address 52.92.233.203
flaws.cloud has address 52.218.220.218

Reverse lookup the IP, if the site is hosted in S3 it will resolve to s3-website-<region>.amazonaws.com

$ host 52.92.242.131
131.242.92.52.in-addr.arpa domain name pointer s3-website-us-west-2.amazonaws.com.

Browsing to <bucket_name>.s3-website-<region>.amazonaws.com will also take you to the site e.g. http://flaws.cloud.s3-website-us-west-2.amazonaws.com/

Bucket Listing

If the bucket hosting the website grants list access to everyone the content of the site can be viewed, in the browser by visiting http://<bucketname>.s3.amazonaws.com/, e.g.

$ curl http://flaws.cloud.s3.amazonaws.com/ -q | xq
<?xml version="1.0" encoding="UTF-8"?>
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <Name>flaws.cloud</Name>
  <Prefix/>
  <Marker/>
  <MaxKeys>1000</MaxKeys>
  <IsTruncated>false</IsTruncated>
  <Contents>
    <Key>hint1.html</Key>
    <LastModified>2017-03-14T03:00:38.000Z</LastModified>
    <ETag>"f32e6fbab70a118cf4e2dc03fd71c59d"</ETag>
    <Size>2575</Size>
    <StorageClass>STANDARD</StorageClass>
  </Contents>
  <Contents>
    <Key>hint2.html</Key>
    <LastModified>2017-03-03T04:05:17.000Z</LastModified>
    <ETag>"565f14ec1dce259789eb919ead471ab9"</ETag>
    <Size>1707</Size>
    <StorageClass>STANDARD</StorageClass>
  </Contents>
  <Contents>
    <Key>hint3.html</Key>
    <LastModified>2017-03-03T04:05:11.000Z</LastModified>
    <ETag>"ffe5dc34663f83aedaffa512bec04989"</ETag>
    <Size>1101</Size>
    <StorageClass>STANDARD</StorageClass>
  </Contents>
  <Contents>
    <Key>index.html</Key>
    <LastModified>2020-05-22T18:16:45.000Z</LastModified>
    <ETag>"f01189cce6aed3d3e7f839da3af7000e"</ETag>
    <Size>3162</Size>
    <StorageClass>STANDARD</StorageClass>
  </Contents>
  <Contents>
    <Key>logo.png</Key>
    <LastModified>2018-07-10T16:47:16.000Z</LastModified>
    <ETag>"0623bdd28190d0583ef58379f94c2217"</ETag>
    <Size>15979</Size>
    <StorageClass>STANDARD</StorageClass>
  </Contents>
  <Contents>
    <Key>robots.txt</Key>
    <LastModified>2017-02-27T01:59:28.000Z</LastModified>
    <ETag>"9e6836f2de6d6e6691c78a1902bf9156"</ETag>
    <Size>46</Size>
    <StorageClass>STANDARD</StorageClass>
  </Contents>
  <Contents>
    <Key>secret-dd02c7c.html</Key>
    <LastModified>2017-02-27T01:59:30.000Z</LastModified>
    <ETag>"c5e83d744b4736664ac8375d4464ed4c"</ETag>
    <Size>1051</Size>
    <StorageClass>STANDARD</StorageClass>
  </Contents>
</ListBucketResult>

or via the cli, e.g.

$ aws s3 ls flaws.cloud --no-sign-request
2017-03-14 03:00:38       2575 hint1.html
2017-03-03 04:05:17       1707 hint2.html
2017-03-03 04:05:11       1101 hint3.html
2020-05-22 18:16:45       3162 index.html
2018-07-10 16:47:16      15979 logo.png
2017-02-27 01:59:28         46 robots.txt
2017-02-27 01:59:30       1051 secret-dd02c7c.html

$ aws s3 ls mega-big-tech --no-sign-request
                           PRE images/

Recovering Account ID

Once you have a bucket name it is possible to obtain the ID for the AWS Account hosting the bucket.

Firstly you need a role in an AWS account you control and can assume with the following permissions:

  • s3:GetObject
  • S3:ListBucket

Once you have set up the role and are able to assume it the tool s3-account-search can be use to recover the account ID:

$ s3-account-search arn:aws:iam::747806891254:role/s3-account-search mega-big-tech
Starting search (this can take a while)
found: 1
found: 10
found: 107
found: 1075
found: 10751
found: 107513
found: 1075135
found: 10751350
found: 107513503
found: 1075135037
found: 10751350379
found: 107513503799

Region

As well as in the URL the region of the bucket can also be found in the "x-amz-bucket-region" HTTP header.

$ curl http://flaws.cloud.s3.amazonaws.com/ -i
HTTP/1.1 200 OK
x-amz-id-2: IwojjzeCXRCG6a3rfYf4iTdDV3bQwmeDjCIYuHTEld3F8fX9VCqumbbxnvHWr0UEGImIq3+fnkA=
x-amz-request-id: Y8S0FKWPD4HCS755
Date: Wed, 31 Jan 2024 14:47:44 GMT
x-amz-bucket-region: us-west-2
Content-Type: application/xml
Transfer-Encoding: chunked
Server: AmazonS3

Account Aliases

It is also possible to identify potential accounts by brute-forcing accounts aliases.

curl -v https://<acount_id>.signin.aws.amazon.com
curl -v https://<aliase>.signin.aws.amazon.com

If you get an HTTP 200 response then the account exists.

This tool can be used to automate the process.

./validate_accounts.py -i /tmp/accounts.txt -o /tmp/out.json

Account ID (API Gateway, Lambda, DataExchange)

You can also recover accounts IDs from API Gateways, Lambda Functions and DataExchange Datasets (plus likely many more)

https://github.com/plerionhq/conditional-love/

Tags

It also possible to recover the value of tags.

https://github.com/plerionhq/conditional-love/

Public AMIs

Once an Account ID is obtained this can be used to see if there are any public Amazon Machine Images available for pillaging.

This can be easily done via the AWS Web Console, EC2 > AMIs > select "Public Images" in drop down menu > search by "owner" and enter the account ID.

or via the CLI:

$ aws ec2 describe-images --owners 807659967283 --region us-east-2
{
    "Images": [
        {
            "Architecture": "x86_64",
            "CreationDate": "2022-10-07T15:12:48.000Z",
            "ImageId": "ami-0426fc5dbcbd7067b",
            "ImageLocation": "807659967283/matillion-etl-for-redshift-ami-1.66.20-byol-07-10-2022-14h-42m",
            "ImageType": "machine",
            "Public": true,
            "OwnerId": "807659967283",
            ...

Launch an instance from any public AMIs you find and search the disk for sensitive information including Access Keys, SSH Keys and Passwords.

Public EBS Snapshots

Just like AMIs, EBS snapshots can also be public and should be checked for sensitive information.

In the web console, navigate to EC2 > Snapshots > Select "public snapshots" in the drop down menu > search "owner" and enter account id.

or via the CLI:

$ aws ec2 describe-snapshots --owner-ids 104506445608 --profile default --region us-east-1
{
    "Snapshots": [
        {
            "Description": "Created by CreateImage(i-06d9095368adfe177) for ami-07c95fb3e41cb227c",
            "Encrypted": false,
            "OwnerId": "104506445608",
            "Progress": "100%",
            "SnapshotId": "snap-0c0679098c7a4e636",
            "StartTime": "2023-06-12T15:20:20.580000+00:00",
            "State": "completed",
            "VolumeId": "vol-0ac1d3295a12e424b",
            "VolumeSize": 8,
            "Tags": [
                {
                    "Key": "Name",
                    "Value": "PublicSnapper"
                }
            ],
            "StorageTier": "standard"
        },
        {
            "Description": "Created by CreateImage(i-0199bf97fb9d996f1) for ami-0e411723434b23d13",
            "Encrypted": false,
            "OwnerId": "104506445608",
            "Progress": "100%",
            "SnapshotId": "snap-035930ba8382ddb15",
            "StartTime": "2023-08-24T19:30:49.742000+00:00",
            "State": "completed",
            "VolumeId": "vol-09149587639d7b804",
            "VolumeSize": 24,
            "StorageTier": "standard"
        }
    ]
}


or

aws ec2 describe-snapshots --owner-id self --restorable-by-user-ids all --no-paginate

Create a volume from any snapshots you find, launch an EC2 instance, attach the newly created volume, mount it and search for sensitive content.

The brilliant Pacu has a module "ebs__enum_snapshots_unauth" for this.

User Enumeration

Once an Account ID has been obtained it is possible to enumerate IAM users.

  1. Create a role in your account with no permissions
  2. Set trusted entity as the target AWS account
  3. Edit the roles trust and enter user name you want to validate as the trusted principle
  4. If user does not exist error is returned
  5. If the policy saves then the user exists in the account.

This process has been automated in the Pacu in the "iam__enum_users" module.

Pacu (aws:imported-mfa) > exec iam__enum_users --role-name arn:aws:iam::707306871275:role/user-enum --account-id 099518971931 --word-list users
  Running module iam__enum_users...
[iam__enum_users] Warning: This script does not check if the keys you supplied have the correct permissions. Make sure they are allowed to use iam:UpdateAssumeRolePolicy on the role that you pass into --role-name!

[iam__enum_users] Targeting account ID: 099518971931

[iam__enum_users] Starting user enumeration...

[iam__enum_users]   Found user: arn:aws:iam::099518971931:user/sec1user

[iam__enum_users] Found 1 user(s):

[iam__enum_users]     arn:aws:iam::099518971931:user/secuser

[iam__enum_users] iam__enum_users completed.

[iam__enum_users] MODULE SUMMARY:

  1 user(s) found after 4 guess(es).

Role Enumeration

Once an Account ID has been obtained it is possible to enumerate roles and if you are lucky assume them!

Once method is to try assume a role in another account. The error message can be used to infer if the role exists or not.

aws sts assume-role --role-arn arn:aws:iam::<accountid>:role/<rolename> --role-session-name <sessonname>

Role exists An error occurred (AccessDenied) when calling the AssumeRole operation: User: arn:aws:iam::012345678901:user/MyUser is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::111111111111:role/aws-service-role/rds.amazonaws.com/AWSServiceRoleForRDS

Role does not exist An error occurred (AccessDenied) when calling the AssumeRole operation: Not authorized to perform sts:AssumeRole

Any cross-account roles granting access to all principals with "AWS": "*" can be assumed from any AWS account!

The Pacu “iam__enum_roles” module used a different approach, but like the user enumeration module, will enumerate a list of roles and attempt to assume them.

Misconfigured Github OIDC Role

See AWS and GitHub OIDC Cross Account Roles

resources