This issue has been assessed as high severity. Review affected configurations immediately.
Cloud storage bucket naming is a global namespace problem. In AWS S3, Google Cloud Storage, and Azure Blob Storage, bucket and container names are globally unique within each service — no two accounts can own the same name in the same service. This property, designed as a convenience feature, creates an attack surface when organisations build pipelines that reference storage endpoints by name without cryptographically binding the reference to a specific account.
Unit 42 documented the practical exploitation of this surface in research published in 2026. The technique — variously called “bucket namespace collision” or “confused deputy storage attack” — allows an attacker to register the same storage bucket name in their own account and intercept data intended for a legitimate bucket.
How the Attack Works
The attack requires two conditions to succeed: a predictable bucket name that the victim organisation will reference, and a code path in the victim’s application or pipeline that addresses the storage endpoint by name without account-level binding.
Consider a common pattern in multi-cloud data pipelines:
# Fragile pattern: bucket addressed by name only
import boto3
s3 = boto3.client('s3', region_name='us-east-1')
s3.upload_file('data.parquet', 'acme-analytics-exports', 'daily/2026-07-16.parquet')
If an attacker registers a bucket named acme-analytics-exports in their own AWS account before the legitimate account creates it (or after it is deleted during infrastructure teardown), this upload will land in the attacker’s bucket. The SDK call succeeds. No error is thrown. The data is gone.
The same pattern applies to:
- CloudFormation and Terraform bucket references that use string names
- CI/CD pipeline configurations that push build artifacts to named buckets
- Logging destinations configured by name in CloudTrail, VPC Flow Logs, or ALB access logs
- Cross-account replication configured by bucket name string rather than ARN
- SDK integrations where the bucket name comes from a configuration file or environment variable
The Namespace Confusion Vector
The attack has three primary variants:
1. Pre-Registration (Squatting)
The attacker identifies likely bucket names before the victim creates them. Common naming patterns are predictable: <orgname>-<environment>-<purpose>, <orgname>-logs-<year>, <orgname>-backups. Automated scanning of GitHub repositories, Terraform plans, and CloudFormation templates frequently reveals intended bucket names before deployment. An attacker who registers the bucket first receives all traffic until the victim notices and takes action.
2. Post-Deletion Squatting (Infrastructure Teardown)
When organisations decommission infrastructure, they may delete storage buckets but retain code that references them. A dormant microservice, a legacy ETL pipeline, or a decommissioned backup workflow may continue attempting to write to a bucket that no longer exists in the victim’s account. An attacker monitoring for bucket deletion events (not directly visible externally, but inferable from failed write errors in public-facing APIs) can register the deleted bucket name and begin receiving the orphaned traffic.
3. Cross-Provider Collision
In multi-cloud environments, a Terraform module or CloudFormation macro that provisions bucket names consistently across providers creates a predictable cross-cloud namespace. If the same name string is used for both an S3 bucket and a GCS bucket, and the application sometimes selects provider by configuration rather than code path, an attacker who controls one namespace can intercept traffic intended for the other.
Detection
Detecting bucket namespace collision attacks requires monitoring both successful and failed API calls across storage services.
AWS: Monitoring for S3 403 and Access Denied on Expected Buckets
# Find S3 writes to buckets not owned by your account
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=PutObject \
--start-time 2026-07-01T00:00:00Z \
--query 'Events[?contains(CloudTrailEvent, `AccessDenied`)].CloudTrailEvent' \
--output text | jq -r '. | fromjson | {time: .eventTime, bucket: .requestParameters.bucketName, key: .requestParameters.key, errorCode: .errorCode}'
Look for AccessDenied errors on PutObject calls to buckets you expect to own. This indicates either that the bucket doesn’t exist in your account or that it exists in another account with a bucket policy that permits your IAM caller.
Checking Bucket Ownership Before Writes
import boto3
from botocore.exceptions import ClientError
def safe_upload(file_path: str, bucket_name: str, key: str, expected_account_id: str):
s3 = boto3.client('s3')
# Verify bucket ownership before writing
try:
result = s3.head_bucket(
Bucket=bucket_name,
ExpectedBucketOwner=expected_account_id # Fail if owned by wrong account
)
except ClientError as e:
error_code = e.response['Error']['Code']
if error_code == '403':
raise PermissionError(
f"Bucket '{bucket_name}' is not owned by account {expected_account_id}. "
"Possible namespace collision attack."
)
raise
s3.upload_file(file_path, bucket_name, key)
The ExpectedBucketOwner parameter is the primary SDK-level defence. When set, the S3 API returns a 403 error if the bucket exists but is owned by a different account — preventing silently misdirected writes.
GCS: Bucket IAM Binding Verification
# Verify GCS bucket owner before use
BUCKET_NAME="acme-analytics-exports"
EXPECTED_PROJECT="my-project-id"
ACTUAL_PROJECT=$(gsutil ls -L -b gs://${BUCKET_NAME} 2>/dev/null | grep "Project:" | awk '{print $2}')
if [ "$ACTUAL_PROJECT" != "$EXPECTED_PROJECT" ]; then
echo "WARNING: Bucket ${BUCKET_NAME} is owned by project ${ACTUAL_PROJECT}, not ${EXPECTED_PROJECT}"
exit 1
fi
Azure: Validating Blob Storage Account Owner
# Verify Azure storage account subscription before writing
STORAGE_ACCOUNT="acmeanalytics"
EXPECTED_SUBSCRIPTION="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
ACTUAL_SUB=$(az storage account show \
--name $STORAGE_ACCOUNT \
--query "id" -o tsv | cut -d/ -f3)
if [ "$ACTUAL_SUB" != "$EXPECTED_SUBSCRIPTION" ]; then
echo "Storage account owned by unexpected subscription: $ACTUAL_SUB"
exit 1
fi
Hardening Recommendations
1. Use Account-Bound References Throughout Infrastructure-as-Code
Replace string-based bucket references with ARN-based references wherever possible. ARNs include the account ID and cannot be redirected to a different account.
# Fragile: name-only reference
resource "aws_s3_object" "export" {
bucket = "acme-analytics-exports"
key = "daily/export.parquet"
source = "export.parquet"
}
# Safe: ARN-based with ownership enforcement
data "aws_s3_bucket" "exports" {
bucket = var.exports_bucket_name
}
resource "aws_s3_object" "export" {
bucket = data.aws_s3_bucket.exports.id
# data.aws_s3_bucket will fail if bucket doesn't exist in the current account
key = "daily/export.parquet"
source = "export.parquet"
}
2. Use S3 Bucket Ownership Controls
Configure S3 Bucket Ownership Controls to require all objects to be owned by the bucket owner account. This prevents cross-account write scenarios where the attacker’s account owns both the bucket and the uploaded objects.
aws s3api put-bucket-ownership-controls \
--bucket YOUR-BUCKET-NAME \
--ownership-controls 'Rules=[{ObjectOwnership=BucketOwnerEnforced}]'
3. Set ExpectedBucketOwner in All SDK Calls
For any application writing to S3, pass ExpectedBucketOwner set to your account ID. This enforces account-level ownership validation on every API call.
aws s3api put-object \
--bucket acme-analytics-exports \
--key daily/2026-07-16.parquet \
--body data.parquet \
--expected-bucket-owner 123456789012
4. Reserve Bucket Names Across All Regions
S3 bucket names are global per-region-set. If your pipeline uses a name like acme-logs, register that name in all AWS regions, even regions you don’t actively use, to prevent squatting. This is particularly important for disaster recovery bucket names that may only be created during failover events.
5. Scan Infrastructure Code and Configurations for Unbound References
Audit Terraform, CloudFormation, CDK, and CI/CD pipeline configurations for bucket name strings that are not validated against account ownership. Automate this check in pre-commit hooks or CI/CD security scans:
# Find S3 bucket references not using ExpectedBucketOwner
grep -r "bucket=" . --include="*.py" | grep -v "expected_bucket_owner"
Impact Assessment
Successful bucket namespace collision has two primary impacts:
Data exfiltration: Data intended for a legitimate analytics, logging, or backup bucket silently routes to the attacker. Depending on the data type, this may constitute a personal data breach under UK GDPR or US state privacy laws, with notification obligations.
Pipeline integrity: Build artifacts, configuration files, or deployment packages written to an attacker-controlled bucket can be replaced with malicious versions before the pipeline reads them back. This converts a storage confusion attack into a supply chain compromise.
Both impact types share a characteristic: they produce no error signals in normal monitoring. The write succeeds, the data is gone, and the loss may not be discovered for days or weeks depending on audit coverage.