T1619

Cloud Storage Object Discovery

Discovery Last updated:

This detection identifies adversary enumeration of cloud storage objects across AWS S3, Azure Blob Storage, and GCP Cloud Storage. Attackers use native cloud APIs (e.g., ListObjectsV2 for S3, List Blobs for Azure) to survey accessible buckets and containers, typically as a precursor to data staging or exfiltration. The detection looks for anomalous listing activity including high-volume object enumeration, access from unexpected identities or IPs, enumeration across multiple buckets in short time windows, and listing operations performed by service principals or IAM roles outside their expected behavioral baseline. Tools such as Pacu and Peirates are known to automate these enumeration workflows.

What is T1619 Cloud Storage Object Discovery?

Cloud Storage Object Discovery (T1619) maps to the Discovery tactic — the adversary is trying to figure out your environment in MITRE ATT&CK.

This page provides production-ready detection logic for Cloud Storage Object Discovery, covering the data sources and telemetry it touches: AWS CloudTrail (via Microsoft Sentinel AWSCloudTrail connector), Azure Storage Analytics / Diagnostic Logs (StorageBlobLogs). The queries below are rated high severity at medium confidence, and ship for 7 SIEM platforms — KQL, SPL, Elastic, QRadar, Sumo, YARA-L, LogScale.

MITRE ATT&CK

Tactic
Discovery
Technique
T1619 Cloud Storage Object Discovery
Canonical reference
https://attack.mitre.org/techniques/T1619/
Microsoft Sentinel / Defender
kusto
let LookbackWindow = 1h;
let HighVolumeThreshold = 50;
let UniqueBucketThreshold = 5;
// AWS S3 enumeration via CloudTrail
let AWSS3Discovery = AWSCloudTrail
| where TimeGenerated >= ago(LookbackWindow)
| where EventName in ("ListBuckets", "ListObjects", "ListObjectsV2", "ListObjectVersions", "ListMultipartUploads", "GetBucketAcl", "GetBucketPolicy", "GetBucketLocation")
| extend UserIdentity = coalesce(UserIdentityArn, UserIdentityUserName, UserIdentityPrincipalid)
| summarize
    OperationCount = count(),
    UniqueBuckets = dcount(tostring(RequestParameters)),
    Operations = make_set(EventName),
    SourceIPs = make_set(SourceIpAddress),
    AWSRegions = make_set(AWSRegion),
    FirstSeen = min(TimeGenerated),
    LastSeen = max(TimeGenerated)
    by UserIdentity, UserIdentityType, UserAgent, bin(TimeGenerated, 10m)
| where OperationCount > HighVolumeThreshold or UniqueBuckets > UniqueBucketThreshold
| extend Platform = "AWS", Severity = iff(OperationCount > 200 or UniqueBuckets > 20, "High", "Medium")
| project TimeGenerated, Platform, UserIdentity, UserIdentityType, UserAgent, OperationCount, UniqueBuckets, Operations, SourceIPs, AWSRegions, FirstSeen, LastSeen, Severity;
// Azure Blob Storage enumeration
let AzureBlobDiscovery = StorageBlobLogs
| where TimeGenerated >= ago(LookbackWindow)
| where OperationName in ("ListBlobs", "ListContainers", "GetContainerProperties", "GetBlobServiceProperties", "ListBlobsFlatSegment", "ListBlobsHierarchySegment")
| summarize
    OperationCount = count(),
    UniqueContainers = dcount(Uri),
    Operations = make_set(OperationName),
    SourceIPs = make_set(CallerIpAddress),
    Accounts = make_set(AccountName),
    FirstSeen = min(TimeGenerated),
    LastSeen = max(TimeGenerated)
    by AuthenticationType, RequesterObjectId, UserAgentHeader, bin(TimeGenerated, 10m)
| where OperationCount > HighVolumeThreshold or UniqueContainers > UniqueBucketThreshold
| extend
    Platform = "Azure",
    Severity = iff(OperationCount > 200 or UniqueContainers > 20, "High", "Medium"),
    UserIdentity = coalesce(RequesterObjectId, "Anonymous"),
    UserAgent = UserAgentHeader,
    AWSRegions = dynamic([])
