validate ICMP6 type+code fields
authorMichael Rash <mbr@cipherdyne.org>
Fri, 23 Mar 2012 03:00:00 +0000 (23:00 -0400)
committerMichael Rash <mbr@cipherdyne.org>
Fri, 23 Mar 2012 03:00:00 +0000 (23:00 -0400)
13 files changed:
icmp6_types [new file with mode: 0644]
icmp_types
install.pl
psad
psad.conf
test/conf/default_psad.conf
test/conf/disable_ipv6_detection.conf
test/conf/enable_ack_detection.conf
test/conf/ignore_tcp.conf
test/conf/ignore_udp.conf
test/conf/require_DROP_syslog_prefix_str.conf
test/conf/require_missing_syslog_prefix_str.conf
test/test-psad.pl

diff --git a/icmp6_types b/icmp6_types
new file mode 100644 (file)
index 0000000..18b1041
--- /dev/null
@@ -0,0 +1,99 @@
+#
+###############################################################################
+#
+# File: icmp6_types
+#
+# Purpose: This file contains all valid icmp6 types and corresponding codes as
+#          defined by IANA.  If a packet is logged by iptables that does
+#          not have a valid type and/or code, then an alert will be generated.
+#
+###############################################################################
+#
+
+# Type
+#  Code values
+
+0    Reserved
+1    Destination Unreachable
+    0   No route to destination
+    1   Communication with destination administratively prohibited
+    2   Beyond scope of source address
+    3   Address unreachable
+    4   Port unreachable
+    5   Source address failed ingress/egress policy
+    6   Reject route to destination
+    7   Error in Source Routing Header
+
+2    Packet Too Big
+    0
+
+3    Time Exceeded
+    0   Hop limit exceeded in transit
+    1   Fragment reassembly time exceeded
+
+4    Parameter Problem
+    0   Erroneous header field encountered
+    1   Unrecognized Next Header type encountered
+    2   Unrecognized IPv6 option encountered
+
+128     Echo Request
+    0
+
+129     Echo Reply
+    0
+
+130     Multicast Listener Query
+    0
+
+131     Multicast Listener Report
+    0
+
+132     Multicast Listener Done
+    0
+
+133     Router Solicitation
+    0
+
+134     Router Advertisement
+    0
+
+135     Neighbor Solicitation
+    0
+
+136     Neighbor Advertisement
+    0
+
+ 137     Redirect Message
+     0
+
+138     Router Renumbering
+    0   Router Renumbering Command
+    1   Router Renumbering Result
+    255   Sequence Number Reset
+
+139     ICMP Node Information Query
+    0   The Data field contains an IPv6 address which is the Subject of this Query.
+    1   The Data field contains a name which is the Subject of this Query, or is empty, as in the case of a NOOP.
+    2   The Data field contains an IPv4 address which is the Subject of this Query.
+
+140     ICMP Node Information Response
+    0   A successful reply.  The Reply Data field may or may not be empty.
+    1   The Responder refuses to supply the answer.  The Reply Data field will be empty.
+    2   The Qtype of the Query is unknown to the Responder.  The Reply Data field will be empty.
+
+141     Inverse Neighbor Discovery Solicitation Message
+    0
+
+142     Inverse Neighbor Discovery Advertisement Message
+    0
+144     Home Agent Address Discovery Request Message
+    0
+
+145     Home Agent Address Discovery Reply Message
+    0
+
+146     Mobile Prefix Solicitation
+    0
+
+147     Mobile Prefix Advertisement
+    0
index 5a8f3cd..cfa3719 100644 (file)
@@ -1,7 +1,7 @@
 #
 ###############################################################################
 #
-# File: psad_icmp_types
+# File: icmp_types
 #
 # Purpose: This file contains all valid icmp types and corresponding codes as
 #          defined in RFC 792.  If a packet is logged by iptables that does
@@ -12,7 +12,6 @@
 
 0   Echo Reply
     0
-    NONE
 
 1   Unassigned
 
@@ -38,7 +37,6 @@
 
 4 Source Quench
     0
-    NONE
 
 5 Redirect
     0   Redirect Datagram for the Network (or subnet)
@@ -53,7 +51,6 @@
 
 8   Echo
     0
-    NONE
 
 9   Router Advertisement
     0   Normal router advertisement
@@ -62,7 +59,6 @@
 
 10  Router Selection
     0
-    NONE
 
 11  Time Exceeded
     0   Time to Live exceeded in Transit
 
 13  Timestamp
     0
-    NONE
 
 14  Timestamp Reply
     0
-    NONE
 
 15  Information Request
     0
-    NONE
 
 16  Information Reply
     0
-    NONE
 
 17  Address Mask Request
     0
-    NONE
 
 18  Address Mask Reply
     0
-    NONE
 
 19  Reserved (for Security)
 
index 9cce112..dcd89d0 100755 (executable)
@@ -61,6 +61,7 @@ my %file_vars = (
     'signatures'    => 'SIGS_FILE',
     'auto_dl'       => 'AUTO_DL_FILE',
     'icmp_types'    => 'ICMP_TYPES_FILE',
+    'icmp6_types'   => 'ICMP6_TYPES_FILE',
     'posf'          => 'POSF_FILE',
     'pf.os'         => 'P0F_FILE',
     'snort_rule_dl' => 'SNORT_RULE_DL_FILE',
@@ -536,7 +537,7 @@ sub install() {
     &perms_ownership($prod_file, 0600);
 
     ### install auto_dl, signatures, icmp_types, posf, and pf.os files
-    for my $filename (qw(signatures icmp_types
+    for my $filename (qw(signatures icmp_types icmp6_types
             posf auto_dl snort_rule_dl pf.os ip_options)) {
         my $file = $config{$file_vars{$filename}};
         if (-e $file) {
diff --git a/psad b/psad
index 3bf251f..3aa30be 100755 (executable)
--- a/psad
+++ b/psad
@@ -208,7 +208,8 @@ my %posf = ();
 my %posf_sigs = ();
 
 ### cache valid icmp types and corresponding codes
-my %valid_icmp_types = ();
+my %valid_icmp_types  = ();
+my %valid_icmp6_types = ();
 
 ### Cache snort rule messages unless --no-snort-sids switch was
 ### given.  This is only useful if iptables includes rules
@@ -429,6 +430,7 @@ my $num_hash_marks = 76;  ### for gnuplot output
 my $no_snort_sids = 0;
 my $no_signatures = 0;
 my $no_icmp_types = 0;
+my $no_icmp6_types = 0;
 my $no_auto_dl    = 0;
 my $no_posf       = 0;
 my $no_daemon     = 0;
@@ -1137,11 +1139,12 @@ sub check_scan() {
             ### validate icmp type and code fields against the official values
             ### in RFC 792.  See %inval_type_code for corresponding signature
             ### message text and danger levels.
-            my $type_code_rv = &check_icmp_type($pkt{'itype'},
-                    $pkt{'icode'});
+            my $type_code_rv = &check_icmp_type(
+                    'icmp', \%valid_icmp_types,
+                    $pkt{'itype'}, $pkt{'icode'});
 
             my $update_dl = 0;
-            if ($type_code_rv == $BAD_ICMP_TYPE) {  ### bad type
+            if ($type_code_rv == $BAD_ICMP_TYPE) {
 
                 $scan{$pkt{'src'}}{$pkt{'dst'}}{'icmp'}
                     {'invalid_type'}{$pkt{'itype'}}
@@ -1167,6 +1170,43 @@ sub check_scan() {
                     $scan_dl{$pkt{'src'}} = 2;
                 }
             }
+
+        } elsif ($pkt{'proto'} eq 'icmp6') {
+
+            ### validate icmp6 type and code fields against the official values
+            ### defined by IANA.  See %inval_type_code for corresponding signature
+            ### message text and danger levels.
+            my $type_code_rv = &check_icmp_type(
+                    'icmp6', \%valid_icmp6_types,
+                    $pkt{'itype'}, $pkt{'icode'});
+
+            my $update_dl = 0;
+            if ($type_code_rv == $BAD_ICMP_TYPE) {
+
+                $scan{$pkt{'src'}}{$pkt{'dst'}}{'icmp6'}
+                    {'invalid_type'}{$pkt{'itype'}}
+                    {$pkt{'chain'}}{'pkts'}++;
+
+                $update_dl = 1;
+
+            } elsif ($type_code_rv == $BAD_ICMP_CODE) {
+
+                $scan{$pkt{'src'}}{$pkt{'dst'}}{'icmp6'}
+                    {'invalid_code'}{$pkt{'itype'}}{$pkt{'icode'}}
+                    {$pkt{'chain'}}{'pkts'}++;
+
+                $update_dl = 1;
+            }
+
+            if ($update_dl) {
+                if (defined $scan_dl{$pkt{'src'}}) {
+                    if ($scan_dl{$pkt{'src'}} < 2) {
+                        $scan_dl{$pkt{'src'}} = 2;
+                    }
+                } else {
+                    $scan_dl{$pkt{'src'}} = 2;
+                }
+            }
         }
 
         unless ($no_snort_sids) {
@@ -1847,17 +1887,17 @@ sub parse_NF_pkt_str() {
             }
 
             $pkt_hr->{'proto'} = 'icmp';
-        }
 
-        if ($pkt_hr->{'itype'} == $ICMP_ECHO_REQUEST
-                or $pkt_hr->{'itype'} == $ICMP_ECHO_REPLY) {
+            if ($pkt_hr->{'itype'} == $ICMP_ECHO_REQUEST
+                    or $pkt_hr->{'itype'} == $ICMP_ECHO_REPLY) {
 
-            ### we expect the ICMP ID and SEQ fields to be populated
-            if ($pkt_str =~ /CODE=(\d+)\s+ID=(\d+)\s+SEQ=(\d+)/) {
-                $pkt_hr->{'icmp_id'}  = $1;
-                $pkt_hr->{'icmp_seq'} = $2;
-            } else {
-                return $PKT_ERROR;
+                ### we expect the ICMP ID and SEQ fields to be populated
+                if ($pkt_str =~ /CODE=(\d+)\s+ID=(\d+)\s+SEQ=(\d+)/) {
+                    $pkt_hr->{'icmp_id'}  = $1;
+                    $pkt_hr->{'icmp_seq'} = $2;
+                } else {
+                    return $PKT_ERROR;
+                }
             }
         }
 
@@ -2116,6 +2156,7 @@ sub match_snort_keywords() {
                 $top_sigs{$sid}++;
                 $top_sig_counts{$pkt_hr->{'src'}}++;
             }
+
         } elsif ($sig_hr->{'proto'} eq 'udp') {
 
             ($rv, $sig_match_rv) = &match_snort_udp_keywords($pkt_hr, $sig_hr);
@@ -2144,6 +2185,7 @@ sub match_snort_keywords() {
                 $top_sigs{$sid}++;
                 $top_sig_counts{$pkt_hr->{'src'}}++;
             }
+
         } elsif ($sig_hr->{'proto'} eq 'icmp') {
 
             ($rv, $sig_match_rv) = &match_snort_icmp_keywords($pkt_hr, $sig_hr);
@@ -2168,8 +2210,10 @@ sub match_snort_keywords() {
                 $top_sigs{$sid}++;
                 $top_sig_counts{$pkt_hr->{'src'}}++;
             }
-        }
 
+        } elsif ($sig_hr->{'proto'} eq 'icmp6') {
+            ### FIXME icmp6 specifc Snort matches?
+        }
     }
     return $dl, $matched_sig;
 }
@@ -3035,16 +3079,16 @@ sub dshield_email_log() {
 }
 
 sub check_icmp_type() {
-    my ($type, $code) = @_;
+    my ($proto, $valid_types_hr, $type, $code) = @_;
     print STDERR "    check_icmp_type(type: $type, code: $code)\n" if $debug;
-    if (not defined $valid_icmp_types{$type}) {
-        print STDERR "        BAD_ICMP_TYPE\n" if $debug;
+    if (not defined $valid_types_hr->{$type}) {
+        print STDERR "        bad $proto type\n" if $debug;
         return $BAD_ICMP_TYPE;
-    } elsif (not defined $valid_icmp_types{$type}{'code'}{$code}) {
-        print STDERR "        BAD_ICMP_CODE\n" if $debug;
+    } elsif (not defined $valid_types_hr->{$type}->{'codes'}->{$code}) {
+        print STDERR "        bad $proto code\n" if $debug;
         return $BAD_ICMP_CODE;
     }
-    print STDERR "        valid type/code\n" if $debug;
+    print STDERR "        valid $proto type/code\n" if $debug;
     return 0;
 }
 
@@ -3231,7 +3275,10 @@ sub psad_init() {
     ### import icmp types and codes from psad_icmp_types; icmp "type"
     ### and "code" fields will be validated against the values in this
     ### file.
-    &import_icmp_types() unless $no_icmp_types;
+    &import_icmp_types('icmp', \%valid_icmp_types, $config{'ICMP_TYPES_FILE'})
+        unless $no_icmp_types;
+    &import_icmp_types('icmp6', \%valid_icmp6_types,
+        $config{'ICMP6_TYPES_FILE'}) unless $no_icmp6_types;
 
     ### import p0f-based passive OS fingerprinting signatures
     &import_p0f_ipv4_sigs() unless $no_posf;
@@ -4258,26 +4305,23 @@ sub check_supported_options() {
 }
 
 sub import_icmp_types() {
+    my ($proto, $type_hr, $file) = @_;
 
-    %valid_icmp_types = ();
-
-    open TYPES, "< $config{'ICMP_TYPES_FILE'}" or die
-        "[*] Could not open $config{'ICMP_TYPES_FILE'}: $!";
-    my @lines = <TYPES>;
-    close TYPES;
+    open F, "< $file" or die "[*] Could not open $file: $!";
     my $icmp_type = -1;
-    for my $line (@lines) {
-        next if $line =~ /^\s*#/;
-        if ($line =~ /^(\d+)\s+(.*)/) {
-            $icmp_type      = $1;
+    while (<F>) {
+        next if /^\s*#/;
+        if (/^(\d+)\s+(.*)/) {
+            $icmp_type = $1;
             my $icmp_type_text = $2;
             if ($icmp_type_text =~ /unassigned/i) {
                 $icmp_type = -1;
             }
-            $valid_icmp_types{$icmp_type}{'text'} = $icmp_type_text;
+            $type_hr->{$icmp_type}->{'text'} = $icmp_type_text;
+            $type_hr->{$icmp_type}->{'codes'}{-1} = '';
             next;
         }
-        if ($icmp_type > -1 and $line =~ /^\s+(\d+)\s+(.*)/) {
+        if (/^\s+(\d+)\s+(.*)/ and $icmp_type > -1) {
             my $icmp_code      = $1;
             my $icmp_code_text = $2;
             next if $icmp_code_text =~ /unassigned/i;
@@ -4285,11 +4329,12 @@ sub import_icmp_types() {
             ### we validate against the icmp type first (i.e. an invalid
             ### icmp code is meaningless unless we first have a valid
             ### icmp type).
-            $valid_icmp_types{$icmp_type}{'codes'}{$icmp_code} = '';
+            $type_hr->{$icmp_type}->{'codes'}->{$icmp_code} = '';
         }
     }
-    print STDERR Dumper %valid_icmp_types if $debug and $verbose;
-    &sys_log('imported valid icmp types and codes');
+    close F;
+    print STDERR Dumper $type_hr if $debug and $verbose;
+    &sys_log("imported valid $proto types and codes");
     return;
 }
 
@@ -10555,6 +10600,7 @@ sub getopt_wrapper() {
                                                   #   fingerprint the remote OS.
         'no-signatures'     => \$no_signatures,   # Disable signature processing.
         'no-icmp-types'     => \$no_icmp_types,   # Disable icmp type/code validation.
+        'no-icmp6-types'    => \$no_icmp6_types,  # Disable icmp6 type/code validation.
         'no-auto-dl'        => \$no_auto_dl,      # Disable auto danger level
                                                   #   assignment.
         'no-daemon'         => \$no_daemon,       # Do not run as a daemon.
@@ -10631,6 +10677,7 @@ sub required_vars() {
         ETC_RSYSLOG_CONF IFCFGTYPE ENABLE_WHOIS_FORCE_ASCII
         ENABLE_WHOIS_FORCE_SRC_IP ENABLE_IPV6_DETECTION
         PERSISTENCE_CTR_THRESHOLD MAX_SCAN_IP_PAIRS INSTALL_ROOT
+        ICMP6_TYPES_FILE
     ));
     &defined_vars(\@required_vars);
     return;
index a5d63df..433e177 100644 (file)
--- a/psad.conf
+++ b/psad.conf
@@ -487,6 +487,7 @@ FW_CHECK_FILE               $PSAD_DIR/fw_check;
 DSHIELD_EMAIL_FILE          $PSAD_DIR/dshield.email;
 SIGS_FILE                   $PSAD_CONF_DIR/signatures;
 ICMP_TYPES_FILE             $PSAD_CONF_DIR/icmp_types;
+ICMP6_TYPES_FILE            $PSAD_CONF_DIR/icmp6_types;
 AUTO_DL_FILE                $PSAD_CONF_DIR/auto_dl;
 SNORT_RULE_DL_FILE          $PSAD_CONF_DIR/snort_rule_dl;
 POSF_FILE                   $PSAD_CONF_DIR/posf;
index 98bf4de..48aefb0 100644 (file)
@@ -487,6 +487,7 @@ FW_CHECK_FILE               $PSAD_DIR/fw_check;
 DSHIELD_EMAIL_FILE          $PSAD_DIR/dshield.email;
 SIGS_FILE                   $PSAD_CONF_DIR/signatures;
 ICMP_TYPES_FILE             $PSAD_CONF_DIR/icmp_types;
+ICMP6_TYPES_FILE            $PSAD_CONF_DIR/icmp6_types;
 AUTO_DL_FILE                $PSAD_CONF_DIR/auto_dl;
 SNORT_RULE_DL_FILE          $PSAD_CONF_DIR/snort_rule_dl;
 POSF_FILE                   $PSAD_CONF_DIR/posf;
index 3af14e3..7589e72 100644 (file)
@@ -487,6 +487,7 @@ FW_CHECK_FILE               $PSAD_DIR/fw_check;
 DSHIELD_EMAIL_FILE          $PSAD_DIR/dshield.email;
 SIGS_FILE                   $PSAD_CONF_DIR/signatures;
 ICMP_TYPES_FILE             $PSAD_CONF_DIR/icmp_types;
+ICMP6_TYPES_FILE            $PSAD_CONF_DIR/icmp6_types;
 AUTO_DL_FILE                $PSAD_CONF_DIR/auto_dl;
 SNORT_RULE_DL_FILE          $PSAD_CONF_DIR/snort_rule_dl;
 POSF_FILE                   $PSAD_CONF_DIR/posf;
index d3600e8..a1f6fa3 100644 (file)
@@ -487,6 +487,7 @@ FW_CHECK_FILE               $PSAD_DIR/fw_check;
 DSHIELD_EMAIL_FILE          $PSAD_DIR/dshield.email;
 SIGS_FILE                   $PSAD_CONF_DIR/signatures;
 ICMP_TYPES_FILE             $PSAD_CONF_DIR/icmp_types;
+ICMP6_TYPES_FILE            $PSAD_CONF_DIR/icmp6_types;
 AUTO_DL_FILE                $PSAD_CONF_DIR/auto_dl;
 SNORT_RULE_DL_FILE          $PSAD_CONF_DIR/snort_rule_dl;
 POSF_FILE                   $PSAD_CONF_DIR/posf;
index 90ef756..48fa341 100644 (file)
@@ -487,6 +487,7 @@ FW_CHECK_FILE               $PSAD_DIR/fw_check;
 DSHIELD_EMAIL_FILE          $PSAD_DIR/dshield.email;
 SIGS_FILE                   $PSAD_CONF_DIR/signatures;
 ICMP_TYPES_FILE             $PSAD_CONF_DIR/icmp_types;
+ICMP6_TYPES_FILE            $PSAD_CONF_DIR/icmp6_types;
 AUTO_DL_FILE                $PSAD_CONF_DIR/auto_dl;
 SNORT_RULE_DL_FILE          $PSAD_CONF_DIR/snort_rule_dl;
 POSF_FILE                   $PSAD_CONF_DIR/posf;
index 8e99275..20400a0 100644 (file)
@@ -487,6 +487,7 @@ FW_CHECK_FILE               $PSAD_DIR/fw_check;
 DSHIELD_EMAIL_FILE          $PSAD_DIR/dshield.email;
 SIGS_FILE                   $PSAD_CONF_DIR/signatures;
 ICMP_TYPES_FILE             $PSAD_CONF_DIR/icmp_types;
+ICMP6_TYPES_FILE            $PSAD_CONF_DIR/icmp6_types;
 AUTO_DL_FILE                $PSAD_CONF_DIR/auto_dl;
 SNORT_RULE_DL_FILE          $PSAD_CONF_DIR/snort_rule_dl;
 POSF_FILE                   $PSAD_CONF_DIR/posf;
index 3414a1e..107c8f0 100644 (file)
@@ -487,6 +487,7 @@ FW_CHECK_FILE               $PSAD_DIR/fw_check;
 DSHIELD_EMAIL_FILE          $PSAD_DIR/dshield.email;
 SIGS_FILE                   $PSAD_CONF_DIR/signatures;
 ICMP_TYPES_FILE             $PSAD_CONF_DIR/icmp_types;
+ICMP6_TYPES_FILE            $PSAD_CONF_DIR/icmp6_types;
 AUTO_DL_FILE                $PSAD_CONF_DIR/auto_dl;
 SNORT_RULE_DL_FILE          $PSAD_CONF_DIR/snort_rule_dl;
 POSF_FILE                   $PSAD_CONF_DIR/posf;
index e968e2b..132edd1 100644 (file)
@@ -487,6 +487,7 @@ FW_CHECK_FILE               $PSAD_DIR/fw_check;
 DSHIELD_EMAIL_FILE          $PSAD_DIR/dshield.email;
 SIGS_FILE                   $PSAD_CONF_DIR/signatures;
 ICMP_TYPES_FILE             $PSAD_CONF_DIR/icmp_types;
+ICMP6_TYPES_FILE            $PSAD_CONF_DIR/icmp6_types;
 AUTO_DL_FILE                $PSAD_CONF_DIR/auto_dl;
 SNORT_RULE_DL_FILE          $PSAD_CONF_DIR/snort_rule_dl;
 POSF_FILE                   $PSAD_CONF_DIR/posf;
index 0439991..a93f79b 100755 (executable)
@@ -18,6 +18,7 @@ my $null_scan_file = 'null_scan_1000_1150';
 my $ack_scan_file  = 'ack_scan_1000_1150';
 my $udp_scan_file  = 'udp_scan_1000_1150';
 my $ipv6_connect_scan_file  = 'ipv6_tcp_connect_nmap_default_scan';
+my $ipv6_ping_scan_file = 'ipv6_ping_scan';
 my $ignore_ipv4_auto_dl_file = "$conf_dir/auto_dl_ignore_192.168.10.55";
 my $ignore_ipv4_subnet_auto_dl_file = "$conf_dir/auto_dl_ignore_192.168.10.0_24";
 my $ignore_ipv6_addr_auto_dl_file = "$conf_dir/auto_dl_ignore_ipv6_addr";
@@ -502,6 +503,20 @@ my @tests = (
     },
     {
         'category'  => 'operations',
+        'detail'    => 'IPv6 allow valid ping packets',
+        'err_msg'   => 'generated detection event',
+        'negative_output_matches' => [
+                qr/SRC\:.*2001\:DB8\:0\:F101\:\:2/],
+        'match_all' => $MATCH_ALL_RE,
+        'function'  => \&generic_exec,
+        'cmdline'   => "$psadCmd --test-mode -A -m $scans_dir/" .
+                &fw_type() . "/$ipv6_ping_scan_file -c $default_conf",
+        'exec_err'  => $NO,
+        'fatal'     => $NO
+    },
+
+    {
+        'category'  => 'operations',
         'detail'    => 'IPv6 disabled',
         'err_msg'   => 'logged IPv6 traffic',
         'positive_output_matches' => [qr/\[NONE\]/],