FYE: Tracking Windows Logon Type and Logon Errors With Graylog tables

I had mentioned in a previous post we use Graylog tables to give more detail for tracking windows password failures. We including information on the type of logon that failed (Interactive, Network, Batch etc.) as well as why the logon failed ( bad username, bad password, password expired, workstation clock out of range etc.) Not so much a start to finish how to but rather nuts and bolts. The tables can be used anywhere, the pipeline code assumes you are using elastic beats. Happy to answer questions.

Key Links for more event id info:

First - Table to decode type of logon. these can be found in the quick reference sheet link above or

"number","type"
"2","Interactive"
"3","Network"
"4","Batch"
"5","Service"
"7","Unlock"
"8","NetworkClearText"
"9","NewCredentials"
"10","RemoteInteractive"
"11","CachedInteractive"

Second - Table to decode reason for logon failure
NOTE: We culled the table to key factors - more detail can be found here: Event ID 4771 - Kerberos pre-auth fail and here: Event ID 4625 - Account Failed to log on

"err_code", "explanation"
"0x6", "Bad username - kerberos"
"0x7", "New computer account(?) - kerberos"
"0x9", "Administrator should reset password - kerberos"
"0xC", "Workstation Restriction - kerberos"
"0x12", "Account Disabled, expired, locked out, logon hours restriction - kerberos"
"0x17", "The users password has expired - kerberos"
"0x18", "Bad Password - kerberos"
"0x20", "Frequently logged by computer accounts - kerberos"
"0x25", "Workstation clock too far out of sync with the DCs - kerberos"
"0xC0000064", "User name does not exist - NTLM"
"0xC000006A", "User name is correct but the password is wrong - NTLM"
"0xC0000234", "User is currently locked out - NTLM"
"0xC0000072", "Account is currently disabled - NTLM"
"0xC000006F", "User tried to logon outside his day of week or time of day restrictions - NTLM"
"0xC0000070", "Workstation restriction - NTLM"
"0xC00000193", "Account expiration - NTLM"
"0xC0000071", "Expired password - NTLM"
"0xC0000133", "Clocks between DC and other computer too far out of sync - NTLM"
"0xC0000224", "User is required to change password at next logon - NTLM"
"0xC0000225", "Evidently a bug in Windows and not a risk (per ultimateITSecurity.com) - NTLM"
"0xC000015b", "The user has not been granted the requested logon type (aka logon right) at this machine - NTLM"

Here is pipeline code we use that implements these tables in password failure detection:

rule "Bad Password Detect"
when
    to_string($message.winlogbeat_event_id) == "4625"             
then
    // Build Alert structures
    // Create subject of (e-mail) alert
    let subject_0 = concat("-GL| PW FAIL: ", to_string($message.winlogbeat_event_data_TargetUserName));
    let subject_1 = concat(subject_0, " connecting to ");
    let subject_fin = concat(subject_1, to_string($message.winlogbeat_host_name));
    set_field("e-mail_subject", subject_fin);
    //
    // create detail of (e-mail) alert
    let LogonTypeNumber = to_string($message.winlogbeat_event_data_LogonType);
    let LogonTypeResult = lookup_value("winLogonType",LogonTypeNumber, 0);
    let LogonTypeErr    = lookup_value("WinLogonErr" ,to_string($message.winlogbeat_event_data_SubStatus), 0);
    let build_mess_0    = concat("Failed Password Attempt - ",  to_string($message.winlogbeat_event_data_TargetUserName));
    let build_mess_1    = concat(build_mess_0, " attempting to log in to ");
    let build_mess_2    = concat(build_mess_1, to_string($message.winlogbeat_event_SubjectDomainName));
    let build_mess_3    = concat(build_mess_2, "-");
    let build_mess_4    = concat(build_mess_3, to_string($message.winlogbeat_host_name));
    let build_mess_5    = concat(build_mess_4, ". Logon Type: ");
    let build_mess_6    = concat(build_mess_5, to_string(LogonTypeResult));
    let build_mess_7    = concat(build_mess_6, ". Attempt came from: ");
    let build_mess_8    = concat(build_mess_7, to_string($message.winlogbeat_event_data_WorkstationName));
    let build_mess_9    = concat(build_mess_8, ".  ERROR: ");
    let build_mess_fin  = concat(build_mess_9, to_string(LogonTypeErr));
    set_field("e-mail_body", build_mess_fin);

    route_to_stream("password_events");
end
8 Likes

Hi there tmacgbay excelente post.
How do you call the tables in the pipeline code?

It’s in the pipeline code at the bottom of my previous post - here is the snippet:

let LogonTypeNumber = to_string($message.winlogbeat_event_data_LogonType);
let LogonTypeResult = lookup_value("winLogonType",LogonTypeNumber, 0);

let LogonERR        = to_string($message.winlogbeat_event_data_SubStatus);
let LogonTypeErr    = lookup_value("WinLogonErr" ,LogonERR , 0);

You can then use LogonTypeResult and LogonTypeErr when you are concatenating things to create a new explanation field like I did in the pipeline rule in the previous post. Then you can then use that field in an alert - I use use the e-mail_subject field I created in the code as the subject line in my alert e-mail.

1 Like

Ok, gonna try. Thank you … again :slight_smile:

This is a fantastic post! Great job @tmacgbay !

Nice post @tmacgbay it has given me some ideas, so thank you for sharing… couple questions.

It seems that the point of the pipeline is just to create the fields e-mail_subject and e-mail_body and route it to the “password_events” stream, so that you can generate an alert in the UI triggering off those fields if a certain criteria is met. Is that accurate?

Is there a reason you use the name of the stream over the uid, since the uid is the “preferred method”?

Sorry for the long delay!

  1. You are correct - the pipeline builds for e-mail reporting but I also use the generated fields when I am doin a general search - I changed the wording from what I have a but for e-mail_subject and e-mail_body actually have more generic names and are common among all (most…) of my reporting/change control so I can use it to get a clear picture of what is going on in found messages.

  2. Names over UID - When I first started I was using names and even after I heard that UID was preferred I stuck with names - it makes for clearer code for what is happening and it would be inconsistent to change how I do it mid…stream… heh. I am hoping a future revision of Graylog will allow me to search pipeline code completely so I could find/change things like stream names to UID’s. :slight_smile:

@tmacgbay
Nice post, Thank you.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.