]> git.ipfire.org Git - people/teissler/ipfire-2.x.git/blame - config/guardian/guardian.pl
Merge branch 'ppp-update' into beyond-next
[people/teissler/ipfire-2.x.git] / config / guardian / guardian.pl
CommitLineData
7c4cc0d8 1#!/usr/bin/perl
6579aab2 2# based on V 1.7 guardian enhanced for IPFire and snort 2.8
7c4cc0d8
CS
3# Read the readme file for changes
4#
fb30e20c
CS
5# Enhanced for IPFire by IPFire Team
6# Added Portscan detection for non syslog system
7# Added SSH-Watch for SSH-Bruteforce Attacks
8# An suppected IP will be blocked on all interfaces
7c4cc0d8
CS
9
10$OS=`uname`;
11chomp $OS;
12print "OS shows $OS\n";
13
14require 'getopts.pl';
15
16&Getopts ('hc:d');
17if (defined($opt_h)) {
6579aab2
CS
18 print "Guardian v1.7 \n";
19 print "guardian.pl [-hd] <-c config>\n";
20 print " -h shows help\n";
21 print " -d run in debug mode (doesn't fork, output goes to STDOUT)\n";
22 print " -c specifiy a configuration file other than the default (/etc/guardian.conf)\n";
23 exit;
7c4cc0d8
CS
24}
25&load_conf;
26&sig_handler_setup;
27
28print "My ip address and interface are: $hostipaddr $interface\n";
29
30if ($hostipaddr !~ /\d+\.\d+\.\d+\.\d+/) {
6579aab2
CS
31 print "This ip address is bad : $hostipaddr\n";
32 die "I need a good host ipaddress\n";
7c4cc0d8
CS
33}
34
35$networkaddr = $hostipaddr;
36$networkaddr =~ s/\d+$/0/;
841f013e 37$gatewayaddr = `cat /var/ipfire/red/remote-ipaddress 2>/dev/null`;
7c4cc0d8
CS
38$broadcastaddr = $hostipaddr;
39$broadcastaddr =~ s/\d+$/255/;
40&build_ignore_hash;
41
841f013e
CS
42print "My gatewayaddess is: $gatewayaddr\n";
43
7c4cc0d8
CS
44# This is the target hash. If a packet was destened to any of these, then the
45# sender of that packet will get denied, unless it is on the ignore list..
46
6579aab2
CS
47%targethash = ( "$networkaddr" => 1,
48 "$broadcastaddr" => 1,
49 "0" => 1, # This is what gets sent to &checkem if no
50 # destination was found.
51 "$hostipaddr" => 1);
7c4cc0d8 52
057249ba
CS
53&get_aliases;
54
fb30e20c
CS
55%sshhash = ();
56
7c4cc0d8 57if ( -e $targetfile ) {
6579aab2 58 &load_targetfile;
7c4cc0d8
CS
59}
60
61if (!defined($opt_d)) {
6579aab2
CS
62 print "Becoming a daemon..\n";
63 &daemonize;
7c4cc0d8
CS
64} else { print "Running in debug mode..\n"; }
65
66open (ALERT, $alert_file) or die "can't open alert file: $alert_file: $!\n";
6579aab2 67seek (ALERT, 0, 2); # set the position to EOF.
7c4cc0d8
CS
68# this is the same as a tail -f :)
69$counter=0;
fb30e20c 70open (ALERT2, "/var/log/messages" ) or die "can't open /var/log/messages: $!\n";
6579aab2 71seek (ALERT2, 0, 2); # set the position to EOF.
fb30e20c 72# this is the same as a tail -f :)
fb30e20c 73
6579aab2
CS
74for (;;) {
75 sleep 1;
76 if (seek(ALERT,0,1)){
77 while (<ALERT>) {
78 chop;
79 if (defined($opt_d)) {
80 print "$_\n";
81 }
82 if (/\[\*\*\]\s+(.*)\s+\[\*\*\]/){
83 $type=$1;
84 }
85 if (/(\d+\.\d+\.\d+\.\d+):\d+ -\> (\d+\.\d+\.\d+\.\d+):\d+/) {
86 &checkem ($1, $2, $type);
87 }
88 if (/(\d+\.\d+\.\d+\.\d+)+ -\> (\d+\.\d+\.\d+\.\d+)+/) {
89 &checkem ($1, $2, $type);
90 }
91 }
92 }
93
94 sleep 1;
95 if (seek(ALERT2,0,1)){
96 while (<ALERT2>) {
97 chop;
726ea1a2
JPT
98 if ($_=~/.*sshd.*Failed password for .* from.*/) {
99 my @array=split(/ /,$_);
100 my $temp = "";
101 if ( $array[11] eq "port" ) {
102 $temp = $array[10];
103 } elsif ( $array[11] eq "from" ) {
104 $temp = $array[12];
105 } else {
106 $temp = $array[11];
107 }
108 &checkssh ($temp, "possible SSH-Bruteforce Attack");}
2a747e37
KW
109
110 # This should catch Bruteforce Attacks with enabled preauth
111 if ($_ =~ /.*sshd.*Received disconnect from (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):.*\[preauth\]/) {
112 &checkssh ($1, "possible SSH-Bruteforce Attack, failed preauth");}
6579aab2
CS
113 }
114 }
115
116# Run this stuff every 30 seconds..
117 if ($counter == 30) {
118 &remove_blocks; # This might get moved elsewhere, depending on how much load
119 # it puts on the system..
120 &check_log_name;
121 $counter=0;
122 } else {
123 $counter=$counter+1;
124 }
7c4cc0d8
CS
125}
126
127sub check_log_name {
6579aab2
CS
128 my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
129 $atime,$mtime,$ctime,$blksize,$blocks) = stat($alert_file);
130 if ($size < $previous_size) { # The filesize is smaller than last
131 close (ALERT); # we checked, so we need to reopen it
132 open (ALERT, "$alert_file"); # This should still work in our main while
133 $previous_size=$size; # loop (I hope)
134 write_log ("Log filename changed. Reopening $alert_file\n");
135 } else {
136 $previous_size=$size;
137 }
7c4cc0d8 138}
6579aab2 139
7c4cc0d8
CS
140
141sub checkem {
6579aab2
CS
142 my ($source, $dest,$type) = @_;
143 my $flag=0;
144
145 return 1 if ($source eq $hostipaddr);
146 # this should prevent is from nuking ourselves
147
148 return 1 if ($source eq $gatewayaddr); # or our gateway
149 if ($ignore{$source} == 1) { # check our ignore list..
150 &write_log("$source\t$type\n");
151 &write_log("Ignoring attack because $source is in my ignore list\n");
152 return 1;
153 }
154
155# if the offending packet was sent to us, the network, or the broadcast, then
156 if ($targethash{$dest} == 1) {
157 &ipchain ($source, $dest, $type);
158 }
159# you will see this if the destination was not in the $targethash, and the
160# packet was not ignored before the target check..
161 else {
162 &write_log ("Odd.. source = $source, dest = $dest - No action done.\n");
163 if (defined ($opt_d)) {
164 foreach $key (keys %targethash) {
165 &write_log ("targethash{$key} = $targethash{$key}\n");
166 }
167 }
168 }
7c4cc0d8
CS
169}
170
fb30e20c 171sub checkssh {
6579aab2
CS
172 my ($source,$type) = @_;
173 my $flag=0;
174
175 return 1 if ($source eq $hostipaddr);
176 # this should prevent is from nuking ourselves
177
178 return 1 if ($source eq $gatewayaddr); # or our gateway
179
726ea1a2
JPT
180 return 0 if ($sshhash{$source} > 4); # allready blocked
181
182 if ( ($ignore{$source} == 1) ){
183 &write_log("Ignoring attack because $source is in my ignore list\n");
184 return 1;
6579aab2 185 }
726ea1a2
JPT
186
187 if ($sshhash{$source} == 4 ) {
188 &write_log ("source = $source, blocking for ssh attack.\n");
6579aab2 189 &ipchain ($source, "", $type);
726ea1a2
JPT
190 $sshhash{$source} = $sshhash{$source}+1;
191 return 0;
6579aab2 192 }
726ea1a2
JPT
193
194 if ($sshhash{$source} eq "" ){
195 $sshhash{$source} = 1;
196 &write_log ("SSH Attack = $source, ssh count only $sshhash{$source} - No action done.\n");
197 return 0;
6579aab2 198 }
726ea1a2
JPT
199
200 $sshhash{$source} = $sshhash{$source}+1;
201 &write_log ("SSH Attack = $source, ssh count only $sshhash{$source} - No action done.\n");
fb30e20c
CS
202}
203
6579aab2
CS
204sub ipchain {
205 my ($source, $dest, $type) = @_;
206 &write_log ("$source\t$type\n");
207 if ($hash{$source} eq "") {
057249ba
CS
208 &write_log ("Running '$blockpath $source $interface'\n");
209 system ("$blockpath $source $interface");
6579aab2
CS
210 $hash{$source} = time() + $TimeLimit;
211 } else {
212# We have already blocked this one, but snort detected another attack. So
213# we should update the time blocked..
214 $hash{$source} = time() + $TimeLimit;
215 }
7c4cc0d8 216}
6579aab2 217
7c4cc0d8
CS
218sub build_ignore_hash {
219# This would cause is to ignore all broadcasts if it
220# got set.. However if unset, then the attacker could spoof the packet to make
221# it look like it came from the network, and a reply to the spoofed packet
6579aab2
CS
222# could be seen if the attacker were on the local network.
223# $ignore{$networkaddr}=1;
7c4cc0d8
CS
224
225# same thing as above, just with the broadcast instead of the network.
226# $ignore{$broadcastaddr}=1;
6579aab2
CS
227 my $count =0;
228 $ignore{$gatewayaddr}=1;
229 $ignore{$hostipaddr}=1;
230 if ($ignorefile ne "") {
231 open (IGNORE, $ignorefile);
232 while (<IGNORE>) {
fd4da55b
CS
233 $_=~ s/\s+$//;
234 chomp;
6579aab2
CS
235 next if (/\#/); #skip comments
236 next if (/^\s*$/); # and blank lines
237 $ignore{$_}=1;
238 $count++;
239 }
240 close (IGNORE);
726ea1a2 241 &write_log("Loaded $count addresses from $ignorefile\n");
6579aab2 242 } else {
726ea1a2 243 &write_log("No ignore file was loaded!\n");
6579aab2 244 }
7c4cc0d8
CS
245}
246
247sub load_conf {
6579aab2
CS
248 if ($opt_c eq "") {
249 $opt_c = "/etc/guardian.conf";
250 }
251
252 if (! -e $opt_c) {
253 die "Need a configuration file.. please use to the -c option to name a configuration file\n";
254 }
255
256 open (CONF, $opt_c) or die "Cannot read the config file $opt_c, $!\n";
257 while (<CONF>) {
258 chop;
259 next if (/^\s*$/); #skip blank lines
260 next if (/^#/); # skip comment lines
261 if (/LogFile\s+(.*)/) {
262 $logfile = $1;
263 }
264 if (/Interface\s+(.*)/) {
265 $interface = $1;
057249ba
CS
266 if ( $interface eq "" ) {
267 $interface = `cat /var/ipfire/ethernet/settings | grep RED_DEV | cut -d"=" -f2`;
268 }
6579aab2
CS
269 }
270 if (/AlertFile\s+(.*)/) {
271 $alert_file = $1;
272 }
273 if (/IgnoreFile\s+(.*)/) {
274 $ignorefile = $1;
275 }
276 if (/TargetFile\s+(.*)/) {
277 $targetfile = $1;
278 }
279 if (/TimeLimit\s+(.*)/) {
280 $TimeLimit = $1;
281 }
282 if (/HostIpAddr\s+(.*)/) {
283 $hostipaddr = $1;
284 }
285 if (/HostGatewayByte\s+(.*)/) {
286 $hostgatewaybyte = $1;
287 }
288 }
289
6579aab2
CS
290 if ($alert_file eq "") {
291 print "Warning! AlertFile is undefined.. Assuming /var/log/snort.alert\n";
292 $alert_file="/var/log/snort.alert";
293 }
294 if ($hostipaddr eq "") {
295 print "Warning! HostIpAddr is undefined! Attempting to guess..\n";
057249ba 296 $hostipaddr = `cat /var/ipfire/red/local-ipaddress`;
6579aab2
CS
297 print "Got it.. your HostIpAddr is $hostipaddr\n";
298 }
299 if ($ignorefile eq "") {
300 print "Warning! IgnoreFile is undefined.. going with default ignore list (hostname and gateway)!\n";
301 }
302 if ($hostgatewaybyte eq "") {
303 print "Warning! HostGatewayByte is undefined.. gateway will not be in ignore list!\n";
304 }
305 if ($logfile eq "") {
306 print "Warning! LogFile is undefined.. Assuming debug mode, output to STDOUT\n";
307 $opt_d = 1;
308 }
309 if (! -w $logfile) {
310 print "Warning! Logfile is not writeable! Engaging debug mode, output to STDOUT\n";
311 $opt_d = 1;
312 }
313
314 foreach $mypath (split (/:/, $ENV{PATH})) {
315 if (-x "$mypath/guardian_block.sh") {
316 $blockpath = "$mypath/guardian_block.sh";
317 }
318 if (-x "$mypath/guardian_unblock.sh") {
319 $unblockpath = "$mypath/guardian_unblock.sh";
320 }
321 }
322
323 if ($blockpath eq "") {
324 print "Error! Could not find guardian_block.sh. Please consult the README. \n";
325 exit;
326 }
327 if ($unblockpath eq "") {
328 print "Warning! Could not find guardian_unblock.sh. Guardian will not be\n";
329 print "able to remove blocked ip addresses. Please consult the README file\n";
330 }
331 if ($TimeLimit eq "") {
332 print "Warning! Time limit not defined. Defaulting to absurdly long time limit\n";
333 $TimeLimit = 999999999;
334 }
7c4cc0d8
CS
335}
336
7c4cc0d8 337sub write_log {
6579aab2
CS
338 my $message = $_[0];
339 my $date = localtime();
340 if (defined($opt_d)) { # we are in debug mode, and not daemonized
341 print STDOUT $message;
342 } else {
343 open (LOG, ">>$logfile");
344 print LOG $date.": ".$message;
345 close (LOG);
346 }
7c4cc0d8
CS
347}
348
7c4cc0d8 349sub daemonize {
6579aab2
CS
350 my ($home);
351 if (fork()) {
352# parent
353 exit(0);
354 } else {
355# child
356 &write_log ("Guardian process id $$\n");
357 $home = (getpwuid($>))[7] || die "No home directory!\n";
358 chdir($home); # go to my homedir
359 setpgrp(0,0); # become process leader
360 close(STDOUT);
361 close(STDIN);
362 close(STDERR);
363 print "Testing...\n";
364 }
365}
7c4cc0d8 366
7c4cc0d8 367sub sig_handler_setup {
057249ba
CS
368 $SIG{INT} = \&clean_up_and_exit; # kill -2
369 $SIG{TERM} = \&clean_up_and_exit; # kill -9
6579aab2 370 $SIG{QUIT} = \&clean_up_and_exit; # kill -3
7c4cc0d8
CS
371# $SIG{HUP} = \&flush_and_reload; # kill -1
372}
373
374sub remove_blocks {
6579aab2
CS
375 my $source;
376 my $time = time();
377 foreach $source (keys %hash) {
378 if ($hash{$source} < $time) {
379 &call_unblock ($source, "expiring block of $source\n");
380 delete ($hash{$source});
381 }
382 }
7c4cc0d8
CS
383}
384
385sub call_unblock {
6579aab2
CS
386 my ($source, $message) = @_;
387 &write_log ("$message");
057249ba 388 system ("$unblockpath $source $interface");
7c4cc0d8
CS
389}
390
391sub clean_up_and_exit {
6579aab2
CS
392 my $source;
393 &write_log ("received kill sig.. shutting down\n");
394 foreach $source (keys %hash) {
395 &call_unblock ($source, "removing $source for shutdown\n");
396 }
397 exit;
7c4cc0d8
CS
398}
399
400sub load_targetfile {
6579aab2
CS
401 my $count = 0;
402 open (TARG, "$targetfile") or die "Cannot open $targetfile\n";
403 while (<TARG>) {
404 chop;
405 next if (/\#/); #skip comments
406 next if (/^\s*$/); # and blank lines
407 $targethash{$_}=1;
408 $count++;
409 }
410 close (TARG);
411 print "Loaded $count addresses from $targetfile\n";
7c4cc0d8 412}
057249ba
CS
413
414sub get_aliases {
415 my $ip;
416 print "Scanning for aliases on $interface and add them to the target hash...";
417
418 open (IFCONFIG, "/sbin/ip addr show $interface |");
419 my @lines = <IFCONFIG>;
420 close(IFCONFIG);
421
422 foreach $line (@lines) {
423 if ( $line =~ /inet (\d+\.\d+\.\d+\.\d+)/) {
424 $ip = $1;
425 print " got $ip on $interface ... ";
426 $targethash{'$ip'} = "1";
427 }
428 }
429
430 print "done \n";
2a747e37 431}