Public/Reconnaissance/anonymous/Find-PublicStorageContainer.ps1
<#
.SYNOPSIS Finds publicly accessible Azure Storage containers for a given storage account name and type. .DESCRIPTION The Find-PublicStorageContainer function attempts to discover public Azure Storage containers (blob, file, queue, table, or dfs) by generating permutations of storage account names and checking their DNS resolution and accessibility. It supports parallel processing for both DNS resolution and container enumeration, and can optionally include empty containers and container metadata in the results. .PARAMETER StorageAccountName The base name of the Azure Storage account to check. Permutations will be generated based on this value. .PARAMETER Type The type of Azure Storage service to check. Valid values are 'blob', 'file', 'queue', 'table', or 'dfs'. Defaults to 'blob'. .PARAMETER WordList Path to a file containing additional words or permutations to use when generating storage account names. .PARAMETER ThrottleLimit The maximum number of parallel operations to run during DNS resolution and container checks. Defaults to 50. .PARAMETER IncludeEmpty Switch to include empty containers in the results. By default, only containers with blobs/files are included. .PARAMETER IncludeMetadata Switch to include container metadata in the results by making an additional metadata request for each discovered container. .PARAMETER OutputFormat Optional. Specifies the output format for results. Valid values are: - Object: Returns PowerShell objects (default when piping) - JSON: Returns results in JSON format - CSV: Returns results in CSV format Aliases: output, o .OUTPUTS System.Collections.ArrayList Returns an array list of PSCustomObject items, each representing a discovered public storage container with properties such as StorageAccountName, Container, FileCount, IsEmpty, Uri, and optionally Metadata. .EXAMPLE Find-PublicStorageContainer -StorageAccountName "examplestorage" -Type "blob" -WordList "permutations.txt" -IncludeEmpty -IncludeMetadata Attempts to find public blob containers for the storage account "examplestorage" using permutations from "permutations.txt", including empty containers and their metadata. .EXAMPLE Find-PublicStorageContainer -StorageAccountName "contoso" -Type "blob" -OutputFormat JSON Searches for public blob containers using "contoso" as the base name and returns results in JSON format. .EXAMPLE Find-PublicStorageContainer -StorageAccountName "company" -Type "file" -ThrottleLimit 100 -OutputFormat CSV Searches for public file storage containers using 100 concurrent threads and returns results in CSV format. .NOTES - Requires appropriate permissions to perform DNS resolution and HTTP requests. - Uses parallel processing for improved performance; adjust ThrottleLimit based on system resources. - Designed for reconnaissance and security assessment purposes. #> function Find-PublicStorageContainer { [cmdletbinding()] [OutputType([System.Collections.ArrayList])] # Updated OutputType [Alias("bl cli public storage accounts")] param ( [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] [Alias("storage-account-name")] [string]$StorageAccountName, [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] [ValidateSet('blob', 'file', 'queue', 'table', 'dfs', ErrorMessage = "Type must be one of the following: Blob, File, Queue, Table")] [Alias("storage-type")] [string]$Type = 'blob', [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] [Alias("word-list", "w")] [string]$WordList, [Parameter(Mandatory = $false)] [Alias("throttle-limit", "t", "threads")] [int]$ThrottleLimit = 50, [Parameter(Mandatory = $false)] [Alias("include-empty")] [switch]$IncludeEmpty, [Parameter(Mandatory = $false)] [Alias("include-metadata")] [switch]$IncludeMetadata, [Parameter(Mandatory = $false)] [ValidateSet("Object", "JSON", "CSV")] [Alias("output", "o")] [string]$OutputFormat ) begin { Write-Verbose "Starting function $($MyInvocation.MyCommand.Name)" Write-Host "🎯 Analyzing Azure Storage for: $StorageAccountName ($Type)" -ForegroundColor Green # Create thread-safe collections $validDnsNames = [System.Collections.Concurrent.ConcurrentBag[string]]::new() $userAgent = ($sessionVariables.userAgents.agents | Get-Random).value $result = New-Object System.Collections.ArrayList # Create a thread-safe collection to track found containers for immediate display $foundContainers = [System.Collections.Concurrent.ConcurrentBag[string]]::new() } process { try { # Read word list efficiently if ($WordList) { Write-Host " 📄 Loading permutations from word list..." -ForegroundColor Cyan $permutations = [System.Collections.Generic.HashSet[string]](Get-Content $WordList) Write-Host " ✅ Loaded $($permutations.Count) permutations from '$WordList'" -ForegroundColor Green } $permutations += $sessionVariables.permutations Write-Host " 📊 Loaded total of $($permutations.Count) permutations" -ForegroundColor Green # Generate DNS names more efficiently $dnsNames = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) foreach ($item in $permutations) { [void] $dnsNames.Add(('{0}{1}.{2}.core.windows.net' -f $StorageAccountName, $($item), $type)) [void] $dnsNames.Add(('{1}{0}.{2}.core.windows.net' -f $StorageAccountName, $($item), $type)) } [void] $dnsNames.Add(('{0}.{1}.core.windows.net' -f $StorageAccountName, $type)) $totalDns = $dnsNames.Count Write-Host " 🎯 Testing $totalDns DNS name candidates..." -ForegroundColor Yellow Write-Host " 🔍 Starting DNS resolution with $ThrottleLimit concurrent threads..." -ForegroundColor Cyan # Parallel DNS resolution with improved error handling and progress $dnsNames | ForEach-Object -Parallel { try { $validDnsNames = $using:validDnsNames $permutations = $using:permutations if ([System.Net.Dns]::GetHostEntry($_)) { $validDnsNames.Add($_) $permutations += $($_).split('.')[0] } } catch [System.Net.Sockets.SocketException] { # DNS resolution failed - this is expected for non-existent storage accounts } } -ThrottleLimit $ThrottleLimit # Generate and test URIs in parallel if ($validDnsNames.Count -gt 0) { Write-Host " ✅ Found $($validDnsNames.Count) valid storage accounts" -ForegroundColor Green $totalContainers = $validDnsNames.Count * $permutations.Count Write-Host " 🔍 Starting container enumeration for $totalContainers combinations..." -ForegroundColor Cyan $validDnsNames | ForEach-Object -Parallel { $dns = $_ $permutations = $using:permutations $result = $using:result $includeEmpty = $using:IncludeEmpty $IncludeMetadata = $using:IncludeMetadata $userAgent = $using:userAgent $foundContainers = $using:foundContainers $permutations | ForEach-Object -Parallel { $dns = $using:dns $result = $using:result $includeEmpty = $using:IncludeEmpty $IncludeMetadata = $using:IncludeMetadata $userAgent = $using:userAgent $foundContainers = $using:foundContainers $uri = "https://$dns/$_/?restype=container&comp=list" $response = Invoke-WebRequest -Uri $uri -Method GET -UserAgent $userAgent -UseBasicParsing -SkipHttpErrorCheck if ($response.StatusCode -eq 200) { if ($includeEmpty -or $response.Content -match '<Blob>') { $currentItem = [PSCustomObject]@{ "StorageAccountName" = $dns.split('.')[0] "Container" = $_ "FileCount" = (Select-String -InputObject $response.Content -Pattern "/Name" -AllMatches).Matches.Count } if ($response.Content -match '<Blob>') { $currentItem | Add-Member -NotePropertyName IsEmpty -NotePropertyValue $false $foundMessage = " ✅ $($dns.split('.')[0])/$_ -> $($currentItem.FileCount) files [$(if($currentItem.IsEmpty){'Empty'}else{'HasContent'})]" } else { $currentItem | Add-Member -NotePropertyName IsEmpty -NotePropertyValue $true $foundMessage = " 📁 $($dns.split('.')[0])/$_ -> Empty container" } $currentItem | Add-Member -NotePropertyName Uri -NotePropertyValue $uri # Add to found containers for immediate display $foundContainers.Add($foundMessage) } if ($IncludeMetadata) { $metadataUri = "https://$dns/$_/?restype=container&comp=metadata" $metaResponse = Invoke-WebRequest -Uri $metadataUri -Method GET -UserAgent $userAgent -UseBasicParsing -SkipHttpErrorCheck $metaHeaders = @{} $metaResponse.Headers.GetEnumerator() | Where-Object { $_.Key -like 'x-ms-meta-*' } | ForEach-Object { $metaHeaders[$_.Key] = $_.Value if ($metaHeaders) {$currentItem | Add-Member -NotePropertyName Metadata -NotePropertyValue $metaHeaders -Force} } } [void] $result.Add($currentItem) } } } -ThrottleLimit $ThrottleLimit # Display found containers immediately after parallel processing if ($foundContainers.Count -gt 0) { foreach ($message in $foundContainers) { Write-Host $message -ForegroundColor Green } } } else { Write-Host " ❌ No valid storage accounts found" -ForegroundColor Red } } catch { Write-Error -Message $_.Exception.Message -ErrorAction Continue } } end { Write-Progress -Activity "Resolving DNS Names" -Completed Write-Progress -Activity "Checking Containers" -Completed Write-Verbose "Function $($MyInvocation.MyCommand.Name) completed" if (-not($result) -or $result.Count -eq 0) { Write-Host "`n❌ No public storage containers found" -ForegroundColor Red Write-Information -MessageData "No public storage account containers found" -InformationAction Continue } else { Write-Host "`n📊 Azure Storage Container Discovery Summary:" -ForegroundColor Magenta Write-Host " Total Containers Found: $($result.Count)" -ForegroundColor Yellow # Group by storage account for summary $accountGroups = $result | Group-Object StorageAccountName | Sort-Object Count -Descending foreach ($group in $accountGroups) { $emptyCount = ($group.Group | Where-Object { $_.IsEmpty }).Count $nonEmptyCount = $group.Count - $emptyCount Write-Host " $($group.Name): $($group.Count) containers ($nonEmptyCount with content, $emptyCount empty)" -ForegroundColor White } # Return results in requested format switch ($OutputFormat) { "JSON" { return $result | ConvertTo-Json -Depth 3 } "CSV" { return $result | ConvertTo-CSV } "Object" { return $result } default { return $result | Format-Table -AutoSize } } } } } |