Blob Blame History Raw
##########################################################################
# $Id$
##########################################################################
# $Log: windows,v $
# Revision 1.4  2008/06/30 23:07:51  kirk
# fixed copyright holders for files where I know who they should be
#
# Revision 1.3  2008/03/24 23:31:27  kirk
# added copyright/license notice to each script
#
# Revision 1.2  2007/09/29 20:48:19  bjorn
# Rewrite and enhancement by Brian Kroth.
#
# Revision 1.1  2006/03/22 17:46:22  bjorn
# Initial commit.  Files submitted by William Roumier.
#
##########################################################################
# This is a logwatch script that looks at a log file composed of windows auth
# security logs counts the number of times a user failed to login and
# optionally the number times they succesfully logged in and some other account
# creation/modification audits.
#
# See the following sites for event id documentation:
# http://www.microsoft.com/technet/prodtechnol/windowsserver2003/technologies/security/bpactlck.mspx
# http://support.microsoft.com/?id=299475
# http://support.microsoft.com/?id=301677

#######################################################
## Copyright (c) 2008 William Roumier
## 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 lib "/usr/share/logwatch/lib";
use Logwatch ':all';

my $detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0;

my ($month, $day, $time, $host, $process, $eventid, $msg);

# Loop through the given input and parse it first to make sure we need to,
# then to sort it into various categories.
while (defined($line = <STDIN>)) {

	($month, $day, $time, $host, $process, $eventid, $msg) = split(/\s+/, $line, 7);

	chomp($host);
	chomp($process);
	chomp($eventid);
	chomp($msg);

	if ($process =~ /security\[failure\]/) { # failure events

		# First count the number of failed logins - we always want that.
		# Events 529 - 537, 539
		if (($eventid >= 529 && $eventid <= 537) || $eventid == 539 || $eventid == 680) {
			$msg =~ /(User Name|Logon account):\s*(\S+)\s+.+(Address|Workstation( Name)?):\s*\\{0,2}(\S+)/;
			# print "DEBUG Logon Failure: name:$2 source:$4\n";
			$loginFailByAccount{$2}{$5}++;
			$loginFailByHost{$5}{$2}++;
		}

		# IKE Failures: Events 544 - 547
		#elsif ($eventid >= 544 && $eventid <= 547 && $line =~ /some_meaningful_pattern/) {
		#}

		elsif ($eventid == 547) { # IKE security association negotiation failed
			$msg =~ /IKE Peer Addr\s+(\S+)\s+.+Failure Reason:\s*(.+)\s+Extra Status:/;
			#print "DEBUG IKE security association negotiation failed: host:$host peer:$1 reason:$2\n";
			$ikeSecNegFail{$host}{$1}{$2}++;
		}

		elsif ($eventid == 644) { # User Account Locked Out
			$msg =~ /Target Account Name:\s*(\S+)\s+.+Caller User Name:\s*(\S+)/;
			#print "DEBUG User Account Locked Out: $1 on $2 by $3\n";
			$lockedOut{$host}{$1}{$2}++;
		}

		elsif ($eventid == 675 || $eventid == 676 || $eventid == 677) { # Kerberos authentication failed
			$msg =~ /User Name:\s*(\S+)\s+.+Client Address:\s*(\S+)/;
			#print "DEBUG Pre-Authentication failed: $1 on $2\n";
			#$krbAuthFail{$host}{$1}{$2}++;
			$krbAuthFailByAccount{$1}{$2}++;
			$krbAuthFailByHost{$2}{$1}++;
		}

		#elsif ($eventid == 681) { # Logon Failure - not used in Windows 2003/XP
		#	$msg =~ /The logon to account:\s*(\S+)\s+by:\s*(\S+)\s+from workstation:\s*(\S+)\s+failed/;
		#	print "DEBUG Logon Failure to $1 by $2 from $3\n";
		#	$logonFailure{$1}{$2}{$3}++;
		#}

		elsif ($eventid == 861) { # The Windows Firewall has detected an application listenin for incomming traffic.
			$msg =~ /Path:\s*(\S+)\s+.+User account:\s*(\S+)\s+.+IP protocol:(\S+)\s+Port number:\s*(\S+)\s+Allowed:\s*(\S+)/;
			#print "DEBUG Server Application Firewalled: path:$1 account:$2 port:$3 $4 allowed:$5\n";
			$firewall{$host}{$2}{$1}{$3}{$4}{$5}++;
		}

		else { # unmatched catch all
			chomp($msg);
			$unmatchedFail{$eventid . " " . $msg}++;
		}
	}

	elsif ($detail > 3 && $process =~ /security\[success\]/) { # success events

		if ($eventid == 528 || $eventid == 540 || $eventid == 680) { # Successful Logon
		 	$msg =~ /(User Name|Logon account|Account Name):\s*(\S+)\s+.+(Address|Workstation( Name)?):\s*\\{0,2}(\S+)/;
			if ($2 !~ /^-|\S+\$$/) { # ignore machines and anonymous
				#print "DEBUG Logon Success name:$2 source:$5\n";
				$loginSuccess{$2}{$5}++;
			}
		}

		elsif ($eventid == 517) { # Audit log was cleared.
			$msg =~ /Primary User Name:\s*(\S+)\s+.*Client User Name:\s*(\S+)/;
			# print "DEBUG Audit log cleared: host:$host primary:$1 client:$2\n";
			$auditLogCleared{$host}{$1}{$2}++;
		}

		elsif ($eventid == 608) { # User Right Assigned
			$msg =~ /User Right:\s*(\S+)\s+Assigned To:\s*(\S+)\s+Assigned By:\s+User Name:\s*(\S+)/;
			# print "DEBUG Rights Added: right:$1 to:$2 by:$3\n";
			$rightsAdded{$host}{$3}{$2}{$1}++;
		}
		elsif ($eventid == 609) { # User Right Removed
			$msg =~ /User Right:\s*(\S+)\s+Removed From:\s*(\S+)\s+Removed By:\s+User Name:\s*(\S+)/;
			# print "DEBUG Rights Removed: right:$1 from:$2 by:$3\n";
			$rightsRemoved{$host}{$3}{$2}{$1}++;
		}

		elsif ($eventid == 610) { # New Trusted Domain
			$msg =~ /New Trusted Domain\s+Domain( Name)?:\s*(\S+)\s+.*Established By:\s+User Name:\s*(\S+)/;
			# print "DEBUG New Trusted Domain: domain:$2 user:$3 host:$host\n";
			$newDomainTrust{$host}{$2}{$3}++;
		}
		elsif ($eventid == 611) { # Removing Trusted Domain
			$msg =~ /Removing Trusted Domain\s+Domain( Name)?:\s*(\S+)\s+.*Established By:\s+User Name:\s*(\S+)/;
			# print "DEBUG New Trusted Domain: domain:$2 user:$3 host:$host\n";
			$rmDomainTrust{$host}{$2}{$3}++;
		}

		elsif ($eventid == 612) { # Audit Policy Changed
			$msg =~ /Changed By:\s+User Name:\s*(\S+)/;
			# print "DEBUG Audit Policy Changed by $1 on $host\n";
			$auditPolChange{$host}{$1}++;
		}

		# Group all account types together - should be clear what's what.
		elsif ($eventid == 624 || $eventid == 631 || $eventid == 635 || $eventid == 645
			|| $eventid == 653 || $eventid == 658 || $eventid == 663) { # Account Created

			$msg =~ /New Account Name:\s*(\S+)\s+.*Caller User Name:\s*(\S+)/;
			#print "DEBUG Account Created: $1 by $2 on $host\n";
			$newAccount{$host}{$2}{$1}++;
		}
		elsif ($eventid == 625) { # User Account Type Change
			$msg =~ /Target Account Name:\s*(\S+)\s+.+New Type:\s*(\S+)\s*Caller User Name:\s*(\S+)/;
			# print "DEBUG User Account Type Change: $1 to $2 by $3 on $host\n";
			$accountTypeChange{$host}{$3}{$1}{$2}++;
		}
		elsif ($eventid == 626) { # User Account Enabled
			$msg =~ /Target Account Name:\s*(\S+)\s+.+Caller User Name:\s*(\S+)/;
			# print "DEBUG User Account Enabled: $1 by $2 on $host\n";
			$accountEnabled{$host}{$2}{$1}++;
		}
		elsif ($eventid == 627) { # Change Password Attempt
			$msg =~ /Target Account Name:\s*(\S+)\s+.+Caller User Name:\s*(\S+)/;
			# print "DEBUG Change Password Attempt: $1 by $2 on $host\n";
			$changePasswordAttempt{$host}{$2}{$1}++;
		}
		elsif ($eventid == 628) { # User Account password set
			$msg =~ /Target Account Name:\s*(\S+)\s+.+Caller User Name:\s*(\S+)/;
			# print "DEBUG User Account password set: $1 by $2 on $host\n";
			$passwordSet{$host}{$2}{$1}++;
		}
		elsif ($eventid == 630 || $eventid == 634 || $eventid == 638 || $eventid == 647
			|| $eventid == 652 || $eventid == 657 || $eventid == 662 || $eventid == 667) { # User Account Deleted

			$msg =~ /Target Account Name:\s*(\S+)\s+.+Caller User Name:\s*(\S+)/;
			# print "DEBUG Account Deleted: $1 by $2 on $host\n";
			$accountDeleted{$host}{$2}{$1}++;
		}

		# Note: This doesn't distinguish between Global and Local Groups
		elsif ($eventid == 632 || $eventid == 636 || $eventid == 650 || $eventid == 655
			|| $eventid == 660 || $eventid == 665) { # Group Member Added

			$msg =~ /Member Name:\s*(\S+)\s+.+Target Account Name:\s*(\S+)\s+.+Caller User Name:\s*(\S+)/;
			# print "DEBUG Group Member Added: $1 to $2 by $3 on $host\n";
			$groupMemberAdded{$host}{$2}{$3}{$1}++;
		}
		elsif ($eventid == 633 || $eventid == 637 || $eventid == 651 || $eventid == 656
			|| $eventid == 661 || $eventid == 666) { # Group Member Removed

			$msg =~ /Member Name:\s*(\S+)\s+.+Target Account Name:\s*(\S+)\s+.+Caller User Name:\s*(\S+)/;
			#print "DEBUG Group Member Removed: $1 to $2 by $3 on $host\n";
			$groupMemberRemoved{$host}{$2}{$3}{$1}++;
		}
		elsif ($eventid == 639 || $eventid == 641 || $eventid == 642 || $eventid == 646 || $eventid == 649
			|| $eventid == 654 || $eventid == 659 || $eventid == 664 || $eventid == 668) { # Account Changed

			$msg =~ /Target Account Name:\s*(\S+)\s+.+Caller User Name:\s*(\S+)/;
			#print "DEBUG Account Changed: $1 by $2 on $host\n";
			$accountChanged{$host}{$2}{$1}++;
		}

		elsif ($eventid == 643) { # Domain Policy Changed
			$msg =~ /Domain Policy Changed:\s*(.+) modified.+Caller User Name:\s*(\S+)/;
			#print "DEBUG Domain Policy Changed: $2 on $host to $1\n";
			$domainPolicyChanged{$host}{$2}{$1}++;
		}

		elsif ($detail > 5 && $eventid == 672) { # Authentication Ticket Granted
			$msg =~ /User Name:\s*(\S+)\s+.+Service Name:\s*(\S+)\s+.+Client Address:\s*(\S+)/;
			#print "DEBUG TGT Granted to $1 for $2 from $3\n";
			$tgtGranted{$host}{$1}{$2}{$3}++;
		}
		elsif ($detail > 5 && $eventid == 673) { # Service Ticket Granted
			$msg =~ /User Name:\s*(\S+)\s+.+Service Name:\s*(\S+)\s+.+Client Address:\s*(\S+)/;
			#print "DEBUG SGT Granted to $1 for $2 from $3\n";
			$sgtGranted{$host}{$1}{$2}{$3}++;
		}
		elsif ($detail > 5 && $eventid == 674) { # Ticket Granted Renewed
			$msg =~ /User Name:\s*(\S+)\s+.+Service Name:\s*(\S+)\s+.+Client Address:\s*(\S+)/;
			#print "DEBUG Ticket Renewal granted to $1 for $2 from $3\n";
			$renewed{$host}{$1}{$2}{$3}++;
		}
	}

}


