Cloud Security Wire
AWS Azure GCP RSS
AWSAzureGCPMulti-Cloud Hardening Guide high

Bucket Namespace Collision: The Cross-Cloud Storage Confusion Attack Silently Redirecting Data

Unit 42 documented a technique where attackers register bucket names identical to those expected by multi-cloud pipelines but in a different cloud provider account, silently intercepting data streams without touching the victim's own cloud environment.

By Cloud Security Wire · ·
#S3#GCS#Azure Blob#bucket name collision#multi-cloud#supply chain#data exfiltration#storage security#namespace#Unit 42#cloud misconfiguration
High Severity

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.

References

← All Analysis Subscribe via RSS