#!/usr/bin/perl # # ModSecurity for Apache (http://www.modsecurity.org) # Copyright (c) 2002-2006 Thinking Stone (http://www.thinkingstone.com) # # $Id: modsec-auditlog-collector,v 1.1.2.1 2006/02/21 09:43:59 ivanr Exp $ # # This is a proof-of-concept script that listens to the # audit log in real time and submits the entries to # a remote HTTP server. This code is not suitable for # non-trivial production use since it can only submit # one audit log entry at a time, plus it does not handle # errors gracefully. # # Usage: # # 1) Enter the correct parameters $CONSOLE_* below # # 2) Configure ModSecurity to use this script for # concurrent audit logging index: # # SecAuditLog "|/path/to/modsec-auditlog-collector.pl \ # /path/to/auditlog/data/ \ # /path/to/auditlog/index" # # 3) Restart Apache. use MIME::Base64(); use IO::Socket::INET; my $CONSOLE_URI = "/rpc/auditLogReceiver"; my $CONSOLE_HOST = "192.168.2.11"; my $CONSOLE_PORT = "8886"; my $CONSOLE_USERNAME = "alpha"; my $CONSOLE_PASSWORD = "sensor"; # --------------------------------------------------- my $logline_regex = ""; # hostname $logline_regex .= "^(\\S+)"; # remote host, remote username, local username $logline_regex .= "\\ (\\S+)\\ (\\S+)\\ (\\S+)"; # date, time, and gmt offset $logline_regex .= "\\ \\[([^:]+):(\\d+:\\d+:\\d+)\\ ([^\\]]+)\\]"; # request method + request uri + protocol (as one field) $logline_regex .= "\\ \"(.*)\""; # status, bytes out $logline_regex .= "\\ (\\d+)\\ (\\S+)"; # referer, user_agent $logline_regex .= "\\ \"(.*)\"\\ \"(.*)\""; # uniqueid, sessionid $logline_regex .= "\\ (\\S+)\\ \"(.*)\""; # filename, offset, size $logline_regex .= "\\ (\\S+)\\ (\\d+)\\ (\\d+)"; # hash $logline_regex .= "\\ (\\S+)"; # the rest (always keep this part of the regex) $logline_regex .= "(.*)\$"; my $therequest_regex = "(\\S+)\\ (.*?)\\ (\\S+)"; sub send_entry { my ($file_name, $file_offset, $file_size, $hash, $summary) = @_; my $buffer; if (!open(F, $file_name)) { print LOG "> Could not open file $file_name.\n"; return; } binmode F; $socket = IO::Socket::INET->new(Proto => 'tcp', PeerAddr => $CONSOLE_HOST, PeerPort => $CONSOLE_PORT, Timeout => 10); binmode $socket; if (!$socket) { print LOG "> Failed to open socket.\n"; return; } $socket->autoflush(1); my $credentials = MIME::Base64::encode($CONSOLE_USERNAME . ":" . $CONSOLE_PASSWORD); chomp($credentials); print $socket "PUT $CONSOLE_URI HTTP/1.0\r\n"; print $socket "Content-Length: " . $file_size . "\r\n"; print $socket "Authorization: Basic " . $credentials . "\r\n"; print $socket "X-ForensicLog-Summary: " . $summary . "\r\n"; print $socket "X-Content-Hash: " . $hash . "\r\n"; print $socket "\r\n"; # send file contents while ( read(F, $buffer, 8192) and print $socket $buffer ) {}; close(F); my $status = 0; while(<$socket>) { # print "> $_"; if (($status == 0) && (/^HTTP\/[0-9]\.[0-9] ([0-9]+).+$/)) { $status = $1; } } print LOG "> Status: " . $status . "\n"; close($socket); } # -- Main -------------------------------------------------------------------- if (@ARGV != 2) { print "Usage: modsec-auditlog-collector auditlog-folder auditlog-index\n"; exit; } my($folder, $index) = @ARGV; open(LOG, ">>$index") || die("Failed to open: $index\n"); $| = 1, select $_ for select LOG; while() { # print LOG "Line: $_"; chomp(); my $summary = $_; next if (/^$/); my @parsed_logline = /$logline_regex/x; if (@parsed_logline == 0) { print LOG "> Failed to parse line: " . $_ . "\n"; } else { ( $request{"hostname"}, $request{"remote_ip"}, $request{"remote_username"}, $request{"username"}, $request{"date"}, $request{"time"}, $request{"gmt_offset"}, $request{"the_request"}, $request{"status"}, $request{"bytes_out"}, $request{"referer"}, $request{"user_agent"}, $request{"unique_id"}, $request{"session_id"}, $request{"filename"}, $request{"file_offset"}, $request{"file_size"}, $request{"hash"}, $request{"the_rest"} ) = @parsed_logline; $_ = $request{"the_request"}; my @parsed_therequest = /$therequest_regex/x; if (@parsed_therequest == 0) { $request{"invalid"} = "1"; $request{"request_method"} = ""; $request{"request_uri"} = ""; $request{"protocol"} = ""; } else { ( $request{"request_method"}, $request{"request_uri"}, $request{"protocol"} ) = @parsed_therequest; } print LOG ($summary . "\n"); send_entry($abs_file_name = $folder . "/" . $request{"filename"}, $request{"file_offset"}, $request{"file_size"}, $request{"hash"}, $summary); } } close(LOG);