Bug fix for NetAddr::IP usage in --analysis-fields IP search mode
authorMichael Rash <mbr@cipherdyne.org>
Wed, 21 Nov 2012 01:58:00 +0000 (20:58 -0500)
committerMichael Rash <mbr@cipherdyne.org>
Wed, 21 Nov 2012 01:58:00 +0000 (20:58 -0500)
Bug fix in --Analyze mode when IP fields are to be searched with the
--analysis-fields argument (such as --analysis-fields "SRC:1.2.3.4").
The bug was reported by Gregorio Narvaez, and looked like this:

  Use of uninitialized value $_[0] in length at
  ../../blib/lib/NetAddr/IP/UtilPP.pm (autosplit into
  ../../blib/lib/auto/NetAddr/IP/UtilPP/hasbits.al) line 126.
  Use of uninitialized value $_[0] in length at
  ../../blib/lib/NetAddr/IP/UtilPP.pm (autosplit into
  ../../blib/lib/auto/NetAddr/IP/UtilPP/hasbits.al) line 126.
  Bad argument length for NetAddr::IP::UtilPP::hasbits, is 0, should be
  128 at ../../blib/lib/NetAddr/IP/UtilPP.pm (autosplit into
  ../../blib/lib/auto/NetAddr/IP/UtilPP/_deadlen.al) line 122.

Added --stdin argument to allow psad to collect iptables log data from
STDIN in --Analyze mode.

ChangeLog
psad
psad.8
test/install.answers [new file with mode: 0644]
test/test-psad.pl

index abe9d76..3b94744 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,21 @@
+psad-2.2.1 (11/24/2012):
+    - Bug fix in --Analyze mode when IP fields are to be searched with the
+      --analysis-fields argument (such as --analysis-fields "SRC:1.2.3.4").
+      The bug was reported by Gregorio Narvaez, and looked like this:
+
+      Use of uninitialized value $_[0] in length at
+      ../../blib/lib/NetAddr/IP/UtilPP.pm (autosplit into
+      ../../blib/lib/auto/NetAddr/IP/UtilPP/hasbits.al) line 126.
+      Use of uninitialized value $_[0] in length at
+      ../../blib/lib/NetAddr/IP/UtilPP.pm (autosplit into
+      ../../blib/lib/auto/NetAddr/IP/UtilPP/hasbits.al) line 126.
+      Bad argument length for NetAddr::IP::UtilPP::hasbits, is 0, should be
+      128 at ../../blib/lib/NetAddr/IP/UtilPP.pm (autosplit into
+      ../../blib/lib/auto/NetAddr/IP/UtilPP/_deadlen.al) line 122.
+
+    - Added --stdin argument to allow psad to collect iptables log data from
+      STDIN in --Analyze mode.
+
 psad-2.2 (02/20/2012):
     - Added support for detection of malicious traffic that is delivered via
       IPv6.  This is accomplished by parsing ip6tables log messages - these are
diff --git a/psad b/psad
index 9e5cce8..0afb0e0 100755 (executable)
--- a/psad
+++ b/psad
@@ -393,6 +393,7 @@ my $csv_end_line     = 0;
 my $csv_regex        = '';
 my $csv_neg_regex    = '';
 my $csv_have_timestamp = 0;
+my $pkts_from_stdin = 0;
 my $dump_ipt_policy  = 0;
 my $fw_include_ips   = 0;
 my $benchmark        = 0;
