Python.org CPython X509 certificate parsing CVE-2019-5010 DOS

CVE NUMBER

CVE-2019-5010

Summary

An exploitable denial-of-service vulnerability exists in the X509 

certificate parser of Python.org Python 2.7.11 / 3.6.6. A specially crafted 

X509 certificate can cause a NULL pointer dereference, resulting in a denial 

of service. An attacker can initiate or accept TLS connections using crafted 

certificates to trigger this vulnerability.

Tested Versions

Python.org CPython 2.7.11 Python.org CPython 3.6.6 Python.org CPython 3.5.2 

Python.org CPython 3 master at 480833808e918a1dcebbbcfd07d5a8de3c5c2a66

Product URLs

https://github.com/python/cpython

CVSSv3 Score

5.9 - CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H

CWE

CWE-476: NULL Pointer Dereference

Details

Python can crash if getpeercert() is called on a TLS connection which uses a 

certificate with invalid DistributionPoint in its extension.

According to RFC 5280:

A DistributionPoint consists of three fields,

each of which is optional: distributionPoint, reasons, and cRLIssuer.

While each of these fields is optional, a DistributionPoint MUST NOT

consist of only the reasons field; either distributionPoint or

cRLIssuer MUST be present.  If the certificate issuer is not the CRL

issuer, then the cRLIssuer field MUST be present and contain the Name

of the CRL issuer.  If the certificate issuer is also the CRL issuer,

then conforming CAs MUST omit the cRLIssuer field and MUST include

the distributionPoint field.

Python assumes a valid distpoint, so a crafted certificate extension 

containing a DistributionPoint with both a blank distributionPoint and 

cRLIssuer causes a NULL pointer dereference:

static PyObject *
_get_crl_dp(X509 *certificate) {
    STACK_OF(DIST_POINT) *dps;
    int i, j;
    PyObject *lst, *res = NULL;

    dps = X509_get_ext_d2i(certificate, NID_crl_distribution_points, NULL, NULL);

    if (dps == NULL)
        return Py_None;

    lst = PyList_New(0);
    if (lst == NULL)
        goto done;

    for (i=0; i < sk_DIST_POINT_num(dps); i++) {
        DIST_POINT *dp;
        STACK_OF(GENERAL_NAME) *gns;

        dp = sk_DIST_POINT_value(dps, i);
        gns = dp->distpoint->name.fullname;
This vulnerability is present in TLS clients and servers that call 

getpeercert() to perform certificate verification. Clients typically call 

getpeercert() as part of the TLS handshake:

def do_handshake(self):
    """Start the SSL/TLS handshake."""
    self._sslobj.do_handshake()
    if self.context.check_hostname:
        if not self.server_hostname:
            raise ValueError("check_hostname needs server_hostname "
                             "argument")
        match_hostname(self.getpeercert(), self.server_hostname)

Note that since the handshake completes before certificate verification, the 

crafted certificate has to be trusted by the remote party (victim). 

Handshake failures (for example, due to untrusted CA) result in skipping the 

call to getpeercert().

Crash Information

Backtrace for python 3.5.2

#0  0x00007fa24f04ee1e in _get_crl_dp (certificate=certificate@entry=0x1f4bce0) at /build/python3.5-IEzsnH/python3.5-3.5.2/Modules/_ssl.c:1096
#1  0x00007fa24f04f763 in _decode_certificate (certificate=0x1f4bce0) at /build/python3.5-IEzsnH/python3.5-3.5.2/Modules/_ssl.c:1275
#2  0x00007fa24f050e68 in _ssl__SSLSocket_peer_certificate_impl (self=self@entry=0x7fa24f35ace8, binary_mode=<optimised out>) at /build/python3.5-IEzsnH/python3.5-3.5.2/Modules/_ssl.c:1398
#3  0x00007fa24f050eae in _ssl__SSLSocket_peer_certificate (self=0x7fa24f35ace8, args=<optimised out>) at /build/python3.5-IEzsnH/python3.5-3.5.2/Modules/clinic/_ssl.c.h:76
#4  0x00000000004a2912 in PyCFunction_Call (func=func@entry=0x7fa24e95be68, args=args@entry=0x7fa24f450d58, kwds=kwds@entry=0x0) at ../Objects/methodobject.c:109
#5  0x000000000054ac7e in call_function (pp_stack=pp_stack@entry=0x7fff8a9f2b90, oparg=oparg@entry=1) at ../Python/ceval.c:4705
#6  0x0000000000547f43 in PyEval_EvalFrameEx (f=f@entry=0x7fa24f2a9db0, throwflag=throwflag@entry=0) at ../Python/ceval.c:3236
#7  0x000000000054a520 in _PyEval_EvalCodeWithName (_co=0x7fa24f324a00, globals=<optimised out>, locals=locals@entry=0x0, args=<optimised out>, argcount=argcount@entry=2, kws=0x7fa24f2a9d70, kwcount=0, defs=0x7fa24e98fb10, defcount=1, kwdefs=0x0, closure=0x0, name=0x7fa24f4162e0,
    qualname=0x7fa24f2e3298) at ../Python/ceval.c:4018
#8  0x000000000054a800 in fast_function (func=func@entry=0x7fa24e96d4a8, pp_stack=pp_stack@entry=0x7fff8a9f2e00, n=n@entry=2, na=na@entry=2, nk=nk@entry=0) at ../Python/ceval.c:4813
#9  0x000000000054ada5 in call_function (pp_stack=pp_stack@entry=0x7fff8a9f2e00, oparg=oparg@entry=1) at ../Python/ceval.c:4730
#10 0x0000000000547f43 in PyEval_EvalFrameEx (f=f@entry=0x7fa24f2a9bc8, throwflag=throwflag@entry=0) at ../Python/ceval.c:3236
#11 0x000000000054a520 in _PyEval_EvalCodeWithName (_co=0x7fa24f346d00, globals=<optimised out>, locals=locals@entry=0x0, args=<optimised out>, argcount=argcount@entry=1, kws=0x1e29338, kwcount=0, defs=0x7fa24e98fd18, defcount=1, kwdefs=0x0, closure=0x0, name=0x7fa24f4162e0,
    qualname=0x7fa24f2e3bf8) at ../Python/ceval.c:4018