| project TimeGenerated, Platform, UserIdentity, AuthenticationType, UserAgent, OperationCount, UniqueContainers, Operations, SourceIPs, Accounts, FirstSeen, LastSeen, Severity;
// Union and surface results
AWSS3Discovery
| union AzureBlobDiscovery
| sort by OperationCount desc

Detects high-volume or broad cloud storage enumeration across AWS S3 (via CloudTrail ListObjectsV2/ListBuckets and related APIs) and Azure Blob Storage (via StorageBlobLogs List operations). Alerts when a single identity performs more than 50 listing operations or touches more than 5 unique buckets/containers within a 10-minute window, which is indicative of automated reconnaissance tools like Pacu or Peirates.

high severity medium confidence

Data Sources

AWS CloudTrail (via Microsoft Sentinel AWSCloudTrail connector) Azure Storage Analytics / Diagnostic Logs (StorageBlobLogs)

Required Tables

AWSCloudTrail StorageBlobLogs

False Positives

  • Legitimate cloud-native backup solutions (e.g., Veeam, AWS Backup, Azure Backup) that enumerate storage objects on a schedule
  • Data lake or ETL pipeline services (Azure Data Factory, AWS Glue) that list objects as part of normal pipeline execution
  • Security posture management tools (Prisma Cloud, Wiz, AWS Security Hub) performing periodic storage inventory scans
  • DevOps CI/CD pipelines that sync or audit S3/Blob content as part of deployment workflows
  • Cloud cost optimization tools (CloudHealth, Spot.io) enumerating storage for billing analysis

Sigma rule & cross-platform mapping

The detection logic for Cloud Storage Object Discovery (T1619) above is provided in a vendor-neutral form so you can deploy it on any SIEM. The same logic is shipped here as native KQL (Microsoft Sentinel / Defender), SPL (Splunk), Elastic (Elastic Security (EQL)), QRadar (IBM QRadar (AQL)), Sumo (Sumo Logic CSE), YARA-L (Google Chronicle / SecOps), LogScale (CrowdStrike LogScale (CQL)) queries. In Sigma terms, this detection targets the following logsource:

logsource:
  product: azure

Browse the community-maintained Sigma rules for this technique:


Testing Methodology

Validate this detection against 3 adversary techniques from Atomic Red Team. Each test below lists the behaviour to exercise and the telemetry you should expect to see. Executable commands and cleanup steps are available with Pro.

  1. Test 1AWS S3 Bucket and Object Enumeration with AWS CLI

    Expected signal: CloudTrail will generate events: eventName=ListBuckets, eventName=ListObjectsV2 (one per bucket enumerated), eventName=GetBucketLocation, eventName=GetBucketAcl, eventName=GetBucketPolicy. All events will share the same userIdentity.arn and originating sourceIPAddress. Events appear in CloudTrail within 15 minutes (standard delivery) or near-real-time (CloudTrail Lake).

  2. Test 2Pacu Framework AWS Storage Enumeration Module

    Expected signal: CloudTrail events with userAgent containing 'Boto3' (Pacu uses Boto3 SDK). EventNames: ListBuckets, GetBucketAcl, GetBucketLocation, DescribeVolumes, DescribeSnapshots. The Pacu session generates many rapid sequential API calls detectable by rate and ordering patterns.

  3. Test 3Azure Blob Storage Container and Object Enumeration via Azure CLI

    Expected signal: Azure StorageBlobLogs table will contain OperationName values: ListContainers, ListBlobs, GetContainerProperties. CallerIpAddress will reflect the test machine IP. AuthenticationType will show 'StorageKey' (when using account key) or 'OAuth' (when using service principal). AzureActivity log will show Microsoft.Storage/storageAccounts/listKeys/action if keys were retrieved.


Response Playbook

Triage

  1. Step 1: Identify the actor identity (IAM role ARN, service principal OID, or API key) responsible for the listing operations. Determine if it is a human identity, service account, or assumed role — check AWS IAM console or Azure Entra ID for the identity's purpose and normal usage pattern.
  2. Step 2: Review the source IP(s) associated with the enumeration. Cross-reference against known corporate egress IPs, VPN ranges, and cloud service IP ranges (AWS/Azure published IP ranges). An unrecognized or foreign-country IP significantly increases suspicion.
  3. Step 3: Examine the user-agent string. Tools like Pacu report 'Boto3' or 'python-requests' with version strings; Peirates may show Go-http-client. Compare against the expected agents for legitimate services using that identity.
  4. Step 4: Determine the scope of enumeration — which buckets or containers were listed, and whether they contain sensitive data (PII, credentials, backups, source code). Use AWS S3 Inventory or Azure Storage Explorer to assess bucket sensitivity.
  5. Step 5: Check whether any GetObject, CopyObject, or PutObject calls followed the listing operations within the same session or the next 30 minutes — this would indicate progression from discovery to access or exfiltration.
  6. Step 6: Review CloudTrail/Azure Activity Logs for the 24 hours prior to the listing event for reconnaissance precursors such as ListBuckets, DescribeInstances, AssumeRole, or GetCallerIdentity — these indicate a broader discovery campaign.
  7. Step 7: Correlate with authentication logs (AADSignInLogs for Azure, CloudTrail ConsoleLogin events for AWS) to determine if the identity authenticated from an unusual location or at an unusual time immediately before the enumeration.

Containment

  1. If the identity is a human IAM user or Entra ID account: immediately revoke active sessions (AWS: delete session tokens via IAM; Azure: revoke refresh tokens via Entra ID Revoke Sessions), then apply an explicit Deny policy to the account pending investigation.
  2. If the identity is an IAM role or service principal: identify all resources currently using this role/SPN (EC2 instance profiles, Lambda functions, Azure Managed Identity assignments) before rotating credentials to avoid breaking production services. Coordinate with the service owner.
  3. For AWS: apply an inline IAM policy with an explicit Deny on s3:ListBucket, s3:GetObject, s3:ListAllMyBuckets to the compromised principal while the investigation proceeds. This blocks further enumeration without deleting credentials.
  4. For Azure: remove the Storage Blob Data Reader or Storage Account Contributor role assignments for the compromised principal on affected storage accounts. Apply a conditional access policy requiring MFA or blocking sign-in from the observed source IP.
  5. If the source IP is external and unknown: submit a block request to the network team or WAF/firewall administrators for the specific IP or CIDR range to prevent follow-on access attempts.
  6. If access keys are suspected compromised (AWS): disable the key immediately via IAM, do not delete until forensics are complete (deletion destroys the audit trail of where the key was used).

Evidence Collection

  1. Export the full CloudTrail event history for the affected principal for the past 7 days: use CloudTrail Lake or S3 bucket queries filtered by userIdentity.arn. Focus on S3 data events (object-level logging must be enabled — verify this is active before concluding no GetObject calls occurred).
  2. For Azure: export Storage Blob diagnostic logs for the affected storage accounts from Log Analytics workspace. Also pull Azure Activity Logs for the subscription showing role assignments, policy changes, and management plane activity.
  3. Capture the full API call sequence including requestParameters (bucket name, prefix, max-keys) and responseElements to understand exactly which objects were enumerated and what metadata was returned.
  4. Document the IAM role trust policy and policy attachments (AWS) or the RBAC role assignments and app registration permissions (Azure) for the compromised identity at the time of the incident.
  5. If an EC2 instance or Azure VM is associated with the role, collect the instance metadata service logs and any process execution logs (CloudWatch, Azure Monitor) to identify the process that called the cloud APIs.
  6. Retrieve any AWS Config or Azure Policy compliance snapshots to establish baseline state — this helps determine if bucket permissions were recently changed to enable the enumeration.
  7. Preserve raw CloudTrail logs in their original S3 location with a legal hold or S3 Object Lock before any cleanup operations are performed.

Escalation Criteria

  • ! Escalate immediately if any GetObject or CopyObject API calls are observed following the listing operations — this indicates active data access, not just discovery, and triggers a potential data breach notification process.
  • ! Escalate if the enumerated buckets contain data classified as sensitive, regulated, or confidential (e.g., buckets named with patterns like 'backup', 'db-dump', 'secrets', 'credentials', 'pii', 'prod').
  • ! Escalate if the source IP geolocates to a country where the organization has no business presence or employees, especially if combined with off-hours activity.
  • ! Escalate if the compromised identity is a high-privilege role (AWS: AdministratorAccess, PowerUserAccess; Azure: Owner, Contributor at subscription scope) — the blast radius of such a compromise is organization-wide.
  • ! Escalate if multiple identities from the same IP range or user-agent are performing parallel enumeration — this indicates a coordinated attack or automated tooling operating across multiple compromised credentials simultaneously.
  • ! Escalate if this activity is detected within 48 hours of a related incident such as a phishing campaign, credential stuffing alert, or newly reported CVE affecting the organization's cloud infrastructure.

Investigation Guide

Forensic Artifacts

  • > AWS CloudTrail data event logs (S3 object-level logging) — must be explicitly enabled per bucket; absence does NOT mean no access occurred
  • > AWS CloudTrail management event logs — captures ListBuckets, GetBucketAcl, GetBucketPolicy without requiring data event logging
  • > AWS Config resource configuration history — shows bucket policy and ACL changes preceding the enumeration
  • > Azure Storage Analytics logs stored in $logs container within the storage account
  • > Azure Monitor diagnostic settings output to Log Analytics workspace (StorageBlobLogs table)
  • > AWS VPC Flow Logs — show network-level connectivity to S3 endpoints (S3 Gateway endpoints log differently than interface endpoints)
  • > AWS IAM Access Advisor — shows last-accessed time for each permission, confirming whether enumeration permissions were recently used for the first time
  • > Azure Entra ID Sign-in Logs — authentication events for service principals accessing storage
  • > GCP Cloud Audit Logs (data_access log type) for storage.objects.list operations if GCP is in scope

Tuning Guidance

The primary tuning levers are the OperationCount (default: 50) and UniqueBuckets/UniqueContainers (default: 5) thresholds. Start by baselining these values for your environment over a 2-week period using the hunting queries before enabling alerting. In environments with active data pipelines (Glue, Data Factory, Spark), consider adding exclusions by UserAgent or by a known-safe identity allowlist rather than raising thresholds globally. For AWS, ensure S3 data event logging is enabled for sensitive buckets — without it, the KQL query only captures management events and will miss object-level listing on already-known buckets. For Azure, enable StorageBlobLogs diagnostic settings on all storage accounts storing sensitive data; accounts not sending logs create blind spots. Apply additional context enrichment by tagging sensitive buckets (via AWS Resource Tags or Azure Resource Tags) and prioritizing alerts where the enumerated resources carry sensitivity classifications.


Hunting Queries

Hunt for storage enumeration by cross-account principals or from external/non-RFC1918 IP addresses, which may indicate compromised credentials being used externally or cross-account role abuse for bucket reconnaissance.

Hunting — KQL
kql
// Hunt: Identify storage enumeration by anonymous or public principals
AWSCloudTrail
| where TimeGenerated >= ago(7d)
| where EventName in ("ListObjectsV2", "ListObjects", "ListBuckets")
| where UserIdentityType == "AWSAccount" or UserIdentityPrincipalid startswith "ANON" or SourceIpAddress !startswith "10." and SourceIpAddress !startswith "172." and SourceIpAddress !startswith "192.168."
| where ErrorCode == "" or isnull(ErrorCode)  // successful calls only
| summarize
    SuccessfulListings = count(),
    TargetBuckets = make_set(tostring(RequestParameters)),
    SourceIPs = make_set(SourceIpAddress)
    by UserIdentityArn, UserIdentityType, UserAgent, bin(TimeGenerated, 1h)
