From dl2kcd@m-net.arbornet.org Wed Oct 8 10:56:57 2003 Return-Path: Received: from mx1.FreeBSD.org (mx1.freebsd.org [216.136.204.125]) by hub.freebsd.org (Postfix) with ESMTP id 8A1E616A4B3 for ; Wed, 8 Oct 2003 10:56:57 -0700 (PDT) Received: from arbornet.org (m-net.arbornet.org [209.142.209.161]) by mx1.FreeBSD.org (Postfix) with ESMTP id 22B9C43FBD for ; Wed, 8 Oct 2003 10:56:56 -0700 (PDT) (envelope-from dl2kcd@m-net.arbornet.org) Received: from m-net.arbornet.org (localhost [127.0.0.1]) by arbornet.org (8.12.3p2/8.11.2) with ESMTP id h98HvaC5017923; Wed, 8 Oct 2003 13:57:36 -0400 (EDT) (envelope-from dl2kcd@m-net.arbornet.org) Received: (from dl2kcd@localhost) by m-net.arbornet.org (8.12.3p2/8.12.3/Submit) id h98HvaPp017917; Wed, 8 Oct 2003 13:57:36 -0400 (EDT) Message-Id: <200310081757.h98HvaPp017917@m-net.arbornet.org> Date: Wed, 8 Oct 2003 13:57:36 -0400 (EDT) From: Joachim Schueth Reply-To: Joachim Schueth To: FreeBSD-gnats-submit@freebsd.org Cc: dl2kcd@darc.de Subject: Psec policy on inbound trafic is not enforced (allows spoofing) X-Send-Pr-Version: 3.113 X-GNATS-Notify: >Number: 57760 >Category: kern >Synopsis: IPsec policy on inbound trafic is not enforced (allows spoofing) >Confidential: no >Severity: serious >Priority: medium >Responsible: gnn >State: closed >Quarter: >Keywords: >Date-Required: >Class: sw-bug >Submitter-Id: current-users >Arrival-Date: Wed Oct 08 11:00:22 PDT 2003 >Closed-Date: Wed Apr 25 22:16:09 GMT 2007 >Last-Modified: Wed Apr 25 22:16:09 GMT 2007 >Originator: Joachim Schueth >Release: FreeBSD 4.8-RELEASE-p13 i386 >Organization: >Environment: System: FreeBSD 4.8-RELEASE-p13 i386 >Description: A host with an IPsec policy that requires ESP with authentication or AH on inbound traffic accepts plain IP packets that carry no authentication. This allows to bypass the IPsec authentication mechanism. >How-To-Repeat: The following example uses ESP with authentication, but the effect is the same with AH. Configure two hosts running FreeBSD 4.8-RELEASE-p13 with IP addresses of 192.168.0.26 and 192.168.0.42, respectively (called host26 and host42 below). On host42 (the target host), use the following setkey script: flush; spdflush; add 192.168.0.26 192.168.0.42 esp 0x026042 -E 3des-cbc "xxxxxxxxxxxxxxxxxxxxxxxx" -A hmac-sha1 "hhhhhhhhhhhhhhhhhhhh"; add 192.168.0.42 192.168.0.26 esp 0x042026 -E 3des-cbc "AAAAAAAAAAAAAAAAAAAAAAAA" -A hmac-sha1 "rrrrrrrrrrrrrrrrrrrr"; spdadd 192.168.0.0/24 192.168.0.0/24 any -P in ipsec esp/transport//require; spdadd 192.168.0.0/24 192.168.0.0/24 any -P out ipsec esp/transport//require; On host26 (the attacking host), use the same setkey script but omit the spadd lines. This means that host26 has the correct security associations to accept the ESP packets of host42, but host26 itself will not use ipsec on outgoing packets. Then establish a TCP connection between host26 and host42, e.g. by connecting host42 from host26 via ftp. The connection succeeds, and a network dump shows ESP from host42 to host26, but plain TCP packets in the other direction. These packets are accepted by host42 despite the -P in .../require policy which is essentially ignored. Thus, an attacker could inject spoofed packets into an ESP connection simply by omitting the IPsec elements. The same behaviour is observed when AH is used. Note that ICMP ping packets are apparently dropped as expected, but not TCP packets. >Fix: This has to be fixed in the kernel. As a workaround, ipfw may be used to limit non-IPsec traffic. >Release-Note: >Audit-Trail: From: Jo To: freebsd-gnats-submit@FreeBSD.org, dl2kcd@darc.de Cc: Subject: Re: kern/57760: Psec policy on inbound trafic is not enforced (allows spoofing) Date: Wed, 08 Oct 2003 20:14:09 +0200 Typo in the synopsis: should be "IPsec", not "PSec". From: "Crist J. Clark" To: Joachim Schueth Cc: FreeBSD-gnats-submit@FreeBSD.org Subject: Re: kern/57760: Psec policy on inbound trafic is not enforced (allows spoofing) Date: Sun, 26 Oct 2003 20:07:15 -0800 On Wed, Oct 08, 2003 at 01:57:36PM -0400, Joachim Schueth wrote: > >How-To-Repeat: > The following example uses ESP with authentication, but the effect is > the same with AH. > > Configure two hosts running FreeBSD 4.8-RELEASE-p13 with IP addresses > of 192.168.0.26 and 192.168.0.42, respectively (called host26 and host42 > below). On host42 (the target host), use the following setkey script: > > flush; > spdflush; > add 192.168.0.26 192.168.0.42 esp 0x026042 > -E 3des-cbc "xxxxxxxxxxxxxxxxxxxxxxxx" > -A hmac-sha1 "hhhhhhhhhhhhhhhhhhhh"; > add 192.168.0.42 192.168.0.26 esp 0x042026 > -E 3des-cbc "AAAAAAAAAAAAAAAAAAAAAAAA" > -A hmac-sha1 "rrrrrrrrrrrrrrrrrrrr"; > spdadd 192.168.0.0/24 192.168.0.0/24 any -P in ipsec esp/transport//require; > spdadd 192.168.0.0/24 192.168.0.0/24 any -P out ipsec esp/transport//require; > > On host26 (the attacking host), use the same setkey script but omit the > spadd lines. This means that host26 has the correct security associations > to accept the ESP packets of host42, but host26 itself will not use ipsec > on outgoing packets. > > Then establish a TCP connection between host26 and host42, e.g. by > connecting host42 from host26 via ftp. The connection succeeds, and > a network dump shows ESP from host42 to host26, but plain TCP packets > in the other direction. These packets are accepted by host42 despite the > -P in .../require policy which is essentially ignored. Thus, an attacker > could inject spoofed packets into an ESP connection simply by omitting > the IPsec elements. The same behaviour is observed when AH is used. I've been trying to reproduce this. I thought I got it to "work" one of the first tries, but I have not been able to do it since. I have the same SAs set on both hosts. One host has the SPDs set, one does not. I have tried TCP and UDP traffic. I cannot get the machine with the SPD set up to accept the traffic. The appropriate SA misses are recorded in the counters. I have tried: - Loading up the SPD after a TCP connection was established and then, + Sending more data from the SPD-less to SPD-ed host. The SPD-less host does not accept any traffic. + Sending more data from the SPD-ed host to the SPD-less host. The SPD-less host successfully processes the ESP-encapsulated TCP packet and tries to ACK (in the clear), but the SPD-ed host does not accept the cleartext ACK. - Load of the SPD before trying TCP connections and then, + Attempt to connect from the SPD-less to the SPD-ed, but the cleartext SYNs are dropped. + Attempt to connect from the SPD-ed host to the SPD-less. The ESP-wrapped SYNs are SYN-ACKed (in the clear) by the SPD-less host, but the SYN-ACKs are dropped. I repeated this for AH. Here's an example setkey(8) input file, # Flush databases. flush; spdflush; # Security Policy Database spdadd 192.168.64.70/32 192.168.64.50/32 any -P out ipsec ah/transport//require; spdadd 192.168.64.50/32 192.168.64.70/32 any -P in ipsec ah/transport//require; # Security Associations Database add 192.168.64.70 192.168.64.50 ah 0x4321 -A hmac-sha1 "testkey1testkey2keys"; add 192.168.64.50 192.168.64.70 ah 0x1234 -A hmac-sha1 "testkey2testkey1keys"; And here's a portion of a packet capture where we're trying to initiate a TCP connection (ssh(1)) over AH from an SPD-ed host to SPD-less, 19:27:02.692097 192.168.64.50 > 192.168.64.70: AH(spi=0x00001234,sumlen=16,seq=0x6): 3784 > 22: S 3709471770:3709471770(0) win 57344 (DF) (ttl 64, id 54876, len 84) 19:27:02.692516 192.168.64.70.22 > 192.168.64.50.3784: S [tcp sum ok] 2467069692:2467069692(0) ack 3709471771 win 57344 (DF) (ttl 64, id 24074, len 60) 19:27:05.684765 192.168.64.70.22 > 192.168.64.50.3784: S [tcp sum ok] 2467069692:2467069692(0) ack 3709471771 win 57344 (DF) (ttl 64, id 24075, len 60) 19:27:05.686206 192.168.64.50 > 192.168.64.70: AH(spi=0x00001234,sumlen=16,seq=0x7): 3784 > 22: S 3709471770:3709471770(0) win 57344 (DF) (ttl 64, id 54885, len 84) Which shows the initial AH-wrapped SYN, the clear SYN-ACK response, and retransmits from both since the SYN-ACKs get dropped by the receiver. Are you using the KAME IPsec (IPSEC and IPSEC_ESP) or the Fast IPsec (FAST_IPSEC) implementation? These results were for the KAME IPsec. -- Crist J. Clark | cjclark@alum.mit.edu | cjclark@jhu.edu http://people.freebsd.org/~cjc/ | cjc@freebsd.org From: Joachim Schueth To: freebsd-gnats-submit@FreeBSD.org, dl2kcd@darc.de Cc: Subject: Re: kern/57760: IPsec policy on inbound trafic is not enforced (allows spoofing) Date: Sat, 10 Apr 2004 01:09:47 +0200 Hi, it is KAME IPsec on FreeBSD 4.8-RELEASE-p13: options IPSEC #IP security options IPSEC_ESP #IP security (crypto; define w/ IPSEC) options IPSEC_DEBUG #debug for IP security The IP related kernel config diffs relative to GENERIC are: #options INET6 #IPv6 communications protocols options IPFIREWALL #firewall options IPFIREWALL_VERBOSE #enable logging to syslogd(8) options IPFIREWALL_VERBOSE_LIMIT=100 #limit verbosity I tried your setkey script, loaded before starting the TCP connection, but again see the following: 00:48:53.244725 192.168.64.50 > 192.168.64.70: AH(spi=0x00001234,seq=0x1): 1043 > 22: S 3733473463:3733473463(0) win 57344 (DF) 00:48:53.245627 192.168.64.70.22 > 192.168.64.50.1043: S 3686763453:3686763453(0) ack 3733473464 win 57344 (DF) 00:48:53.245717 192.168.64.50 > 192.168.64.70: AH(spi=0x00001234,seq=0x2): 1043 > 22: . ack 1 win 57920 (DF) 00:48:53.256277 192.168.64.70.22 > 192.168.64.50.1043: P 1:40(39) ack 1 win 57920 (DF) 00:48:53.256549 192.168.64.50 > 192.168.64.70: AH(spi=0x00001234,seq=0x3): 1043 > 22: P 1:40(39) ack 40 win 57920 (DF) 00:48:53.271242 192.168.64.70.22 > 192.168.64.50.1043: P 40:576(536) ack 40 win 57920 (DF) So .50 connects to .70 with AH, .70 replies without AH, but the connection continues anyways. The security policies displayed with setkey -DP are: On 192.168.64.50: 192.168.64.70[any] 192.168.64.50[any] any in ipsec ah/transport//require spid=9 seq=1 pid=352 refcnt=1 192.168.64.50[any] 192.168.64.70[any] any out ipsec ah/transport//require spid=10 seq=0 pid=352 refcnt=1 On 192.168.64.70: No SPD entries. Obviously, 192.168.64.50 ignores the policy in inbound traffic. Joachim Schueth From: Joachim Schueth To: freebsd-gnats-submit@FreeBSD.org, dl2kcd@darc.de Cc: Subject: Re: kern/57760: IPsec policy on inbound trafic is not enforced (allows spoofing) Date: Sat, 10 Apr 2004 10:09:53 +0200 I did some further testing. The results are: - The problem still persists in FreeBSD-4.9-RELEASE-p4. - The problem does NOT occur when IPv6 support is included. To reproduce the problem, you have to comment out or remove the line "options INET6" in the kernel config. Joachim Schueth Responsible-Changed-From-To: freebsd-bugs->bms Responsible-Changed-By: bms Responsible-Changed-When: Wed Jun 23 01:48:50 GMT 2004 Responsible-Changed-Why: I'll try to look at this http://www.freebsd.org/cgi/query-pr.cgi?pr=57760 From: Joachim Schueth To: freebsd-gnats-submit@FreeBSD.org Cc: Subject: Re: kern/57760: IPsec policy on inbound trafic is not enforced (allows spoofing) Date: Thu, 16 Sep 2004 14:35:20 +0200 Hi, the problem is still there in 4.10. I finnally found the time to look at the kernel code, and by inserting some extra printf() statements I could somewhat narrow down the problem. All functions mentioned below are in /usr/src/sys/netinet6/ipsec.c . For incoming packets, ipsec4_in_reject_so() is called from somewhere and you get the following call tree: ipsec4_in_reject_so(m, so) ipsec4_getpolicybysock(m, IPSEC_DIR_INBOUND, so, &error) ipsec4_setspidx_inpcb(m, sotoinpcb(so)) ipsec_setspidx(m, spidx, 1) In ipsec_setspidx(), the following happens: ip = mtod(m, struct ip *); v = ip->ip_v; switch (v) { ... } If the option INET6 is not included and an IPv4 TCP packet is received, v has the value 0 instead of 4. For IPv4 ICMP packets or if INET6 is configured, v == 4 as it should. The case v == 0 causes ipsec_setspidx() to return an error which propagates upwards. Unfortunately, the fact that a matching policy is not found is treated by letting the packet pass despite of the error code (EINVAL). One might argue that a more defensive error treatment would be better in terms of security. The actual bug is that the IP version is not set correctly in mbuf *m. I have not yet figured out where it should be set, but apparently the inclusion of INET6 causes some other code to be compiled which sets it correctly. The following hack causes the incoming policy to be respected. In netinet6/ipsec.c, function ipsec_setspidx(), change the switch(v){} like this: Original: switch (v) { case 4: error = ipsec4_setspidx_ipaddr(m, spidx); ... } Modified: switch (v) { case 0: case 4: error = ipsec4_setspidx_ipaddr(m, spidx); ... } This is not really meant as a fix or workaround but rather as a demonstration for the location of the problem. A real workaround is to inclute the kernel option INET6. Joachim Schüth From: Joachim Schueth To: freebsd-gnats-submit@FreeBSD.org Cc: Subject: Re: kern/57760: IPsec policy on inbound trafic is not enforced (allows spoofing) Date: Sat, 18 Sep 2004 14:09:43 +0200 Hi, the zeroing of the IP version happens in tcp_input() in netinet/tcp_input.c . The call chain and state of the IP header until ipsec4_in_reject_so() is called are: ip_input() IP header ok (4X ...) tcp_input() IP header ok (4X ...) ipsec4_in_reject_so() IP header damaged (00 00 00 ...) The damage to the IP header is done in tcp_input() with the followong statements in netinet/tcp_input.c. The surrounding if()-statements are not shown here: ip = mtod(m, struct ip *); line 448 ipov = (struct ipovly *)ip; line 449 bzero(ipov->ih_x1, sizeof(ipov->ih_x1)); line 468 #ifdef INET6 /* Re-initialization for later version check */ ip->ip_v = IPVERSION; line 479 #endif The re-initilization of ip_v in line 479 is only done if INET6 is defined. When the #ifdef INET6 is removed so that the IP version is re-initialized in any case, the IPsec policy is found for incoming TCP packets. So removing the #ifdef INET6 around line 479 fixes the bug for TCP. I have not looked at other protocols such as UDP, but there may be similar effects. It would be desirable to catch the error at a more central location, such as better error handling for the error returned by ipsec_setspidx() and the other functions up in the chain. Summary of the bug: In netinet/tcp_input.c, the function tcp_input() zeroes part of the IP header. The IP version is restored only if INET6 is defined. This part of the code is not directly related to IPsec, but some of the IPsec code relies on the IP version to be passed on correctly. The function ipsec_setspidx() which is called to find the IPsec policy for an incoming packet returns an error when the IP version is not valid. This error is not honoured by ipsec4_in_reject_so() which lets the packet pass when no policy was found. Joachim Schüth State-Changed-From-To: open->feedback State-Changed-By: matteo State-Changed-When: Sun Mar 5 21:13:20 UTC 2006 State-Changed-Why: Does this bug still exist in recent -RELEASE's? http://www.freebsd.org/cgi/query-pr.cgi?pr=57760 From: Jo To: bug-followup@FreeBSD.org Cc: Subject: Re: kern/57760: IPsec policy on inbound trafic is not enforced (allows spoofing) Date: Sun, 12 Mar 2006 16:22:04 +0100 The problem still exists in FreeBSD 6.0-RELEASE. The relevant code parts have not changed. To reproduce, generate a kernel config from GENERIC with only these changes: #options INET6 #commented out options IPSEC options IPSEC_ESP The scenarios as previously described can be used for the tests. Assuming that the sole function of zeroing part of the IPv4 header is to prepare for the checksum calculation done by in_cksum(), the following patch can be used to fix the problem: --- tcp_input.c.orig Sat Oct 1 17:56:43 2005 +++ tcp_input.c Sun Mar 12 14:50:36 2006 @@ -527,11 +527,14 @@ /* * Checksum extended TCP header and data. */ + u_char temp_x1[sizeof(ipov->ih_x1)]; len = sizeof (struct ip) + tlen; + (void)memcpy(temp_x1, ipov->ih_x1, sizeof(ipov->ih_x1)); bzero(ipov->ih_x1, sizeof(ipov->ih_x1)); ipov->ih_len = (u_short)tlen; ipov->ih_len = htons(ipov->ih_len); th->th_sum = in_cksum(m, len); + (void)memcpy(ipov->ih_x1, temp_x1, sizeof(ipov->ih_x1)); } if (th->th_sum) { tcpstat.tcps_rcvbadsum++; This saves and restores the relevant part of the IPv4 header so that later on the matching SPD entry can be found. Better error handling inside the ipsec_...() functions would still be desirable. For comparison, here is what the other two big BSDs do: NetBSD 3.0: The header checksum is calculated without overwriting the packet. Error handling inside ipsec_...() functions is as in FreeBSD 6.0 (i.e. unsafe). OpenBSD 3.8: The header checksum is calculated without overwriting the packet. Their IPsec implementation looks entirely different. SPD entries are performed by a function ipsp_spd_lookup(), and any error returned by that function causes the packet to be dropped. Joachim Schueth State-Changed-From-To: feedback->analyzed State-Changed-By: bms State-Changed-When: Sat Sep 23 16:21:00 UTC 2006 State-Changed-Why: Whilst I can't reproduce this problem with my virtual setup, it seems like clear and good analysis work. The 'uncomment IPVERSION' solution sounds like the most elegant one to me, is this still enough alone to solve the problem? http://www.freebsd.org/cgi/query-pr.cgi?pr=57760 State-Changed-From-To: analyzed->patched State-Changed-By: bms State-Changed-When: Sat Sep 23 16:26:47 UTC 2006 State-Changed-Why: Patched, mfc pending http://www.freebsd.org/cgi/query-pr.cgi?pr=57760 From: dfilter@FreeBSD.ORG (dfilter service) To: bug-followup@FreeBSD.org Cc: Subject: Re: kern/57760: commit references a PR Date: Sat, 23 Sep 2006 16:26:42 +0000 (UTC) bms 2006-09-23 16:26:31 UTC FreeBSD src repository Modified files: sys/netinet tcp_input.c Log: Always set the IP version in the TCP input path, to preserve the header field for possible later IPSEC SPD lookup, even when the kernel is built without 'options INET6'. PR: kern/57760 MFC after: 1 week Submitted by: Joachim Schueth Revision Changes Path 1.308 +0 -2 src/sys/netinet/tcp_input.c _______________________________________________ cvs-all@freebsd.org mailing list http://lists.freebsd.org/mailman/listinfo/cvs-all To unsubscribe, send any mail to "cvs-all-unsubscribe@freebsd.org" Responsible-Changed-From-To: bms->gnn Responsible-Changed-By: bms Responsible-Changed-When: Sun Sep 24 08:57:02 UTC 2006 Responsible-Changed-Why: by request http://www.freebsd.org/cgi/query-pr.cgi?pr=57760 From: Jo To: bug-followup@FreeBSD.org Cc: bms@FreeBSD.org, gnn@FreeBSD.org Subject: Re: kern/57760: IPsec policy on inbound trafic is not enforced (allows spoofing) Date: Sun, 01 Oct 2006 15:37:31 +0200 Yes, I just verified that in FreeBSD 6.1-RELEASE removing the #ifdef INET6 around the ip_v restore is enough to solve the problem. So the following patch works: --- tcp_input.c.orig Sun Oct 1 13:55:10 2006 +++ tcp_input.c Sun Oct 1 13:56:02 2006 @@ -537,10 +537,8 @@ tcpstat.tcps_rcvbadsum++; goto drop; } -#ifdef INET6 /* Re-initialization for later version check */ ip->ip_v = IPVERSION; -#endif } /* Joachim Schueth State-Changed-From-To: patched->closed State-Changed-By: linimon State-Changed-When: Wed Apr 25 22:13:30 UTC 2007 State-Changed-Why: Committed quite some time ago. http://www.freebsd.org/cgi/query-pr.cgi?pr=57760 >Unformatted: