Detection rules › Kusto

Malicious web application requests linked with Microsoft Defender for Endpoint (formerly Microsoft Defender ATP) alerts

Severity
medium
Time window
7d
Group by
AttackerIP, FileName, HostName, ShellLocation, SiteName, SystemAlertId
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

'Takes Microsoft Defender for Endpoint (formerly Microsoft Defender ATP) alerts where web scripts are present in the evidence and correlates with requests made to those scripts in the WCSIISLog to surface new alerts for potentially malicious web request activity. The lookback for alerts is set to 1h and the lookback for W3CIISLogs is set to 7d. A sample set of popular web script extensions has been provided in scriptExtensions that should be tailored to your environment.'

MITRE ATT&CK coverage

TacticTechniques
Persistence

Rule body

id: fbfbf530-506b-49a4-81ad-4030885a195c
name: Malicious web application requests linked with Microsoft Defender for Endpoint (formerly Microsoft Defender ATP) alerts
description: |
  'Takes Microsoft Defender for Endpoint (formerly Microsoft Defender ATP) alerts where web scripts are present in the evidence and correlates with requests made to those scripts in the WCSIISLog to surface new alerts for potentially malicious web request activity.
  The lookback for alerts is set to 1h and the lookback for W3CIISLogs is set to 7d. A sample set of popular web script extensions has been provided in scriptExtensions that should be tailored to your environment.'
severity: Medium
requiredDataConnectors:
  - connectorId: MicrosoftDefenderAdvancedThreatProtection
    dataTypes:
      - SecurityAlert
  - connectorId: AzureMonitor(IIS)
    dataTypes:
      - W3CIISLog
queryFrequency: 1h
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
relevantTechniques:
  - T1505
query: |
  let alertTimeWindow = 1h;
  let logTimeWindow = 7d;
  // Define script extensions that suit your web application environment - a sample are provided below
  let scriptExtensions = dynamic([".php", ".jsp", ".js", ".aspx", ".asmx", ".asax", ".cfm", ".shtml"]);
  let alertData = materialize(SecurityAlert
  | where TimeGenerated > ago(alertTimeWindow)
  | where ProviderName == "MDATP"
  // Parse and expand the alert JSON
  | extend alertData = parse_json(Entities)
  | mvexpand alertData);
  let fileData = alertData
  // Extract web script files from MDATP alerts - our malicious web scripts - candidate webshells
  | where alertData.Type =~ "file"
  | where alertData.Name has_any(scriptExtensions)
  | extend FileName = tostring(alertData.Name), Directory = tostring(alertData.Directory);
  let hostData = alertData
  // Extract server details from alerts and map to alert id
  | where alertData.Type =~ "host"
  | project HostName = tostring(alertData.HostName), DnsDomain = tostring(alertData.DnsDomain), SystemAlertId
  | distinct HostName, DnsDomain, SystemAlertId;
  // Join the files on their impacted servers
  let webshellData = fileData
  | join kind=inner (hostData) on SystemAlertId
  | project TimeGenerated, FileName, Directory, HostName, DnsDomain;
  webshellData
  | join (
  // Find requests that were made to this file on the impacted server in the W3CIISLog table
  W3CIISLog
  | where TimeGenerated > ago(logTimeWindow)
  // Restrict to accesses to script extensions
  | where csUriStem has_any(scriptExtensions)
  | extend splitUriStem = split(csUriStem, "/")
  | extend FileName = splitUriStem[-1], HostName = sComputerName
  // Summarize potential attacker activity
  | summarize count(), StartTime=min(TimeGenerated), EndTime=max(TimeGenerated), RequestUserAgents=make_set(csUserAgent), ReqestMethods=make_set(csMethod), RequestStatusCodes=make_set(scStatus), RequestCookies=make_set(csCookie), RequestReferers=make_set(csReferer), RequestQueryStrings=make_set(csUriQuery) by AttackerIP=cIP, SiteName=sSiteName, ShellLocation=csUriStem, tostring(FileName), HostName
  ) on FileName, HostName
  | project StartTime, EndTime, AttackerIP, RequestUserAgents, Computer = HostName, SiteName, ShellLocation, ReqestMethods, RequestStatusCodes, RequestCookies, RequestReferers, RequestQueryStrings, RequestCount = count_
  | extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: AttackerIP
