1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 |
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. |