Better IP spoofing support (udpraw and icmp)
authorMichael Rash <mbr@cipherdyne.org>
Thu, 4 Oct 2012 02:56:10 +0000 (22:56 -0400)
committerMichael Rash <mbr@cipherdyne.org>
Thu, 4 Oct 2012 02:56:10 +0000 (22:56 -0400)
- [client] Added '-P udpraw' to allow the client to send SPA packets over
  UDP with a spoofed source IP address.  This is in addition to the
  original 'tcpraw' and 'icmp' protocols that also support a spoofed
  source IP.
- [server] Bug fix to accept SPA packets over ICMP if the fwknop client
  is executed with '-P icmp' and the user has the required privileges.

ChangeLog
Makefile.am
client/config_init.c
client/spa_comm.c
common/common.h
doc/fwknop.man.asciidoc
server/process_packet.c
test/conf/icmp_pcap_filter_fwknopd.conf [new file with mode: 0644]
test/conf/tcp_pcap_filter_fwknopd.conf [new file with mode: 0644]
test/test-fwknop.pl

index 11f3f6c..5a2e972 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -13,6 +13,12 @@ fwknop-2.0.4 (09/20/2012):
       been checked in under extras/openbsd/.
     - [server] Bug fix to allow GPG_ALLOW_NO_PW to result in not also having
       to specify a Rijndael key.
+    - [client] Added '-P udpraw' to allow the client to send SPA packets over
+      UDP with a spoofed source IP address.  This is in addition to the
+      original 'tcpraw' and 'icmp' protocols that also support a spoofed
+      source IP.
+    - [server] Bug fix to accept SPA packets over ICMP if the fwknop client
+      is executed with '-P icmp' and the user has the required privileges.
 
 fwknop-2.0.3 (09/03/2012):
     - [server] Fernando Arnaboldi from IOActive found several DoS/code
index 59b032f..0f8aee4 100644 (file)
@@ -154,6 +154,8 @@ EXTRA_DIST = \
     test/conf/fuzzing_source_access.conf \
     test/conf/fuzzing_open_ports_access.conf \
     test/conf/fuzzing_restrict_ports_access.conf \
+    test/conf/tcp_pcap_filter_fwknopd.conf \
+    test/conf/icmp_pcap_filter_fwknopd.conf \
     test/hardening-check \
     test/local_spa.key \
     test/test-fwknop.pl \
