CVSS3
Attack Vector
NETWORK
Attack Complexity
LOW
Privileges Required
NONE
User Interaction
REQUIRED
Scope
UNCHANGED
Confidentiality Impact
NONE
Integrity Impact
NONE
Availability Impact
HIGH
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H
AI Score
Confidence
High
EPSS
Percentile
23.6%
When a specially-crafted certificate is passed to Curl_extract_certinfo
to parse, it may read bytes beyond the end of the buffer in which the certificate is held. According to the application, this may be a stack read overflow or a heap read overflow.
Specifically the issue is in function GTime2str
, in which the specially-crafted input may cause it to set fracl = -1
and then pass it to Curl_dyn_addf
, which in turn treats this -1
as “no length given” and goes on to run strlen(tzp)
which goes beyond the end of the certificate buffer (assuming there are no null bytes).
I believe the issue is in this loop (in lib/vtls/x509asn1.c
):
524 /* Strip leading zeroes in fractional seconds. */
525 for(fracl = tzp - fracp - 1; fracl && fracp[fracl - 1] == '0'; fracl--)
526 ;
If tzp == fracp
, then fracl
is set to -1 in the loop initialization.
I tested this on curl 8.9.0 commit 2a59c8d4cebfd199f930213ee82ae95f71e44578
(2024-07-24). I haven’t looked when the issue was introduced.
-fsanitize=address
and with gnutls. I used clang. CC=clang CFLAGS=-fsanitize=address ../configure --disable-shared --enable-debug --with-gnutls=/usr/lib/aarch64-linux-gnu
poc.c
program which uses libcurl’s Curl_extract_certinfo
../poc bad_cert_1.bin
The resulting report from AddressSanitizer:
=================================================================
==2166==ERROR: AddressSanitizer: stack-buffer-overflow on address 0xffffaae02020 at pc 0xaaaad3fedb44 bp 0xffffee270350 sp 0xffffee26fb40
READ of size 4471 at 0xffffaae02020 thread T0
#0 0xaaaad3fedb40 in strlen (/root/work/curl/fuzz2/tests/unit/poc+0x11db40) (BuildId: 950d22dbc354c1f19b0a0459aa9b72f968a5aff4)
#1 0xaaaad40dfb58 in formatf /root/work/curl/fuzz2/lib/../../lib/mprintf.c:883:15
#2 0xaaaad40e1f14 in Curl_dyn_vprintf /root/work/curl/fuzz2/lib/../../lib/mprintf.c:1105:9
#3 0xaaaad427c2ec in Curl_dyn_vaddf /root/work/curl/fuzz2/lib/../../lib/dynbuf.c:198:8
#4 0xaaaad427c844 in Curl_dyn_addf /root/work/curl/fuzz2/lib/../../lib/dynbuf.c:231:12
#5 0xaaaad41f0338 in GTime2str /root/work/curl/fuzz2/lib/../../lib/vtls/x509asn1.c:542:10
#6 0xaaaad41ec5fc in ASN1tostr /root/work/curl/fuzz2/lib/../../lib/vtls/x509asn1.c:632:14
#7 0xaaaad41eb410 in Curl_extract_certinfo /root/work/curl/fuzz2/lib/../../lib/vtls/x509asn1.c:1185:12
#8 0xaaaad40b4f4c in main /root/work/curl/fuzz2/tests/unit/poc.c:36:14
#9 0xffffac9b84c0 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#10 0xffffac9b8594 in __libc_start_main csu/../csu/libc-start.c:360:3
#11 0xaaaad3fd886c in _start (/root/work/curl/fuzz2/tests/unit/poc+0x10886c) (BuildId: 950d22dbc354c1f19b0a0459aa9b72f968a5aff4)
Address 0xffffaae02020 is located in stack of thread T0 at offset 8224 in frame
#0 0xaaaad40b4cc8 in main /root/work/curl/fuzz2/tests/unit/poc.c:9
This frame has 1 object(s):
[32, 8224) 'buf' (line 14) <== Memory access at offset 8224 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow (/root/work/curl/fuzz2/tests/unit/poc+0x11db40) (BuildId: 950d22dbc354c1f19b0a0459aa9b72f968a5aff4) in strlen
Shadow bytes around the buggy address:
0xffffaae01d80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0xffffaae01e00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0xffffaae01e80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0xffffaae01f00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0xffffaae01f80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0xffffaae02000: 00 00 00 00[f3]f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3
0xffffaae02080: f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3
0xffffaae02100: f3 f3 f3 f3 00 00 00 00 00 00 00 00 00 00 00 00
0xffffaae02180: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0xffffaae02200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0xffffaae02280: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==2166==ABORTING
Note that this will only affect libcurl when built with gnutls, schannel, sectransp, mbedtls (only then it’ll use Curl_extract_certinfo
).
poc.c
: The client code to reproduce; it simply reads a file and passes its content to Curl_extract_certinfo
. The code uses a buffer on the stack, but it also works if you switch it to be a heap buffer (see the commented-out malloc call).bad_cert_1.bin
: The certificate with which causes the memory over-read.Attacker-controller HTTPS server can return a specially-crafted certificates that can crash libcurl-based clients when fetching the certificates and parsing them.
I couldn’t see a way where the remote attacker can actually get the content of the over-read memory bytes.