Skip to content

ssl fails to validate incorrect extended key usage

Moderate
IngelaAndin published GHSA-qw6r-qh9v-638v Dec 5, 2024

Package

erlang ssl (Erlang)

Affected versions

<= OTP-25.3.2.8

Patched versions

None

Description

Summary

A regression was introduced into the ssl application of OTP starting at OTP-25.3.2.8, OTP-26.2, and OTP-27.0, resulting in a server or client verifying the peer when incorrect extended key usage is presented (i.e., a server will verify a client if they have server auth ext key usage and vice versa).

Affected versions

  • >= OTP-25.3.2.8
  • >= OTP-26.2
  • >= OTP-27.0

Fixed in

OTP 27.1.3
OTP 26.2.5.6
OTP 25.3.2.16

Details

The change that introduced this regression was : e7cd7fc#diff-ad2e52bd3adefb3dc79ab09e6124161de02878ff04d0e27c09fa0d548f0e21a7

Per the change, incorrect ext key usage (i.e., the client has ext key usage as server auth, or vice versa) would lead to a silent error and if one were not using a verify_fun with explicit matching such that an unknown reason would result in the termination of a session attempt, the server would verify the peer (or vice versa).

Such a scenario requires the user of the ssl application to misuse certificates and keys for unintended purposes, such as server to server auth, where by one server acts as client, and the server grants permissions to the client based off the extended key usage.

PoC

Below is a set of scripts to first exhibit how openssl s_server and s_client behave in this situation and then how Erlang SSL behaves. In the case of OpenSSL, the server will disconnect the client with following error :

verify error:unsupported certificate purpose
8544580160:error:1417C086:SSL routines:tls_process_client_certificate:certificate verify failed:ssl/statem/statem_srvr.c:3713:
shutting down SSL
CONNECTION CLOSED

While, Erlang ssl will verify the peer and create the session.

openssl behavior

The following is configuration and scripts to replicate the bug using openssl, you will see that the server disconnects the client :

openssl.cnf
[ca]
default_ca = root_ca

[ root_ca ]
dir = .
certificate = $dir/ca_certificate.pem
database = $dir/index.txt
new_certs_dir = $dir/certs
private_key = $dir/private/ca_private_key.pem
serial = $dir/serial

default_crl_days = 7
default_days = 365
default_md = sha256

policy = root_ca_policy
x509_extensions = certificate_extensions

[ root_ca_policy ]
commonName = supplied
stateOrProvinceName = optional
countryName = optional
emailAddress = optional
organizationName = optional
organizationalUnitName = optional
domainComponent = optional

[ certificate_extensions ]
basicConstraints = CA:false

[ req ]
default_bits = 2048
default_keyfile = ./private/ca_private_key.pem
default_md = sha256
prompt = yes
distinguished_name = root_ca_distinguished_name
x509_extensions = root_ca_extensions

[ root_ca_distinguished_name ]
commonName = elixir-mllp-root-ca

[ root_ca_extensions ]
basicConstraints = CA:true
keyUsage = keyCertSign, cRLSign

[ client_ca_extensions ]
basicConstraints = CA:false
keyUsage = digitalSignature,keyEncipherment
extendedKeyUsage = 1.3.6.1.5.5.7.3.2

[ server_ca_extensions ]
basicConstraints = CA:false
keyUsage = digitalSignature,keyEncipherment
extendedKeyUsage = 1.3.6.1.5.5.7.3.1
cert generation script
#!/bin/bash

cd tls

mkdir root-ca server
cd root-ca
rm index.txt
touch index.txt
echo 01 > serial
mkdir certs private

openssl req -x509 -sha256 -days 1825 -newkey rsa:2048 -config ../openssl.cnf -out ca_certificate.pem -outform PEM -subj /CN=my-root-ca/ -nodes
openssl x509 -in ca_certificate.pem -out ca_certificate.cer -outform DER

cd ../server

openssl genrsa -out private_key.pem 2048
openssl req -new -key private_key.pem -out req.pem -outform PEM \
    -subj /CN=localhost/O=server/ -nodes

cd ../root-ca

