记一次失败的代码审计复现

  1. 环境
  2. 参考链接
  3. 复现过程

原本想着复现wpDiscuz插件任意文件上传的。后面没成功
跟了一下代码不清楚是不是修复了,做个笔记

环境

phpstudy
php 7.0.2
wpdiscuz.7.0.3

参考链接

https://xz.aliyun.com/t/8276

复现过程

将给出的下载插件链接: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请求的处理

关键点在getMimeTypeisAllowedFileType
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://422926799.github.io/posts/8a502742.html

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录