# Always print login failures grouped by name and host in that order.
if (keys %loginFailByAccount) {
	printLevel2("Windows Failed Logins by Account, Host", \%loginFailByAccount);
}
if (keys %loginFailByHost) {
	printLevel2("Windwos Failed Logins by Host, Account", \%loginFailByHost);
}

if (keys %krbAuthFailByAccount) {
	printLevel2("Kerberos Authentication Failures by Account, Host", \%krbAuthFailByAccount);
}

if (keys %krbAuthFailByHost) {
	printLevel2("Kerberos Authentication Failures by Host, Account", \%krbAuthFailByHost);
}

if (keys %lockedOut) {
	printLevel3("Account Locked Out by Host, Target, Caller", \%lockedOut);
}

if (keys %ikeSecNegFail) {
	printLevel3("IKE Security Association Negotiation Failed by Host, Peer, Reason", \%ikeSecNegFail);
}

if (keys %unmatchedFail) {
	print "\t---- Unmatched Failure Audits ----\n\n";
	foreach $msg (keys %unmatchedFail) {
		print "\t" . $unmatchedFail{$msg} . " Time(s):  $msg\n";
	}
}

# Start printing some other optional data like login successes, account creation/modification audits, etc.
if ($detail > 3) {
	if (keys %loginSuccess) {
		printLevel2("Windows Successful Logins by Account, Host", \%loginSuccess);
	}

	if ($detail > 5) {
		if (keys %tgtGranted) {
			printLevel4("TGT Granted by Host, Account, Service, Client Addr", \%tgtGranted);
		}
		if (keys %sgtGranted) {
			printLevel4("SGT Granted by Host, Account, Service, Client Addr", \%sgtGranted);
		}
		if (keys %renewed) {
			printLevel4("Ticket Renewed by Host, Account, Service, Client Addr", \%renewed);
		}
	}

	if (keys %auditLogCleared) {
		printLevel3("Audit Log Cleared by Host, Primary Account, Client Account", \%auditLogCleared);
	}

	if (keys %rightsAdded) {
		printLevel4("User Rights Added by Host, Modifier, Account, Right", \%rightsAdded);
	}
	if (keys %rightsRemoved) {
		printLevel4("User Rights Removed by Host, Modifier, Account, Right", \%rightsRemoved);
	}

	if (keys %newDomainTrust) {
		printLevel3("New Domain Trust by Host, Domain, Modifier", \%newDomainTrust);
	}
	if (keys %rmDomainTrust) {
		printLevel3("Domain Trust Removed by Host, Domain, Modifier", \%rmDomainTrust);
	}

	if (keys %auditPolChange) {
		printLevel2("Audit Policy Changed by Host, Modifier", \%auditPolChange);
	}

	if (keys %newAccount) {
		printLevel3("New Accounts by Host, Modifier, Account", \%newAccount);
	}
	if (keys %accountTypeChange) {
		printLevel4("Account Type Changed by Host, Modifier, Account, Type", \%accountTypeChange);
	}
	if (keys %accountEnabled) {
		printLevel3("Account Enabled by Host, Modifier, Account", \%accountEnabled);
	}
	if (keys %changePasswordAttempt) {
		printLevel3("Change Password Attempt by Host, Modifier, Account", \%changePasswordAttempt);
	}
	if (keys %passwordSet) {
		printLevel3("Password Set by Host, Modifier, Account", \%passwordSet);
	}
	if (keys %accountDeleted) {
		printLevel3("Account Deleted by Host, Modifier, Account", \%accountDeleted);
	}

	if (keys %groupMemberAdded) {
		printLevel4("Group Member Added by Host, Group, Modifier, Account", \%groupMemberAdded);
	}
	if (keys %groupMemberRemoved) {
		printLevel4("Group Member Removed by Host, Group, Modifier, Account", \%groupMemberRemoved);
	}
	if (keys %accountChanged) {
		printLevel3("Account Changed by Host, Modifier, Account", \%accountChanged);
	}

	if (keys %domainPolicyChanged) {
		printLevel3("Domain Policy Changed by Host, Modifier, Change", \%domainPolicyChanged);
	}
}



