wordpress插件wpdiscuz任意文件上传漏洞分析
下载链接
https://downloads.wordpress.org/plugin/wpdiscuz.7.0.3.zip
测试
环境
- windows phpstudy
- phpstorm
解压插件到wp-content/plugins
目录下
登录管理员启用插件,这时文章下面评论区
上传带GIF头的php文件
可以看到上传成功,图片地址也在回显中
http://127.0.0.1/wordpress/wp-content/uploads/2020/08/1-1598075857.9448.php
分析
wp-setting.php 将wp_ajax_nopriv_wmuUploadFiles
写入了$wp_filter
foreach ( wp_get_active_and_valid_plugins() as $plugin ) {
wp_register_plugin_realpath( $plugin );
include_once $plugin;
/**
* Fires once a single activated plugin has loaded.
*
* @since 5.1.0
*
* @param string $plugin Full path to the plugin's main file.
*/
do_action( 'plugin_loaded', $plugin );
}
$action = ( isset( $_REQUEST['action'] ) ) ? $_REQUEST['action'] : '';
wordpress先加载插件
入口文件
上传的关键代码在wp-content\plugins\wpdiscuz\utils\class.WpdiscuzHelperUpload.php
376行
public function uploadFiles() {
……
require_once(ABSPATH . "wp-admin/includes/image.php");
foreach ($files as $file) {
$error = false;
$extension = pathinfo($file["name"], PATHINFO_EXTENSION); //$extension="php"
$mimeType = $this->getMimeType($file, $extension);
检测文件mime类型
private function getMimeType($file, $extension) {
$mimeType = "";
if (function_exists("mime_content_type")) {
$mimeType = mime_content_type($file["tmp_name"]); //image/gif
} elseif (function_exists("finfo_open") && function_exists("finfo_file")) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $file["tmp_name"]);
} elseif ($extension) {
$matches = wp_check_filetype($file["name"], $this->options->content["wmuMimeTypes"]);
$mimeType = empty($matches["type"]) ? "" : $matches["type"];
}
return $mimeType;
}
继续跟进
……
if ($this->isAllowedFileType($mimeType)) {
if (empty($extension)) { //$extension="php" 没进入
……
}
$file["type"] = $mimeType;
} else {
……
}
do_action("wpdiscuz_mu_preupload", $file); //没什么用
if (!$error) {
$attachmentData = $this->uploadSingleFile($file);
isAllowedFileType
private function isAllowedFileType($mimeType) {
$isAllowed = false;
if (!empty($this->options->content["wmuMimeTypes"]) && is_array($this->options->content["wmuMimeTypes"])) {
$isAllowed = in_array($mimeType, $this->options->content["wmuMimeTypes"]);
}
return $isAllowed;
}
$this->options->content["wmuMimeTypes"] 内容
在数组内
$isAllowed=true
上传操作在
340行 $attachmentData = $this->uploadSingleFile($file);
uploadSingleFile
private function uploadSingleFile($file) {
$currentTime = WpdiscuzHelper::getMicrotime();
$attachmentData = [];
$path = $this->wpUploadsPath . "/";
$fName = $file["name"];
$pathInfo = pathinfo($fName);
$realFileName = $pathInfo["filename"];
$ext = empty($pathInfo["extension"]) ? "" : strtolower($pathInfo["extension"]);
$sanitizedName = sanitize_file_name($realFileName);
$cleanFileName = $sanitizedName . "-" . $currentTime . "." . $ext;
$cleanRealFileName = $sanitizedName . "." . $ext;
$fileName = $path . $cleanFileName;
if (in_array($ext, ["jpeg", "jpg"])) {
$this->imageFixOrientation($file["tmp_name"]);
}
$success = apply_filters("wpdiscuz_mu_compress_image", false, $file["tmp_name"], $fileName, $q = 60);
if ($success || @move_uploaded_file($file["tmp_name"], $fileName)) {
到这里已经把文件上传了
上传文件名为原文件名+时间戳+后缀
v7.0.5的改进
isAllowedFileType 对mime的后缀和文件后缀进行比较
private function isAllowedFileType($mimeType, $extension) {
$isAllowed = false;
if (!empty($this->mimeTypes) && is_array($this->mimeTypes)) {
foreach ($this->mimeTypes as $ext => $mimes) {
if ($ext === $extension) {
if ($isAllowed = in_array($mimeType, explode("|", $mimes))) {
break;
}
}
}
}
return $isAllowed;
}
总结
整个上传过程只对文件头进行检测,并没有对文件后缀进行检测,导致可以设置一句话木马内容为
图片文件头+php代码
,进行getshell