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