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