@@ -1925,7 +1926,7 @@ sub parse_NF_pkt_str() {
         ### we are looking to analyze packets from a specific IP/subnet
         if ($pkt_hr->{'is_ipv6'}) {
             if ($restrict_ip->version() == 6) {
-                return $PKT_IGNORE unless 
+                return $PKT_IGNORE unless
                     $pkt_hr->{'s_obj'}->within($restrict_ip) or
                     $pkt_hr->{'d_obj'}->within($restrict_ip);
             }
@@ -7194,13 +7195,20 @@ sub analysis_mode() {
     }
 
     print "[+] Entering analysis mode.  Parsing $fw_data_file\n";
-    open MSGS, "< $fw_data_file" or die "[*] Could not open ",
-        "$fw_data_file: $!";
-    my @lines = <MSGS>;
-    close MSGS;
+    my $fh = '';
+    if ($pkts_from_stdin) {
+        $fh = *STDIN;
+    } else {
+        open MSGS, "< $fw_data_file" or die "[*] Could not open ",
+            "$fw_data_file: $!";
+        $fh = *MSGS;
+    }
     my @ipt_msgs = ();
     my $pkt_ctr = 0;
-    PKT: for my $line (@lines) {
+    my $line_ctr = 0;
+    PKT: while (<$fh>) {
+        my $line = $_;
+        $line_ctr++;
         if ($num_packets > 0) {
             last PKT if $pkt_ctr >= $num_packets;
         }
@@ -7223,8 +7231,10 @@ sub analysis_mode() {
             }
         }
     }
-    print "[+] Found ", ($#ipt_msgs+1), " iptables log messages out of ",
-        ($#lines+1), " total lines.\n";
+    close $fh unless $pkts_from_stdin;
+
+    print "[+] Found ", ($#ipt_msgs+1), " iptables log messages out of " .
+        "$line_ctr total lines.\n";
     print "    This may take a while...\n" if $#ipt_msgs > 15000;
 
     ### analyze all packets
@@ -7278,18 +7288,20 @@ sub ipt_match_criteria() {
                     return [], '' unless $pkt_hr->{$tok} =~ m|$match_hr->{'re'}|;
                 }
             } elsif (defined $match_hr->{'net'} or defined $match_hr->{'ip'}) {
+                my $net_or_ip_key = 'ip_obj';
+                $net_or_ip_key = 'net_obj' if defined $match_hr->{'net'};
                 if ($pkt_hr->{$tok} =~ m|$ipv4_re|
                         or $pkt_hr->{$tok} =~ m|$ipv6_re|) {
                     my $ip_match_obj = '';
                     if ($tok eq 'src') {
                         $ip_match_obj = $pkt_hr->{'s_obj'};
                     } elsif ($tok eq 'dst') {
-                        $ip_match_obj = $pkt_hr->{'s_obj'};
+                        $ip_match_obj = $pkt_hr->{'d_obj'};
                     }
                     if ($match_hr->{'negate'}) {
-                        return [], '' if $ip_match_obj->within($match_hr->{'net'});
+                        return [], '' if $ip_match_obj->within($match_hr->{$net_or_ip_key});
                     } else {
-                        return [], '' unless $ip_match_obj->within($match_hr->{'net'});
+                        return [], '' unless $ip_match_obj->within($match_hr->{$net_or_ip_key});
                     }
                 } else {
                     return [], '';
@@ -7413,7 +7425,7 @@ sub csv_mode() {
                 }
             }
         }
-        close MSGS;
+        close $fh unless $csv_stdin;
     }
 
     if ($gnuplot_mode) {
@@ -8113,6 +8125,7 @@ sub csv_tokens() {
                     die "[*] $tok_str requires a search criteria in -A mode.";
                 }
             }
+            $search =~ s/\,$//;
             if ($token eq 'timestamp') {
                 $csv_have_timestamp = 1;
             }
@@ -8195,10 +8208,27 @@ sub csv_tokens() {
                     $search_hsh{'str'} = $1;
                 } elsif ($search =~ m|^$ipv4_re/$ipv4_re$|) {
                     $search_hsh{'net'} = $search;
+                    $search_hsh{'net_obj'} = new NetAddr::IP($search)
+                        or die "[*] NetAddr::IP($search) error";
                 } elsif ($search =~ m|^$ipv4_re/\d+$|) {
                     $search_hsh{'net'} = $search;
+                    $search_hsh{'net_obj'} = new NetAddr::IP($search)
+                        or die "[*] NetAddr::IP($search) error";
                 } elsif ($search =~ m|^$ipv4_re$|) {
                     $search_hsh{'ip'} = $search;
+                    $search_hsh{'ip_obj'} = new NetAddr::IP($search)
+                        or die "[*] NetAddr::IP($search) error";
+                } elsif ($search =~ m|\:|) {
+                    ### see if this is an IPv6 address
+                    if ($search =~ m|\/|) {
+                        $search_hsh{'net'} = $search;
+                        $search_hsh{'net_obj'} = new6 NetAddr::IP($search)
+                            or die "[*] NetAddr::IP($search) error";
+                    } else {
+                        $search_hsh{'net'} = $search;
+                        $search_hsh{'net_obj'} = new6 NetAddr::IP($search)
+                            or die "[*] NetAddr::IP($search) error";
+                    }
                 } else {
                     die "[*] Unrecognized value for $token";
                 }
@@ -9825,7 +9855,7 @@ sub get_scale_factor() {
             $val = 50000;
         }
     }
-    $val++ if $val == 0;
+    $val++;
     return $val;
 }
 
@@ -10509,7 +10539,8 @@ sub getopt_wrapper() {
         'analysis-fields=s' => \$analysis_fields, # Place a criteria on various fields
                                                   #   that are parsed from an iptables
                                                   #   logfile.
-        'analyze-fields=s'  => \$analysis_fields,
+        'analyze-fields=s'  => \$analysis_fields, # Synonym.
+        'stdin'             => \$pkts_from_stdin,
         'whois-analysis'    => \$analysis_whois,  # Issue whois lookups in analysis
                                                   #   mode.
         'dns-analysis'      => \$enable_analysis_dns,  # Issue DNS lookups in -A mode.
diff --git a/psad.8 b/psad.8
index 2f7c8df..a0d22a7 100644 (file)
--- a/psad.8
+++ b/psad.8
@@ -82,6 +82,12 @@ to point psad at your
 .I /var/log/messages
 file.
 .TP
+.BR \-\^\-analysis-fields\ \<search\ fields>
+In --Analyze mode restrict analysis to iptables log messages that have specific
+values for particular fields.  Examples include "SRC:1.2.3.4", "DST:10.0.0.0/24,
+and "TTL:64", and multiple fields are supported as a comma-separated list like
+"SRC:1.2.3.4, LEN:44, DST:10.0.0.0/24".
+.TP
 .BR \-i "\fR,\fP " \-\^\-interface\ \<interface>
 Specify the interface that
 .B psad
diff --git a/test/install.answers b/test/install.answers
new file mode 100644 (file)
index 0000000..2c0112e
--- /dev/null
@@ -0,0 +1,24 @@
+Would you like to merge the config from the existing psad installation:        y;
+Preserve any user modfications in etc psad signatures:        y;
+Preserve any user modfications in etc psad icmp_types:        y;
+Preserve any user modfications in etc psad icmp6_types:        y;
+Preserve any user modfications in etc psad posf:        y;
+Preserve any user modfications in etc psad auto_dl:        y;
+Preserve any user modfications in etc psad snort_rule_dl:        y;
+Preserve any user modfications in etc psad pf os:        y;
+Preserve any user modfications in etc psad ip_options:        y;
+Would you like alerts sent to a different address:        n;
+Email addresses:        root@localhost;
+Would you like psad to only parse specific strings in iptables messages:        n;
+First is it ok to leave the HOME_NET setting as any:        y;
+Would you like to enable DShield alerts:        n;
+Would you like to install the latest signatures from http www cipherdyne org psad signatures:        n;
+Enable psad at boot time:        n;
+Preserve any user modfications in home mbr git psad git test psad install etc psad signatures:        y;
+Preserve any user modfications in home mbr git psad git test psad install etc psad icmp_types:        y;
+Preserve any user modfications in home mbr git psad git test psad install etc psad icmp6_types:        y;
+Preserve any user modfications in home mbr git psad git test psad install etc psad posf:        y;
+Preserve any user modfications in home mbr git psad git test psad install etc psad auto_dl:        y;
+Preserve any user modfications in home mbr git psad git test psad install etc psad snort_rule_dl:        y;
+Preserve any user modfications in home mbr git psad git test psad install etc psad pf os:        y;
+Preserve any user modfications in home mbr git psad git test psad install etc psad ip_options:        y;
index ee87d78..524394e 100755 (executable)
@@ -1,5 +1,6 @@
 #!/usr/bin/perl -w
 
+use Cwd;
 use File::Copy;
 use File::Path;
 use Getopt::Long 'GetOptions';
@@ -11,6 +12,7 @@ my $output_dir     = 'output';
 my $conf_dir       = 'conf';
 my $run_dir        = 'run';
 my $scans_dir      = 'scans';
+my $test_install_dir = 'psad-install';
 my $syn_scan_file  = 'syn_scan_1000_1500';
 my $fin_scan_file  = 'fin_scan_1000_1150';
 my $xmas_scan_file = 'xmas_scan_1000_1150';
@@ -34,7 +36,7 @@ my $dl5_ipv4_subnet_auto_dl_file = "$conf_dir/auto_dl_5_192.168.10.0_24";
 my $dl5_ipv4_subnet_auto_dl_file_tcp = "$conf_dir/auto_dl_5_192.168.10.0_24_tcp";
 my $dl5_ipv4_subnet_auto_dl_file_udp = "$conf_dir/auto_dl_5_192.168.10.0_24_udp";
 
-my $psadCmd        = 'psad-install/usr/sbin/psad';
+my $psadCmd        = "$test_install_dir/usr/sbin/psad";
 
 my $cmd_out_tmp    = 'cmd.out';
 my $default_conf   = "$conf_dir/default_psad.conf";
@@ -106,6 +108,16 @@ if ($test_system_install) {
 ### define all tests
 my @tests = (
     {
+        'category' => 'install',
+        'detail'   => "test directory: $test_install_dir",
+        'err_msg'  => 'could not install',
+        'function' => \&install_test_dir,
+        'cmdline'  => "./install.pl --install-test-dir --Use-answers " .
+            "--answers-file test/install.answers",
+        'exec_err' => $NO,
+        'fatal'    => $YES
+    },
+    {
         'category' => 'compilation',
         'detail'   => 'psad compiles',
         'err_msg'  => 'could not compile',
@@ -418,6 +430,146 @@ my @tests = (
         'exec_err'  => $NO,
         'fatal'     => $NO
     },
+    {
+        'category'  => 'operations',
+        'detail'    => 'ignore src via --analysis-fields SRC:1.2.3.4',
+        'err_msg'   => 'did not ignore SRC:1.2.3.4',
+        'positive_output_matches' => [
+            qr/Level 1\: 0 IP addresses/,
+            qr/Level 2\: 0 IP addresses/,
+            qr/Level 3\: 0 IP addresses/,
+            qr/Level 4\: 0 IP addresses/,
+            qr/Level 5\: 0 IP addresses/,
+        ],
+        'match_all' => $MATCH_ALL_RE,
+        'function'  => \&generic_exec,
+        'cmdline'   => "$psadCmd --test-mode -A --analysis-fields SRC:1.2.3.4 " .
+                "-m $scans_dir/" .  &fw_type() . "/$syn_scan_file -c $default_conf $normal_root_override_str",
+        'exec_err'  => $NO,
+        'fatal'     => $NO
+    },
+    {
+        'category'  => 'operations',
+        'detail'    => 'match src via --analysis-fields SRC:192.168.10.55',
+        'err_msg'   => 'did not match SRC:192.168.10.55',
+        'positive_output_matches' => [
+            qr/Top\s\d+\sattackers/i,
+            qr/scanned\sports.*?1000\-1500\b/i,
+            qr/Source\sOS/i, qr/BACKDOOR/i,
+            qr/IP\sstatus/i,
+            qr/192\.168\.10\.55/
+        ],
+        'match_all' => $MATCH_ALL_RE,
+        'function'  => \&generic_exec,
+        'cmdline'   => "$psadCmd --test-mode -A --analysis-fields SRC:192.168.10.55 " .
+                "-m $scans_dir/" .  &fw_type() . "/$syn_scan_file -c $default_conf $normal_root_override_str",
+        'exec_err'  => $NO,
+        'fatal'     => $NO
+    },
+    {
+        'category'  => 'operations',
+        'detail'    => 'ignore src via --analysis-fields DST:1.2.3.4',
+        'err_msg'   => 'did not ignore DST:1.2.3.4',
+        'positive_output_matches' => [
+            qr/Level 1\: 0 IP addresses/,
+            qr/Level 2\: 0 IP addresses/,
+            qr/Level 3\: 0 IP addresses/,
+            qr/Level 4\: 0 IP addresses/,
+            qr/Level 5\: 0 IP addresses/,
+        ],
+        'match_all' => $MATCH_ALL_RE,
+        'function'  => \&generic_exec,
+        'cmdline'   => "$psadCmd --test-mode -A --analysis-fields DST:1.2.3.4 " .
+                "-m $scans_dir/" .  &fw_type() . "/$syn_scan_file -c $default_conf $normal_root_override_str",
+        'exec_err'  => $NO,
+        'fatal'     => $NO
+    },
+    {
+        'category'  => 'operations',
+        'detail'    => 'match src via --analysis-fields DST:192.168.10.1',
+        'err_msg'   => 'did not match DST:192.168.10.1',
+        'positive_output_matches' => [
+            qr/Top\s\d+\sattackers/i,
+            qr/scanned\sports.*?1000\-1500\b/i,
+            qr/Source\sOS/i, qr/BACKDOOR/i,
+            qr/IP\sstatus/i,
+            qr/192\.168\.10\.55/
+        ],
+        'match_all' => $MATCH_ALL_RE,
+        'function'  => \&generic_exec,
+        'cmdline'   => "$psadCmd --test-mode -A --analysis-fields DST:192.168.10.1 " .
+                "-m $scans_dir/" .  &fw_type() . "/$syn_scan_file -c $default_conf $normal_root_override_str",
+        'exec_err'  => $NO,
+        'fatal'     => $NO
+    },
+    {
+        'category'  => 'operations',
+        'detail'    => '--analysis-fields SRC:192.168.10.55, DST:192.168.10.1',
+        'err_msg'   => 'did not match SRC:192.168.10.55, DST:192.168.10.1',
+        'positive_output_matches' => [
+            qr/Top\s\d+\sattackers/i,
+            qr/scanned\sports.*?1000\-1500\b/i,
+            qr/Source\sOS/i, qr/BACKDOOR/i,
+            qr/IP\sstatus/i,
+            qr/192\.168\.10\.55/
+        ],
+        'match_all' => $MATCH_ALL_RE,
+        'function'  => \&generic_exec,
+        'cmdline'   => qq|$psadCmd --test-mode -A --analysis-fields "SRC:192.168.10.55, DST:192.168.10.1" | .
+                "-m $scans_dir/" .  &fw_type() . "/$syn_scan_file -c $default_conf $normal_root_override_str",
+        'exec_err'  => $NO,
+        'fatal'     => $NO
+    },
+    {
+        'category'  => 'operations',
+        'detail'    => 'ignore length via --analysis-fields LEN:15',
+        'err_msg'   => 'did not ignore LEN:15',
+        'positive_output_matches' => [
+            qr/Level 1\: 0 IP addresses/,
+            qr/Level 2\: 0 IP addresses/,
+            qr/Level 3\: 0 IP addresses/,
+            qr/Level 4\: 0 IP addresses/,
+            qr/Level 5\: 0 IP addresses/,
+        ],
+        'match_all' => $MATCH_ALL_RE,
+        'function'  => \&generic_exec,
+        'cmdline'   => "$psadCmd --test-mode -A --analysis-fields LEN:15 " .
+                "-m $scans_dir/" .  &fw_type() . "/$syn_scan_file -c $default_conf $normal_root_override_str",
+        'exec_err'  => $NO,
+        'fatal'     => $NO
+    },
+    {
+        'category'  => 'operations',
+        'detail'    => 'match length via --analysis-fields LEN:44',
+        'err_msg'   => 'did not match LEN:44',
+        'positive_output_matches' => [
+            qr/Top\s\d+\sattackers/i,
+            qr/scanned\sports.*?1000\-1500\b/i,
+            qr/Source\sOS/i, qr/BACKDOOR/i,
+            qr/IP\sstatus/i,
+            qr/192\.168\.10\.55/
+        ],
+        'match_all' => $MATCH_ALL_RE,
+        'function'  => \&generic_exec,
+        'cmdline'   => "$psadCmd --test-mode -A --analysis-fields LEN:44 " .
+                "-m $scans_dir/" .  &fw_type() . "/$syn_scan_file -c $default_conf $normal_root_override_str",
+        'exec_err'  => $NO,
+        'fatal'     => $NO
+    },
+    {
+        'category'  => 'operations',
+        'detail'    => 'invalid --analysis-fields BOGUS:44',
+        'err_msg'   => 'allowed BOGUS:44',
+        'positive_output_matches' => [
+            qr/valid fields are/
+        ],
+        'match_all' => $MATCH_ALL_RE,
+        'function'  => \&generic_exec,
+        'cmdline'   => "$psadCmd --test-mode -A --analysis-fields BOGUS:44 " .
+                "-m $scans_dir/" .  &fw_type() . "/$syn_scan_file -c $default_conf $normal_root_override_str",
+        'exec_err'  => $YES,
+        'fatal'     => $NO
+    },
 
     {
         'category'  => 'operations',
@@ -796,6 +948,49 @@ sub look_for_warnings() {
     return $rv;
 }
 
+sub install_test_dir() {
+    my $test_hr = shift;
+
+    my $rv = 1;
+    my $curr_pwd = cwd() or die $!;
+
+    if (-d $test_install_dir) {
+        rmtree $test_install_dir or die $!;
+    }
+    mkdir $test_install_dir  or die $!;
+
+    chdir '..' or die $!;
+
+    my $exec_rv = &run_cmd($test_hr->{'cmdline'},
+                "test/$cmd_out_tmp", "test/$current_test_file");
+
+    if ($test_hr->{'exec_err'} eq $YES) {
+        $rv = 0 if $exec_rv;
+    } elsif ($test_hr->{'exec_err'} eq $NO) {
+        $rv = 0 unless $exec_rv;
+    } else {
+        $rv = 1;
+    }
+
+    if ($test_hr->{'positive_output_matches'}) {
+        $rv = 0 unless &file_find_regex(
+            $test_hr->{'positive_output_matches'},
+            $test_hr->{'match_all'},
+            $current_test_file);
+    }
+
+    if ($test_hr->{'negative_output_matches'}) {
+        $rv = 0 if &file_find_regex(
+            $test_hr->{'negative_output_matches'},
+            $test_hr->{'match_all'},
+            $current_test_file);
+    }
+
+    chdir $curr_pwd or die $!;
+
+    return $rv;
+}
+
 sub generic_exec() {
     my $test_hr = shift;