index e3c7e31..fb9c46a 100644 (file)
@@ -57,7 +57,9 @@ digest_strtoint(const char *dt_str)
 static int
 proto_strtoint(const char *pr_str)
 {
-    if (strcasecmp(pr_str, "udp") == 0)
+    if (strcasecmp(pr_str, "udpraw") == 0)
+        return(FKO_PROTO_UDP_RAW);
+    else if (strcasecmp(pr_str, "udp") == 0)
         return(FKO_PROTO_UDP);
     else if (strcasecmp(pr_str, "tcpraw") == 0)
         return(FKO_PROTO_TCP_RAW);
index eabdb58..97ddabf 100644 (file)
@@ -316,6 +316,105 @@ send_spa_packet_tcp_raw(const char *spa_data, const int sd_len,
 #endif /* !WIN32 */
 }
 
+/* Send the SPA data via raw UDP packet.
+*/
+static int
+send_spa_packet_udp_raw(const char *spa_data, const int sd_len,
+    const struct sockaddr_in *saddr, const struct sockaddr_in *daddr,
+    const fko_cli_options_t *options)
+{
+#ifdef WIN32
+    fprintf(stderr,
+        "send_spa_packet_udp_raw: raw packets are not yet supported.\n");
+    return(-1);
+#else
+    int  sock, res = 0;
+    char pkt_data[2048] = {0}; /* Should be enough for our purposes */
+
+    struct iphdr  *iph  = (struct iphdr *) pkt_data;
+    struct udphdr *udph = (struct udphdr *) (pkt_data + sizeof (struct iphdr));
+
+    int hdrlen = sizeof(struct iphdr) + sizeof(struct udphdr);
+
+    /* Values for setsockopt.
+    */
+    int         one     = 1;
+    const int  *so_val  = &one;
+
+    if (options->test)
+    {
+        fprintf(stderr,
+            "test mode enabled, SPA packet not actually sent.\n");
+        return res;
+    }
+
+    sock = socket (PF_INET, SOCK_RAW, IPPROTO_RAW);
+    if (sock < 0)
+    {
+        perror("send_spa_packet_udp_raw: create socket: ");
+        return(sock);
+    }
+
+    /* Put the spa data in place.
+    */
+    memcpy((pkt_data + hdrlen), spa_data, sd_len);
+
+    /* Construct our own header by filling in the ip/udp header values,
+     * starting with the IP header values.
+    */
+    iph->ihl        = 5;
+    iph->version    = 4;
+    iph->tos        = 0;
+    /* Total size is header plus payload */
+    iph->tot_len    = hdrlen + sd_len;
+    /* The value here does not matter */
+    iph->id         = random() & 0xffff;
+    iph->frag_off   = 0;
+    iph->ttl        = 255;
+    iph->protocol   = IPPROTO_UDP;
+    iph->check      = 0;
+    iph->saddr      = saddr->sin_addr.s_addr;
+    iph->daddr      = daddr->sin_addr.s_addr;
+
+    /* Now the UDP header values.
+    */
+    udph->source    = saddr->sin_port;
+    udph->dest      = daddr->sin_port;
+    udph->check     = 0;
+    udph->len       = sd_len + sizeof(struct udphdr);
+
+    /* No we can compute our checksum.
+    */
+    iph->check = chksum((unsigned short *)pkt_data, iph->tot_len);
+
+    /* Make sure the kernel knows the header is included in the data so it
+     * doesn't try to insert its own header into the packet.
+    */
+    if (setsockopt (sock, IPPROTO_IP, IP_HDRINCL, so_val, sizeof(one)) < 0)
+        perror("send_spa_packet_udp_raw: setsockopt HDRINCL: ");
+
+    res = sendto (sock, pkt_data, iph->tot_len, 0,
+        (struct sockaddr *)daddr, sizeof(*daddr));
+
+    if(res < 0)
+    {
+        perror("send_spa_packet_udp_raw: sendto error: ");
+    }
+    else if(res != sd_len + hdrlen) /* account for the header ?*/
+    {
+        fprintf(stderr,
+            "[#] Warning: bytes sent (%i) not spa data length (%i).\n",
+            res, sd_len
+        );
+    }
+
+    close(sock);
+
+    return(res);
+
+#endif /* !WIN32 */
+}
+
 /* Send the SPA data via ICMP packet.
 */
 static int
@@ -378,7 +477,7 @@ send_spa_packet_icmp(const char *spa_data, const int sd_len,
 
     /* Now the ICMP header values.
     */
-    icmph->type     = ICMP_ECHOREPLY; /* Make it an echo reply */
+    icmph->type     = ICMP_ECHO; /* Make it an echo reply */
     icmph->code     = 0;
     icmph->checksum = 0;
 
@@ -556,6 +655,7 @@ send_spa_packet(fko_ctx_t ctx, fko_cli_options_t *options)
         res = send_spa_packet_http(spa_data, sd_len, options);
     }
     else if (options->spa_proto == FKO_PROTO_TCP_RAW
+            || options->spa_proto == FKO_PROTO_UDP_RAW
             || options->spa_proto == FKO_PROTO_ICMP)
     {
         memset(&saddr, 0, sizeof(saddr));
@@ -597,6 +697,10 @@ send_spa_packet(fko_ctx_t ctx, fko_cli_options_t *options)
         {
             res = send_spa_packet_tcp_raw(spa_data, sd_len, &saddr, &daddr, options);
         }
+        else if (options->spa_proto == FKO_PROTO_UDP_RAW)
+        {
+            res = send_spa_packet_udp_raw(spa_data, sd_len, &saddr, &daddr, options);
+        }
         else
         {
             res = send_spa_packet_icmp(spa_data, sd_len, &saddr, &daddr, options);
index 9ec4388..47902f0 100644 (file)
@@ -89,6 +89,7 @@
 
 enum {
     FKO_PROTO_UDP,
+    FKO_PROTO_UDP_RAW,
     FKO_PROTO_TCP,
     FKO_PROTO_TCP_RAW,
     FKO_PROTO_ICMP,
index 2f28960..eee79fa 100644 (file)
@@ -240,11 +240,11 @@ SPA OPTIONS
     over UDP port 62201.
 
 *-P, --server-proto*='<protocol>'::
-    Set the protocol (udp, tcp, http, tcpraw, or icmp) for the outgoing SPA
-    packet.  Note: The *tcpraw* and *icmp* modes use raw sockets and thus
-    require root access to run.  Also note: The *tcp* mode expects to establish
-    a TCP connection to the server before sending the SPA packet.  This is
-    not normally done, but is useful for compatibility with the Tor for
+    Set the protocol (udp, tcp, http, udpraw, tcpraw, or icmp) for the outgoing
+    SPA packet.  Note: The *udpraw*, *tcpraw*, and *icmp* modes use raw sockets
+    and thus require root access to run.  Also note: The *tcp* mode expects to
+    establish a TCP connection to the server before sending the SPA packet.
+    This is not normally done, but is useful for compatibility with the Tor for
     strong anonymity; see 'http://tor.eff.org/'.  In this case, the
     *fwknopd* server will need to be configured to listen on the target TCP
     port (which is 62201 by default).
index 8923b23..5d8c75d 100644 (file)
@@ -47,6 +47,7 @@ process_packet(unsigned char *args, const struct pcap_pkthdr *packet_header,
     struct iphdr        *iph_p;
     struct tcphdr       *tcph_p;
     struct udphdr       *udph_p;
+    struct icmphdr      *icmph_p;
 
     unsigned char       *pkt_data;
     unsigned short      pkt_data_len;
@@ -58,8 +59,8 @@ process_packet(unsigned char *args, const struct pcap_pkthdr *packet_header,
     unsigned int        src_ip;
     unsigned int        dst_ip;
 
-    unsigned short      src_port;
-    unsigned short      dst_port;
+    unsigned short      src_port = 0;
+    unsigned short      dst_port = 0;
 
     unsigned short      eth_type;
 
@@ -165,6 +166,16 @@ process_packet(unsigned char *args, const struct pcap_pkthdr *packet_header,
         pkt_data = ((unsigned char*)(udph_p + 1));
         pkt_data_len = (pkt_end-(unsigned char*)iph_p)-(pkt_data-(unsigned char*)iph_p);
     }
+    else if (proto == IPPROTO_ICMP)
+    {
+        /* Process ICMP packet
+        */
+        icmph_p = (struct icmphdr*)((unsigned char*)iph_p + (ip_hdr_words << 2));
+
+        pkt_data = ((unsigned char*)(icmph_p + 1));
+        pkt_data_len = (pkt_end-(unsigned char*)iph_p)-(pkt_data-(unsigned char*)iph_p);
+    }
+
     else
         return;
 
diff --git a/test/conf/icmp_pcap_filter_fwknopd.conf b/test/conf/icmp_pcap_filter_fwknopd.conf
new file mode 100644 (file)
index 0000000..edadfbf
--- /dev/null
@@ -0,0 +1,5 @@
+#
+# The default fwknopd.conf contains only comments since defaults are defined in
+# code and modified via the config file
+#
+PCAP_FILTER         icmp;
diff --git a/test/conf/tcp_pcap_filter_fwknopd.conf b/test/conf/tcp_pcap_filter_fwknopd.conf
new file mode 100644 (file)
index 0000000..481868d
--- /dev/null
@@ -0,0 +1,5 @@
+#
+# The default fwknopd.conf contains only comments since defaults are defined in
+# code and modified via the config file
+#
+PCAP_FILTER         port 62201;
index cbae350..2f4e61b 100755 (executable)
@@ -35,6 +35,8 @@ my %cf = (
     'dual_key_access'         => "$conf_dir/dual_key_usage_access.conf",
     'gpg_access'              => "$conf_dir/gpg_access.conf",
     'gpg_no_pw_access'        => "$conf_dir/gpg_no_pw_access.conf",
+    'tcp_pcap_filter'         => "$conf_dir/tcp_pcap_filter_fwknopd.conf",
+    'icmp_pcap_filter'        => "$conf_dir/icmp_pcap_filter_fwknopd.conf",
     'open_ports_access'       => "$conf_dir/open_ports_access.conf",
     'multi_gpg_access'        => "$conf_dir/multi_gpg_access.conf",
     'multi_stanza_access'     => "$conf_dir/multi_stanzas_access.conf",
@@ -74,6 +76,7 @@ my $default_spa_port = 62201;
 my $non_std_spa_port = 12345;
 
 my $spoof_user = 'testuser';
+my $spoof_ip   = '1.2.3.4';
 my $cmd_exec_test_file = '/tmp/fwknoptest';
 #================== end config ===================
 
@@ -813,6 +816,53 @@ my @tests = (
         'fw_rule_created' => $REQUIRE_NO_NEW_RULE,
         'fatal'    => $NO
     },
+
+    ### spoof the source IP on the SPA packet
+    {
+        'category' => 'Rijndael SPA',
+        'subcategory' => 'client+server',
+        'detail'   => "udpraw spoof src IP (tcp/22 ssh)",
+        'err_msg'  => "could not spoof source IP",
+        'function' => \&spa_cycle,
+        'cmdline'  => "$default_client_args -P udpraw -Q $spoof_ip",
+        'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+            "$fwknopdCmd $default_server_conf_args $intf_str",
+        'fw_rule_created' => $NEW_RULE_REQUIRED,
+        'fw_rule_removed' => $NEW_RULE_REMOVED,
+        'server_positive_output_matches' => [qr/SPA\sPacket\sfrom\sIP\:\s$spoof_ip\s/],
+        'fatal'    => $NO
+    },
+    {
+        'category' => 'Rijndael SPA',
+        'subcategory' => 'client+server',
+        'detail'   => "tcpraw spoof src IP (tcp/22 ssh)",
+        'err_msg'  => "could not spoof source IP",
+        'function' => \&spa_cycle,
+        'cmdline'  => "$default_client_args -P tcpraw -Q $spoof_ip",
+        'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+            "$fwknopdCmd -c $cf{'tcp_pcap_filter'} -a $cf{'def_access'} " .
+            "-d $default_digest_file -p $default_pid_file $intf_str",
+        'fw_rule_created' => $NEW_RULE_REQUIRED,
+        'fw_rule_removed' => $NEW_RULE_REMOVED,
+        'server_positive_output_matches' => [qr/SPA\sPacket\sfrom\sIP\:\s$spoof_ip\s/],
+        'fatal'    => $NO
+    },
+    {
+        'category' => 'Rijndael SPA',
+        'subcategory' => 'client+server',
+        'detail'   => "icmp spoof src IP (tcp/22 ssh)",
+        'err_msg'  => "could not spoof source IP",
+        'function' => \&spa_cycle,
+        'cmdline'  => "$default_client_args -P icmp -Q $spoof_ip",
+        'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+            "$fwknopdCmd -c $cf{'icmp_pcap_filter'} -a $cf{'def_access'} " .
+            "-d $default_digest_file -p $default_pid_file $intf_str",
+        'fw_rule_created' => $NEW_RULE_REQUIRED,
+        'fw_rule_removed' => $NEW_RULE_REMOVED,
+        'server_positive_output_matches' => [qr/SPA\sPacket\sfrom\sIP\:\s$spoof_ip\s/],
+        'fatal'    => $NO
+    },
+
     {
         'category' => 'Rijndael SPA',
         'subcategory' => 'client+server',