oss-sec mailing list archives

CVE-2016-9015: Python urllib3 1.17 and 1.18 certificate verification failure


From: Cory Benfield <cory () lukasa co uk>
Date: Thu, 27 Oct 2016 12:26:20 +0100

Versions 1.17 and 1.18 of the Python urllib3 library suffer from a vulnerability that can cause them, in certain 
configurations, to not correctly validate TLS certificates. This places users of the library with those configurations 
at risk of man-in-the-middle and information leakage attacks. This vulnerability affects users using versions 1.17 and 
1.18 of the urllib3 library, who are using the optional PyOpenSSL support for TLS instead of the regular standard 
library TLS backend, and who are using OpenSSL 1.1.0 via PyOpenSSL. This is an extremely uncommon configuration, so the 
security impact of this vulnerability is low.

Affected users should upgrade to urllib3 1.18.1, which has been published today and contains only the mitigation for 
this vulnerability on top of the changes in 1.18. If unable to upgrade, users should downgrade their OpenSSL version or 
temporarily stop injecting PyOpenSSL into urllib3 until they are able to upgrade. A more lengthy description of the 
vulnerability follows.

—

This vulnerability was introduced in a substantial refactor of the PyOpenSSL contrib module. During this refactor, a 
branch of code that mapped the Python standard library certificate verification constants (ssl.CERT_NONE, 
ssl.CERT_OPTIONAL, ssl.CERT_REQUIRED) to OpenSSL verification mode flags (SSL_VERIFY_NONE, SSL_VERIFY_PEER, etc.) was 
accidentally lost. This meant that Python standard library constants would be passed directly to OpenSSL via the 
SSL_CTX_set_verify function’s mode argument.

Unfortunately, these Python standard library constants are Python wrappers around the values of a C enumerated type, 
declared like this:

    enum py_ssl_cert_requirements {
        PY_SSL_CERT_NONE,
        PY_SSL_CERT_OPTIONAL,
        PY_SSL_CERT_REQUIRED
    };

Per the standard C enumerated type rules, these constants have the values 0, 1, and 2 respectively. These integers do 
not all map to their OpenSSL verification mode flag equivalents. While PY_SSL_CERT_NONE and SSL_VERIFY_NONE have the 
same value (0) and PY_SSL_CERT_OPTIONAL and SSL_VERIFY_PEER have the same value (1), PY_SSL_CERT_REQUIRED has the value 
2, which maps to SSL_VERIFY_FAIL_IF_NO_PEER_CERT. This flag is defined by the OpenSSL manual page as being meaningless 
on its own, requiring SSL_VERIFY_PEER to also be set in order to have any effect. Additionally, the manual page 
declares that SSL_VERIFY_FAIL_IF_NO_PEER_CERT has no effect in client mode.

In OpenSSL versions prior to 1.1.0, an implementation detail in the OpenSSL codebase would mean that if any nonzero 
value was passed to the mode argument of SSL_CTX_set_verify this would implicitly have the same effect as setting 
SSL_VERIFY_PEER. Essentially, the only value of mode in OpenSSL versions prior to 1.1.0 that would cause certificate 
validation to be disabled was 0 (SSL_VERIFY_NONE).

In the work done for OpenSSL 1.1.0, this implementation detail was changed to check for the SSL_VERIFY_PEER bit 
directly. As the OpenSSL flags are a bit mask, passing PY_SSL_CERT_REQUIRED (2) would *not* have the SSL_VERIFY_PEER 
bit (1) set, which means that OpenSSL would act act as though SSL_VERIFY_PEER was not set. Thus, if 
PY_SSL_CERT_REQUIRED is passed to OpenSSL directly in client mode, OpenSSL 1.0.2 and earlier treat it as equivalent to 
SSL_VERIFY_PEER, whereas OpenSSL 1.1.0 and later treat it as equivalent to SSL_VERIFY_NONE.

This is unquestionably an application error. The OpenSSL documentation is clear that the 
SSL_VERIFY_FAIL_IF_NO_PEER_CERT flag is both meaningless by itself and in client mode. However, OpenSSL’s unexpected 
change in behaviour, combined with the fact that it has no way to report an invalid flag combination to 
SSL_CTX_set_verify, means that this application error leads to a catastrophic silent security failure when used with 
OpenSSL 1.1.0.

The fix for urllib3 is to reintroduce a mapping between the Python standard library enumerated type and the OpenSSL 
flags. Other applications should audit and confirm that they always successfully pass SSL_VERIFY_PEER to 
SSL_CTX_set_verify. The OpenSSL team have been notified of this behaviour and have concluded that it is not a security 
vulnerability in OpenSSL. However, they are considering reverting this change regardless, on the principle that it is 
better to fail closed than to fail open: https://github.com/openssl/openssl/pull/1793

Fortunately, urllib3’s test suite caught this failure, which led to this investigation. Unfortunately, due to the 
relative scarcity of OpenSSL 1.1.0, two released versions of urllib3 had passed before anyone attempted to run urllib3 
with OpenSSL 1.1.0.

The urllib3 team have contacted downstream redistributors for Red Hat and Debian: both distributions are not using 
versions of urllib3 later than 1.16, and so are unaffected. Additionally, the Python Requests library is using urllib3 
version 1.16 and is also not affected.


Current thread: