Detection rules › Kusto
Shadow Credentials Added to Account (Alternative)
This query searches for modifications to the 'msDS-KeyCredentialLink' property in Active Directory. There are two different events which contain information to detect such changes: 5136 and 4662. This detection uses the 4662, which is an alternative if 5136 is not available.
Known false positives
- The property can also be used for legitimate purposes. However, the legitimate use of the property is limited in most environments.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation |
References
- https://docs.microsoft.com/en-us/defender-for-identity/configure-windows-event-collection
- https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4662
- https://posts.specterops.io/shadow-credentials-abusing-key-trust-account-mapping-for-takeover-8ee1a53566ab
- https://github.com/ShutdownRepo/pywhisker
- https://github.com/MichaelGrafnetter/DSInternals
Telemetry coverage
| Provider | Record / event type |
|---|---|
| Security-Auditing | Event ID 4662: An operation was performed on an object. |
Rule body
let timeframe = 2*1h;
let RuleId = "0297";
let DedupFields = dynamic(["TimeGenerated"]);
SecurityEvent
| where ingestion_time() >= ago(timeframe)
| where EventID == 4662
| where Properties has "5b47d60f-6090-40b2-9f37-2a4de88f3063" // msDS-KeyCredentialLink.
| where Properties has "%%7685" or (binary_and(toint(AccessMask), 0x10) == 0x10) // "Write Property" or "Write Extended Attributes": https://gist.github.com/brianreitz/d5b9397a2e8b3d52ceb9359897e07c3f
| extend HostName=tostring(split(Computer,".")[0]),DnsDomain=iif(Computer contains ".", substring(Computer, indexof(Computer, ".") + 1, strlen(Computer)),"")
| extend AccountName=iif(Account contains @"\",tostring(split(Account,@"\")[1]),Account),AccountDomain=iif(Account contains @"\",tostring(split(Account,@"\")[0]),"")
// Begin environment-specific filter.
// End environment-specific filter.
// Begin de-duplication logic.
| extend DedupFieldValues=pack_all()
| mv-apply e=DedupFields to typeof(string) on (
extend DedupValue=DedupFieldValues[tostring(e)]
| order by e // Sorting is required to ensure make_list is deterministic.
| summarize DedupValues=make_list(DedupValue)
)
| extend DedupEntity=strcat_array(DedupValues, "|")
| project-away DedupFieldValues, DedupValues
| join kind=leftanti (
SecurityAlert
| where AlertName has RuleId and ProviderName has "ASI"
| where TimeGenerated >= ago(timeframe)
| extend DedupEntity = tostring(parse_json(tostring(parse_json(ExtendedProperties)["Custom Details"])).DedupEntity[0])
| project DedupEntity
) on DedupEntity
// End de-duplication logic.
Stages and Predicates
Parameters
let timeframe = 2*1h;
let RuleId = "0297";
let DedupFields = dynamic(["TimeGenerated"]);
Stage 1: source
SecurityEvent
Stage 2: where
| where ingestion_time() >= ago(timeframe)
Stage 3: where
| where EventID == 4662
Stage 4: where
| where Properties has "5b47d60f-6090-40b2-9f37-2a4de88f3063"
Stage 5: where
| where Properties has "%%7685" or (binary_and(toint(AccessMask), 0x10) == 0x10)
Stage 6: extend (3 consecutive steps)
| extend HostName=tostring(split(Computer,".")[0]),DnsDomain=iif(Computer contains ".", substring(Computer, indexof(Computer, ".") + 1, strlen(Computer)),"")
| extend AccountName=iif(Account contains @"\",tostring(split(Account,@"\")[1]),Account),AccountDomain=iif(Account contains @"\",tostring(split(Account,@"\")[0]),"")
| extend DedupFieldValues=pack_all()
Stage 7: kusto:mv-apply
| mv-apply e=DedupFields to typeof(string) on (
extend DedupValue=DedupFieldValues[tostring(e)]
| order by e
| summarize DedupValues=make_list(DedupValue)
)
Stage 8: extend
| extend DedupEntity=strcat_array(DedupValues, "|")
Stage 9: project-away
| project-away DedupFieldValues, DedupValues
Stage 10: join (negated)
| join kind=leftanti (
SecurityAlert
| where AlertName has RuleId and ProviderName has "ASI"
| where TimeGenerated >= ago(timeframe)
| extend DedupEntity = tostring(parse_json(tostring(parse_json(ExtendedProperties)["Custom Details"])).DedupEntity[0])
| project DedupEntity
) on DedupEntity
Stage 11: summarize aggregation inside the mv-apply branch
summarize
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
AlertName | match | 0297 | excludes:AlertName field:"AlertName" value:"0297" |
ProviderName | match | ASI | excludes:ProviderName field:"ProviderName" value:"ASI" |
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AccessMask | eq |
| field:"AccessMask" kind:eq value:"16" |
EventID | eq |
| field:"EventID" kind:eq value:"4662" |
Properties | match |
| field:"Properties" kind:match |