Exploit-DB #30395
Description: PHP - 'openssl_x509_parse()' Memory Corruption
Type: dos
Platform: php
Date: 2013-12-17
Author: Stefan Esser

Exploit:
                         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 = '\0';
     thestr -= 2;
     thetime.tm_min = atoi(thestr);
     *thestr = '\0';
     thestr -= 2;
     thetime.tm_hour = atoi(thestr);
     *thestr = '\0';
     thestr -= 2;
     thetime.tm_mday = atoi(thestr);
     *thestr = '\0';
     thestr -= 2;
     thetime.tm_mon = atoi(thestr)-1;
     *thestr = '\0';
     thestr -= 2;
     thetime.tm_year = atoi(thestr);

  The problem with this code is that ASN1 strings can contain NUL
  bytes, while the parser is not binary safe. This means if a
  timestamp string inside a x509 certificate contains a NUL byte
  at e.g. position 13 the estrdup() will only allocate 14 bytes
  for a copy of the string, but the parser will attempt to write
  five NUL bytes to memory addressed by the ASN1 length of the
  string. If the real string length is longer than 16 bytes this
  will result in writes of NUL bytes outside of the allocated
  buffer.
 
  Because of PHP's deterministic heap memory layout that can be
  controlled a lot by sending e.g. POST variables and using
  duplicate variable names to poke