Detection rules › Kusto

Shadow Credentials Added to Account

Author
FalconForce
Source
github.com/FalconForceTeam/FalconFriday

This query searches for modifications to the 'msDS-KeyCredentialLink' property in Active Directory, introduced in Windows Server 2016. There are two different events which contain information to detect such changes 5136 and 4662. This detection uses the 5136, which is the preferred event to use.

MITRE ATT&CK coverage

References

Event coverage

Rule body kusto

let timeframe = 2*1h;
let RuleId = "0275";
let DedupFields = dynamic(["TimeGenerated", "SubjectUserName", "Computer"]);
SecurityEvent
| where ingestion_time() >= ago(timeframe)
| where EventID == 5136
| extend AttributeName = extract("<Data Name=\"AttributeLDAPDisplayName\">(.*?)</Data>", 1, EventData)
| extend ObjectDN = extract("<Data Name=\"ObjectDN\">(.*?)</Data>", 1, EventData)
| extend SubjectUserName = extract("<Data Name=\"SubjectUserName\">(.*?)</Data>", 1, EventData)
| where AttributeName has "msDS-KeyCredentialLink"
| where not(SubjectUserName endswith "$" and ObjectDN startswith strcat("CN=", replace_string(SubjectUserName, "$", ""), ",")) // Machine account changing its own msDS-KeyCredentialLink.
| 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 = "0275";
let DedupFields = dynamic(["TimeGenerated", "SubjectUserName", "Computer"]);

Stage 1: source

SecurityEvent

Stage 2: where

| where ingestion_time() >= ago(timeframe)

Stage 3: where

| where EventID == 5136

Stage 4: extend (3 consecutive steps)

| extend AttributeName = extract("<Data Name=\"AttributeLDAPDisplayName\">(.*?)</Data>", 1, EventData)
| extend ObjectDN = extract("<Data Name=\"ObjectDN\">(.*?)</Data>", 1, EventData)
| extend SubjectUserName = extract("<Data Name=\"SubjectUserName\">(.*?)</Data>", 1, EventData)

Stage 5: where

| where AttributeName has "msDS-KeyCredentialLink"

Stage 6: where

| where not(SubjectUserName endswith "$" and ObjectDN startswith strcat("CN=", replace_string(SubjectUserName, "$", ""), ","))

Stage 7: 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 8: 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 9: extend

| extend DedupEntity=strcat_array(DedupValues, "|")

Stage 10: project-away

| project-away DedupFieldValues, DedupValues

Stage 11: 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 12: summarize

summarize

Exclusions

Top-level NOT(...) conjuncts: predicates this rule actively suppresses.

FieldKindExcluded values
SubjectUserNameends_with$
AlertNamematch0275
ProviderNamematchASI

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
AttributeNamematch
  • msDS-KeyCredentialLink transforms: term
EventIDeq
  • 5136 transforms: cased corpus 30 (splunk 24, kusto 5, elastic 1)