version: 1.0.4
kind: Scheduled
metadata:
    source:
        kind: Scheduled
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Others" ]

Stages and Predicates

Parameters

let alertTimeWindow = 1h;
let logTimeWindow = 7d;
let scriptExtensions = dynamic([".php", ".jsp", ".js", ".aspx", ".asmx", ".asax", ".cfm", ".shtml"]);

let alertData, let fileData and let webshellData are inlined into the numbered stages below.

Let binding: hostData used in Stage 9

let hostData = alertData
| where alertData.Type =~ "host"
| project HostName = tostring(alertData.HostName), DnsDomain = tostring(alertData.DnsDomain), SystemAlertId
| distinct HostName, DnsDomain, SystemAlertId;

Stages 1 to 10 define let webshellData (the rule's main pipeline source); stages 11 to 15 run on it.

Stage 1: source

SecurityAlert

Stage 2: where

| where TimeGenerated > ago(alertTimeWindow)

Stage 3: where

| where ProviderName == "MDATP"

Stage 4: extend

| extend alertData = parse_json(Entities)

Stage 5: mv-expand

| mvexpand alertData

Stage 6: where

| where alertData.Type =~ "file"

Stage 7: where

| where alertData.Name has_any(scriptExtensions)

Stage 8: extend

| extend FileName = tostring(alertData.Name), Directory = tostring(alertData.Directory)

Stage 9: join

| join kind=inner (hostData) on SystemAlertId

Stage 10: project

| project TimeGenerated, FileName, Directory, HostName, DnsDomain

Stage 11: join

webshellData
| join (
W3CIISLog
| where TimeGenerated > ago(logTimeWindow)
| where csUriStem has_any(scriptExtensions)
| extend splitUriStem = split(csUriStem, "/")
| extend FileName = splitUriStem[-1], HostName = sComputerName
| summarize count(), StartTime=min(TimeGenerated), EndTime=max(TimeGenerated), RequestUserAgents=make_set(csUserAgent), ReqestMethods=make_set(csMethod), RequestStatusCodes=make_set(scStatus), RequestCookies=make_set(csCookie), RequestReferers=make_set(csReferer), RequestQueryStrings=make_set(csUriQuery) by AttackerIP=cIP, SiteName=sSiteName, ShellLocation=csUriStem, tostring(FileName), HostName
) on FileName, HostName

Stage 12: project

| project StartTime, EndTime, AttackerIP, RequestUserAgents, Computer = HostName, SiteName, ShellLocation, ReqestMethods, RequestStatusCodes, RequestCookies, RequestReferers, RequestQueryStrings, RequestCount = count_

Stage 13: extend

| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))

Stage 14: extend

| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
HostNameDomain =
ifDomainIndex != -1substring(Computer, (DomainIndex + 1))
elseComputer

Stage 15: summarize aggregation inside the join branch

summarize by AttackerIP, SiteName, ShellLocation, HostName

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Namematch
  • .asax transforms: term
  • .asmx transforms: term
  • .aspx transforms: term
  • .cfm transforms: term
  • .js transforms: term
  • .jsp transforms: term
  • .php transforms: term
  • .shtml transforms: term
field:"Name" kind:match
ProviderNameeq
  • MDATP corpus 7 (kusto 7)
field:"ProviderName" kind:eq value:"MDATP"
Typeeq
  • file corpus 2 (kusto 2)
  • host corpus 2 (kusto 2)
field:"Type" kind:eq
csUriStemmatch
  • .asax transforms: term
  • .asmx transforms: term
  • .aspx transforms: term
  • .cfm transforms: term
  • .js transforms: term
  • .jsp transforms: term
  • .php transforms: term
  • .shtml transforms: term
field:"csUriStem" kind:match

Output fields

These fields are emitted when the rule matches.

FieldSource
AttackerIPsummarize
HostNamesummarize
ShellLocationsummarize
SiteNamesummarize