]>
Commit | Line | Data |
---|---|---|
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`; | |
11 | chomp $OS; | |
12 | print "OS shows $OS\n"; | |
13 | ||
14 | require 'getopts.pl'; | |
15 | ||
16 | &Getopts ('hc:d'); | |
17 | if (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 | ||
28 | print "My ip address and interface are: $hostipaddr $interface\n"; | |
29 | ||
30 | if ($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 |
42 | print "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 | 57 | if ( -e $targetfile ) { |
6579aab2 | 58 | &load_targetfile; |
7c4cc0d8 CS |
59 | } |
60 | ||
61 | if (!defined($opt_d)) { | |
6579aab2 CS |
62 | print "Becoming a daemon..\n"; |
63 | &daemonize; | |
7c4cc0d8 CS |
64 | } else { print "Running in debug mode..\n"; } |
65 | ||
66 | open (ALERT, $alert_file) or die "can't open alert file: $alert_file: $!\n"; | |
6579aab2 | 67 | seek (ALERT, 0, 2); # set the position to EOF. |
7c4cc0d8 CS |
68 | # this is the same as a tail -f :) |
69 | $counter=0; | |
fb30e20c | 70 | open (ALERT2, "/var/log/messages" ) or die "can't open /var/log/messages: $!\n"; |
6579aab2 | 71 | seek (ALERT2, 0, 2); # set the position to EOF. |
fb30e20c | 72 | # this is the same as a tail -f :) |
fb30e20c | 73 | |
6579aab2 CS |
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; | |
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");} | |
6579aab2 CS |
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 | } | |
7c4cc0d8 CS |
121 | } |
122 | ||
123 | sub check_log_name { | |
6579aab2 CS |
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 | } | |
7c4cc0d8 | 134 | } |
6579aab2 | 135 | |
7c4cc0d8 CS |
136 | |
137 | sub checkem { | |
6579aab2 CS |
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 | } | |
7c4cc0d8 CS |
165 | } |
166 | ||
fb30e20c | 167 | sub checkssh { |
6579aab2 CS |
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 | ||
726ea1a2 JPT |
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; | |
6579aab2 | 181 | } |
726ea1a2 JPT |
182 | |
183 | if ($sshhash{$source} == 4 ) { | |
184 | &write_log ("source = $source, blocking for ssh attack.\n"); | |
6579aab2 | 185 | &ipchain ($source, "", $type); |
726ea1a2 JPT |
186 | $sshhash{$source} = $sshhash{$source}+1; |
187 | return 0; | |
6579aab2 | 188 | } |
726ea1a2 JPT |
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; | |
6579aab2 | 194 | } |
726ea1a2 JPT |
195 | |
196 | $sshhash{$source} = $sshhash{$source}+1; | |
197 | &write_log ("SSH Attack = $source, ssh count only $sshhash{$source} - No action done.\n"); | |
fb30e20c CS |
198 | } |
199 | ||
6579aab2 CS |
200 | sub ipchain { |
201 | my ($source, $dest, $type) = @_; | |
202 | &write_log ("$source\t$type\n"); | |
203 | if ($hash{$source} eq "") { | |
057249ba CS |
204 | &write_log ("Running '$blockpath $source $interface'\n"); |
205 | system ("$blockpath $source $interface"); | |
6579aab2 CS |
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 | } | |
7c4cc0d8 | 212 | } |
6579aab2 | 213 | |
7c4cc0d8 CS |
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 | |
6579aab2 CS |
218 | # could be seen if the attacker were on the local network. |
219 | # $ignore{$networkaddr}=1; | |
7c4cc0d8 CS |
220 | |
221 | # same thing as above, just with the broadcast instead of the network. | |
222 | # $ignore{$broadcastaddr}=1; | |
6579aab2 CS |
223 | my $count =0; |
224 | $ignore{$gatewayaddr}=1; | |
225 | $ignore{$hostipaddr}=1; | |
226 | if ($ignorefile ne "") { | |
227 | open (IGNORE, $ignorefile); | |
228 | while (<IGNORE>) { | |
fd4da55b CS |
229 | $_=~ s/\s+$//; |
230 | chomp; | |
6579aab2 CS |
231 | next if (/\#/); #skip comments |
232 | next if (/^\s*$/); # and blank lines | |
233 | $ignore{$_}=1; | |
234 | $count++; | |
235 | } | |
236 | close (IGNORE); | |
726ea1a2 | 237 | &write_log("Loaded $count addresses from $ignorefile\n"); |
6579aab2 | 238 | } else { |
726ea1a2 | 239 | &write_log("No ignore file was loaded!\n"); |
6579aab2 | 240 | } |
7c4cc0d8 CS |
241 | } |
242 | ||
243 | sub load_conf { | |
6579aab2 CS |
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; | |
057249ba CS |
262 | if ( $interface eq "" ) { |
263 | $interface = `cat /var/ipfire/ethernet/settings | grep RED_DEV | cut -d"=" -f2`; | |
264 | } | |
6579aab2 CS |
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 | ||
6579aab2 CS |
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"; | |
057249ba | 292 | $hostipaddr = `cat /var/ipfire/red/local-ipaddress`; |
6579aab2 CS |
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 | } | |
7c4cc0d8 CS |
331 | } |
332 | ||
7c4cc0d8 | 333 | sub write_log { |
6579aab2 CS |
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 | } | |
7c4cc0d8 CS |
343 | } |
344 | ||
7c4cc0d8 | 345 | sub daemonize { |
6579aab2 CS |
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 | } | |
7c4cc0d8 | 362 | |
7c4cc0d8 | 363 | sub sig_handler_setup { |
057249ba CS |
364 | $SIG{INT} = \&clean_up_and_exit; # kill -2 |
365 | $SIG{TERM} = \&clean_up_and_exit; # kill -9 | |
6579aab2 | 366 | $SIG{QUIT} = \&clean_up_and_exit; # kill -3 |
7c4cc0d8 CS |
367 | # $SIG{HUP} = \&flush_and_reload; # kill -1 |
368 | } | |
369 | ||
370 | sub remove_blocks { | |
6579aab2 CS |
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 | } | |
7c4cc0d8 CS |
379 | } |
380 | ||
381 | sub call_unblock { | |
6579aab2 CS |
382 | my ($source, $message) = @_; |
383 | &write_log ("$message"); | |
057249ba | 384 | system ("$unblockpath $source $interface"); |
7c4cc0d8 CS |
385 | } |
386 | ||
387 | sub clean_up_and_exit { | |
6579aab2 CS |
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; | |
7c4cc0d8 CS |
394 | } |
395 | ||
396 | sub load_targetfile { | |
6579aab2 CS |
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"; | |
7c4cc0d8 | 408 | } |
057249ba CS |
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 | } |