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