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 AccessT1190 Exploit Public-Facing Application

Event coverage

Rule body kusto

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

Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.

FieldKindValues
AccessMaskin
  • 0x10 transforms: cased
  • 0x100 transforms: cased corpus 5 (sigma 2, splunk 2, elastic 1)
  • 0x2 transforms: cased corpus 4 (sigma 2, splunk 2)
  • 0x4 transforms: cased
ActingProcessCommandLinematch
  • SysAidServer transforms: term
ActingProcessNamematch
  • java.exe
  • javaw.exe
CommandLinematch
  • SysAidServer transforms: term
EventIDeq
  • 4663 transforms: cased corpus 34 (splunk 29, kusto 5)
  • 4688 transforms: cased corpus 313 (splunk 283, kusto 30)
EventTypeeq
  • FileCreated transforms: cased corpus 8 (kusto 8)
FileNameends_with
  • .jsp
FilePathends_with
  • .jsp
InitiatingProcessCommandLinematch
  • SysAidServer transforms: term
InitiatingProcessFileNamematch
  • java.exe
  • javaw.exe
ObjectNameends_with
  • .jsp
Processmatch
  • java.exe
  • javaw.exe

Output fields

Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.

FieldSource
Accountsummarize
Computersummarize
ObjectNamesummarize
ParentProcessNamesummarize
ProcessNamesummarize
SubjectLogonIdsummarize
TimeGeneratedsummarize
timekeysummarize