# Process Windows security events logged to a server, using Snare Agent or # similar. ######################################################## ## Copyright (c) 2008-2014 Orion Poplawski ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; use URI::URL; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; my $Ignore_messages = $ENV{'ignore_messages'} || '^$'; my $SuccessAudits = 0; my %SuccessAuditUsers; my %FailureAudits; my %SuccessAudits; my %ClockSkew; my %Errors; my %Information; my %UnknownUser; my %UnknownClient; my %BadPasswords; my %TicketExpired; my %AccessDenied; my %AccountChanged; my %AccountCreated; my %AccountDeleted; my %AccountDisabled; my %AccountEnabled; my %AccountLocked; my %AuditPolicyChanged; my %ExpiredPassword; my %PasswordChanged; my %Logon; my %PrivilegedLogon; my %Logoff; my %WorkstationLocked; my %WorkstationUnlocked; my %OtherList; while (defined(my $ThisLine = )) { # User specified ignore messages, lower cased next if $ThisLine =~ /$Ignore_messages/i; my ($Hostname,$Criticality,$SourceName,$DateTime,$EventID,$SourceName2,$UserName,$SIDType,$EventLogType,$CategoryString,$DataString,$ExpandedString,$Extra); #Determine format if ($ThisLine =~ /MSWinEventLog\[/) { # Snare 4 #Parse ($Criticality,$SourceName,$DateTime,$EventID,$SourceName2,$UserName,$SIDType,$EventLogType,$Hostname,$CategoryString,$DataString,$ExpandedString,$Extra) = ($ThisLine =~ /MSWinEventLog\[(\d+)\]:(\w+)\t\d+\t([^\t]+)\t(\d+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t?([^\t]*)\t?([^\t]*)\t?([^\t]*)\t?([^\t]*)/); } elsif ($ThisLine =~ /MSWinEventLog\t/) { # Snare 3 #Parse ($Criticality,$SourceName,$DateTime,$EventID,$SourceName2,$UserName,$SIDType,$EventLogType,$Hostname,$CategoryString,$DataString,$ExpandedString,$Extra) = ($ThisLine =~ /MSWinEventLog\t(\d+)\t(\w+)\t\d+\t([^\t]+)\t(\d+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t?([^\t]*)\t?([^\t]*)\t?([^\t]*)\t?([^\t]*)/); } if (!defined($Hostname)) { print STDERR "Cannot parse $ThisLine"; next; } # Modify some items that prevent de-duplication if ($Detail < 10) { $ExpandedString =~ s/Filter Run-Time ID: \d+/Filter Run-Time ID: XXX/; $ExpandedString =~ s/(Key Name:)\s+\{[0-9A-F\-]+\}/$1 {XXX}/g; $ExpandedString =~ s/Logon ID:\s+0x[0-9A-F]+/Logon ID: 0xXXX/; } if ($Detail < 5) { # Reduce ephemeral ports $ExpandedString =~ s/(Client|Destination|Source) Port:(\s+)[3-6]\d{4}/$1 Port:${2}XXXXX/g; $ExpandedString =~ s/Relative Target Name: ([^\t]+)/Relative Target Name: XXX/; } my $url = URI::URL->new("https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventID=$EventID"); if ($EventID == 4673 or $EventID == 4674) { # An operation was attempted on a privileged object. # These are basically noise # https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventID=4673 # https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventID=4674 # Ignore } elsif ($EventLogType eq "Success Audit") { if ($EventID == 4608 # Windows is starting up. (startups logged by evtsystem) or $EventID == 4688 # A new process has been created. or $EventID == 4689 # A process has exited. ) { # Ignore } elsif ($EventID == 4624 or $EventID == 4648) { $Logon{"$Hostname $UserName"}++ if $Detail >= 5; } elsif ($EventID == 4634 or $EventID == 4647) { $Logoff{"$Hostname $UserName"}++ if $Detail >= 5; } elsif ($EventID == 4672) { $PrivilegedLogon{"$Hostname $UserName"}++ if $Detail > 0; } elsif ($EventID == 4719) { $AuditPolicyChanged{$Hostname}++; } elsif ($EventID == 4720) { $AccountCreated{$UserName}++; } elsif ($EventID == 4722) { $AccountEnabled{$UserName}++; } elsif ($EventID == 4723) { $PasswordChanged{$UserName}++; } elsif ($EventID == 4725) { $AccountDisabled{$UserName}++; } elsif ($EventID == 4726) { $AccountDeleted{$UserName}++; } elsif ($EventID == 4738 or $EventID == 4742) { $AccountChanged{$UserName}++; } elsif ($EventID == 4800) { $WorkstationLocked{"$Hostname $UserName"}++ if $Detail >= 10; } elsif ($EventID == 4801) { $WorkstationUnlocked{"$Hostname $UserName"}++ if $Detail >= 10; } else { $SuccessAudits++; $SuccessAuditUsers{$UserName}++; $SuccessAudits{"$Hostname $ExpandedString\n$url"}++ if $Detail >= 10; } } elsif ($EventLogType eq "Failure Audit") { if ($EventID == 4625) { # An account failed to log on if (my ($account,$domain,$reason) = ($ExpandedString =~ /Account For Which Logon Failed:.*Account Name:\s+(\S+)\s+Account Domain:\s+(\S+).*Failure Reason:\s+(.+)\s+Status:.*Sub Status:/)) { $FailureAudits{"$Hostname Log On Failure for $domain\\$account: $reason"}++; } elsif (my ($account,$domain,$reason,$process) = ($ExpandedString =~ /Account Name:\s+(\S+)\s+Account Domain:\s+(\S+).*Failure Reason:\s+(.+)\s+Status:.*Sub Status:.*Caller Process Name:\s+(.*)\s+Network Informaion:/)) { $FailureAudits{"$Hostname Log On Failure for $domain\\$account by $process: $reason"}++; } } elsif (my ($account,$domain,$process) = ($ExpandedString =~ /^A privileged service was called\..*Account Name:\s+(\S+)\s+Account Domain:\s+(\S+).*Process Name:\s+(.+)\sService/)) { $FailureAudits{"$Hostname Privileged service called for $domain\\$account: $process"}++ if $Detail; } elsif ($EventID == 4768) { # A Kerberos authentication ticket (TGT) was requested my ($Account,$Realm,$Client,$FailureCode) = $ExpandedString =~ /Account Name:\s+(\S*)\s.*Supplied Realm Name:\s+(\S*)\s.*Client Address:\s+(\S+)\s.*Result Code:\s+(\w+)/; if ($FailureCode eq "0x6") { # Client not found in Kerberos database $UnknownClient{"$Account\\$Realm $Client"}++; } elsif ($FailureCode eq "0x12") { $AccountDisabled{"$Account\@$Realm $Client"}++; } elsif ($FailureCode eq "0x17") { # Password has expired $ExpiredPassword{"$UserName"}++; } else { $FailureAudits{"$Hostname $ExpandedString\n$url"}++; } } elsif ($EventID == 4769) { # A Kerberos service ticket was requested my ($Client,$FailureCode) = $ExpandedString =~ /Client Address:\s+(\S+)\s.*Failure Code:\s+(\w+)/; #print STDER "EventID=$EventID Client=$Client FailureCode=$FailureCode ExpandedString=$ExpandedString\n"; if ($FailureCode eq "0x12") { $AccountDisabled{"$Client"}++; } elsif ($FailureCode eq "0x1B") { # KDC_ERR_MUST_USE_USER2USER Server principal valid for user-to-user only # This is an informational response and not an issue } elsif ($FailureCode eq "0x20") { # Ticket expired $TicketExpired{$Client}++; } elsif ($FailureCode eq "0x25") { # Clock skew too great $ClockSkew{$Client}++; } else { $FailureAudits{"$Hostname $ExpandedString\n$url"}++; } } elsif ($EventID == 4771) { # Kerberos pre-authentication failed my ($Account,$Client,$FailureCode) = $ExpandedString =~ /Account Name:\s+(\S+)\s.*Client Address:\s+(\S+)\s.*Failure Code:\s+(\w+)/; if ($FailureCode eq "0x12") { #Clients credentials have been revoked Account disabled, expired, locked out, logon hours. $AccountLocked{"$Account $Client"}++; } elsif ($FailureCode eq "0x18") { #Pre-authentication information was invalid - bad password $BadPasswords{"$Account $Client"}++; } elsif ($FailureCode eq "0x25") { # Clock skew too great $ClockSkew{$Client}++; } else { $FailureAudits{"$Hostname $ExpandedString\n$url"}++; } } elsif ($EventID == 4776) { # The domain controller attempted to validate the credentials for an account my ($Account,$Client,$FailureCode) = $ExpandedString =~ /Logon Account:\s+(\S+)\s+Source Workstation:\s+(\S*)\s.*Error Code:\s+(\w+)/; if (lc($FailureCode) eq "0xc0000064") { # user name does not exist $UnknownUser{"$Account $Client"}++; } elsif (lc($FailureCode) eq "0xc000006a") { # user name is correct but the password is wrong $BadPasswords{"$Account $Client"}++; } elsif (lc($FailureCode) eq "0xc0000071") { # expired password $ExpiredPassword{"$Account $Client"}++; } elsif (lc($FailureCode) eq "0xc0000234") { # account locked $AccountLocked{"$UserName $Client"}++; } else { $FailureAudits{"$Hostname $ExpandedString\n$url"}++; } } elsif ($EventID == 4957 and $ExpandedString =~ /resolved to an empty set/) { # Windows Firewall did not apply the following rule - because it was not applicable } elsif ($EventID == 6273) { my ($account,$domain,$client) = ($ExpandedString =~ /Account Name:\s+(\S+)\s+Account Domain:\s+(\S+).*Client Friendly Name:\s+(\S+)/); $AccessDenied{"$account\\$domain $client"}++; } else { $FailureAudits{"$Hostname $ExpandedString\n$url"}++; } } elsif ($EventLogType eq "Error") { $ExpandedString =~ s/\s+\d+\s+\d+//; $Errors{"$Hostname $ExpandedString\n$url"}++; } elsif ($EventLogType eq "Information") { next if $ExpandedString =~ /The event logging service has shut down/; next if $Detail < 5; $Information{"$Hostname $ExpandedString\n$url"}++; } else { # Report any unmatched entries... chomp($ThisLine); $OtherList{"Type=$EventLogType $ThisLine"}++; } } if (keys %Errors) { print "\nERRORS:\n"; foreach my $Error (sort keys %Errors) { print " $Error : $Errors{$Error} Times\n"; } } if (keys %ClockSkew) { print "\nClock skew too great\n"; foreach my $Client (sort keys %ClockSkew) { print " $Client : $ClockSkew{$Client} Times\n"; } } if (keys %AccountCreated) { print "\nAccount Created\n"; foreach my $Account (sort keys %AccountCreated) { print " $Account : $AccountCreated{$Account} Times\n"; } } if (keys %AccountDeleted) { print "\nAccount Deleted\n"; foreach my $Account (sort keys %AccountDeleted) { print " $Account : $AccountDeleted{$Account} Times\n"; } } if (keys %AccountDisabled) { print "\nAccount Disabled\n"; foreach my $Account (sort keys %AccountDisabled) { print " $Account : $AccountDisabled{$Account} Times\n"; } } if (keys %AccountEnabled) { print "\nAccount Enabled\n"; foreach my $Account (sort keys %AccountEnabled) { print " $Account : $AccountEnabled{$Account} Times\n"; } } if (keys %AccountChanged) { print "\nAccount Changed\n"; foreach my $Account (sort keys %AccountChanged) { print " $Account : $AccountChanged{$Account} Times\n"; } } if (keys %PasswordChanged) { print "\nPassword Changed\n"; foreach my $Account (sort keys %PasswordChanged) { print " $Account : $PasswordChanged{$Account} Times\n"; } } if (keys %AccountLocked) { print "\nAccount Locked\n"; foreach my $Account (sort keys %AccountLocked) { print " $Account : $AccountLocked{$Account} Times\n"; } } if (keys %ExpiredPassword) { print "\nPassword Expired\n"; foreach my $Account (sort keys %ExpiredPassword) { print " $Account : $ExpiredPassword{$Account} Times\n"; } } if (keys %AccessDenied) { print "\nAccess Denied\n"; foreach my $Item (sort keys %AccessDenied) { print " $Item : $AccessDenied{$Item} Times\n"; } } if (keys %UnknownUser) { print "\nUnknown Users\n"; foreach my $Account (sort keys %UnknownUser) { print " $Account : $UnknownUser{$Account} Times\n"; } } if (keys %UnknownClient) { print "\nUnknown Clients\n"; foreach my $Account (sort keys %UnknownClient) { print " $Account : $UnknownClient{$Account} Times\n"; } } if (keys %BadPasswords) { print "\nBad Passwords\n"; foreach my $Account (sort keys %BadPasswords) { print " $Account : $BadPasswords{$Account} Times\n"; } } if (keys %TicketExpired) { print "\nTicket Expired\n"; foreach my $Client (sort keys %TicketExpired) { print " $Client : $TicketExpired{$Client} Times\n"; } } if (keys %FailureAudits) { print "\nFailure Audits\n"; foreach my $Error (sort keys %FailureAudits) { print " $Error : $FailureAudits{$Error} Times\n"; } } if (keys %AuditPolicyChanged) { print "\nAudit Policy Changed\n"; foreach my $Hostname (sort keys %AuditPolicyChanged) { print " $Hostname : $AuditPolicyChanged{$Hostname} Times\n"; } } # Detail > 0 if (keys %PrivilegedLogon) { print "\nPrivileged Logons\n"; foreach my $User (sort keys %PrivilegedLogon) { print " $User : $PrivilegedLogon{$User} Times\n"; } } # Detail >= 5 if (keys %Logon) { print "\nLogons\n"; foreach my $User (sort keys %Logon) { print " $User : $Logon{$User} Times\n"; } } # Detail >= 5 if (keys %Logoff) { print "\nLogoffs\n"; foreach my $User (sort keys %Logoff) { print " $User : $Logoff{$User} Times\n"; } } # Detail >= 10 if (keys %WorkstationLocked) { print "\nWorkstation Locked\n"; foreach my $User (sort keys %WorkstationLocked) { print " $User : $WorkstationLocked{$User} Times\n"; } } # Detail >= 10 if (keys %WorkstationUnlocked) { print "\nWorkstation Unlocked\n"; foreach my $User (sort keys %WorkstationUnlocked) { print " $User : $WorkstationUnlocked{$User} Times\n"; } } # Detail >= 5 if ($SuccessAudits and ($Detail >= 5) ) { print "\nSuccess Audits " . $SuccessAudits . " Time(s)\n"; foreach my $User (keys %SuccessAuditUsers) { print " $User : $SuccessAuditUsers{$User} Times\n"; } if ($Detail >= 10) { print "\nSuccess Audits\n"; foreach my $Error (sort keys %SuccessAudits) { print " $Error : $SuccessAudits{$Error} Times\n"; } } } if (keys %Information) { print "\nInformational Messages:\n"; foreach my $Item (sort keys %Information) { print " $Item : $Information{$Item} Times\n"; } } if (keys %OtherList) { print "\n**** Unmatched entries ****\n"; foreach my $Error (keys %OtherList) { print " $Error : $OtherList{$Error} Times\n"; } } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: