Home / exploits PHP openssl_x509_parse() Memory Corruption
Posted on 16 December 2013
SektionEins GmbH www.sektioneins.de -= Security Advisory =- Advisory: PHP openssl_x509_parse() Memory Corruption Vulnerability Release Date: 2013/12/13 Last Modified: 2013/12/13 Author: Stefan Esser [stefan.esser[at]sektioneins.de] Application: PHP 4.0.6 - PHP 4.4.9 PHP 5.0.x PHP 5.1.x PHP 5.2.x PHP 5.3.0 - PHP 5.3.27 PHP 5.4.0 - PHP 5.4.22 PHP 5.5.0 - PHP 5.5.6 Severity: PHP applications using openssl_x509_parse() to parse a malicious x509 certificate might trigger a memory corruption that might result in arbitrary code execution Risk: Critical Vendor Status: Vendor has released PHP 5.5.7, PHP 5.4.23 and PHP 5.3.28 that contain a fix for this vulnerability Reference: http://www.sektioneins.de/advisories/advisory-012013-php-openssl_x509_parse-memory-corruption-vulnerability.html Overview: Quote from http://www.php.net "PHP is a widely-used general-purpose scripting language that is especially suited for Web development and can be embedded into HTML." The PHP function openssl_x509_parse() uses a helper function called asn1_time_to_time_t() to convert timestamps from ASN1 string format into integer timestamp values. The parser within this helper function is not binary safe and can therefore be tricked to write up to five NUL bytes outside of an allocated buffer. This problem can be triggered by x509 certificates that contain NUL bytes in their notBefore and notAfter timestamp fields and leads to a memory corruption that might result in arbitrary code execution. Depending on how openssl_x509_parse() is used within a PHP application the attack requires either a malicious cert signed by a compromised/malicious CA or can be carried out with a self-signed cert. Details: The PHP function openssl_x509_parse() is used by PHP applications to parse additional information out of x509 certificates, usually to harden SSL encrypted communication channels against MITM attacks. In the wild we have seen the following use cases for this function: * output certificate debugging information (e.g. cacert.org/analyse.php) * webmail application with SMIME support * client certificate handling * certificate pinning * verification of other certificate properties (e.g. a default Wordpress install if ext/curl is not loaded) When we backported security fixes for some previous security vulnerabilities in PHP's openssl to PHP 4.4.9 as part of our PHP security backport services that we provide to customers, we performed a quick audit of openssl_x509_parse() and all the functions it calls, which led to the discovery of a memory corruption vulnerability. Within the function openssl_x509_parse() the helper function asn1_time_to_time_t() is called two times to parse the notBefore and notAfter ASN1 string timestamps from the cert into integer time_t values as you can see below: add_assoc_long(return_value, "validFrom_time_t", asn1_time_to_time_t(X509_get_notBefore(cert) TSRMLS_CC)); add_assoc_long(return_value, "validTo_time_t", asn1_time_to_time_t(X509_get_notAfter(cert) TSRMLS_CC)); When you take a look into this helper function you will see that it only contains a quickly hacked parser that was never really improved since its introduction in PHP 4.0.6. The author of this parser was even aware of its hackishness as you can see from the error message contained in the code: static time_t asn1_time_to_time_t(ASN1_UTCTIME * timestr TSRMLS_DC) /* {{{ */ { /* This is how the time string is formatted: snprintf(p, sizeof(p), "%02d%02d%02d%02d%02d%02dZ",ts->tm_year%100, ts->tm_mon+1,ts->tm_mday,ts->tm_hour,ts->tm_min,ts->tm_sec); */ time_t ret; struct tm thetime; char * strbuf; char * thestr; long gmadjust = 0; if (timestr->length < 13) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "extension author too lazy to parse %s correctly", timestr->data); return (time_t)-1; } However the actual problem of the code should become obvious when you read the rest of the parsing code that attempts to first duplicate the timestamp string and then parses the timestamp by going through the copy in reverse order and writing five NUL bytes into the duplicated string. strbuf = estrdup((char *)timestr->data); memset(&thetime, 0, sizeof(thetime)); /* we work backwards so that we can use atoi more easily */ thestr = strbuf + timestr->length - 3; thetime.tm_sec = atoi(thestr); *thestr = '
