Headline
wolfSSL Buffer Overflow
In wolfSSL versions prior to 5.5.1, malicious clients can cause a buffer overflow during a resumed TLS 1.3 handshake. If an attacker resumes a previous TLS session by sending a maliciously crafted Client Hello, followed by another maliciously crafted Client Hello. In total 2 Client Hellos have to be sent. One which pretends to resume a previous session and a second one as a response to a Hello Retry Request message.
wolfssl before 5.5.1: CVE-2022-39173 Buffer overflow when refining
cipher suites
INFO
=======
The CVE project has assigned the id CVE-2022-39173 to this issue.
Severity: high 7.5
Affected version: before 5.5.1
End of embargo: The embargo for this vulnerability ended 29th of September, 2022
SUMMARY
==========
In wolfSSL before 5.5.1 malicious clients can cause a buffer-overflow
during a resumed TLS 1.3 handshake. If an attacker resumes a previous
TLS session by sending a maliciously crafted Client Hello, followed by
another maliciously crafted Client Hello. In total 2 Client Hellos
have to be sent. One which pretends to resume a previous session and a
second one as a response to a Hello Retry Request message.
The malicious Client Hellos contain a list of supported cipher suites,
which contain at least ⌊sqrt(150)⌋ + 1 = 13
duplicates and less than
150 ciphers in total. The buffer-overflow occurs in the RefineSuites
function. An overflow of 44700 bytes has been confirmed. Therefore,
large portions of the stack can get overwritten, including return
addresses.
We confirmed the vulnerability by sending packets over TCP to a
Wolfssl server, freshly built from the sources with the
--enable-session-ticket
flags (or simply --enable-all
). We can
provide sources for our software (tlspuffin) that produce those
packets (and that automatically found the attack trace). The command
given at the end of this document triggers the buffer overflow.
It is very likely that there is a way to craft an exploit which can
cause a RCE. We have not yet created such an exploit as it would
likely depend on the memory layout of the binary which uses wolfSSL.
Moreover, the size of the overflow can be fine-tuned in order to not
smash the stack and continue the execution with a too large length of
suites buffer and that will cause other routines that iterate over
thus buffer (e.g., FindSuiteSSL
) to misbehave. Hypothetically, this
might be exploited to make the server use a cipher it should not
accept such as nullcipher
that would open up new attack vectors such
as downgrade attacks.
While this has not been confirmed yet, the buffer overflow itself has
been confirmed.
DETAILS
==========
Line numbers below are valid for the wolfSSL Git tag
v5.4.0-stable.
The bug we found is in the RefineSuites
function. In the following
we want to explain why the function is able to overflow the suites
array.
/* Refine list of supported cipher suites to those common to server and client.
*
* ssl SSL/TLS object.
* peerSuites The peer's advertised list of supported cipher suites.
*/
static void RefineSuites(WOLFSSL* ssl, Suites* peerSuites)
{
byte suites[WOLFSSL_MAX_SUITE_SZ];
word16 suiteSz = 0;
word16 i, j;
XMEMSET(suites, 0, WOLFSSL_MAX_SUITE_SZ);
for (i = 0; i < ssl->suites->suiteSz; i += 2) {
for (j = 0; j < peerSuites->suiteSz; j += 2) {
if (ssl->suites->suites[i+0] == peerSuites->suites[j+0] &&
ssl->suites->suites[i+1] == peerSuites->suites[j+1]) {
suites[suiteSz++] = peerSuites->suites[j+0];
suites[suiteSz++] = peerSuites->suites[j+1];
}
}
}
ssl->suites->suiteSz = suiteSz;
XMEMCPY(ssl->suites->suites, &suites, sizeof(suites));
#ifdef WOLFSSL_DEBUG_TLS
[...]
#endif
}
tls13.c:4355
The RefineSuites
function expects a WOLFSSL
struct which contains
a list of acceptable ciphers suites (ssl->suites->suites
), as well
as an array of peer cipher suites (peerSuites
). Both inputs are
bounded by WOLFSSL_MAX_SUITE_SZ
, which is equal to 300 bytes or 150
cipher suites.
Let us assume that ssl->suites
consists of a single cipher suite
like TLS_AES_256_GCM_SHA384
and the peerSuites
list contains the
same cipher repeated thirteen times. The RefineSuites
function will
iterate for each element in ssl->suites
over peerSuites
and append
the suite to suites
if it is a match. The suites
array has a
maximum length of WOLFSSL_MAX_SUITE_SZ == 300 bytes == 150 suites
.
With the just mentioned example input, the length of suites
will now
equal thirteen. The suites
array is now copied to the WOLFSSL
struct in the last line of the listing above. Therefore, ssl->suites
contains now thirteen times the TLS_AES_256_GCM_SHA384
cipher suite.
Let us now call the same RefineSuites
function again on the modified
WOLFSSL
struct and the same peerSuites
list. The RefineSuites
function will iterate for each element in ssl->suites
over
peerSuites
and append the suite to suites
if it is a match.
Because ssl->suites
contains already 13 times the
TLS_AES_256_GCM_SHA384
cipher suite, in total 13 x 13 = 169 cipher
suites are written to suites
. 169 cipher suites require 338 bytes,
which is more than what’s available on the stack. The suites
buffer
overflows.
The maximum size of peerSuites
is 150 cipher suites. Therefore, an
overflow of 44700 bytes is possible and has been confirmed.
The buffer ssl->suites->suites
is supposed to be reset to only
contain the acceptable ciphers at each session start, and thus
initially contains no duplicate. However, by provoking a HELLO CLIENT RETRY REQUEST
, it is possible to make the server call RefineSuites
twice as explained next.
TRIGGERING THE BUFFER OVERFLOW
=================================
In order to cause the above buffer-overflow, it is required to call
RefineSuites
twice. Malicious clients need to perform the handshake
in a certain way to reach this situation.
The buffer overflow at the attacked server can be obtained at least in
the following situation:
Resume the previous session by sending a second Client Hello
(CH2
) with the following criteria:- Exclude the
support_group_extension
, to cause a Hello Retry Request - Include a binder which cryptographically binds this session to
the previous one. - Include a list of cipher suites that contains a repetition of
n
times the same cipherc
with13 <= n < 150
, deemed acceptable by
the server.
The server will parse this message, enters the state
SERVER_HELLO_RETRY_REQUEST_COMPLETE
and stores at leastn
times
the cipherc
inssl->suites->suites
by callingRefineSuites
.- Exclude the
Sending a third Client Hello (
CH3
) with the same criteria as in step 2.
The server will parse this message and because
ssl->suites->suites
already containsn
times the cipherc
,
RefineSuites
will write insuites
at least untilsuites[nˆ2]
which overflows sincenˆ2 > 300
.
DETAILS ABOUT STEP 1.
========================
During step 2., we want to cause the server to perform a Hello Retry Request.
This is possible by not sending a supported group in the CH2
. By not
sending a support group extension, the function
TLSX_SupportedGroups_Find
will return false.
static int TLSX_SupportedGroups_Find(WOLFSSL* ssl, word16 name)
{
...
/* Check consistency now - extensions in any order. */
if (!TLSX_SupportedGroups_Find(ssl, clientKSE->group))
continue;
...
tls.c:8374
This will cause clientKSE to be NULL
and doHelloRetry
will be set to 1.
int TLSX_KeyShare_Establish(WOLFSSL *ssl, int* doHelloRetry)
{
...
/* No supported group found - send HelloRetryRequest. */
if (clientKSE == NULL) {
/* Set KEY_SHARE_ERROR to indicate HelloRetryRequest required. */
*doHelloRetry = 1;
return TLSX_KeyShare_SetSupported(ssl);
}
...
tls.c:9273
Finally, the server enters the state
SERVER_HELLO_RETRY_REQUEST_COMPLETE
in the function
VerifyServerSuite
while verifying the server suite when processing
CH2
.
/* Make sure server cert/key are valid for this suite, true on success
* Returns 1 for valid server suite or 0 if not found
* For asynchronous this can return WC_PENDING_E
*/
static int VerifyServerSuite(WOLFSSL* ssl, word16 idx)
{
...
if (IsAtLeastTLSv1_3(ssl->version) &&
ssl->options.side == WOLFSSL_SERVER_END) {
int doHelloRetry = 0;
/* Try to establish a key share. */
int ret = TLSX_KeyShare_Establish(ssl, &doHelloRetry);
if (doHelloRetry) {
ssl->options.serverState = SERVER_HELLO_RETRY_REQUEST_COMPLETE;
}
...
}
...
tls.c:30688
DETAILS ABOUT STEP 2.
====================
The server is now in a state in which it expects another Client Hello
(CH3
) from the client.
The server is now in the state SERVER_HELLO_RETRY_REQUEST_COMPLETE
and will process the third ClientHello (CH3
) with the call of
ProcessReply
before reaching the TLS13_ACCEPT_SECOND_REPLY_DONE
state.
int wolfSSL_accept_TLSv13(WOLFSSL* ssl)
{
...
case TLS13_ACCEPT_FIRST_REPLY_DONE :
if (ssl->options.serverState ==
SERVER_HELLO_RETRY_REQUEST_COMPLETE) {
ssl->options.clientState = CLIENT_HELLO_RETRY;
while (ssl->options.clientState < CLIENT_HELLO_COMPLETE) {
if ((ssl->error = ProcessReply(ssl)) < 0) {
WOLFSSL_ERROR(ssl->error);
return WOLFSSL_FATAL_ERROR;
}
}
}
ssl->options.acceptState = TLS13_ACCEPT_SECOND_REPLY_DONE;
WOLFSSL_MSG("accept state ACCEPT_SECOND_REPLY_DONE");
FALL_THROUGH;
...
tls13.c:10909
VULNERABILITY VARIANTS
=========================
Adjusting the overflow
Note that the length of the list of ciphers in CH2
does not
necessarily have to be the same as the one of CH1
and can be
adjusted to fine-tune the size of the overflow.
Make the server parse CH2
Note also that, on the contrary to CH1
, CH2
does not necessarily
have to put the server in the SERVER_HELLO_RETRY_REQUEST_COMPLETE
state (which should be forbidden by the TLS 1.3 RFC) or make it return
an error, and can thus contain a supported group, which could be
included to possibly make the server continue the processing of CH2
without returning an error.
We have confirmed that we can make the server parses CH2
until the
end and starts computing a Server Hello with ssl->suites->suiteSz
that exceeds 300.
Resuming an existent session
It is also possible to trigger the vulnerability by trying to resume
an existent and genuine session established through a full initial
handshake (step 0.):
- Sending an initial genuine Client Hello (
CH1
) to the server and
then completing a full handshake, thus establishing a PSK.
EXPLOITATION
===============
We suspect that it is possible to craft an exploit which could lead to
RCE if any of the above bytes coincides with the memory address of
executable code. Depending on the memory layout of the binary it could
be possible to gain RCE.
More bytes could be used to overflow suites
if more ciphers were
configured to be accepted with the server, e.g., with options like
--enable-blake2
.
We confirmed that this could also be exploited to smash the stack and
cause the server to crash with a segmentation fault by using a large
list of ciphers.
Finally, by fine-tuning the length of the overflow and by including
the supported group in CH3
, it could be possible to make the server
process CH3
with a ssl->suites->suites->suiteSz
value that exceeds
- This way, routines like
FindSuiteSSL
that will iterate over
ssl->suites->suites
(allocated on 300 bytes) until
ssl->suites->suiteSz
(>300) will also iterate over bytes that
contain other fields such asssl->suites->hashSigAlgo
. It is likely
that this could be exploited to make such routines return arbitrary
values. For example, it might be exploited to make the server use a
cipher it should not accept such asnullcipher
; thus breaking
confidentiality.
FURTHER CONCERNS
==================
We observed that the server is accepting the CH2
Client Hello
message and issues a Hello Retry Request, even though CH2
does not
contain supported groups. Clients are not allowed to add the supported
groups extension in the retry Client Hello (CH3
) according to the
RFC 8446 in section
4.1.2. The
addition of supported groups is not allowed when retrying the Client
Hello.
We suggest aborting the handshake when receiving CH2
instead of
offering the client a retry.