# Prints a hash that's two levels deep in a generic hierarchical manor
sub printLevel2 {
	my $msg = $_[0];
	my %data = %{$_[1]};

	print "\n\t---- $msg ----\n\n";
	foreach $first (sort(keys %data)) {
		$total = 0;
		foreach $second (keys %{$data{$first}}) {
			$total += $data{$first}{$second};
		}

		print "\t" . LookupIP($first) . "  $total Time(s)\n";
		foreach $second (sort(keys %{$data{$first}})) {
			print  "\t\t" . LookupIP($second) . "  " . $data{$first}{$second} . " Time(s)\n";
		}
		print "\n";
	}
}

# Prints a hash that's three levels deep in a generic hierarchical manor
sub printLevel3 {
	my $msg = $_[0];
	my %data = %{$_[1]};

	print "\n\t---- $msg ----\n\n";
	foreach $first (sort(keys %data)) {
		$first_total = 0;
		foreach $second (keys %{$data{$first}}) {
			$second_total{$second} = 0;
			foreach $third (keys %{$data{$first}{$second}}) {
				$second_total{$second} += $data{$first}{$second}{$third};
			}
			$first_total += $second_total{$second};
		}

		print "\t" . LookupIP($first) . ":  $first_total Time(s)\n";
		foreach $second (sort(keys %{$data{$first}})) {
			print "\t\t" . LookupIP($second) . ":  $second_total{$second} Time(s)\n";
			foreach $third (sort(keys %{$data{$first}{$second}})) {
				print "\t\t\t" . LookupIP($third) . "  $data{$first}{$second}{$third} Time(s)\n";
			}
		}
		print "\n";
	}
}

# Prints a hash that's four levels deep in a generic hierarchical manor
sub printLevel4 {
	my $msg = $_[0];
	my %data = %{$_[1]};

	print "\n\t---- $msg ----\n\n";
	foreach $first (sort(keys %data)) {
		$first_total = 0;
		foreach $second (keys %{$data{$first}}) {
			$second_total{$second} = 0;
			foreach $third (keys %{$data{$first}{$second}}) {
				$third_total{$second}{$third} = 0;
				foreach $fourth (keys %{$data{$first}{$second}{$third}}) {
					$third_total{$second}{$third} += $data{$first}{$second}{$third}{$fourth};
				}
				$second_total{$second} += $third_total{$second}{$third};
			}
			$first_total += $second_total{$second};
		}

		print "\t" . LookupIP($first) . ":  $first_total Time(s)\n";
		foreach $second (sort(keys %{$data{$first}})) {
			print "\t\t" . LookupIP($second) . ":  $second_total{$second} Time(s)\n";
			foreach $third (sort(keys %{$data{$first}{$second}})) {
				print "\t\t\t" . LookupIP($third) . ":  $third_total{$second}{$third} Time(s)\n";
				foreach $fourth (sort(keys %{$data{$first}{$second}{$third}})) {
					print "\t\t\t\t" . LookupIP($fourth) . "  $data{$first}{$second}{$third}{$fourth} Time(s)\n";
				}
			}
		}
		print "\n";
	}
}

exit(0);

# vi: shiftwidth=3 tabstop=3 syntax=perl et
# Local Variables:
# mode: perl
# perl-indent-level: 3
# indent-tabs-mode: nil
# End: