A user publicly reported a bug related certificate verification in TLS-based EAP methods that leads to an authentication bypass followed by an expired pointer dereference that results in a denial of service but possibly even remote code execution.
Incorrectly Accepted Untrusted Public Key With Incorrect Refcount
The TLS implementation in libtls
incorrectly treats the public key from the peer's certificate as trusted, even if the certificate can't be verified successfully (CWE-295). However, the public key also doesn't have the correct reference count, which then causes a dereference of an expired pointer (CWE-825). This commonly leads to a segmentation fault and a denial of service, but information exposure or code execution might be possible.
An attacker is able to trigger this issue by sending a self-signed (or otherwise untrusted) certificate to a server that authenticates clients with a TLS-based EAP method like EAP-TLS. Clients may be similarly vulnerable to attackers that send them a request for such an EAP method followed by an untrusted server certificate. Affected are strongSwan versions 5.9.8 and 5.9.9.
CVE-2023-26463 has been assigned for this vulnerability.
Incorrect Reuse of a Local Variable
When libtls
is verifying a peer's certificate during a TLS exchange, it calls tls_find_public_key()
, which uses libstrongswan
's credential manager and the peer's identity to search for a trusted public key and associated certificate that matches the one sent by the peer. If the peer's certificate is found to be trusted, a reference to the public key is taken and returned from the function in order to verify the TLS signature (CertificateVerify for TLS 1.3, ServerKeyExchange for older versions). Afterwards, the reference is destroyed/released.
With 63fd718915b5 ("libtls: call create_public_enumerator() with key_type"), a small optimization was added to the function. It uses the type of the peer's certificate's public key as input to the search. So if there was, for some reason, e.g. an ECDSA and an RSA key for the same identity loaded by the daemon, it could avoid building an eventually unused trust chain by limiting the search to the correct key type.
The main part of the diff of that commit looks innocent enough:
+ public = cert->get_public_key(cert);
+ if (public)
+ {
+ key_type = public->get_type(public);
+ public->destroy(public);
+ }
enumerator = lib->credmgr->create_public_enumerator(lib->credmgr,
- KEY_ANY, id, peer_auth, TRUE);
+ key_type, id, peer_auth, TRUE);
while (enumerator->enumerate(enumerator, ¤t, &auth))
{
So the local variable public
is used to determine the key type, which is then passed to the search.
The problem is that this variable was already used in that function to store the result of the search (simplified code):
public_key_t *public = NULL, *current;
while (enumerator->enumerate(enumerator, ¤t, &auth))
{
if (/* cert matches */)
{
public = current->get_ref(current)
break;
}
}
return public;
As can be seen, it was set if, and only if, a matching trusted public key/certificate was found, otherwise, NULL
was returned. With the referenced commit this changed and public
is always defined, even if the certificate is untrusted and the search doesn't yield a result. So the authentication is bypassed and the TLS handshake continues successfully even for untrusted peer certificates.
It does not end there, though. If the certificate is untrusted, public
points to a key object whose reference was already released after determining the key type. So once the signature is verified and the supposed reference returned from tls_find_public_key()
is released, the public key object is actually destroyed. The certificate object from which the reference was obtained now points to an expired pointer.
After the EAP authentication is complete, the peer's certificate is destroyed and that pointer is dereferenced when the key's destroy()
method is called. Both the x509 and the openssl plugin use the DESTROY_IF()
macro for this, which expands to something like this:
if (this->public_key)
{
this->public_key->destroy(this->public_key);
}
Depending on whether the pointer is valid (i.e. points to memory allocated to the process) and what exactly was allocated there after the public key was freed, this may cause a segmentation fault or even execute code, which could again cause a segmentation fault, but might potentially be under the attacker's control.
Remote code execution might therefore be possible due to this issue.
Mitigation
Servers that don't load plugins that implement TLS-based EAP methods (EAP-TLS, EAP-TTLS, EAP-PEAP, EAP-TNC) are not vulnerable. If they are loaded, they must not be configured as remote authentication method. Neither must the eap-dynamic plugin be used as it allows clients to select their preferred EAP method. Servers that use TLS-based methods via the eap-radius plugin and only configure that as remote authentication method are also not vulnerable.
Clients that don't load plugins for TLS-based EAP methods, or if they do, don't configure such an EAP method or the generic eap
as their authentication method, are not vulnerable (if a non-TLS EAP method is configured, the client will respond with an EAP-Nak if the server requests a TLS-based method, even if a plugin that implements the method is loaded).
The just released strongSwan 5.9.10 fixes this vulnerability. For older releases, we provide a patch that fixes the vulnerability and should apply with appropriate hunk offsets.