| where SuccessfulListings > 10
| extend RiskIndicator = case(
    UserIdentityType == "AWSAccount", "Cross-account enumeration",
    SourceIPs has "TOR", "TOR exit node source",
    UserAgent has_any ("pacu", "peirates", "cloudbrute", "s3scanner"), "Known enumeration tool",
    "Anomalous external access"
  )
| project TimeGenerated, UserIdentityArn, UserIdentityType, RiskIndicator, SuccessfulListings, TargetBuckets, SourceIPs
Hunting — SPL
spl
index=* sourcetype="aws:cloudtrail" eventName IN ("ListObjectsV2", "ListObjects", "ListBuckets")
| eval user_type=userIdentity.type
| eval source_ip=sourceIPAddress
| eval actor=coalesce(userIdentity.arn, userIdentity.userName, "unknown")
| eval is_private_ip=if(match(source_ip, "^(10\\.|172\\.(1[6-9]|2[0-9]|3[01])\\.|192\.168\.)"), 1, 0)
| where user_type="AWSAccount" OR is_private_ip=0
| search errorCode=""
| bin _time span=1h
| stats count AS listing_count, dc(requestParameters.bucketName) AS unique_buckets, values(source_ip) AS sources by actor, _time
| where listing_count > 10
| eval risk="Anomalous cross-account or external storage enumeration"
| table _time, actor, listing_count, unique_buckets, sources, risk

Hunt for storage enumeration by identities with no recent history of performing such operations (new behavior), or for identities showing statistically significant spikes above their daily baseline — a pattern that detects compromised credentials being used for discovery even when volumes are below absolute thresholds.

Hunting — KQL
kql
// Hunt: First-time storage listing operations by service principals (new behavior baseline)
let KnownStorageUsers = AWSCloudTrail
| where TimeGenerated between(ago(30d) .. ago(1d))
| where EventName in ("ListObjectsV2", "ListObjects", "ListBuckets", "ListBlobs", "ListContainers")
| summarize HistoricalCount = count() by UserIdentityArn;
AWSCloudTrail
| where TimeGenerated >= ago(1d)
| where EventName in ("ListObjectsV2", "ListObjects", "ListBuckets")
| summarize TodayCount = count(), SourceIPs = make_set(SourceIpAddress), UserAgents = make_set(UserAgent) by UserIdentityArn
| join kind=leftanti KnownStorageUsers on UserIdentityArn
| extend Alert = "First-time storage enumeration by this identity in 30 days"
| project UserIdentityArn, TodayCount, SourceIPs, UserAgents, Alert
| sort by TodayCount desc
Hunting — SPL
spl
index=* sourcetype="aws:cloudtrail" eventName IN ("ListObjectsV2", "ListObjects", "ListBuckets")
| eval actor=coalesce(userIdentity.arn, userIdentity.userName)
| bin _time span=1d
| stats count by actor, _time
| eventstats avg(count) AS avg_daily, stdev(count) AS stdev_daily by actor
| where count > (avg_daily + (3 * stdev_daily)) AND avg_daily > 0
| eval deviation_score=round((count - avg_daily) / stdev_daily, 2)
| eval alert="Statistically anomalous spike in storage listing by " . actor . " (" . deviation_score . " standard deviations above baseline)"
| table _time, actor, count, avg_daily, deviation_score, alert
| sort - deviation_score

Hunt for Azure Blob Storage enumeration performed via anonymous access or shared access signature (SAS) tokens rather than authenticated Entra ID identities. This pattern is common when adversaries discover publicly accessible storage or obtain a leaked SAS token, and bypasses identity-based detection.

Hunting — KQL
kql
// Hunt: Azure Blob enumeration via anonymous/SAS token access (no Entra ID identity)
StorageBlobLogs
| where TimeGenerated >= ago(7d)
| where OperationName in ("ListBlobs", "ListContainers", "ListBlobsFlatSegment", "ListBlobsHierarchySegment")
| where AuthenticationType in ("Anonymous", "SAS") or isnull(RequesterObjectId) or RequesterObjectId == ""
| summarize
    EnumerationCount = count(),
    UniqueContainers = dcount(Uri),
    SourceIPs = make_set(CallerIpAddress),
    UserAgents = make_set(UserAgentHeader),
    Accounts = make_set(AccountName)
    by AuthenticationType, bin(TimeGenerated, 1h)
