Detection rules › Kusto
Exchange OAB Virtual Directory Attribute Containing Potential Webshell
This query uses Windows Event ID 5136 in order to detect potential webshell deployment by exploitation of CVE-2021-27065. This query looks for changes to the InternalHostName or ExternalHostName properties of Exchange OAB Virtual Directory objects in AD Directory Services where the new objects contain potential webshell objects.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Telemetry coverage
| Provider | Record / event type |
|---|---|
| Security-Auditing | Event ID 5136: A directory service object was modified. |
Rule body
id: faf1a6ff-53b5-4f92-8c55-4b20e9957594
name: Exchange OAB Virtual Directory Attribute Containing Potential Webshell
description: |
'This query uses Windows Event ID 5136 in order to detect potential webshell deployment by exploitation of CVE-2021-27065.
This query looks for changes to the InternalHostName or ExternalHostName properties of Exchange OAB Virtual Directory objects in AD Directory Services where the new objects contain potential webshell objects.'
severity: High
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvent
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- InitialAccess
relevantTechniques:
- T1190
query: |
SecurityEvent
// Look for specific Directory Service Changes and parse data
| where EventID == 5136
| extend EventData = parse_xml(EventData).EventData.Data
| mv-expand bagexpansion = array EventData
| evaluate bag_unpack(EventData)
| extend Key = tostring(column_ifexists('@Name', "")), Value = column_ifexists('#text', "")
| evaluate pivot(Key, any(Value),TimeGenerated, EventID, Computer, Account, AccountType, EventSourceName, Activity, SubjectAccount)
// Where changes relate to Exchange OAB
| extend ObjectClass = column_ifexists("ObjectClass", "")
| where ObjectClass =~ "msExchOABVirtualDirectory"
// Look for InternalHostName or ExternalHostName properties being changed
| extend AttributeLDAPDisplayName = column_ifexists("AttributeLDAPDisplayName", "")
| where AttributeLDAPDisplayName in~ ("msExchExternalHostName", "msExchInternalHostName")
// Look for suspected webshell activity
| extend AttributeValue = column_ifexists("AttributeValue", "")
| where AttributeValue has "script"
| project-rename LastSeen = TimeGenerated
| extend ObjectDN = column_ifexists("ObjectDN", "")
| project-reorder LastSeen, Computer, Account, ObjectDN, AttributeLDAPDisplayName, AttributeValue
| extend timestamp = LastSeen
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| extend AccountName = tostring(split(Account, @'\')[1]), AccountNTDomain = tostring(split(Account, @'\')[0])
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.4
kind: Scheduled
Stages and Predicates
Stage 1: source
SecurityEvent
Stage 2: where
| where EventID == 5136
Stage 3: extend
| extend EventData = parse_xml(EventData).EventData.Data
Stage 4: mv-expand
| mv-expand bagexpansion = array EventData
Stage 5: evaluate
| evaluate bag_unpack(EventData)
Stage 6: extend
| extend Key = tostring(column_ifexists('@Name', "")), Value = column_ifexists('#text', "")
Stage 7: evaluate
| evaluate pivot(Key, any(Value),TimeGenerated, EventID, Computer, Account, AccountType, EventSourceName, Activity, SubjectAccount)
Stage 8: extend
| extend ObjectClass = column_ifexists("ObjectClass", "")
Stage 9: where
| where ObjectClass =~ "msExchOABVirtualDirectory"
Stage 10: extend
| extend AttributeLDAPDisplayName = column_ifexists("AttributeLDAPDisplayName", "")
Stage 11: where
| where AttributeLDAPDisplayName in~ ("msExchExternalHostName", "msExchInternalHostName")
Stage 12: extend
| extend AttributeValue = column_ifexists("AttributeValue", "")
Stage 13: where
| where AttributeValue has "script"
Stage 14: project-rename
| project-rename LastSeen = TimeGenerated
Stage 15: extend
| extend ObjectDN = column_ifexists("ObjectDN", "")
Stage 16: project-reorder
| project-reorder LastSeen, Computer, Account, ObjectDN, AttributeLDAPDisplayName, AttributeValue
Stage 17: extend (4 consecutive steps)
| extend timestamp = LastSeen
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| extend AccountName = tostring(split(Account, @'\')[1]), AccountNTDomain = tostring(split(Account, @'\')[0])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AttributeLDAPDisplayName | in |
| field:"AttributeLDAPDisplayName" kind:in |
AttributeValue | match |
| field:"AttributeValue" kind:match value:"script" |
EventID | eq |
| field:"EventID" kind:eq value:"5136" |
ObjectClass | eq |
| field:"ObjectClass" kind:eq value:"msExchOABVirtualDirectory" |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
EventData | extend |
Key | extend |
Value | extend |
ObjectClass | extend |
AttributeLDAPDisplayName | extend |
AttributeValue | extend |
LastSeen | project-rename |
ObjectDN | extend |
timestamp | extend |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |
AccountNTDomain | extend |
AccountName | extend |