solarigate queries
This commit is contained in:
Родитель
55b4411b19
Коммит
45d6059d37
|
@ -0,0 +1,36 @@
|
|||
id: 6d7214d9-4a28-44df-aafb-0910b9e6ae3e
|
||||
name: New CloudShell User
|
||||
description: |
|
||||
'Identifies when a user creates an Azure CloudShell for the first time.
|
||||
Monitor this activity to ensure only expected user are using CloudShell'
|
||||
severity: Low
|
||||
requiredDataConnectors:
|
||||
- connectorId: AzureActivity
|
||||
dataTypes:
|
||||
- AzureActivity
|
||||
queryFrequency: 1d
|
||||
queryPeriod: 1d
|
||||
triggerOperator: gt
|
||||
triggerThreshold: 0
|
||||
tactics:
|
||||
- Execution
|
||||
relevantTechniques:
|
||||
- T1059
|
||||
query: |
|
||||
|
||||
AzureActivity
|
||||
| extend message_ = tostring(parse_json(Properties).message)
|
||||
| extend Appid = tostring(parse_json(Claims).appid)
|
||||
| where Appid contains "c44b4083-3bb0-49c1-b47d-974e53cbdf3c"
|
||||
| where OperationName =~ "Microsoft.Portal/consoles/write"
|
||||
| extend timestamp = TimeGenerated, AccountCustomEntity = Caller, IPCustomEntity = CallerIpAddress
|
||||
|
||||
entityMappings:
|
||||
- entityType: Account
|
||||
fieldMappings:
|
||||
- identifier: FullName
|
||||
columnName: AccountCustomEntity
|
||||
- entityType: IP
|
||||
fieldMappings:
|
||||
- identifier: Address
|
||||
columnName: IPCustomEntity
|
|
@ -0,0 +1,82 @@
|
|||
id: cecdbd4c-4902-403c-8d4b-32eb1efe460b
|
||||
name: Solarigate Network Beacon
|
||||
description: |
|
||||
'Identifies a match across various data feeds for domains IOCs related to the Solarigate incident.
|
||||
References: https://blogs.microsoft.com/on-the-issues/2020/12/13/customers-protect-nation-state-cyberattacks/,
|
||||
https://www.fireeye.com/blog/threat-research/2020/12/evasive-attacker-leverages-solarwinds-supply-chain-compromises-with-sunburst-backdoor.html?1'
|
||||
severity: High
|
||||
requiredDataConnectors:
|
||||
- connectorId: DNS
|
||||
dataTypes:
|
||||
- DnsEvents
|
||||
- connectorId: AzureMonitor(VMInsights)
|
||||
dataTypes:
|
||||
- VMConnection
|
||||
- connectorId: CiscoASA
|
||||
dataTypes:
|
||||
- CommonSecurityLog
|
||||
- connectorId: PaloAltoNetworks
|
||||
dataTypes:
|
||||
- CommonSecurityLog
|
||||
- connectorId: Microsoft 365 Defender
|
||||
dataTypes:
|
||||
- DeviceNetworkEvents
|
||||
queryFrequency: 6h
|
||||
queryPeriod: 6h
|
||||
triggerOperator: gt
|
||||
triggerThreshold: 0
|
||||
tactics:
|
||||
- CommandAndControl
|
||||
relevantTechniques:
|
||||
- T1102
|
||||
query: |
|
||||
|
||||
let domains = dynamic(["incomeupdate.com","zupertech.com","databasegalore.com","panhardware.com","avsvmcloud.com","digitalcollege.org","freescanonline.com","deftsecurity.com","thedoccloud.com","virtualdataserver.com","lcomputers.com","webcodez.com","globalnetworkissues.com","kubecloud.com","seobundlekit.com","solartrackingsystem.net","virtualwebdata.com"]);
|
||||
let timeframe = 6h;
|
||||
(union isfuzzy=true
|
||||
(CommonSecurityLog
|
||||
| where TimeGenerated >= ago(timeframe)
|
||||
| parse Message with * '(' DNSName ')' *
|
||||
| where DNSName in~ (DomainNames) or DestinationHostName has_any (domains) or RequestURL has_any(domains)
|
||||
| extend AccountCustomEntity = SourceUserID, HostCustomEntity = DeviceName, IPCustomEntity = SourceIP
|
||||
),
|
||||
(DnsEvents
|
||||
| where TimeGenerated >= ago(timeframe)
|
||||
| extend DNSName = Name
|
||||
| where isnotempty(DNSName)
|
||||
| where DNSName in~ (DomainNames)
|
||||
| extend IPCustomEntity = ClientIP
|
||||
),
|
||||
(VMConnection
|
||||
| where TimeGenerated >= ago(timeframe)
|
||||
| parse RemoteDnsCanonicalNames with * '["' DNSName '"]' *
|
||||
| where isnotempty(DNSName)
|
||||
| where DNSName in~ (DomainNames)
|
||||
| extend IPCustomEntity = RemoteIp
|
||||
),
|
||||
(DeviceNetworkEvents
|
||||
| where TimeGenerated >= ago(timeframe)
|
||||
| where isnotempty(RemoteUrl)
|
||||
| where RemoteUrl has_any (domains)
|
||||
| extend DNSName = RemoteUrl
|
||||
| extend IPCustomEntity = RemoteIP
|
||||
| extend HostCustomEntity = DeviceName
|
||||
)
|
||||
)
|
||||
entityMappings:
|
||||
- entityType: Account
|
||||
fieldMappings:
|
||||
- identifier: FullName
|
||||
columnName: AccountCustomEntity
|
||||
- entityType: Host
|
||||
fieldMappings:
|
||||
- identifier: FullName
|
||||
columnName: HostCustomEntity
|
||||
- entityType: IP
|
||||
fieldMappings:
|
||||
- identifier: Address
|
||||
columnName: IPCustomEntity
|
||||
- entityType: DNS
|
||||
fieldMappings:
|
||||
- identifier: DomainName
|
||||
columnName: DNSName
|
|
@ -0,0 +1,42 @@
|
|||
id: 18e6a87e-9d06-4a4e-8b59-3469cd49552d
|
||||
name: ADFS Certificate Export
|
||||
description: |
|
||||
'Identifies an export of ADFS certificate material from an ADFS host.
|
||||
References: https://blogs.microsoft.com/on-the-issues/2020/12/13/customers-protect-nation-state-cyberattacks/,
|
||||
https://www.fireeye.com/blog/threat-research/2020/12/evasive-attacker-leverages-solarwinds-supply-chain-compromises-with-sunburst-backdoor.html?1'
|
||||
severity: Medium
|
||||
requiredDataConnectors:
|
||||
- connectorId: SecurityEvents
|
||||
dataTypes:
|
||||
- SecurityEvents
|
||||
- connectorId: Microsoft 365 Defender
|
||||
dataTypes:
|
||||
- DeviceEvents
|
||||
queryFrequency: 1d
|
||||
queryPeriod: 1d
|
||||
triggerOperator: gt
|
||||
triggerThreshold: 0
|
||||
tactics:
|
||||
- Collection
|
||||
relevantTechniques:
|
||||
- T1005
|
||||
query: |
|
||||
|
||||
(union isfuzzy=true (SecurityEvent
|
||||
| where EventID == 4662
|
||||
| where ObjectName contains "thumbnailPhoto"
|
||||
| extend timestamp = TimeGenerated, HostCustomEntity = Computer, AccountCustomEntity = SubjectAccount),
|
||||
(DeviceEvents
|
||||
| where ActionType =~ "LdapSearch"
|
||||
| where AdditionalFields.AttributeList contains "thumbnailPhoto"
|
||||
| extend timestamp = TimeGenerated, HostCustomEntity = DeviceName, AccountCustomEntity = InitiatingProcessAccountName)
|
||||
)
|
||||
entityMappings:
|
||||
- entityType: Account
|
||||
fieldMappings:
|
||||
- identifier: FullName
|
||||
columnName: AccountCustomEntity
|
||||
- entityType: Host
|
||||
fieldMappings:
|
||||
- identifier: FullName
|
||||
columnName: HostCustomEntity
|
|
@ -0,0 +1,40 @@
|
|||
id: e70fa6e0-796a-4e85-9420-98b17b0bb749
|
||||
name: Solarigate Defender Detections
|
||||
description: |
|
||||
'Detects any Defender Alert for Solarigate Events'
|
||||
severity: High
|
||||
requiredDataConnectors:
|
||||
- connectorId: MicrosoftDefenderAdvancedThreatProtection
|
||||
dataTypes:
|
||||
- SecurityAlert (MDATP)
|
||||
- DeviceInfo
|
||||
queryFrequency: 1d
|
||||
queryPeriod: 1d
|
||||
triggerOperator: gt
|
||||
triggerThreshold: 0
|
||||
tactics:
|
||||
- InitialAccess
|
||||
relevantTechniques:
|
||||
- T1195
|
||||
query: |
|
||||
|
||||
DeviceInfo
|
||||
| extend DeviceName = tolower(DeviceName)
|
||||
| join (SecurityAlert
|
||||
| where ProviderName =~ "MDATP"
|
||||
| extend ThreatName = tostring(parse_json(ExtendedProperties).ThreatName)
|
||||
| where ThreatName has "Solarigate"
|
||||
| extend HostCustomEntity = tolower(CompromisedEntity)
|
||||
| take 10) on $left.DeviceName =~ $right.HostCustomEntity
|
||||
| project TimeGenerated, DisplayName, ThreatName, CompromisedEntity, PublicIP, MachineGroup, AlertSeverity, Description, LoggedOnUsers, DeviceId, TenantId
|
||||
| extend timestamp = TimeGenerated, IPCustomEntity = PublicIP
|
||||
|
||||
entityMappings:
|
||||
- entityType: Host
|
||||
fieldMappings:
|
||||
- identifier: FullName
|
||||
columnName: HostCustomEntity
|
||||
- entityType: IP
|
||||
fieldMappings:
|
||||
- identifier: Address
|
||||
columnName: IPCustomEntity
|
|
@ -0,0 +1,49 @@
|
|||
id: 2560515c-07d1-434e-87fb-ebe3af267760
|
||||
name: MailRead Permissions Granted to Application
|
||||
description: |
|
||||
'This query look for applications that have been granted permissions to Read Mail and subsequently has been consented to.
|
||||
This can help identify applications that have been abused to gain access to mailboxes.'
|
||||
requiredDataConnectors:
|
||||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- AuditLogs
|
||||
tactics:
|
||||
- Persistence
|
||||
relevantTechniques:
|
||||
- T1098
|
||||
query: |
|
||||
|
||||
AuditLogs
|
||||
| where Category == "ApplicationManagement"
|
||||
| where ActivityDisplayName == "Add delegated permission grant"
|
||||
| where Result =~ "success"
|
||||
| where tostring(InitiatedBy.user.userPrincipalName) has "@" or tostring(InitiatedBy.app.displayName) has "@"
|
||||
| extend props = parse_json(tostring(TargetResources[0].modifiedProperties))
|
||||
| mv-expand props
|
||||
| extend UserAgent = tostring(AdditionalDetails[0].value)
|
||||
| extend InitiatingUser = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)
|
||||
| extend UserIPAddress = tostring(parse_json(tostring(InitiatedBy.user)).ipAddress)
|
||||
| extend DisplayName = tostring(props.displayName)
|
||||
| extend Permissions = tostring(parse_json(tostring(props.newValue)))
|
||||
| where Permissions contains "Mail.Read"
|
||||
| extend PermissionsAddedTo = tostring(TargetResources[0].displayName)
|
||||
| extend Type = tostring(TargetResources[0].type)
|
||||
| project-away props
|
||||
| join kind=leftouter(
|
||||
AuditLogs
|
||||
| where ActivityDisplayName == "Consent to application"
|
||||
| extend AppName = tostring(TargetResources[0].displayName)
|
||||
| extend AppId = tostring(TargetResources[0].id)
|
||||
| project AppName, AppId, CorrelationId) on CorrelationId
|
||||
| project-reorder TimeGenerated, OperationName, InitiatingUser, UserIPAddress, UserAgent, PermissionsAddedTo, Permissions, AppName, AppId, CorrelationId
|
||||
| extend timestamp = TimeGenerated, AccountCustomEntity = InitiatingUser, IPCustomEntity = UserIPAddress
|
||||
|
||||
entityMappings:
|
||||
- entityType: Account
|
||||
fieldMappings:
|
||||
- identifier: FullName
|
||||
columnName: AccountCustomEntity
|
||||
- entityType: IP
|
||||
fieldMappings:
|
||||
- identifier: Address
|
||||
columnName: IPCustomEntity
|
|
@ -0,0 +1,34 @@
|
|||
id: 42831fb3-f61d-41e9-95d9-f08797479a0e
|
||||
name: Azure CloudShell Usage
|
||||
description: |
|
||||
'This query look for users starting an Azure CloudShell session and summarizes the Azure Activity from that
|
||||
user account during that timeframe (by default 1 hour). This can be used to help identify abuse of the CloudShell
|
||||
to modify Azure resources.'
|
||||
requiredDataConnectors:
|
||||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- AuditLogs
|
||||
tactics:
|
||||
- Execution
|
||||
relevantTechniques:
|
||||
- T1059
|
||||
query: |
|
||||
|
||||
AzureActivity
|
||||
| where ActivityStatusValue == "Succeeded"
|
||||
| where ResourceGroup contains "cloud-shell-storage"
|
||||
| where OperationNameValue == "Microsoft.Storage/storageAccounts/listKeys/action"
|
||||
// Change the timekey scope below to get activity for a longer window
|
||||
| summarize by Caller, timekey= bin(TimeGenerated, 1h)
|
||||
| join (AzureActivity
|
||||
| where OperationNameValue != "Microsoft.Storage/storageAccounts/listKeys/action"
|
||||
| where isnotempty(OperationName)
|
||||
// Change the timekey scope below to get activity for a longer window
|
||||
| summarize make_set(OperationName) by Caller, timekey=bin(TimeGenerated, 1h)) on Caller, timekey
|
||||
| extend timestamp = timekey, AccountCustomEntity = Caller
|
||||
|
||||
entityMappings:
|
||||
- entityType: Account
|
||||
fieldMappings:
|
||||
- identifier: FullName
|
||||
columnName: AccountCustomEntity
|
|
@ -0,0 +1,34 @@
|
|||
id: 0fb54a5c-5599-4ff9-80a2-f788c3ed285e
|
||||
name: Solarigate DNS Pattern
|
||||
description: |
|
||||
'Looks for DGA pattern of the domain associated with Solarigate in order to find other domains with the same activity pattern.'
|
||||
requiredDataConnectors:
|
||||
- connectorId: DNS
|
||||
dataTypes:
|
||||
- DnsEvents
|
||||
tactics:
|
||||
- CommandAndControl
|
||||
relevantTechniques:
|
||||
- T1568
|
||||
query: |
|
||||
|
||||
let cloudApiTerms = dynamic(["api", "east", "west"]);
|
||||
DnsEvents
|
||||
| where IPAddresses != "" and IPAddresses != "127.0.0.1"
|
||||
| where Name endswith ".com" or Name endswith ".org" or Name endswith ".net"
|
||||
| extend domain_split = split(Name, ".")
|
||||
| where tostring(domain_split[-5]) != "" and tostring(domain_split[-6]) == ""
|
||||
| extend sub_domain = tostring(domain_split[0])
|
||||
| where sub_domain !contains "-"
|
||||
| extend sub_directories = strcat(domain_split[-3], " ", domain_split[-4])
|
||||
| where sub_directories has_any(cloudApiTerms)
|
||||
//Based on sample communications the subdomain is always between 20 and 30 bytes
|
||||
| where strlen(domain_split) < 32 or strlen(domain_split) > 20
|
||||
| extend domain = strcat(tostring(domain_split[-2]), ".", tostring(domain_split[-1]))
|
||||
| extend subdomain_no = countof(sub_domain, @"(\d)", "regex")
|
||||
| extend subdomain_ch = countof(sub_domain, @"([a-z])", "regex")
|
||||
| where subdomain_no > 1
|
||||
| extend percentage_numerical = toreal(subdomain_no) / toreal(strlen(sub_domain)) * 100
|
||||
| where percentage_numerical < 50 and percentage_numerical > 5
|
||||
| summarize count(), make_set(Name), FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated) by Name
|
||||
| order by count_ asc
|
|
@ -0,0 +1,62 @@
|
|||
id: 29a1815a-3ada-4182-a178-e52c483d2f95
|
||||
name: Solarigate Encoded Domain in URL
|
||||
description: |
|
||||
'Looks for a logon domain seen in Azure AD logs appearing in a DNS query encoded with the DGA encoding used in the Solarigate incident.
|
||||
Reference: https://blogs.microsoft.com/on-the-issues/2020/12/13/customers-protect-nation-state-cyberattacks/'
|
||||
requiredDataConnectors:
|
||||
- connectorId: DNS
|
||||
dataTypes:
|
||||
- DnsEvents
|
||||
- connectorId: AzureActiveDirectory
|
||||
dataTypes:
|
||||
- SigninLogs
|
||||
tactics:
|
||||
- CommandAndControl
|
||||
relevantTechniques:
|
||||
- T1568
|
||||
query: |
|
||||
|
||||
let dictionary = dynamic(["r","q","3","g","s","a","l","t","6","u","1","i","y","f","z","o","p","5","7","2","d","4","9","b","n","x","8","c","v","m","k","e","w","h","j"]);
|
||||
let regex_bad_domains = SigninLogs
|
||||
//Collect domains from tenant from signin logs
|
||||
| where TimeGenerated > ago(1d)
|
||||
| extend domain = tostring(split(UserPrincipalName, "@", 1)[0])
|
||||
| where domain != ""
|
||||
| summarize by domain
|
||||
| extend split_domain = split(domain, ".")
|
||||
//This cuts back on domains such as na.contoso.com by electing not to match on the "na" portion
|
||||
| extend target_string = iff(strlen(split_domain[0]) <= 2, split_domain[1], split_domain[0])
|
||||
| extend target_string = split(target_string, "-")
|
||||
| mv-expand target_string
|
||||
//Rip all of the alphanumeric out of the domain name
|
||||
| extend string_chars = extract_all(@"([a-z0-9])", tostring(target_string))
|
||||
//Guid for tracking our data
|
||||
| extend guid = new_guid()
|
||||
//Expand to get all of the individual chars from the domain
|
||||
| mv-expand string_chars
|
||||
| extend chars = tostring(string_chars)
|
||||
//Conduct computation to encode the domain as per actor spec
|
||||
| extend computed_char = array_index_of(dictionary, chars)
|
||||
| extend computed_char = dictionary[(computed_char + 4) % array_length(dictionary)]
|
||||
| summarize make_list(computed_char) by guid, domain
|
||||
| extend target_encoded = tostring(strcat_array(list_computed_char, ""))
|
||||
//These are probably too small, but can be edited (expect FP's when going too small)
|
||||
| where strlen(target_encoded) > 5
|
||||
| distinct target_encoded
|
||||
| summarize make_set(target_encoded)
|
||||
//Key to join to DNS
|
||||
| extend key = 1;
|
||||
DnsEvents
|
||||
| where TimeGenerated > ago(1d)
|
||||
| summarize by Name
|
||||
| extend key = 1
|
||||
//For each DNS query join the malicious domain list
|
||||
| join kind=inner (
|
||||
regex_bad_domains
|
||||
) on key
|
||||
| project-away key
|
||||
//Expand each malicious key for each DNS query observed
|
||||
| mv-expand set_target_encoded
|
||||
//IndexOf allows us to fuzzy match on the substring
|
||||
| extend match = indexof(Name, set_target_encoded)
|
||||
| where match > -1
|
|
@ -0,0 +1,45 @@
|
|||
id: 0f153385-2050-4c49-b896-e167f8e18fbc
|
||||
name: Exchange workflow MailItemsAccessed operation anomaly
|
||||
description: |
|
||||
'Identifies anomalous increases in Exchange mail items accessed operations.
|
||||
The query leverages KQL built-in anomaly detection algorithms to find large deviations from baseline patterns.
|
||||
Sudden increases in execution frequency of sensitive actions should be further investigated for malicious activity.
|
||||
Manually change scorethreshold from 1.5 to 3 or higher to reduce the noise based on outliers flagged from the query criteria.
|
||||
Read more about MailItemsAccessed: https://docs.microsoft.com/en-us/microsoft-365/compliance/advanced-audit?view=o365-worldwide#mailitemsaccessed'
|
||||
tactics:
|
||||
- Collection
|
||||
relevantTechniques:
|
||||
- T1114
|
||||
query: |
|
||||
|
||||
let starttime = 14d;
|
||||
let endtime = 1d;
|
||||
let timeframe = 1h;
|
||||
let scorethreshold = 1.5;
|
||||
let percentthreshold = 5;
|
||||
// Preparing the time series data aggregated on BytesTransferredOut column in the form of multi-value array so that it can be used with time series anomaly function.
|
||||
let TimeSeriesData=
|
||||
OfficeActivity
|
||||
| where TimeGenerated between (startofday(ago(starttime))..startofday(ago(endtime)))
|
||||
| where OfficeWorkload=~ "Exchange" and Operation =~ "MailItemsAccessed" and ResultStatus =~ "Succeeded"
|
||||
| project TimeGenerated, Operation, MailboxOwnerUPN
|
||||
| make-series Total=count() on TimeGenerated from startofday(ago(starttime)) to startofday(ago(endtime)) step timeframe;
|
||||
// Use the time series data prepared in previous step with time series aomaly function to generate baseline pattern and flag the outlier based on scorethreshold value.
|
||||
let TimeSeriesAlerts = TimeSeriesData
|
||||
| extend (anomalies, score, baseline) = series_decompose_anomalies(Total, scorethreshold, -1, 'linefit')
|
||||
| mv-expand Total to typeof(double), TimeGenerated to typeof(datetime), anomalies to typeof(double), score to typeof(double), baseline to typeof(long)
|
||||
| where anomalies > 0
|
||||
| project TimeGenerated, Total, baseline, anomalies, score;
|
||||
// Joining the flagged outlier from the previous step with the original dataset to present contextual information during the anomalyhour to analysts to conduct investigation or informed decistions.
|
||||
TimeSeriesAlerts
|
||||
// Join against base logs since specified timeframe to retrive records associated with the hour of anomoly
|
||||
| join kind=inner (
|
||||
OfficeActivity
|
||||
| where TimeGenerated between (startofday(ago(starttime))..startofday(ago(endtime)))
|
||||
| where OfficeWorkload=~ "Exchange" and Operation =~ "MailItemsAccessed" and ResultStatus =~ "Succeeded"
|
||||
| summarize Count=count(), IPAdresses = make_set(Client_IPAddress), ClientInfoStrings= make_set(ClientInfoString) by bin(TimeGenerated,1h), MailboxOwnerUPN, Logon_Type, TenantId, UserType
|
||||
| order by Count desc
|
||||
) on TimeGenerated
|
||||
| extend PercentofTotal = round(Count/Total, 2) * 100
|
||||
| where PercentofTotal > percentthreshold // Filter Users with count of less than 5 percent of TotalEvents per Hour to remove FPs/ users with very low count of MailItemsAccessed events
|
||||
| order by PercentofTotal desc
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Загрузка…
Ссылка в новой задаче