· 7 min read
A publicly listable S3 bucket is usually reported as a medium: information disclosure, fix the ACL, move on. The severity depends entirely on what is in it, and what is in it is usually not known until someone reads every object. That is work, which is why it often does not happen.
This campaign is what happens when it does. The target was dev.huge-logistics.com, a Pwned Labs cloud scenario, and Darkmoon was given nothing but the domain name. It went from anonymous listing to a valid IT administrator credential in the same AWS account, then read what that identity could reach.
What the agent found
| Severity | Count |
|---|---|
| Critical | 5 (all exploited) |
| High | 1 (confirmed) |
| Medium | 2 (confirmed) |
| Low | 1 (unconfirmed) |
| Total | 9 |
The bucket held 15 objects across four prefixes. Anonymous listing was the entry point and is recorded as its own high severity finding.
aws s3 ls s3://dev.huge-logistics.com/ --no-sign-request --region us-east-1
PRE admin/
PRE migration-files/
PRE shared/
PRE static/
2023-10-16 17:00:47 5347 index.htmlThe chain, one step at a time
The report records this as a single critical finding titled as a privilege escalation chain, with the five steps enumerated. Each step is also its own finding, so that a remediation team can fix any link independently.
# 1. anonymous: list the bucket, spot the archive
aws s3 cp s3://dev.huge-logistics.com/shared/hl_migration_project.zip \
/tmp/hl_migration_project.zip --no-sign-request --region us-east-1
unzip /tmp/hl_migration_project.zip -d /tmp/hl_migration
# 2. anonymous: the archive contains a PowerShell script with hardcoded keys
cat /tmp/hl_migration/migrate_secrets.ps1
-> $accessKey = "AKIA<redacted>"
$secretKey = "<redacted>"
# 3. as pam-test: the keys are live
aws sts get-caller-identity
-> Arn: arn:aws:iam::794929857501:user/pam-test
# 4. as pam-test: read an object anonymous access could not
aws s3 cp s3://dev.huge-logistics.com/migration-files/test-export.xml /tmp/test-export.xml
# 5. as it-admin: the export contains six more credential sets
aws sts get-caller-identity
-> Arn: arn:aws:iam::794929857501:user/it-adminThe pivot file is the interesting one. test-export.xml is a credential export holding six plaintext credential sets for different systems: an Oracle database account, a root account on an HP server cluster, an AWS IT admin access key pair, an Iron Mountain backup portal login, an Office 365 admin password and a Jira admin account. One file, six systems, three of them outside AWS entirely.
With the it-admin identity the agent read the admin/ prefix, which anonymous and pam-test could not. It found a transaction export with 29 credit card records including card numbers, CVVs, expiry dates, cardholder names and, in the same rows, usernames with plaintext passwords. The report treats that as a PCI data breach finding in its own right. It also retrieved the lab flag file, which is the scenario's proof of full compromise.
Two things the report refused to inflate
A second key set that did not work. A second copy of migrate_secrets.ps1 under migration-files/ carried a different access key. The agent tried it and recorded the answer: InvalidClientTokenId: The security token included in the request is invalid. Rather than listing it as another critical credential leak, the finding is rated medium, because a hardcoded key that no longer authenticates is a hygiene problem, not an access path.
A sibling bucket it could not open. The agent enumerated eight plausible sibling bucket names. Seven returned NoSuchBucket. One, huge-logistics.com, returned AccessDenied, which proves the bucket exists and nothing more. That finding is rated low and marked unconfirmed, which is the correct status for an inference.
Why the chain worked
None of the five steps required an exploit. Every step was an authorised operation performed by an identity that should not have had it:
- The bucket allowed anonymous listing, so the archive was discoverable.
- A migration script carried long lived static keys instead of using a role.
- Those keys belonged to a user that could read an object containing more credentials, which is the definition of a lateral movement primitive.
- The second identity had access to cardholder data sitting unencrypted in object storage next to the public prefixes.
The report also records the AWS account id as disclosed through the leaked credentials, which is a small finding on its own and a useful one for anyone mapping the target.
Remediation
- Turn on S3 Block Public Access at the account level, then fix the bucket policy. Account level enforcement is what survives the next mistake.
- Rotate every credential in
test-export.xmland in both copies ofmigrate_secrets.ps1, including the non-AWS ones. Assume they are public. - Replace static IAM keys in scripts with roles and short lived credentials, and enable MFA on the human accounts.
- Get cardholder data out of general purpose object storage. If it must be stored, it is encrypted, tokenised and access logged, and CVVs are never retained.
- Scan buckets for credential shaped content on write, not on audit day, and alert on
GetObjectfrom unauthenticated principals.
What this proves about autonomous pentesting
The chain is five steps long and each step depends on reading the content of the previous one. That is exactly the kind of work that gets skipped in a time boxed manual test and that a scanner cannot do at all, because a scanner checks whether a bucket is public, not what is inside the ZIP inside the bucket. An agent that can unzip a file, read a PowerShell script, extract two variables, authenticate with them and try the next prefix is doing analyst work, and it does not get bored on object 12 of 15.
The platform is open source under GPL-3.0: repository, documentation.