diff --git a/Powershell scripts/MalwareScanScript/Images/script1.png b/Powershell scripts/MalwareScanScript/Images/script1.png new file mode 100644 index 000000000..b7a956d84 Binary files /dev/null and b/Powershell scripts/MalwareScanScript/Images/script1.png differ diff --git a/Powershell scripts/MalwareScanScript/Images/script2.png b/Powershell scripts/MalwareScanScript/Images/script2.png new file mode 100644 index 000000000..0cf991220 Binary files /dev/null and b/Powershell scripts/MalwareScanScript/Images/script2.png differ diff --git a/Powershell scripts/MalwareScanScript/MalwareScanScript.ps1 b/Powershell scripts/MalwareScanScript/MalwareScanScript.ps1 new file mode 100644 index 000000000..8a8f7c2b8 --- /dev/null +++ b/Powershell scripts/MalwareScanScript/MalwareScanScript.ps1 @@ -0,0 +1,247 @@ +<# + .DESCRIPTION + Runbook to initiate and monitor malware scans on Azure Storage Accounts with a specific tag using Interactive Azure login. +#> + +[CmdletBinding()] +param( + [Parameter(Mandatory = $true)] + [string]$SubscriptionId, + + [Parameter(Mandatory = $true)] + [string]$TagKey = "ScanForMalware", + + [Parameter(Mandatory = $true)] + [string]$TagValue = "True", + + [Parameter(Mandatory = $true)] + [string]$OutputCsvPath +) + +function Write-ErrorAndExit { + param([string]$ErrorMessage) + Write-Error $ErrorMessage + throw $ErrorMessage +} + +function Connect-ToAzure { + param( + [string]$SubscriptionId + ) + try { + Write-Output "Logging in to Azure using Interactive login..." + # This will prompt for login via a browser + Connect-AzAccount -ErrorAction Stop + + # Set the Azure context to the specified Subscription + Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop + Write-Output "Azure context set to Subscription ID: $SubscriptionId" + } + catch { + Write-ErrorAndExit "Failed to set Azure context. Error: $_" + } +} + +function Start-MalwareScan { + param( + [Microsoft.Azure.Commands.Management.Storage.Models.PSStorageAccount]$StorageAccount, + [string]$SubscriptionId + ) + $resourceGroupName = $StorageAccount.ResourceGroupName + $storageAccountName = $StorageAccount.StorageAccountName + $uri = "https://management.azure.com/subscriptions/$SubscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Storage/storageAccounts/$storageAccountName/providers/Microsoft.Security/defenderForStorageSettings/current/startMalwareScan?api-version=2024-10-01-preview" + + try { + $response = Invoke-AzRestMethod -Method POST -Uri $uri -ErrorAction Stop + return $response + } + catch { + Write-Warning "Failed to start scan for '$storageAccountName'. Error: $_" + return $null + } +} + +function Get-MalwareScanStatus { + param( + [Microsoft.Azure.Commands.Management.Storage.Models.PSStorageAccount]$StorageAccount, + [string]$SubscriptionId + ) + $resourceGroupName = $StorageAccount.ResourceGroupName + $storageAccountName = $StorageAccount.StorageAccountName + $uri = "https://management.azure.com/subscriptions/$SubscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Storage/storageAccounts/$storageAccountName/providers/Microsoft.Security/defenderForStorageSettings/current/malwareScans/latest?api-version=2024-10-01-preview" + + try { + $response = Invoke-AzRestMethod -Method GET -Uri $uri -ErrorAction Stop + return $response + } + catch { + Write-Warning "Failed to get scan status for '$storageAccountName'. Error: $_" + return $null + } +} + +try { + # Authenticate and set the subscription context + Connect-ToAzure -SubscriptionId $SubscriptionId + + Write-Output "`nFetching storage accounts with tag '$TagKey=$TagValue'..." + $storageAccounts = Get-AzStorageAccount | Where-Object { $_.Tags[$TagKey] -eq $TagValue } + if (-not $storageAccounts) { + Write-ErrorAndExit "No storage accounts found with tag '$TagKey=$TagValue'." + } + + Write-Output "Found $($storageAccounts.Count) storage account(s) with the specified tag.`n" + $scanStatuses = @{} + $scanResults = @() + + foreach ($storageAccount in $storageAccounts) { + Write-Output "Starting malware scan for storage account: $($storageAccount.StorageAccountName)" + $response = Start-MalwareScan -StorageAccount $storageAccount -SubscriptionId $SubscriptionId + if ($response) { + $responseContent = $response.Content | ConvertFrom-Json + if ($responseContent.scanStatus -in @("Queued", "WaitingForCompletion")) { + Write-Output "Successfully initiated scan for '$($storageAccount.StorageAccountName)'. Status: $($responseContent.scanStatus)`n" + $scanStatuses[$storageAccount.StorageAccountName] = @{ + Status = $responseContent.scanStatus + LastChecked = Get-Date + ScanDetails = $responseContent + } + } + else { + Write-Warning "Failed to start scan for '$($storageAccount.StorageAccountName)'. Status: $($responseContent.scanStatus)`n" + $scanStatuses[$storageAccount.StorageAccountName] = @{ + Status = "Failed" + LastChecked = Get-Date + ScanDetails = $responseContent + } + } + } + else { + Write-Warning "Failed to start scan for '$($storageAccount.StorageAccountName)'.`n" + $scanStatuses[$storageAccount.StorageAccountName] = @{ + Status = "Failed" + LastChecked = Get-Date + ScanDetails = $null + } + } + } + + # Continuously check the status of each scan until all are completed or failed + $allCompleted = $false + while (-not $allCompleted) { + $allCompleted = $true + foreach ($accountName in $scanStatuses.Keys) { + $currentStatus = $scanStatuses[$accountName] + if ($currentStatus.Status -notin @("Completed", "Failed")) { + $allCompleted = $false + $storageAccount = $storageAccounts | Where-Object { $_.StorageAccountName -eq $accountName } + $status = Get-MalwareScanStatus -StorageAccount $storageAccount -SubscriptionId $SubscriptionId + if ($status) { + $statusContent = $status.Content | ConvertFrom-Json + $scanStatuses[$accountName].Status = $statusContent.scanStatus + $scanStatuses[$accountName].LastChecked = Get-Date + $scanStatuses[$accountName].ScanDetails = $statusContent + $blobSummary = $statusContent.scanSummary.blobs + Write-Output "Storage Account: $accountName" + Write-Output " Status: $($statusContent.scanStatus)" + Write-Output " Total Blobs Scanned: $($blobSummary.totalBlobsScanned)" + Write-Output " Malicious Blobs Count: $($blobSummary.maliciousBlobsCount)" + Write-Output " Scanned Blobs in GB: $([math]::Round($blobSummary.scannedBlobsInGB, 4))`n" + } + else { + Write-Warning "Failed to get scan status for '$accountName'.`n" + } + } + } + + if (-not $allCompleted) { + Write-Output "Waiting 10 seconds before next status check...`n" + Start-Sleep -Seconds 10 + } + } + + # Collect scan results for CSV export + $overallSummary = @{ + TotalStorageAccounts = $storageAccounts.Count + SuccessfulScans = 0 + FailedScans = 0 + TotalBlobsScanned = 0 + TotalMaliciousBlobs = 0 + TotalSkippedBlobs = 0 + TotalScannedBlobsInGB = 0.0 + EstimatedTotalScanCost = 0.0 + } + + foreach ($accountName in $scanStatuses.Keys) { + $status = $scanStatuses[$accountName] + Write-Output "----------------------------------------" + Write-Output "Storage Account: $accountName" + Write-Output "Status: $($status.Status)" + Write-Output "Last Checked: $($status.LastChecked)" + + if ($status.Status -eq "Completed" -and $status.ScanDetails) { + $details = $status.ScanDetails + Write-Output " Scan ID: $($details.scanId)" + Write-Output " Scan Start Time: $($details.scanStartTime)" + Write-Output " Scan End Time: $($details.scanEndTime)" + Write-Output " Total Blobs Scanned: $($details.scanSummary.blobs.totalBlobsScanned)" + Write-Output " Malicious Blobs Count: $($details.scanSummary.blobs.maliciousBlobsCount)" + Write-Output " Skipped Blobs Count: $($details.scanSummary.blobs.skippedBlobsCount)" + Write-Output " Scanned Blobs in GB: $($details.scanSummary.blobs.scannedBlobsInGB)" + Write-Output " Estimated Scan Cost (USD): $($details.scanSummary.estimatedScanCostUSD)`n" + + # Update overall summary + $overallSummary.SuccessfulScans++ + $overallSummary.TotalBlobsScanned += $details.scanSummary.blobs.totalBlobsScanned + $overallSummary.TotalMaliciousBlobs += $details.scanSummary.blobs.maliciousBlobsCount + $overallSummary.TotalSkippedBlobs += $details.scanSummary.blobs.skippedBlobsCount + $overallSummary.TotalScannedBlobsInGB += $details.scanSummary.blobs.scannedBlobsInGB + $overallSummary.EstimatedTotalScanCost += $details.scanSummary.estimatedScanCostUSD + + # Add to the CSV results + $scanResults += [PSCustomObject]@{ + StorageAccountName = $accountName + Status = $status.Status + LastChecked = $status.LastChecked + ScanId = $details.scanId + ScanStartTime = $details.scanStartTime + ScanEndTime = $details.scanEndTime + TotalBlobsScanned = $details.scanSummary.blobs.totalBlobsScanned + MaliciousBlobsCount = $details.scanSummary.blobs.maliciousBlobsCount + SkippedBlobsCount = $details.scanSummary.blobs.skippedBlobsCount + ScannedBlobsInGB = $details.scanSummary.blobs.scannedBlobsInGB + EstimatedScanCostUSD = $details.scanSummary.estimatedScanCostUSD + } + } + elseif ($status.Status -eq "Failed") { + Write-Output " Scan not started or failed.`n" + $overallSummary.FailedScans++ + } + else { + Write-Output " Scan Status: $($status.Status)`n" + } + } + + # Export results to CSV + Write-Output "Exporting scan results to CSV..." + $scanResults | Export-Csv -Path $OutputCsvPath -NoTypeInformation + Write-Output "Results exported to $OutputCsvPath" + + # Display the overall summary + Write-Output "----------------------------------------" + Write-Output "`nOverall Summary:" + Write-Output " Total Storage Accounts: $($overallSummary.TotalStorageAccounts)" + Write-Output " Successful Scans: $($overallSummary.SuccessfulScans)" + Write-Output " Failed Scans: $($overallSummary.FailedScans)" + Write-Output " Total Blobs Scanned: $($overallSummary.TotalBlobsScanned)" + Write-Output " Total Malicious Blobs: $($overallSummary.TotalMaliciousBlobs)" + Write-Output " Total Skipped Blobs: $($overallSummary.TotalSkippedBlobs)" + Write-Output " Total Scanned Blobs in GB: $([math]::Round($overallSummary.TotalScannedBlobsInGB, 4))" + Write-Output " Estimated Total Scan Cost (USD): $([math]::Round($overallSummary.EstimatedTotalScanCost, 6))" +} +catch { + Write-ErrorAndExit "An error occurred: $_" +} +finally { + Write-Output "`nScript execution completed." +} diff --git a/Powershell scripts/MalwareScanScript/Readme.md b/Powershell scripts/MalwareScanScript/Readme.md new file mode 100644 index 000000000..bf9961f90 --- /dev/null +++ b/Powershell scripts/MalwareScanScript/Readme.md @@ -0,0 +1,90 @@ +# Azure Storage Account Malware Scan Script + +This PowerShell script allows you to initiate and monitor malware scans on **Azure Storage Accounts** that are tagged with a specific key-value pair. The script interacts with **Microsoft Defender for Storage's** feature **on-demand malware scanning** and uses the **Azure REST API** to trigger malware scans and retrieve scan status. + +## Description + +This script is designed to: +- Authenticate to Azure using interactive login. +- Search for **Azure Storage Accounts** with a specific tag. +- Initiate malware scans on these storage accounts using Microsoft Defender for Storage. +- Continuously monitor the progress of these scans. +- Export the results to a CSV file for further analysis. + +## Requirements + +- **PowerShell 7.0** or later. +- **Azure PowerShell module** (`Az` module) installed. + - To install the module, use the following command: + ```powershell + Install-Module -Name Az -AllowClobber -Force -Scope CurrentUser + ``` +- **Azure Subscription** with Microsoft Defender for Storage enabled on the desired storage accounts. +- **On-upload malware scanning** enabled in the storage accounts. **On-demand malware scanning** depends on it. + +## Authentication + +This script uses **interactive login** to authenticate to Azure. When the script runs, you will be prompted to log in via a browser. Ensure that the user account has the necessary permissions to access the Azure resources (e.g., **Reader** or **Security Admin** role). + +## Parameters + +The script accepts the following parameters: + +- `-SubscriptionId` (Mandatory): The **Azure Subscription ID** in which the storage accounts reside. +- `-TagKey` (Mandatory): The **key** of the tag used to filter storage accounts. +- `-TagValue` (Mandatory): The **value** of the tag used to filter storage accounts. +- `-OutputCsvPath` (Mandatory): The **path to the CSV file** where the results will be exported. + +### Run it: +```powershell +.\MalwareScanScript.ps1 -SubscriptionId "your-subscription-id" ` + -TagKey "ScanForMalware" ` + -TagValue "True" ` + -OutputCsvPath "C:\path\to\output\scanResults.csv" +``` +### Example: +```powershell +.\MalwareScanScript.ps1 -SubscriptionId xx00xxx0-xx00-0000-0xx0-0x000x000x00 -TagKey ScanForMalware -TagValue True -OutputCsvPath "/Users/myUsername/Library/Documents" +``` + +## Script Workflow +1. **Authentication**: The script authenticates using interactive login with the Connect-AzAccount cmdlet. You will be prompted to log in via your browser. + +2. **Fetching Storage Accounts**: The script fetches all Azure Storage Accounts under the provided subscription and filters them based on the provided tag (TagKey=TagValue). + +3. **Initiating Malware Scans**: For each matching storage account, the script uses the Azure REST API to initiate a malware scan using Microsoft Defender for Storage. +```pwsh + https://management.azure.com/subscriptions/$SubscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Storage/storageAccounts/$storageAccountName/providers/Microsoft.Security/defenderForStorageSettings/current/startMalwareScan?api-version=2024-10-01-preview +``` + +4. **Monitoring Scan Status**: The script continuously monitors the status of each malware scan, checking every 10 seconds until all scans are either completed or failed. +```pwsh + https://management.azure.com/subscriptions/$SubscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Storage/storageAccounts/$storageAccountName/providers/Microsoft.Security/defenderForStorageSettings/current/malwareScans/latest?api-version=2024-10-01-preview" +``` + +5. **Exporting Results**: Once all scans are completed, the results (including blob counts, malicious blob counts, scan costs, etc.) are exported to the specified CSV file. + +6. **CSV Export**: The final scan results are saved in the specified path (OutputCsvPath), which includes detailed information about each storage account and its corresponding malware scan results. + +## Example Output +### Terminal +![script1](Images/script1.png) +![script2](Images/script2.png) +### CSV +After the script runs, the output CSV will include the following columns: + +- **StorageAccountName**: The name of the Azure Storage Account. +- **Status**: The status of the scan (e.g., Completed, Failed). +- **LastChecked**: The last time the scan status was checked. +- **ScanId**: The ID of the malware scan. +- **ScanStartTime**: The start time of the malware scan. +- **ScanEndTime**: The end time of the malware scan (if completed). +- **TotalBlobsScanned**: The total number of blobs scanned. +- **MaliciousBlobsCount**: The number of blobs detected as malicious. +- **SkippedBlobsCount**: The number of blobs skipped during the scan. +- **ScannedBlobsInGB**: The total size of scanned blobs in GB. +- **EstimatedScanCostUSD**: The estimated cost of the scan in USD. + +| StorageAccountName | Status | LastChecked | ScanId | ScanStartTime | ScanEndTime | TotalBlobsScanned | MaliciousBlobsCount | SkippedBlobsCount | ScannedBlobsInGB | EstimatedScanCostUSD | +|--------------------|-----------|----------------------|---------|-------------------|-------------------|--------------------|---------------------|-------------------|-------------------|----------------------| +| mystorageaccount | Completed | 2024-11-12 12:30:00 | abc123 | 2024-11-12 12:00 | 2024-11-12 12:30 | 500 | 10 | 5 | 1.2 | 0.25 |