sequence_detect step conditions dependant on previous step?

Occasional Contributor

I'm looking for a way to build step-specific conditions into usage of the sequence_detect plugin. What I'm trying to do is make conditions that are dependent on values which appear in earlier sequence steps. I'd imagine it something like when you make a sed find and replace that has a \1 reference in the replace code, to refer to something matched in the original find portion.. Put another way, I'd like to define a variable or value reference in one condition that can be referenced by comparison operators in subsequent steps.

 

One use case for this is an attempt to trace delegation activity using SecurityEvents data. Here's a partially redacted sample query to demonstrate the concept:

 

let myusers = materialize(SecurityEvent
    | where TimeGenerated > ago(1h)
    | where (EventID == 4624 and ImpersonationLevel == "%%1840" and Computer !contains "wdc" and TargetUserName !contains "$")
    | distinct TargetUserName);
let myevents = materialize(SecurityEvent
    | where TimeGenerated > ago(1d)
    | where ((EventID == 4624 and ImpersonationLevel == "%%1840" and TargetUserName in (myusers) and Computer !contains "wdc" and TargetUserName !contains "$") 
    or (EventID == 4769 and EventData has_any (myusers) and EventData !has "krbtgt" and EventData !contains "$@" and EventData !contains_cs "WDC"))
    | parse EventData with * @'"TargetUserName">' parsedTargetUserName  @'<' * @'"TargetDomainName">' parsedTargetDomainName @'<' *'"ServiceName">' parsedServiceName "<" * @'"TicketEncryptionType">' TicketEncryptionType @'<' * @'"IpAddress">::ffff:' parsedIpAddress @'<' * @'"LogonGuid">{' parsedLogonGuid @'}' *
    | extend
        TargetUserName = toupper(replace(@'@.*', @'', coalesce(TargetUserName, parsedTargetUserName))),
        TargetDomainName = toupper(replace(@'@.*', @'', coalesce(TargetDomainName, parsedTargetDomainName))),
        ServiceName = coalesce(ServiceName, parsedServiceName),
        LogonGuid = coalesce(LogonGuid, tolower(parsedLogonGuid)),
        IpAddress = coalesce(IpAddress, parsedIpAddress)
    | extend TargetDomainName = case(

//multiple lines with embedded domain names redacted - this section translates the dns style domain names from the 4769 evemnts into NT stype domain names for compatibility with the format used by the 4624 events

, TargetDomainName));
let mysequences = materialize(myevents
    | project
        TimeGenerated,
        EventID,
        LogonGuid,
        TargetUserName,
        ServiceName,
        IpAddress,
        TargetDomainName,
        TicketEncryptionType
    | evaluate sequence_detect (TimeGenerated, 29s, 30s, firstticket=(EventID == 4769 and ServiceName !endswith "$"), login=(EventID == 4624), secondticket=(EventID == 4769 and TicketEncryptionType == "0x17"), TargetUserName, TargetDomainName)
    | where Duration <> "00: 00: 00"
    | extend TimeGenerated = firstticket_TimeGenerated - 1ms);
let firsttickettimes = mysequences
    | distinct firstticket_TimeGenerated
    | project times=firstticket_TimeGenerated;
let logintimes = mysequences
    | distinct login_TimeGenerated
    | project times=login_TimeGenerated;
let secondtickettimes = mysequences
    | distinct secondticket_TimeGenerated
    | project times=secondticket_TimeGenerated;
let sequencetimes=union firsttickettimes, logintimes, secondtickettimes
    | distinct times;
let relatedevents=myevents
    | where TimeGenerated in (sequencetimes);
mysequences
| union relatedevents
| sort by TargetDomainName asc, TargetUserName asc, TimeGenerated asc
| project-reorder
    TimeGenerated,
    EventID,
    TargetDomainName,
    TargetUserName,
    ServiceName,
    IpAddress,
    Computer,
    TicketEncryptionType,
    LogonGuid,
    Duration

The major problem with the above query is that there's no way to ensure that the IP from which the 2nd 4769 event comes isn't the same as where the first one came from. Ideally, you'd want to also specify that the 2nd 4769 event must have come from the IP associated with the Computer name for the 4624 event, but there's no simple way to get that data.

 

Thoughts?

1 Reply

For those who don't actually want to read through the above query to figure out exactly what it does, it's looking for a sequence of the following security events for the same user, within a 30 second period:

  • a 4769 event (kerberos service ticket request)
  • a 4624 (logon) event with an impersonation type of Delegation
  • another 4769 event

It then formats the sequence information together with the complete 4769 and 4624 events for output.

 

The hope/expectation would be that this would indicate a user coming from some workstation IP address (the IP in the first 4769 event), requesting access to a particular servicename, on some particular server Computer/IP which has delegation rights (the Computer name in the 4624 event), and that Computer/IP then immediately using those delegation rights to request a service ticket for itself on behalf of that user (the IP of the 2nd 4769 event should be the IP assigned to the Computer listed in the 4624 event).