TP-5.24-反序列化复现
前言
TP代码审计一直是个坎,很多次CTF比赛中都会出现,都不是特别拿手,所以简单复现TP的经典漏洞,也算是学习记录。
复现
环境
官方源码下载即可
控制器修改代码
<?php
namespace app\index\controller;
class Index
{
public function index()
{
echo ("Welcome");
unserialize($_GET['payload']);
}
}
分析
入口函数,全局搜索 __destruct
windows.php
跟进removeFiles
这里的file_exists()函数会触发__toString()方法
全局搜索 __toString
,找到Model类
跟进toJson
跟进toArray
发现了魔法函数 __call
利用点,
如果value 和 attr 可以控制,就可以触发__call方法,
回溯法,判断变量是否可控
那么怎样才可以执行这条语句呢?
七大关
1.!empty($this->append) # $this->append不为空
2.!is_array($name) #$name不能为数组
3.!strpos($name, '.') #$name不能有.
4.method_exists($this, $relation)#$relation必须为Model类里的方法
5.method_exists($modelRelation, 'getBindAttr')#$modelRelation必须存在getBindAttr方法
6.$bindAttr #必须有值
7.!isset($this->data[$key]) #$key不能在$this->data这个数组里有相同的值。
这里的$this->append可控,并且把值赋给了name,所以前3关过了
第四关,判断$relation必须为Model类里的方法
溯源$relaiton
$relation = Loader::parseName($name, 1, false);
parseName此函数,只是将 $name小写转换赋给$relation,所以$relation可控
由于
$modelRelation = $this->$relation();
要是$modelRelation可控,所以要找既在Model类,也要可以控返回值的方法,找到了getError函数
继续跟
$value = $this->getRelationData($modelRelation);
跟进getRelationData
注意:$modelRelation
必须为Relation
对象,通过$this->error
控制,并且$modelRelation
这个对象还要有isSelfRelation()
、getModel()
方法,
搜索这两种方法发现Relation类中都有,但因为Relation为抽象类,需要寻找他的子类。全局搜索。
除了最后一个是抽象类外,都可以拿来用,但是!!!,我们别忘了第五关,需要$modelRelation必须存在getBindAttr方法,但是Relation没有getBindAttr方法,只有OneToOne类里有,且OneToOne类正好继承Relation类,不过是抽象类,所以我们需要找它的自类。全局搜索
发现存在两个可用的,我们选择第二个HasOne,即$this->error=new HasOne()。好了,调用方法的问题解决了,但是还需要过三小关
总结一下$modelRelation的要求
$modelRelation必须为Relation对象
要有`isSelfRelation()`、`getModel()`方法
必须存在getBindAttr方法
只要满足 if,value
便可控
if ($this->parent && !$modelRelation->isSelfRelation() && get_class($modelRelation->getModel()) == get_class($this->parent)) {
$value = $this->parent;
}
$this->parent
可控,我们要使用Output类中的__call,所以$value
必须为output对象,所以$this->parent
必须控制为output对象,即$this->parent=new Output().
跟进isSelfRelation
可控,跟进$modelRelation->getModel()
get_class()返回对象实例 obj 所属类的名字
$this->parent已经确定为Output类了,所以我们要控制get_class($modelRelation->getModel())为Output类
query可控,所以全局找有getModel(),并且可以控制返回值的对象
在query.php中找到
if满足,value赋值成功
第六关
$bindAttr = $modelRelation->getBindAttr();
跟进getBindAttr,可控。
第七关
!isset($this->data[$key]) #$key不能在$this->data这个数组里有相同的值。
key值溯源,$bindAttr 可控,所以可控。
调用Output的__call方法
此时 method = getAttr(),args=$bindAttr,
this->styles可控,所以让this->styles == getAttr
即可
array_unshift函数将method插入到args的头部
跟进block方法
跟进writeln
handle可控,全局找write方法
找到memcached的write方法
跟进set
file_put_contents写webshell操作,data由value控制,但是value=true,不可控。
跟进getCacheKey
由于name是可控制的,所以
调用了setTagItem函数,跟进
重置了value,但是这次name变量是可控的,所以value可控,继续调用set方法,这次set的两个参数都是可控的,key进入了getCacheKey函数,发现option['path']可控,而且它的值会作为二次调用set函数中的value值,写入到文件中,则此函数的返回值可控。
绕过exit()死亡函数,写入shell即可。
总结
-
先找漏洞点,敏感危险函数是入门的第一步。
-
学习了新的审计方法:
1.找到了危险函数,列举执行危险函数需要的条件,然后一步一步满足
-
PHP语言中类的继承和抽象类的继承特性
1.抽象类继承抽象类时,不能重写父类中的抽象方法
2.非抽象类继承抽象类时,必须实现抽象类中的所有方法
审计过程中,如果出现方法在抽象类中,不能直接实例,要找继承抽象类的非抽象子类
3.至少有一个抽象方法的类就是抽象类
-
温顾了以前的trick:
file_exists()函数会触发__toString()方法
绕过死亡函数写入shell。
参考————两条链子
https://www.freebuf.com/articles/web/284091.html
上面这个审计方法很不错!值得学习
较为简单的另一条链子https://kath4rs1s.github.io/2020/09/13/thinkphp5-rce/
本文来自博客园,作者:kzd的前沿思考,转载请注明原文链接:https://www.cnblogs.com/Fram3/p/15865976.html