2018-MM-DD (published patch date) TALOS-2019-0758 CVE-2019-5010 Python.org CPython X509 certificate parsing denial-of-service vulnerability ### 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](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=) at /build/python3.5-IEzsnH/python3.5-3.5.2/Modules/_ssl.c:1398 #3 0x00007fa24f050eae in _ssl__SSLSocket_peer_certificate (self=0x7fa24f35ace8, args=) 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=, locals=locals@entry=0x0, args=, 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=, locals=locals@entry=0x0, args=, 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=, 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') ### Credit Discovered by Colin Read and Nicolas Edet of Cisco. ### Timeline YYYY-MM-DD - Vendor Disclosure YYYY-MM-DD - Public Release