Detection rules › Kusto

Identify SysAid Server web shell creation

Severity
high
Time window
5m
Group by
Account, CommandLine, Computer, ObjectName, ParentProcessName, Process, ProcessName, SubjectLogonId, TimeGenerated, timekey
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

This query looks for potential webshell creation by the threat actor Mercury after the sucessful exploitation of SysAid server. Reference: https://www.microsoft.com/security/blog/2022/08/25/mercury-leveraging-log4j-2-vulnerabilities-in-unpatched-systems-to-target-israeli-organizations/

MITRE ATT&CK coverage

TacticTechniques
Initial Access

Telemetry coverage

Rule body

id: 50eb4cbd-188f-44f4-b964-bab84dcdec10
name: Identify SysAid Server web shell creation 
description: |
  'This query looks for potential webshell creation by the threat actor Mercury after the sucessful exploitation of SysAid server. 
  Reference:  https://www.microsoft.com/security/blog/2022/08/25/mercury-leveraging-log4j-2-vulnerabilities-in-unpatched-systems-to-target-israeli-organizations/'
severity: High
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent 
  - connectorId: WindowsSecurityEvents
    dataTypes:
      - SecurityEvent 
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceFileEvents
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
tags:
  - Mercury
  - Schema: ASIMFileEvent
    SchemaVersion: 0.1.0
query: |
  let timeframe = 1d;
  let time_window = 5m;
  (union isfuzzy=true
  (SecurityEvent
  | where TimeGenerated > ago(timeframe)
  | where EventID == 4688
  | where Process has_any ("java.exe", "javaw.exe") and CommandLine has "SysAidServer" 
  | summarize by ParentProcessName,Process, Account, Computer, CommandLine, timekey= bin(TimeGenerated, time_window), TimeGenerated, SubjectLogonId
  | join kind=inner(
  SecurityEvent
  | where TimeGenerated > ago(timeframe)
  | where EventID == 4663
  | where Process has_any ("java.exe", "javaw.exe")
  | where AccessMask in ('0x2','0x100', '0x10', '0x4')
  | where ObjectName endswith ".jsp" 
  | summarize by ParentProcessName, Account, Computer, ObjectName, ProcessName, timekey= bin(TimeGenerated, time_window), TimeGenerated, SubjectLogonId)
   on timekey, Computer, SubjectLogonId
  ),
  (DeviceFileEvents 
  | where InitiatingProcessFileName has_any ("java.exe", "javaw.exe")  
  | where InitiatingProcessCommandLine has "SysAidServer"  
  | where FileName endswith ".jsp" 
  | extend Account = strcat(InitiatingProcessAccountDomain, @'\', InitiatingProcessAccountName), Computer = DeviceName
  ),
  (imFileEvent
  | where TimeGenerated > ago(timeframe)
  | where EventType == "FileCreated"
  | where ActingProcessName has_any ("java.exe", "javaw.exe") 
  | where ActingProcessCommandLine has "SysAidServer"  
  | where FilePath endswith ".jsp" 
  | extend Account = ActorUsername, Computer = DvcHostname
  )
  )
  | extend AccountName = tostring(split(Account, @'\')[1]), AccountNTDomain = tostring(split(Account, @'\')[0])
  | extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Account
      - identifier: Name
        columnName: AccountName
      - identifier: NTDomain
        columnName: AccountNTDomain
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
version: 1.0.3
kind: Scheduled
metadata:
    source:
        kind: Scheduled
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Others" ]

Stages and Predicates

Parameters

let timeframe = 1d;
let time_window = 5m;

union isfuzzy=true (3 sources)

Each leg below queries one source; the rule matches if any leg does. Sources: SecurityEvent, DeviceFileEvents, imFileEvent

Leg 1: SecurityEvent

SecurityEvent
| where TimeGenerated > ago(timeframe)
| where EventID == 4688
| where Process has_any ("java.exe", "javaw.exe") and CommandLine has "SysAidServer" 
| summarize by ParentProcessName,Process, Account, Computer, CommandLine, timekey= bin(TimeGenerated, time_window), TimeGenerated, SubjectLogonId
| join kind=inner(
SecurityEvent
| where TimeGenerated > ago(timeframe)
| where EventID == 4663
| where Process has_any ("java.exe", "javaw.exe")
| where AccessMask in ('0x2','0x100', '0x10', '0x4')
| where ObjectName endswith ".jsp" 
| summarize by ParentProcessName, Account, Computer, ObjectName, ProcessName, timekey= bin(TimeGenerated, time_window), TimeGenerated, SubjectLogonId)
 on timekey, Computer, SubjectLogonId

Leg 2: DeviceFileEvents

DeviceFileEvents 
| where InitiatingProcessFileName has_any ("java.exe", "javaw.exe")  
| where InitiatingProcessCommandLine has "SysAidServer"  
| where FileName endswith ".jsp" 
| extend Account = strcat(InitiatingProcessAccountDomain, @'\', InitiatingProcessAccountName), Computer = DeviceName

Leg 3: imFileEvent

imFileEvent
| where TimeGenerated > ago(timeframe)
| where EventType == "FileCreated"
| where ActingProcessName has_any ("java.exe", "javaw.exe") 
| where ActingProcessCommandLine has "SysAidServer"  
| where FilePath endswith ".jsp" 
| extend Account = ActorUsername, Computer = DvcHostname

Applied to the combined result

| extend AccountName = tostring(split(Account, @'\')[1]), AccountNTDomain = tostring(split(Account, @'\')[0])
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
AccessMaskin
  • 0x10
  • 0x100 corpus 3 (splunk 2, elastic 1)
  • 0x2 corpus 2 (sigma 2)
  • 0x4
field:"AccessMask" kind:in
ActingProcessCommandLinematch
  • SysAidServer transforms: term corpus 2 (sigma 1, kusto 1)
field:"ParentCommandLine" kind:match value:"SysAidServer"
ActingProcessNamematch
  • java.exe transforms: term
  • javaw.exe transforms: term
field:"ParentImage" kind:match
CommandLinematch
  • SysAidServer transforms: term
field:"CommandLine" kind:match value:"SysAidServer"
EventIDeq
  • 4663 corpus 35 (splunk 30, kusto 5)
  • 4688 corpus 317 (splunk 283, kusto 33, elastic 1)
field:"EventID" kind:eq
EventTypeeq
  • FileCreated corpus 2 (kusto 2)
field:"EventType" kind:eq value:"FileCreated"
FileNameends_with
  • .jsp
field:"file_name" kind:ends_with value:".jsp"
FilePathends_with
  • .jsp
field:"FilePath" kind:ends_with value:".jsp"
InitiatingProcessCommandLinematch
  • SysAidServer transforms: term
field:"CommandLine" kind:match value:"SysAidServer"
InitiatingProcessFileNamematch
  • java.exe transforms: term
  • javaw.exe transforms: term
field:"parent_process_name" kind:match
ObjectNameends_with
  • .jsp
field:"ObjectName" kind:ends_with value:".jsp"
Processmatch
  • java.exe transforms: term
  • javaw.exe transforms: term
field:"Process" kind:match

Output fields

These fields are emitted when the rule matches.

FieldSource
Accountsummarize
Computersummarize
ObjectNamesummarize
ParentProcessNamesummarize
ProcessNamesummarize
SubjectLogonIdsummarize
TimeGeneratedsummarize
timekeysummarize