| where EnumerationCount > 20 or UniqueContainers > 3
| extend RiskNote = case(
    AuthenticationType == "Anonymous", "Public blob container enumeration — verify intended public access",
    AuthenticationType == "SAS", "SAS token used for broad listing — verify token scope and recipient",
    "Unauthenticated listing detected"
  )
| project TimeGenerated, AuthenticationType, EnumerationCount, UniqueContainers, SourceIPs, UserAgents, Accounts, RiskNote
Hunting — SPL
spl
index=* sourcetype="azure:monitor:storage:blob" operationName IN ("ListBlobs", "ListContainers", "ListBlobsFlatSegment")
| eval auth_type=coalesce(properties.authType, "unknown")
| eval source_ip=callerIpAddress
| search auth_type IN ("Anonymous", "SAS", "unknown")
| bin _time span=1h
| stats count AS enum_count, dc(uri) AS unique_containers, values(source_ip) AS sources, values(properties.accountName) AS accounts by auth_type, _time
| where enum_count > 20 OR unique_containers > 3
| eval alert="Unauthenticated/SAS storage enumeration: " . enum_count . " operations across " . unique_containers . " containers via " . auth_type
| table _time, auth_type, enum_count, unique_containers, sources, accounts, alert

Atomic Red Team Tests

Test 1 AWS S3 Bucket and Object Enumeration with AWS CLI
linux

Simulates adversary reconnaissance of S3 storage using native AWS CLI commands. Enumerates all accessible buckets and then lists objects in each, mimicking the initial phase of tools like Pacu or manual attacker activity.

Command

bash
# Prerequisites: AWS CLI configured with credentials (aws configure)
# Step 1: List all accessible S3 buckets
aws s3api list-buckets --query 'Buckets[*].[Name,CreationDate]' --output table

# Step 2: Enumerate objects in a specific bucket (replace BUCKET_NAME)
aws s3api list-objects-v2 --bucket BUCKET_NAME --max-items 1000 --output json | python3 -c "import sys,json; data=json.load(sys.stdin); print(f'Objects found: {len(data.get(\"Contents\",[]))}')"

# Step 3: Get bucket metadata for reconnaissance
aws s3api get-bucket-location --bucket BUCKET_NAME
aws s3api get-bucket-acl --bucket BUCKET_NAME
aws s3api get-bucket-policy --bucket BUCKET_NAME 2>/dev/null || echo 'No bucket policy'

# Step 4: Enumerate across all buckets (high-volume simulation)
for bucket in $(aws s3api list-buckets --query 'Buckets[*].Name' --output text); do
  echo "=== Listing: $bucket ==="
  aws s3api list-objects-v2 --bucket "$bucket" --max-items 50 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); print(f'  {len(d.get(\"Contents\",[]))} objects')"
done

Cleanup

bash
# No cleanup required — this is read-only enumeration
# Rotate AWS credentials used during the test if they have broad permissions
# aws iam delete-access-key --access-key-id <KEY_ID>  # if test key was created

Expected Telemetry

CloudTrail will generate events: eventName=ListBuckets, eventName=ListObjectsV2 (one per bucket enumerated), eventName=GetBucketLocation, eventName=GetBucketAcl, eventName=GetBucketPolicy. All events will share the same userIdentity.arn and originating sourceIPAddress. Events appear in CloudTrail within 15 minutes (standard delivery) or near-real-time (CloudTrail Lake).

Expected Detection

The detection should fire when the loop enumerates more than 5 buckets within the 10-minute window, triggering the UniqueBuckets threshold. The KQL/SPL queries should surface the identity ARN, operation count, and source IP in the alert output.

Test 2 Pacu Framework AWS Storage Enumeration Module
linux

Uses the Pacu AWS attack framework to enumerate S3 buckets and EBS volumes, simulating a tool-assisted adversary performing cloud storage discovery. Pacu is explicitly referenced in ATT&CK procedure examples for T1619.