#12 0x000000000054a800 in fast_function (func=func@entry=0x7fa24e96e280, pp_stack=pp_stack@entry=0x7fff8a9f3070, n=n@entry=1, na=na@entry=1, nk=nk@entry=0) at ../Python/ceval.c:4813
#13 0x000000000054ada5 in call_function (pp_stack=pp_stack@entry=0x7fff8a9f3070, oparg=oparg@entry=0) at ../Python/ceval.c:4730
#14 0x0000000000547f43 in PyEval_EvalFrameEx (f=f@entry=0x1e29198, throwflag=throwflag@entry=0) at ../Python/ceval.c:3236
#15 0x000000000054a520 in _PyEval_EvalCodeWithName (_co=_co@entry=0x7fa25065b400, globals=globals@entry=0x7fa2506efc98, locals=locals@entry=0x7fa2506efc98, args=args@entry=0x0, argcount=argcount@entry=0, kws=kws@entry=0x0, kwcount=0, defs=0x0, defcount=0, kwdefs=0x0, closure=0x0,
    name=0x0, qualname=0x0) at ../Python/ceval.c:4018
#16 0x000000000054a610 in PyEval_EvalCodeEx (_co=_co@entry=0x7fa25065b400, globals=globals@entry=0x7fa2506efc98, locals=locals@entry=0x7fa2506efc98, args=args@entry=0x0, argcount=argcount@entry=0, kws=kws@entry=0x0, kwcount=0, defs=0x0, defcount=0, kwdefs=0x0, closure=0x0)
    at ../Python/ceval.c:4039
#17 0x000000000054a639 in PyEval_EvalCode (co=co@entry=0x7fa25065b400, globals=globals@entry=0x7fa2506efc98, locals=locals@entry=0x7fa2506efc98) at ../Python/ceval.c:777
#18 0x0000000000424ee8 in run_mod (mod=mod@entry=0x1dfba68, filename=filename@entry=0x7fa24f3ed890, globals=globals@entry=0x7fa2506efc98, locals=locals@entry=0x7fa2506efc98, flags=flags@entry=0x7fff8a9f3350, arena=arena@entry=0x1e5af70) at ../Python/pythonrun.c:976
#19 0x000000000042797a in PyRun_FileExFlags (fp=fp@entry=0x1dffb20, filename_str=filename_str@entry=0x7fa250684130 "server.py", start=start@entry=257, globals=globals@entry=0x7fa2506efc98, locals=locals@entry=0x7fa2506efc98, closeit=closeit@entry=1, flags=0x7fff8a9f3350)
    at ../Python/pythonrun.c:929
#20 0x0000000000427d17 in PyRun_SimpleFileExFlags (fp=fp@entry=0x1dffb20, filename=<optimised out>, filename@entry=0x7fa250684130 "server.py", closeit=closeit@entry=1, flags=flags@entry=0x7fff8a9f3350) at ../Python/pythonrun.c:396
#21 0x0000000000427e7a in PyRun_AnyFileExFlags (fp=fp@entry=0x1dffb20, filename=0x7fa250684130 "server.py", closeit=closeit@entry=1, flags=flags@entry=0x7fff8a9f3350) at ../Python/pythonrun.c:80
#22 0x00000000004370dc in run_file (fp=fp@entry=0x1dffb20, filename=filename@entry=0x1d95150 L"server.py", p_cf=p_cf@entry=0x7fff8a9f3350) at ../Modules/main.c:318
#23 0x0000000000437c0b in Py_Main (argc=argc@entry=2, argv=argv@entry=0x1d95020) at ../Modules/main.c:768
#24 0x000000000041f319 in main (argc=2, argv=0x7fff8a9f3568) at ../Programs/python.c:65
Exploit Proof-of-Concept
The following code decodes a supplied certificate, and crashes if the certificate has an invalid distribution point extension.

      import ssl
      ssl._ssl._test_decode_cert('bad-cert.pem')
Timeline
2019-01-15 - Vendor Disclosure
2019-01-15 - Vendor patched
2019-01-28 - Public Release

CREDIT

Discovered by Colin Read and Nicolas Edet of Cisco.