PHP – ‘openssl_x509_parse()’ Memory Corruption

  • 作者: Stefan Esser
    日期: 2013-12-17
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/30395/
  •  SektionEins GmbH
    www.sektioneins.de
    
     -= SecurityAdvisory =-
    
     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 memory holes this vulnerability
    must be considered exploitable. However the actual exploit will
    depend a lot on how the PHP application uses openssl_x509_parse()
    and a lot of other factors.
    
    Depending on which of the actual use cases the function is used
    for by an application, an attacker can trigger the memory
    corruption with a self-signed certificate. An example for this
    is the public analyse.php x509 cert debugging script provided
    by CACert on their webserver.
     
    Other applications like WordPress use openssl_x509_parse() to
    further verify SSL certificates whenever WordPress connects to
    a HTTPS URL (in case ext/curl is not loaded which is the default
    for several linux distributions). Because the parsing only
    happens after the initial SSL connection is established this
    can only be abused by attackers controlling a malicious trusted
    cert. However recent disclosures of alleged NSA capabilities,
    the French incident and disclosures about fully compromised
    trusted CAs in the past years have shown that this capability
    might be in the reach of malicious attackers.
    
    
    Proof of Concept:
    
    The following x509 certificate demonstrates the out of bounds write:
     
    -----BEGIN CERTIFICATE-----
    MIIEpDCCA4ygAwIBAgIJAJzu8r6u6eBcMA0GCSqGSIb3DQEBBQUAMIHDMQswCQYD
    VQQGEwJERTEcMBoGA1UECAwTTm9yZHJoZWluLVdlc3RmYWxlbjEQMA4GA1UEBwwH
    S8ODwrZsbjEUMBIGA1UECgwLU2VrdGlvbkVpbnMxHzAdBgNVBAsMFk1hbGljaW91
    cyBDZXJ0IFNlY3Rpb24xITAfBgNVBAMMGG1hbGljaW91cy5zZWt0aW9uZWlucy5k
    ZTEqMCgGCSqGSIb3DQEJARYbc3RlZmFuLmVzc2VyQHNla3Rpb25laW5zLmRlMHUY
    ZDE5NzAwMTAxMDAwMDAwWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    AAAAAAAXDTE0MTEyODExMzkzNVowgcMxCzAJBgNVBAYTAkRFMRwwGgYDVQQIDBNO
    b3JkcmhlaW4tV2VzdGZhbGVuMRAwDgYDVQQHDAdLw4PCtmxuMRQwEgYDVQQKDAtT
    ZWt0aW9uRWluczEfMB0GA1UECwwWTWFsaWNpb3VzIENlcnQgU2VjdGlvbjEhMB8G
    A1UEAwwYbWFsaWNpb3VzLnNla3Rpb25laW5zLmRlMSowKAYJKoZIhvcNAQkBFhtz
    dGVmYW4uZXNzZXJAc2VrdGlvbmVpbnMuZGUwggEiMA0GCSqGSIb3DQEBAQUAA4IB
    DwAwggEKAoIBAQDDAf3hl7JY0XcFniyEJpSSDqn0OqBr6QP65usJPRt/8PaDoqBu
    wEYT/Na+6fsgPjC0uK9DZgWg2tHWWoanSblAMoz5PH6Z+S4SHRZ7e2dDIjPjdhjh
    0mLg2UMO5yp0V797Ggs9lNt6JRfH81MN2obXWs4NtztLMuD6egqpr8dDbr34aOs8
    pkdui5UawTZksy5pLPHq5cMhFGm06v65CLo0V2Pd9+KAokPrPcN5KLKebz7mLpk6
    SMeEXOKP4idEqxyQ7O7fBuHMedsQhu+prY3si3BUyKfQtP5CZnX2bp0wKHxX12DX
    1nfFIt9DbGvHTcyOuN+nZLPBm3vWxntyIIvVAgMBAAGjQjBAMAkGA1UdEwQCMAAw
    EQYJYIZIAYb4QgEBBAQDAgeAMAsGA1UdDwQEAwIFoDATBgNVHSUEDDAKBggrBgEF
    BQcDAjANBgkqhkiG9w0BAQUFAAOCAQEAG0fZYYCTbdj1XYc+1SnoaPR+vI8C8CaD
    8+0UYhdnyU4gga0BAcDrY9e94eEAu6ZqycF6FjLqXXdAboppWocr6T6GD1x33Ckl
    VArzG/KxQohGD2JeqkhIMlDomxHO7ka39+Oa8i2vWLVyjU8AZvWMAruHa4EENyG7
    lW2AagaFKFCr9TnXTfrdxGVEbv7KVQ6bdhg5p5SjpWH1+Mq03uR3ZXPBYdyV8319
    o0lVj1KFI2DCL/liWisJRoof+1cR35Ctd0wYBcpB6TZslMcOPl76dwKwJgeJo2Qg
    Zsfmc2vC1/qOlNuNq/0TzzkVGv8ETT3CgaU+UXe4XOVvkccebJn2dg==
    -----END CERTIFICATE-----
    
    
    Disclosure Timeline: 
    
    01. December 2013 - Notified security@php.net
    Provided description, POC cert, demo
    valgrind output and patch
    02. December 2013 - security@php.net acknowledges and
    says thank you for report and patch
    02. December 2013 - security@php.net announces that planned
    release date is 12th December
    03. December 2013 - Notification from RedHat Security that
    CVE-2013-6420 was assigned to this issue
    09. December 2013 - RedHat Security tells php.net that they
    should commit the fix silently and add
    info about it only after release
    They further tell php.net to tell us to
    not discuss the vulnerability in public
    prior to patches being available
    10. December 2013 - security@php.net fixes the vulnerability
    openly and does not attempt to hide that
    the commit is a security fix as RedHat
    Security suggested
    11. December 2013 - RedHat Security Announces that they now
    consider this vulnerability public and
    sends out their own patches with big
    announcement one day before php.net is
    ready to release their own fixes
    12. December 2013 - security@php.net pushes PHP updates to
    the PHP 5.3, PHP 5.3 and PHP 5.5 branches
    to the mirros as was previously agreed upon
    13. December 2013 - New PHP releases are announce on php.net
    13. December 2013 - Public Disclosure of this advisory
    
    
    Recommendation:
    
    It is recommended to upgrade to the latest version of PHP
    which also fixes additional non security problems reported
    by third parties.
    
    Grab your copy at:
    http://www.php.net/get/php-5.5.7.tar.bz2/from/a/mirror
    
    
    CVE Information:
    
    The Common Vulnerabilities and Exposures project (cve.mitre.org) has
    assigned the name CVE-2013-6420 to this vulnerability.
    
    
    GPG-Key:
    
    pub 4096R/D6A3FE46 2013-11-06 Stefan Esser
    Key fingerprint = 0A04 AB88 90D2 E67C 3D3D86E1 AA39 B97F D6A3 FE46
    
    
    Copyright 2013 SektionEins GmbH. All rights reserved.