Command

bash
# Prerequisites: Python 3, pip, git
# Install Pacu
git clone https://github.com/RhinoSecurityLabs/pacu.git /tmp/pacu-test
cd /tmp/pacu-test && pip install -r requirements.txt -q

# Launch Pacu and run storage enumeration
# Note: Pacu requires interactive session for full use; below uses direct module execution
python3 /tmp/pacu-test/cli.py << 'EOF'
new_session atomic_test_session
import_keys --all
run s3__enum_buckets
run s3__get_bucket_acls
run ebs__enum_volumes_snapshots
exit
EOF

# Alternatively, use Pacu's non-interactive mode for specific modules:
# python3 /tmp/pacu-test/cli.py --session atomic_test --module s3__enum_buckets

Cleanup

bash
rm -rf /tmp/pacu-test
# Revoke any sessions or temporary credentials used
# Review CloudTrail for all API calls made during the test

Expected Telemetry

CloudTrail events with userAgent containing 'Boto3' (Pacu uses Boto3 SDK). EventNames: ListBuckets, GetBucketAcl, GetBucketLocation, DescribeVolumes, DescribeSnapshots. The Pacu session generates many rapid sequential API calls detectable by rate and ordering patterns.

Expected Detection

Alert should fire on high-volume listing operations from the Pacu user-agent pattern. The OperationCount threshold trigger within the 10-minute window. User-agent string 'Boto3/x.x.x Python/x.x.x' may also be correlated with known tool signatures in threat intelligence enrichment.

Test 3 Azure Blob Storage Container and Object Enumeration via Azure CLI
linux

Enumerates Azure Blob Storage containers and objects using Azure CLI, simulating an adversary with compromised Azure credentials performing storage reconnaissance. Tests the StorageBlobLogs detection path.

Command

bash
# Prerequisites: Azure CLI installed and authenticated (az login or service principal)
# az login --service-principal -u CLIENT_ID -p CLIENT_SECRET --tenant TENANT_ID

# Step 1: List all storage accounts in subscription
az storage account list --query '[*].[name,resourceGroup,location]' -o table

# Step 2: Enumerate containers in a specific storage account
ACCOUNT_NAME="<target_storage_account>"
ACCOUNT_KEY=$(az storage account keys list --account-name $ACCOUNT_NAME --query '[0].value' -o tsv)
az storage container list --account-name $ACCOUNT_NAME --account-key $ACCOUNT_KEY --output table

# Step 3: List blobs in each container
for container in $(az storage container list --account-name $ACCOUNT_NAME --account-key $ACCOUNT_KEY --query '[*].name' -o tsv); do
  echo "=== Container: $container ==="
  az storage blob list --account-name $ACCOUNT_NAME --account-key $ACCOUNT_KEY --container-name "$container" --query '[*].[name,properties.contentLength]' -o table 2>/dev/null | head -20
done

# Step 4: Get container properties and ACL for reconnaissance
az storage container show --account-name $ACCOUNT_NAME --account-key $ACCOUNT_KEY --name $container
az storage container show-permission --account-name $ACCOUNT_NAME --account-key $ACCOUNT_KEY --name $container

Cleanup

bash
# Read-only operation — no direct cleanup required
# If a service principal was created for the test:
# az ad sp delete --id <SP_OBJECT_ID>
# Ensure diagnostic logging was enabled on the target storage account before the test to generate StorageBlobLogs entries

Expected Telemetry

Azure StorageBlobLogs table will contain OperationName values: ListContainers, ListBlobs, GetContainerProperties. CallerIpAddress will reflect the test machine IP. AuthenticationType will show 'StorageKey' (when using account key) or 'OAuth' (when using service principal). AzureActivity log will show Microsoft.Storage/storageAccounts/listKeys/action if keys were retrieved.

Expected Detection

StorageBlobLogs-based detection should fire when the loop enumerates more than 5 containers. The AzureActivity alert for listKeys/action should also trigger if the key retrieval step is included, indicating full account compromise rather than object-only access.

Related Detections

Tactic Hub