root/psad/tags/psad-2.0.2-pre7/fwcheck_psad.pl

Revision 1857, 14.6 kB (checked in by mbr, 2 years ago)

minor update to document default path installation

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
Line 
1 #!/usr/bin/perl -w
2 #
3 ###############################################################################
4 #
5 # File: fwcheck_psad.pl (/usr/sbin/fwcheck_psad)
6 #
7 # Purpose: To parse the iptables ruleset on the underlying system to see if
8 #          iptables has been configured to log and block unwanted packets by
9 #          default.  This program is called by psad, but can also be executed
10 #          manually from the command line.
11 #
12 # Author: Michael Rash (mbr@cipherdyne.org)
13 #
14 # Credits: (see the CREDITS file bundled with the psad sources.)
15 #
16 # Copyright (C) 1999-2006 Michael Rash (mbr@cipherdyne.org)
17 #
18 # License (GNU Public License):
19 #
20 #    This program is distributed in the hope that it will be useful,
21 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
22 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23 #    GNU General Public License for more details.
24 #
25 #    You should have received a copy of the GNU General Public License
26 #    along with this program; if not, write to the Free Software
27 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
28 #    USA
29 #
30 ###############################################################################
31 #
32 # $Id$
33 #
34
35 use Getopt::Long 'GetOptions';
36 use strict;
37
38 ### path to default psad library directory for psad perl modules
39 my $psad_lib_dir = '/usr/lib/psad';
40
41 ### default psad config file.
42 my $config_file  = '/etc/psad/psad.conf';
43
44 ### default fw_search file where FW_MSG_SEARCH strings
45 ### are set.  Both psad and kmsgsd reference this single
46 ### file now instead of having FW_MSG_SEARCH appear in
47 ### psad.conf and kmsgsd.conf.
48 my $fw_search_file = '/etc/psad/fw_search.conf';
49
50 ### default config file for ALERTING_METHODS keyword, which
51 ### is referenced by both psad and psadwatchd.  This keyword
52 ### allows email alerting or syslog alerting (or both) to be
53 ### disabled.
54 my $alert_conf_file = '/etc/psad/alert.conf';
55
56 ### config hash
57 my %config = ();
58
59 ### commands hash
60 my %cmds;
61
62 ### fw search string array
63 my @fw_search = ();
64
65 my $help = 0;
66 my $fw_analyze = 0;
67 my $fw_file    = '';
68 my $fw_search_all = 1;
69 my $no_fw_search_all = 0;
70
71 &usage(1) unless (GetOptions(
72     'config=s'    => \$config_file, # Specify path to configuration file.
73     'alert-conf=s'=> \$alert_conf_file, # Path to psad alert.conf file.
74     'fw-search=s' => \$fw_search_file,  # Specify path to fw_search.conf.
75     'fw-file=s'   => \$fw_file,     # Analyze ruleset contained within
76                                     # $fw_file instead of a running
77                                     # policy.
78     'fw-analyze'  => \$fw_analyze,  # Analyze the local iptables ruleset
79                                     # and exit.
80     'no-fw-search-all' => \$no_fw_search_all, # looking for specific log
81                                               # prefixes
82     'help'        => \$help,        # Display help.
83 ));
84 &usage(0) if $help;
85
86 $fw_search_all = 0 if $no_fw_search_all;
87
88 ### Everthing after this point must be executed as root.
89 $< == 0 && $> == 0 or
90     die '[*] fwcheck_psad.pl: You must be root (or equivalent ',
91         "UID 0 account) to execute fwcheck_psad.pl!  Exiting.\n";
92
93 ### import psad perl modules
94 &import_psad_perl_modules();
95
96 if ($fw_file) {
97     die "[*] iptables dump file: $fw_file does not exist."
98         unless -e $fw_file;
99 }
100
101 ### import psad.conf
102 &Psad::buildconf(\%config, \%cmds, $config_file);
103
104 ### import alerting config (psadwatchd also references this file
105 &Psad::buildconf(\%config, \%cmds, $alert_conf_file);
106
107 ### import FW_MSG_SEARCH strings
108 &import_fw_search();
109
110 ### expand any embedded vars within config values
111 &Psad::expand_vars(\%config, \%cmds);
112
113 ### check to make sure the commands specified in the config section
114 ### are in the right place, and attempt to correct automatically if not.
115 &Psad::check_commands(\%cmds);
116
117 open FWCHECK, "> $config{'FW_CHECK_FILE'}" or die "[*] Could not ",
118     "open $config{'FW_CHECK_FILE'}: $!";
119
120 unless ($fw_search_all) {
121     print FWCHECK "[+] Available search strings in $fw_search_file:\n\n";
122     print FWCHECK "        $_\n" for @fw_search;
123     print FWCHECK
124 "\n[+] Additional search strings can be added be specifying more\n",
125     "    FW_MSG_SEARCH lines in $fw_search_file\n\n";
126 }
127
128 ### check the iptables policy
129 my $rv = &fw_check();
130
131 close FWCHECK;
132
133 exit $rv;
134
135 #========================== end main =========================
136
137 sub fw_check() {
138
139     ### only send a firewall config alert if we really need to.
140     my $send_alert = 0;
141
142     my $forward_chain_rv = 1;
143     my $input_chain_rv = &ipt_chk_chain('INPUT');
144
145     unless ($input_chain_rv) {
146         &print_fw_help('INPUT');
147         $send_alert = 1;
148     }
149
150     ### we don't always have more than one interface or forwarding
151     ### turned on, so we only check the FORWARD iptables chain if we
152     ### do and we have multiple interfaces on the box.
153     if (&check_forwarding()) {
154         $forward_chain_rv = &ipt_chk_chain('FORWARD');
155         unless ($forward_chain_rv) {
156             &print_fw_help('FORWARD');
157             $send_alert = 1;
158         }
159     }
160
161     if ($send_alert) {
162         unless ($fw_search_all) {
163             print FWCHECK
164 "\n[+] NOTE: IPTables::Parse does not yet parse user defined chains and so\n",
165 "    it is possible your firewall config is compatible with psad anyway.\n";
166         }
167
168         unless ($config{'ALERTING_METHODS'} =~ /no.?e?mail/i) {
169             &Psad::sendmail("[psad-status] firewall setup warning on " .
170                 "$config{'HOSTNAME'}!", $config{'FW_CHECK_FILE'},
171                 $config{'EMAIL_ADDRESSES'},
172                 $cmds{'mail'}
173             );
174         }
175         if ($fw_analyze) {
176             print "[-] Errors found in firewall config.\n";
177             print "[-] Results in ",
178                 "$config{'FW_CHECK_FILE'}\n";
179             print "    emailed to ",
180                 "$config{'EMAIL_ADDRESSES'}\n";
181         }
182     } else {
183         print FWCHECK
184 "[+] The iptables ruleset on $config{'HOSTNAME'} will log and block unwanted\n",
185 "    packets in both the INPUT and FORWARD chains.  Firewall config success!\n";
186
187         if ($fw_analyze) {
188             print "[+] Firewall config looks good.\n";
189             print "[+] Completed check of firewall ruleset.\n";
190         }
191     }
192     if ($fw_analyze) {
193         print "[+] Exiting.\n";
194     }
195     return $forward_chain_rv && $input_chain_rv;
196 }
197
198 sub print_fw_help() {
199     my $chain = shift;
200     print FWCHECK
201 "[-] You may just need to add a default logging rule to the $chain chain on\n",
202 "    $config{'HOSTNAME'}.  For more information, see the file \"FW_HELP\" in\n",
203 "    the psad sources directory or visit:\n\n",
204 "    http://www.cipherdyne.org/psad/docs/fwconfig.html\n\n";
205     return;
206 }
207
208 sub check_forwarding() {
209     ### check to see if there are multiple interfaces on the
210     ### machine and return false if no since the machine will
211     ### not be able to forward packets anyway (e.g. desktop
212     ### machines).  Also return false if forwarding is turned
213     ### off (we have to trust the machine config is as the
214     ### admin wants it).
215     my $forwarding;
216     if (-e $config{'PROC_FORWARD_FILE'}) {
217         open F, "< $config{'PROC_FORWARD_FILE'}"
218             or die "[*] Could not open $config{'PROC_FORWARD_FILE'}: $!";
219         $forwarding = <F>;
220         close F;
221         chomp $forwarding;
222         return 0 if $forwarding == 0;
223     } else {
224         die "[*] Make sure the path to the IP forwarding file correct.\n",
225             "    The PROC_FORWARD_FILE in $config_file points to\n",
226             "    $config{'PROC_FORWARD_FILE'}";
227     }
228     open IFC, "$cmds{'ifconfig'} -a |" or die "[*] Could not ",
229         "execute: $cmds{'ifconfig'} -a: $!";
230     my @if_out = <IFC>;
231     close IFC;
232     my $num_intf = 0;
233     for my $line (@if_out) {
234         if ($line =~ /inet\s+/i && $line !~ /127\.0\.0\.1/) {
235             $num_intf++;
236         }
237     }
238     if ($num_intf < 2) {
239         return 0;
240     }
241     return 1;
242 }
243
244 sub ipt_chk_chain() {
245     my $chain = shift;
246     my $rv = 1;
247
248     my $ipt = new IPTables::Parse 'iptables' => $cmds{'iptables'}
249         or die "[*] Could not acquite IPTables::Parse object: $!";
250
251     if ($fw_analyze) {
252         print "[+] Parsing iptables $chain chain rules.\n";
253     }
254
255     if ($fw_search_all) {
256         ### we are not looking for specific log
257         ### prefixes, but we need _some_ logging rule
258         my $ipt_log = $ipt->default_log('filter', $chain, $fw_file);
259         return 0 unless $ipt_log;
260         if (defined $ipt_log->{'all'}) {
261             ### found real default logging rule (assuming it is above a default
262             ### drop rule, which we are not actually checking here).
263             return 1;
264         } else {
265             my $log_protos    = '';
266             my $no_log_protos = '';
267             for my $proto qw(tcp udp icmp) {
268                 if (defined $ipt_log->{$proto}) {
269                     $log_protos .= "$proto/";
270                 } else {
271                     $no_log_protos .= "$proto/";
272                 }
273             }
274             $log_protos =~ s|/$||;
275             $no_log_protos =~ s|/$||;
276
277             print FWCHECK
278 "[-] Your firewall config on $config{'HOSTNAME'} includes logging rules for\n",
279 "    $log_protos but not for $no_log_protos in the $chain chain.\n\n";
280             return 0;
281         }
282     } else {
283         ### we are looking for specific log prefixes.
284         ### for now we are only looking at the filter table, so if
285         ### the iptables ruleset includes the log and drop rules in
286         ### a user defined chain then psad will not see this.
287         my $ld_hr = $ipt->default_drop('filter', $chain, $fw_file);
288
289         my $num_keys = 0;
290         if (defined $ld_hr and keys %$ld_hr) {
291             $num_keys++;
292             my @protos;
293             if (defined $ld_hr->{'all'}) {
294                 @protos = qw(all);
295             } else {
296                 @protos = qw(tcp udp icmp);
297             }
298             for my $proto (@protos) {
299                 my $str1;
300                 my $str2;
301                 if (! defined $ld_hr->{$proto}->{'LOG'}) {
302                     if ($proto eq 'all') {
303                         $str1 = 'for all protocols';
304                         $str2 = 'scans';
305                     } else {
306                         $str1 = "for the $proto protocol";
307                         $str2 = "$proto scans";
308                     }
309                     print FWCHECK
310 "[-] The $chain chain in the iptables ruleset on $config{'HOSTNAME'} does not\n",
311 "    appear to include a default LOG rule $str1.  psad will not be able to\n",
312 "    detect $str2 without such a rule.\n\n";
313
314                     $rv = 0;
315                 }
316                 if (defined $ld_hr->{$proto}->{'LOG'}->{'prefix'}) {
317                     my $found = 0;
318                     for my $fwstr (@fw_search) {
319                         $found = 1
320                             if $ld_hr->{$proto}->{'LOG'}->{'prefix'} =~ /$fwstr/;
321                     }
322                     unless ($found) {
323                         if ($proto eq 'all') {
324                             $str1 = "[-] The $chain chain in the iptables ruleset " .
325                             "on $config{'HOSTNAME'} includes a default\n    LOG rule for " .
326                             "all protocols,";
327                             $str2 = 'scans';
328                         } else {
329                             $str1 = "[-] The $chain chain in the iptables ruleset " .
330                             "on $config{'HOSTNAME'} inclues a default\n    LOG rule for " .
331                             "the $proto protocol,";
332                             $str2 = "$proto scans";
333                         }
334                         print FWCHECK
335 "$str1\n",
336 "    but the rule does not include one of the log prefixes mentioned above.\n",
337 "    It appears as though the log prefix is set to \"$ld_hr->{$proto}->{'LOG'}->{'prefix'}\"\n",
338 "    psad will not be able to detect $str2 without adding one of the above\n",
339 "    logging prefixes to the rule.\n\n";
340                         $rv = 0;
341                     }
342                 }
343                 if (! defined $ld_hr->{$proto}->{'DROP'}) {
344                     if ($proto eq 'all') {
345                         $str1 = "for all protocols";
346                     } else {
347                         $str1 = "for the $proto protocol";
348                     }
349                     print FWCHECK
350 "[-] The $chain chain in the iptables ruleset on $config{'HOSTNAME'} does not\n",
351 "    appear to include a default DROP rule $str1.\n\n";
352                     $rv = 0;
353                 }
354             }
355         }
356         ### make sure there was _something_ returned from the IPTables::Parse
357         ### module.
358         return 0 unless $num_keys > 0;
359     }
360     return $rv;
361 }
362
363 sub import_psad_perl_modules() {
364
365     my $mod_paths_ar = &get_psad_mod_paths();
366
367     push @$mod_paths_ar, @INC;
368     splice @INC, 0, $#$mod_paths_ar+1, @$mod_paths_ar;
369
370     require Psad;
371     require IPTables::Parse;
372
373     return;
374 }
375
376 sub get_psad_mod_paths() {
377
378     my @paths = ();
379
380     unless (-d $psad_lib_dir) {
381         my $dir_tmp = $psad_lib_dir;
382         $dir_tmp =~ s|lib/|lib64/|;
383         if (-d $dir_tmp) {
384             $psad_lib_dir = $dir_tmp;
385         } else {
386             die "[*] psad lib directory: $psad_lib_dir does not exist, ",
387                 "use --Lib-dir <dir>";
388         }
389     }
390
391     opendir D, $psad_lib_dir or die "[*] Could not open $psad_lib_dir: $!";
392     my @dirs = readdir D;
393     closedir D;
394
395     push @paths, $psad_lib_dir;
396
397     for my $dir (@dirs) {
398         ### get directories like "/usr/lib/psad/x86_64-linux"
399         next unless -d "$psad_lib_dir/$dir";
400         push @paths, "$psad_lib_dir/$dir"
401             if $dir =~ m|linux| or $dir =~ m|thread|;
402     }
403     return \@paths;
404 }
405
406 sub import_fw_search() {
407     open F, "< $fw_search_file" or die "[*] Could not open fw search ",
408         "string file $fw_search_file: $!";
409     my @lines = <F>;
410     close F;
411     for my $line (@lines) {
412         next unless $line =~ /\S/;
413         next if $line =~ /^\s*#/;
414         if ($line =~ /^\s*FW_MSG_SEARCH\s+(.*?);/) {
415             push @fw_search, $1;
416         }
417     }
418     return;
419 }
420
421 sub usage() {
422     my $exitcode = shift;
423     print <<_HELP_;
424
425 Options:
426     --config <config_file>            - Specify path to configuration
427                                         file.
428     --alert-conf <alert_conf_file>    - Path to psad alert.conf file.
429     --fw-search  <fw_search_file>     - Specify path to fw_search.conf.
430     --fw-file    <fw_file>            - Analyze ruleset contained within
431                                         fw_file instead of a running
432                                         policy.
433     --fw-analyze                      - Analyze the local iptables
434                                         ruleset and exit.
435     --no-fw-search-all                - looking for specific log
436                                         prefixes
437     --help                            - Display help.
438
439 _HELP_
440     exit $exitcode;
441 }
Note: See TracBrowser for help on using the browser.