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