Detecting the SolarWinds Compromise Signals with Active Directory PowerShell | Quisitive
Detecting the SolarWinds Compromise Signals with Active Directory PowerShell
December 14, 2020
Matthew Dowst
In this blog, you'll see a few scripts that may help you quickly identify suspicious activity using Active Directory PowerShell.

With the recent announcement of the SolarWinds attack Microsoft has provide additional signals for Azure Sentinel to help detect activity related to this attack. However, if you don’t have Azure Sentinel setup, you will have to manually search your Unified Audit Log for activity. To help with that, I’ve put together a few scripts that may help you quickly identify suspicious activity using PowerShell.

Note: These scripts should not be considered exhaustive for this observed activity, but they are a good starting point to help you find what you need. For complete details on this and the detections, please refer to the Customer Guidance on Recent Nation-State Cyber Attacks from Microsoft Security Response Center.

For each detection, I tried to provide the minimal amount of filtering, so you can see as much data as you need, and filter as needed. These scripts are based on the Kusto queries in Azure Sentinel. Each one contains a link to the query, so you can see the full filtering and matching Microsoft suggests.

Anomalous Azure Active Directory PowerShell behavior

This will search the Azure AD sign-in logs to users or applications that used Azure Active Directory PowerShell to access non-Active Directory resources.

if(-not (Get-Module AzureADPreview)){
    Install-Module AzureADPreview -AllowClobber -Scope CurrentUser
}
 
# Unload the AzureAD module if it is already loaded in this session
Get-Module AzureAD | Remove-Module
 
#Import the AzureADPreview module
Import-Module AzureADPreview
 
# Connect to Azure AD
Connect-AzureAD
 
# Return sign in logs for Azure Active Directory PowerShell
$SignInLogs = Get-AzureADAuditSignInLogs -Filter "appId eq '1b730954-1685-4b74-9bfd-dac224a7b894'"
 
# Display all logs for the Azure Active Directory PowerShell
$SignInLogs | Select-Object TokenIssuerType, ResourceId, Status, IPAddress, UserAgent, ResourceDisplayName, UserDisplayName, UserId, UserPrincipalName
 
# Display the logs for entries that are not Windows Azure Active Directory
$SignInLogs | Where-Object{$_.ResourceId -ne "00000002-0000-0000-c000-000000000000"} | 
    Select-Object TokenIssuerType, ResourceId, Status, IPAddress, UserAgent, ResourceDisplayName, UserDisplayName, UserId, UserPrincipalName

Source: Azure Active Directory PowerShell accessing non-AAD resources

New access credential added to OAuth Application or Service Principal

This will search the audit logs and show when an admin or app owner account has added a new credential to an Application or Service Principal.

# Set the start and end date of the log search
$StartDate = (Get-Date).AddDays(-30).ToUniversalTime()
$EndDate = (Get-Date).ToUniversalTime()
 
# Install the ExchangeOnlineManagement module if not already installed
if(-not (Get-Module ExchangeOnlineManagement -ListAvailable)){
    Install-Module ExchangeOnlineManagement -AllowClobber -Scope CurrentUser
}
 
# Import the ExchangeOnlineManagement module
Import-Module ExchangeOnlineManagement 
 
# connect to Exchange Online
Connect-ExchangeOnline
 
# Search audit logs depending on number of logs it may need to run in batches
[System.Collections.Generic.List[PSObject]]$LogResults = @()
$sessionId = "AD_SPN_$((Get-Date).ToFileTime())"
do{
    $logs = Search-UnifiedAuditLog -StartDate $StartDate -EndDate $EndDate -RecordType AzureActiveDirectory -Operations 'Add service principal','Add service principal credentials' -SessionId $sessionId -SessionCommand ReturnLargeSet
    $logs | Select-Object -Property *, @{l='Data';e={($_.AuditData | ConvertFrom-Json)}} | ForEach-Object { $LogResults.Add($_) }
    Write-Host "$($logs.Count) - $($LogResults.Count)"
} while($logs.Count -gt 0)
 
# function to extract the properties from the returned data
Function Get-ModifiedPropertiesField($Data, $Key){
    $str = (($Data.ModifiedProperties | Where-Object{$_.Name -eq 'KeyDescription'}).NewValue | ConvertFrom-Json)[0]
    $str = ($str.Substring(1,$str.Length-2))
    $table = $str.Split(',') | Where-Object{$_} | ConvertFrom-StringData
    $table | ForEach-Object{
        if($_.Item($key)){
            $_.Item($key)
        }
    }
}
 
# display the log results
$LogResults | Select CreationDate, Operations, @{l='InitiatingUserOrApp';e={$_.Data.Actor[0].Id}}, @{l='InitiatingIpAddress';e={$_.Data.ActorIpAddress}},
     @{l='targetDisplayName';e={($_.Data.Target | Where-Object{$_.Type -eq 1}).Id}}, @{l='targetId';e={$_.Data.ObjectId}},
     @{l='keyDisplayName';e={Get-ModifiedPropertiesField $_.Data 'DisplayName'}}, @{l='KeyType';e={Get-ModifiedPropertiesField $_.Data 'KeyType'}},
     @{l='KeyUsage';e={Get-ModifiedPropertiesField $_.Data 'KeyUsage'}}, @{l='KeyIdentifier';e={Get-ModifiedPropertiesField $_.Data 'KeyIdentifier'}}

Keep in mind this script does not filter as deeply as the Microsoft Sentinel query. In theirs they are filter out results that don’t have an “@” in the UPN or Display Name. This appears to filter out any internal Azure ones that get created. You can filter these from your results with a simple Where-Object clause. See the full source linked below for all the filtering they are doing.

Source: New access credential added to Application or Service Principal

Modified domain federation trust settings

This will search the audit logs for modifications to the federation setting on your domain.


# Set the start and end date of the log search
$StartDate = (Get-Date).AddDays(-90).ToUniversalTime()
$EndDate = (Get-Date).ToUniversalTime()
 
# Install the ExchangeOnlineManagement module if not already installed
if(-not (Get-Module ExchangeOnlineManagement -ListAvailable)){
    Install-Module ExchangeOnlineManagement -AllowClobber -Scope CurrentUser
}
 
# Import the ExchangeOnlineManagement module
Import-Module ExchangeOnlineManagement 
 
# connect to Exchange Online
Connect-ExchangeOnline
 
# Search audit logs depending on number of logs it may need to run in batches
[System.Collections.Generic.List[PSObject]]$LogResults = @()
$sessionId = "AD_Domain_$((Get-Date).ToFileTime())"
do{
    $logs = Search-UnifiedAuditLog -StartDate $StartDate -EndDate $EndDate -Operations 'Set federation settings on domain' -SessionId $sessionId -SessionCommand ReturnLargeSet
    $logs | Select-Object -Property *, @{l='Data';e={($_.AuditData | ConvertFrom-Json)}} | ForEach-Object { $LogResults.Add($_) }
    Write-Host "$($logs.Count) - $($LogResults.Count)"
} while($logs.Count -gt 0)
 
$LogResults

Source: Modified domain federation trust settings

This post id offered “as is”, with no guarantee that this code or psuedocode will work in your environment, and that the information will not change. Links have been provided to the original sources so that you can check if any of the guidance from Microsoft has changed.