openssl ca -config ../openssl.cnf -in ../server/req.pem -out \
    ../server/server_certificate.pem -notext -batch -extensions server_ca_extensions

cd ../../

mkdir -p priv/certs
cp -f tls/root-ca/ca_certificate.pem  priv/certs/root_ca.pem
cp -f  tls/server/server_certificate.pem priv/certs/server_cert.pem
cp -f tls/server/private_key.pem priv/certs/server_key.pem
Server start up
openssl s_server -debug -CAfile priv/certs/root_ca.pem -verify_return_error -Verify 1 -key priv/certs/server_key.pem -cert priv/certs/server_cert.pem -accept 4433
Client startup
openssl s_client -CAfile priv/certs/root-ca.pem -key priv/certs/server_key.pem -cert priv/certs/server_cert.pem   -servername localhost -connect 127.0.0.1:4433

Erlang module

The certs generated to exhibit the openssl behavior should be used with the module below :

-module(ssl_example).

-export([start/0, init_connect/1]).

start() ->
    {ok, StartedApps} = application:ensure_all_started(ssl),

    {ok, LSock} = ssl:listen(0, mk_opts(listen)),
    {ok, {_, LPort}} = ssl:sockname(LSock),
    io:fwrite("Listen: port = ~w.~n", [LPort]),

    spawn(?MODULE, init_connect, [LPort]),

    {ok, ASock} = ssl:transport_accept(LSock),
    {ok, SslSocket} = ssl:handshake(ASock),
    io:fwrite("Accept: accepted.~n"),
    {ok, Cert} = ssl:peercert(SslSocket),
    io:fwrite("Accept: peer cert:~n~p~n", [public_key:pkix_decode_cert(Cert, plain)]),
    io:fwrite("Accept: sending \"hello\".~n"),
    ssl:send(SslSocket, "hello"),
    {error, closed} = ssl:recv(SslSocket, 0),
    io:fwrite("Accept: detected closed.~n"),
    ssl:close(SslSocket),
    io:fwrite("Listen: closing and terminating.~n"),
    ssl:close(LSock),

    lists:foreach(fun application:stop/1, lists:reverse(StartedApps)).


init_connect(LPort) ->
    {ok, CSock} = ssl:connect("localhost", LPort, mk_opts(connect)),
    io:fwrite("Connect: connected.~n"),
    {ok, Cert} = ssl:peercert(CSock),
    io:fwrite("Connect: peer cert:~n~p~n", [public_key:pkix_decode_cert(Cert, plain)]),
    {ok, Data} = ssl:recv(CSock, 0),
    io:fwrite("Connect: got data: ~p~n", [Data]),
    io:fwrite("Connect: closing and terminating.~n"),
    ssl:close(CSock).

mk_opts(listen) ->
    mk_opts("server");
mk_opts(connect) ->
    [{server_name_indication, disable}] ++ mk_opts("client");
mk_opts(_Role) ->
    Dir =  filename:join([code:lib_dir(ssl_example), "priv", "certs"]),
    CACert = filename:join([Dir, "root_ca.pem" ]),
    public_key:cacerts_load(CACert),
    [{active, false},
     {log_level, debug},
     {verify, verify_peer},
     {fail_if_no_peer_cert, true},
     {depth, 0},
     {cacerts, public_key:cacerts_get()},
     {versions, ['tlsv1.3']},
     {certs_keys, [#{certfile => filename:join([Dir, "server_cert.pem"]), keyfile => filename:join([Dir, "server_key.pem"])}]}].

Impact

Severity : Moderate

Vulnerability type : CWE-295: Improper Certificate Validation

Affected applications : All applications using >= OTP-25.3.2.8, >= OTP-26.2, and >= OTP-27.0 and potentially misusing certificates with extended key usage to both protect and escalate access on endpoints in a mutual TLS setup.

Severity

Moderate

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
High
Privileges required
High
User interaction
None
Scope
Changed
Confidentiality
Low
Integrity
Low
Availability
Low

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:C/C:L/I:L/A:L

CVE ID

CVE-2024-53846

Weaknesses

Credits