Imported Upstream version 2.5.1
[ossec-hids.git] / contrib / ossec2mysqld.pl
1 #!/usr/bin/perl -w
2 use strict;
3 use Socket;
4 use POSIX 'setsid';
5 # ---------------------------------------------------------------------------
6 # Author: Meir Michanie (meirm@riunx.com)
7 # Co-Author: J.A.Senger (jorge@br10.com.br)
8 # $Id$
9 # $Revision$
10 ## ---------------------------------------------------------------------------
11 # http://www.riunx.com/
12 # ---------------------------------------------------------------------------
13 #
14 # ---------------------------------------------------------------------------
15 # About this script
16 # ---------------------------------------------------------------------------
17 #
18 # "Ossec to Mysql" records the OSSEC HIDS alert logs in MySQL database.
19 # It can run as a daemon (ossec2mysqld.pl), recording in real-time the logs in database or
20 # as a simple script (ossec2mysql.pl).
21 #
22 # ---------------------------------------------------------------------------
23 # Prerequisites
24 # ---------------------------------------------------------------------------
25 #
26 # MySQL Server
27 # Perl DBD::mysql module
28 # Perl DBI module
29 #
30 # ---------------------------------------------------------------------------
31 # Installation steps
32 # ---------------------------------------------------------------------------
33
34 # 1) Create new database
35 # 2a) Run ossec2mysql.sql to create MySQL tables in your database
36 # 2b) Create BASE tables with snort tables extention
37 # 3) Create a user to access the database;
38 # 4) Copy ossec2mysql.conf to /etc/ossec2mysql.conf with 0600 permissions
39 # 3) Edit /etc/ossec2mysql.conf according to your configuration:
40 #       dbhost=localhost
41 #       database=ossecbase
42 #       debug=5
43 #       dbport=3306
44 #       dbpasswd=mypassword
45 #       dbuser=ossecuser
46 #       daemonize=0
47 #       resolve=1
48 #       
49 #
50 # ---------------------------------------------------------------------------
51 # License
52 # ---------------------------------------------------------------------------
53 #
54 # This program is free software; you can redistribute it and/or
55 # modify it under the terms of the GNU General Public License
56 # as published by the Free Software Foundation; either version 2
57 # of the License, or (at your option) any later version.
58 #
59 # This program is distributed in the hope that it will be useful,
60 # but WITHOUT ANY WARRANTY; without even the implied warranty of
61 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
62 # GNU General Public License for more details.
63 #
64 # You should have received a copy of the GNU General Public License
65 # along with this program; if not, write to the Free Software
66 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
67 #
68 # ---------------------------------------------------------------------------
69 # About OSSEC HIDS
70 # ---------------------------------------------------------------------------
71 #
72 # OSSEC HIDS is an Open Source Host-based Intrusion Detection System.
73 # It performs log analysis and correlation, integrity checking,
74 # rootkit detection, time-based alerting and active response.
75 # http://www.ossec.net
76 #
77 # ---------------------------------------------------------------------------
78
79 # ---------------------------------------------------------------------------
80 # Parameters
81 # ---------------------------------------------------------------------------
82 $SIG{TERM} = sub { &gracefulend('TERM')};
83 $SIG{INT} = sub { &gracefulend('INT')};
84 my ($RUNASDAEMON)=0;
85 my ($DAEMONLOGFILE)='/var/log/ossec2mysql.log';
86 my ($DAEMONLOGERRORFILE) = '/var/log/ossec2mysql.err';
87 my ($LOGGER)='ossec2mysql';
88 use ossecmysql;
89
90 my %conf;
91 $conf{dbhost}='localhost';
92 $conf{database}='snort';
93 $conf{debug}=5;
94 $conf{dbport}='3306';
95 $conf{dbpasswd}='password';
96 $conf{dbuser}='user';
97 $conf{daemonize}=0;
98 $conf{sensor}='sensor';
99 $conf{hids_interface}='ossec';
100 $conf{resolve}=1;
101
102
103 my($OCT) = '(?:25[012345]|2[0-4]\d|1?\d\d?)';
104
105 my($IP) = $OCT . '\.' . $OCT . '\.' . $OCT . '\.' . $OCT;
106
107 my $VERSION="0.4";
108 my $sig_class_id=1;
109 &help() unless @ARGV;
110 my $dump=0;
111 my ($hids_id,$hids,$hids_interface,$last_cid)=(undef, 'localhost', 'ossec',0);
112 my ($tempvar,$VERBOSE)=(0,0);
113 # ---------------------------------------------------------------------------
114 #  Arguments parsing
115 # ---------------------------------------------------------------------------
116  
117 while (@ARGV){
118         $_= shift @ARGV;
119         if (m/^-d$|^--daemon$/){
120                 $conf{daemonize}=1;
121         }elsif ( m/^-h$|^--help$/){
122                 &help();
123         }elsif ( m/^-n$|^--noname$/){
124                 $conf{'resolve'}=0;
125         }elsif ( m/^-v$|^--verbose$/){
126                 $VERBOSE=1;
127         }elsif ( m/^--interface$/){
128                 $conf{hids_interface}= shift @ARGV if @ARGV; # ossec-rt/ossec-feed
129         }elsif ( m/^--sensor$/){
130                 $conf{sensor}= shift @ARGV if @ARGV; # monitor
131         }elsif ( m/^--conf$/){
132                 $conf{conf}= shift @ARGV if @ARGV; # localhost
133                 &loadconf(\%conf);
134         }elsif ( m/^--dbhost$/){
135                 $conf{dbhost}= shift @ARGV if @ARGV; # localhost
136         }elsif ( m/^--dbport$/){
137                 $conf{dbport}= shift @ARGV if @ARGV; # localhost
138         }elsif ( m/^--dbname$/){
139                 $conf{database}= shift @ARGV if @ARGV; # snort
140         }elsif ( m/^--dbuser$/){
141                 $conf{dbuser}= shift @ARGV if @ARGV; # root
142         }elsif ( m/^--dbpass$/){
143                 $conf{dbpasswd}= shift @ARGV if @ARGV; # monitor
144         }
145
146 }
147 if ($conf{dbpasswd}=~ m/^--stdin$/){
148         print "dbpassword:";
149         $conf{dbpasswd}=<>;
150         chomp $conf{dbpasswd};
151 }
152 $hids=$conf{sensor} if exists($conf{sensor});
153 $hids_interface=$conf{hids_interface} if exists($conf{hids_interface});
154
155 &daemonize() if $conf{daemonize};
156 my $dbi= ossecmysql->new(%conf) || die ("Could not connect to $conf{dbhost}:$conf{dbport}:$conf{database} as $conf{dbpasswd}\n");
157 ####
158 # SQL vars;
159 my ($query,$numrows,$row_ref);
160 ####
161 #get sensor id
162 $query= 'select sid,last_cid from sensor where hostname=? and interface=?';
163 $numrows= $dbi->execute($query,$hids,$hids_interface);
164 if (1==$numrows){
165         $row_ref=$dbi->{sth}->fetchrow_hashref;
166         $hids_id=$row_ref->{sid};
167         $last_cid=$row_ref->{last_cid};
168 }else{
169         $query="INSERT INTO sensor ( sid , hostname , interface , filter , detail , encoding , last_cid )
170 VALUES (
171 NULL , ?, ? , NULL , ? , ?, ?
172 )";
173         $numrows= $dbi->execute($query,$hids,$hids_interface,1,2,0);
174         $hids_id=$dbi->lastid();
175 }
176 $dbi->{sth}->finish;
177 &forceprintlog ("SENSOR:$hids; feed:$hids_interface; id:$hids_id; last cid:$last_cid");
178 #exit ;
179
180 my $newrecord=0;
181 my %stats;
182 my %resolv;
183 my ($timestamp,$sec,$mail,$date,$alerthost,$alerthostip,$datasource,$rule,$level,$description,
184         $srcip,$dstip,$user,$text)=();
185 my $lasttimestamp=0;
186 my $delta=0;
187 ########################################################
188 my $datepath=`date "+%Y/%b/ossec-alerts-%d.log"`;
189 my $LOG='/var/ossec/logs/alerts/'. $datepath;
190 chomp $LOG;
191 &taillog($last_cid,$LOG);
192 ###############################################################
193 sub forceprintlog(){
194         $tempvar=$VERBOSE;
195         $VERBOSE=1;
196         &printlog (@_);
197         $VERBOSE=$tempvar;
198 }
199 sub taillog {
200    my ($last_cid,$LOG)=@_;
201    my($offset, $line, $stall) = '';
202
203    $offset = (-s $LOG); # Don't start at beginning, go to end
204
205    while (1==1) {
206        sleep(1);
207         %resolv=();
208        $| = 1;
209        $stall += 1;
210         $datepath=`date "+%Y/%b/ossec-alerts-%d.log"`;
211         $LOG='/var/ossec/logs/alerts/'. $datepath;
212         chomp $LOG;
213         unless ( -f $LOG){&forceprintlog ("Error -f $LOG"); next; }
214        if ((-s $LOG) < $offset) {
215            &forceprintlog ("Log shrunk, resetting..");
216            $offset = 0;
217        }
218
219         unless (open(TAIL, $LOG)){ &forceprintlog ("Error opening $LOG: $!\n");next ;}
220
221         if (seek(TAIL, $offset, 0)) {
222            # found offset, log not rotated
223        } else {
224            # log reset, follow
225            $offset=0;
226            seek(TAIL, $offset, 0);
227        }
228        while (<TAIL>) {
229         if (m/^$/){
230                 $newrecord=1;
231                 next unless $timestamp;
232                 $alerthostip=$alerthost if $alerthost=~ m/^$IP$/;
233                 if ($alerthostip){
234                         $dstip=$alerthostip;
235                         $resolv{$alerthost}=$dstip;
236                 }else{
237                         if (exists $resolv{$alerthost}){
238                                 $dstip=$resolv{$alerthost};
239                         }else{
240                                 if ($conf{'resolve'}){
241                                         $dstip=`host $alerthost 2>/dev/null | grep 'has address' `;
242                                         if ($dstip =~m/(\d+\.\d+\.\d+\.\d+)/ ){
243                                                 $dstip=$1;
244                                         }else{
245                                                 $dstip=$srcip;
246                                         }
247                                 }else{
248                                         $dstip=$alerthost;
249                                 }
250                                 $resolv{$alerthost}=$dstip;
251                                 
252                         }
253                 }
254                 $last_cid= &prepair2basedata(
255                         $hids_id,
256                         $last_cid,
257                         $timestamp,
258                         $sec,
259                         $mail,
260                         $date,
261                         $alerthost,
262                         $datasource,
263                         $rule,
264                         $level,
265                         $description,
266                         $srcip,
267                         $dstip,
268                         $user,
269                         $text
270                 );
271                 ($timestamp,$sec,$mail,$date,$alerthost,$alerthostip,$datasource,$rule,$level,$description,
272                 $srcip,$dstip,$user,$text)=();
273                 next ;
274         }
275         if (m/^\*\* Alert ([0-9]+).([0-9]+):(.*)$/){
276                 $timestamp=$1;
277                 if ( $timestamp == $lasttimestamp){
278                         $delta++;
279                 }else{
280                         $delta=0;
281                         $lasttimestamp=$timestamp;
282                 }
283                 $sec=$2;
284                 $mail=$3;
285                 $mail=$mail ? $mail : 'nomail';
286 #2006 Aug 29 17:19:52 firewall -> /var/log/messages
287 #2006 Aug 30 11:52:14 192.168.0.45->/var/log/secure
288 #2006 Sep 12 11:12:16 92382-Snort1 -> 172.16.176.132
289 #
290         }elsif ( m/^([0-9]+\s\w+\s[0-9]+\s[0-9]+:[0-9]+:[0-9]+)\s+(\S+)\s*->(.*)$/){
291                 $date=$1;
292                 $alerthost=$2;
293                 $datasource=$3;
294                 if ($datasource=~ m/(\d+\.\d+\.\d+\.\d+)/){
295                         $alerthost=$1;
296                         $datasource="remoted";
297                 }
298
299 #2006 Aug 29 17:33:31 (recepcao) 10.0.3.154 -> syscheck
300         }elsif ( m/^([0-9]+\s\w+\s[0-9]+\s[0-9]+:[0-9]+:[0-9]+)\s+\((.*?)\)\s+(\S+)\s*->(.*)$/){
301                 $date=$1;
302                 $alerthost=$2;
303                 $alerthostip=$3;
304                 $datasource=$4;
305         }elsif ( m/^([0-9]+\s\w+\s[0-9]+\s[0-9]+:[0-9]+:[0-9]+)\s(.*?)$/){
306                 $date=$1;
307                 $alerthost='localhost';
308                 $datasource=$2;
309         }elsif ( m/Rule: ([0-9]+) \(level ([0-9]+)\) -> (.*)$/ ){
310                 $rule=$1;
311                 $level=$2;
312                 $description= $3;
313         }elsif ( m/Src IP:/){
314                 if ( m/($IP)/){
315                         $srcip=$1;
316                 }else{
317                         $srcip='0.0.0.0';
318                 }
319         }elsif ( m/User: (.*)$/){
320                 $user=$1;
321         }elsif( m/(.*)$/){
322                 $text .=$1;
323         }
324                 
325
326        } # End while read line
327        $offset=tell(TAIL);
328        close(TAIL);
329    }
330 }
331
332
333 sub ossec_aton(){
334         my ($ip)=@_;
335         if ($ip=~ m/(\d+)\.(\d+)\.(\d+)\.(\d+)/){
336                 my $num= ($1 * 256 ** 3) + ($2 * 256 ** 2)+ ($3 * 256 ** 1)+ ($4);
337
338                 return "$num";
339         }else{
340                 return "0";
341         }
342
343 }
344
345 sub prepair2basedata(){
346         my (
347                 $hids_id,
348                 $last_cid,
349                 $timestamp,
350                 $sec,
351                 $mail,
352                 $date,
353                 $alerthost,
354                 $datasource,
355                 $rule,
356                 $level,
357                 $description,
358                 $srcip,
359                 $dstip,
360                 $user,
361                 $text
362         )=@_;
363         my ($count,$query,$row_ref,$sig_id);
364 ###
365 #
366 # Get/Set signature id
367         $query = "SELECT sig_id FROM signature where sig_name=? and sig_class_id=? and sig_priority=? and sig_rev=? and sig_sid=? and sig_gid is NULL";
368         $dbi->execute($query,$description,1,$level,0,$rule);
369         $count=$dbi->{sth}->rows;
370         if ($count){
371                 $row_ref=$dbi->{sth}->fetchrow_hashref;
372                 $sig_id=$row_ref->{sig_id};
373                 &printlog ("REUSING SIGNATURE\n");
374         }else{
375                 $query="INSERT INTO signature ( sig_id , sig_name , sig_class_id , sig_priority , sig_rev , sig_sid , sig_gid )
376 VALUES (
377 NULL ,?, ? , ? , ? , ?, NULL
378 )";
379                 $dbi->execute($query,$description,1,$level,0,$rule);
380                 $sig_id = $dbi->lastid();
381         }
382 $dbi->{sth}->finish;
383 &printlog ("SIGNATURE: $sig_id\n");
384 #######
385 #
386 # Set event
387         $query="INSERT INTO event ( sid , cid , signature , timestamp )
388 VALUES (
389 ? , ? , ? ,? 
390 )";
391         $last_cid++;
392         $dbi->execute($query,$hids_id,$last_cid,$sig_id,&fixdate2base($date));
393
394 &printlog ("EVENT: ($query,$hids_id,$last_cid,$sig_id,&fixdate2base($date)\n");
395 $dbi->{sth}->finish;
396 #########
397 #
398 # Set acid_event
399         $query=" INSERT INTO acid_event ( sid , cid , signature , sig_name , sig_class_id , sig_priority , timestamp , ip_src , ip_dst , ip_proto , layer4_sport , layer4_dport )
400 VALUES (
401 ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ?, ?
402 ) ";
403         $dbi->execute($query,$hids_id,$last_cid,$sig_id,$description,1,$level,&fixdate2base($date),&ossec_aton($srcip),&ossec_aton($dstip),undef,undef,undef);
404 &printlog ("ACID_EVENT: ($query,$hids_id,$last_cid,$sig_id,$description,1,$level,&fixdate2base($date),&ossec_aton($srcip),&ossec_aton($dstip),undef,undef)\n");
405 $dbi->{sth}->finish;
406
407 #########
408 #
409 #
410 # Set data
411         $text = "** Alert $timestamp.$sec:\t$mail\n$date $alerthost -> $datasource\nRule: $rule (level $level) -> $description\nSrc IP: ($srcip)\nUser: $user\n$text";
412         $query=" INSERT INTO data ( sid , cid , data_payload ) 
413 VALUES (
414 ?,?,?)";
415         $dbi->execute($query,$hids_id,$last_cid,$text);
416 &printlog ("DATA: ($query,$hids_id,$last_cid,$text)\n");
417 $dbi->{sth}->finish;
418 ##########
419 #
420         $query="UPDATE sensor SET last_cid=? where sid=? limit 1";
421         $numrows= $dbi->execute($query,$last_cid,$hids_id);
422 # end sub
423 $dbi->{sth}->finish;
424 return $last_cid;
425 }
426
427 sub fixdate2base(){
428         my ($date)=@_;
429         $date=~ s/ Jan /-01-/;
430         $date=~ s/ Feb /-02-/;
431         $date=~ s/ Mar /-03-/;
432         $date=~ s/ Apr /-04-/;
433         $date=~ s/ May /-05-/;
434         $date=~ s/ Jun /-06-/;
435         $date=~ s/ Jul /-07-/;
436         $date=~ s/ Aug /-08-/;
437         $date=~ s/ Sep /-09-/;
438         $date=~ s/ Oct /-10-/;
439         $date=~ s/ Nov /-11-/;
440         $date=~ s/ Dec /-12-/;
441         $date=~ s/\s$//g;
442         return $date;
443 }
444 sub version(){
445         print "OSSEC report tool $VERSION\n";
446         print "Licensed under GPL\n";
447         print "Contributor Meir Michanie\n";
448 }
449
450 sub help(){
451         &version();
452         print "This tool helps you import into base the alerts generated by ossec."
453         . " More info in the doc directory .\n";
454         print "Usage:\n";
455         print "$0 [-h|--help] # This text you read now\n";
456         print "Options:\n";
457         print "\t--dbhost <hostname>\n";
458         print "\t--dbname <database>\n";
459         print "\t--dbport <[0-9]+>\n";
460         print "\t--dbpass <dbpasswd>\n";
461         print "\t--dbuser <dbuser>\n";
462         print "\t-d|--daemonize\n";
463         print "\t-n|--noname\n";
464         print "\t-v|--verbose\n";
465         print "\t--conf <ossec2based-config>\n";
466         print "\t--sensor <sensor-name>\n";
467         print "\t--interface <ifname>\n";
468         
469         exit 0;
470 }
471
472
473 sub daemonize {
474         chdir '/'               or die "Can't chdir to /: $!";
475         open STDIN, '/dev/null' or die "Can't read /dev/null: $!";
476         open STDOUT, ">>$DAEMONLOGFILE"
477                                or die "Can't write to $DAEMONLOGFILE: $!";
478         defined(my $pid = fork) or die "Can't fork: $!";
479         if ($pid){
480                 open (PIDFILE , ">/var/run/ossec2base2.pid") ;
481                 print PIDFILE "$pid\n";
482                 close (PIDFILE);
483                 exit 0;
484         }
485         setsid                  or die "Can't start a new session: $!";
486         open STDERR, ">>$DAEMONLOGERRORFILE" or die "Can't write to $DAEMONLOGERRORFILE: $!";
487 }
488
489 sub gracefulend(){
490         my ($signal)=@_;
491         &forceprintlog ("Terminating upon signal $signal");
492         close TAIL;
493         &forceprintlog ("Daemon halted");
494         close STDOUT;
495         close STDERR;
496         exit 0;
497 }
498
499 sub printlog(){
500         return  unless $VERBOSE;
501         my (@lines)=@_;
502         foreach my $line(@lines){
503                 chomp $line;
504                 my ($date)=scalar localtime;
505                 $date=~ s/^\S+\s+(\S+.*\s[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}).*$/$1/;
506                 print "$date $LOGGER: $line\n";
507         }
508 }
509
510
511 sub loadconf(){
512         my ($hash_ref)=@_;
513         my $conf=$hash_ref->{conf};
514         unless (-f $conf) { &printlog ("ERROR: I can't find config file $conf"); exit 1;}
515         unless (open ( CONF , "$conf")){ &printlog ("ERROR: I can't open file $conf");exit 1;}
516         while (<CONF>){
517                 next if m/^$|^#/;
518                 if ( m/^(\S+)\s?=\s?(.*?)$/) {
519                         $hash_ref->{$1} = $2;
520                 }
521         }
522         close CONF;
523 }
524