前言

漏洞公布已经有几天时间了,凑个周末也看了看,ispirit/im/upload.php已经有很多前辈分析过了,这里就不在赘述,在分析复现过程中发现了一些问题记录一下,分析的版本主要是2015和v11,在源码解密中,测试了v11和2015,2015使用的是zend5.3,v11使用的是zend5.4。

文件上传1

另一处未授权文件上传:

general/file_folder/swfupload.php


引入的文件中没有对权限的校验,首先获取了$_POST["PHPSESSID"],设置会话id,此处没有什么限制继续向下看

Line:23-29 典型的变量覆盖


这里判断了是否是post上传文件以及文件上传中是否产生错误,只要post构造表单传入一个正常大小的文件即可满足。

Line 35~61 需要传入参数FILE_SORT,满足条件则会执行数据库查询当前用户的文件容量,这里看到了SQL语句中拼接了$_SESSION["LOGIN_UID"],再结合上边的变量覆盖是不是可以导致注入呢?答案是不行的,因为在上文引用文件中引入了inc/common.inc.php

function CheckRequest(&$val)
{
    if (is_array($val)) {
        foreach ($val as $_k => $_v ) {
            checkrequest($_k);
            checkrequest($val[$_k]);
        }
    }
    else {
        if ((0 < strlen($val)) && preg_match("#^(MYOA_|GLOBALS|_GET|_POST|_COOKIE|_ENV|_SERVER|_FILES|_SESSION)#", $val)) {
            exit("Invalid Parameters!");
        }
    }
}
....
checkrequest($_REQUEST);

if (0 < count($_COOKIE)) {
    foreach ($_COOKIE as $s_key => $s_value ) {
        $_COOKIE[$s_key] = strip_tags(securerequest($s_value));
        $$s_key = $_COOKIE[$s_key];
    }

    reset($_COOKIE);
}

if (0 < count($_POST)) {
    $arr_html_fields = array();

    foreach ($_POST as $s_key => $s_value ) {
        if (substr($s_key, 0, 15) != "TD_HTML_EDITOR_") {
            if (is_array($s_value)) {
                $_POST[$s_key] = securerequest($s_value);
            }
            else {
                $_POST[$s_key] = strip_tags(securerequest($s_value));
            }

            $$s_key = $_POST[$s_key];
        }
        else {
            unset($_POST[$s_key]);
            $s_key = substr($s_key, 15);
            $$s_key = securerequest($s_value);
            $arr_html_fields[$s_key] = $$s_key;
        }
    }

    reset($_POST);
    $_POST = array_merge($_POST, $arr_html_fields);
}

if (0 < count($_GET)) {
    foreach ($_GET as $s_key => $s_value ) {
        $_GET[$s_key] = strip_tags(securerequest($s_value));
        $$s_key = $_GET[$s_key];
    }

    reset($_GET);
}

作用就是对传入进来的内容进行正则判断,如果存在_COOKIE、_SESSION等字符串则进行拦截,所以此处无法利用,继续向下看。


重点在于Line:91-93和97-98
这里将插入数据库的内容写入到了当前目录下的aa.txt或者bb.txt

在流程的最后返回了一串md5加密的字符,那么上传文件的地址就需要从上文中的aa.txtbb.txt来获得。
构造上传数据包:

可以看到文件名是随机字符+.+原文件名

因为在上传表单中传入了SORT_ID=1所以sql语句会保存在bb.txt

拼接起来就是我们上传后的文件名了/general/../../attach/file_folder/2003/xxx.xxxx.xxx

在v11版本中此文件进行了修改删除了保存到文件的代码。

if ($SORT_ID == "0") {
        $query = "insert into FILE_CONTENT(SORT_ID,SUBJECT,CONTENT,SEND_TIME,ATTACHMENT_ID,ATTACHMENT_NAME,ATTACHMENT_DESC,USER_ID,CONTENT_NO,CREATER) values ($SORT_ID,'$SUBJECT','','$SEND_TIME','$ATTACHMENT_ID','$ATTACHMENT_NAME','$ATTACHMENT_DESC','" . $_SESSION["LOGIN_USER_ID"] . "','$CONTENT_NO','" . $_SESSION["LOGIN_USER_ID"] . "')";
        exequery(TD::conn(), $query);
    }
    else {
        $query = "insert into FILE_CONTENT(SORT_ID,SUBJECT,CONTENT,SEND_TIME,ATTACHMENT_ID,ATTACHMENT_NAME,ATTACHMENT_DESC,USER_ID,CONTENT_NO,CREATER) values ($SORT_ID,'$SUBJECT','','$SEND_TIME','$ATTACHMENT_ID','$ATTACHMENT_NAME','$ATTACHMENT_DESC','','$CONTENT_NO','" . $_SESSION["LOGIN_USER_ID"] . "')";
        exequery(TD::conn(), $query);
        $CONTENT_ID = mysql_insert_id();
        add_log(16, _("新建文件,名称:") . $SUBJECT, $_SESSION["LOGIN_USER_ID"]);

但是在insert sql语句中可以看到拼接了$SORT_ID,在exequery函数中最后sql语句的执行会进行检查是否有敏感函数,有的话就会打印出错误的语句,相应文件在inc/conn.php


因为在执行sql语句之前文件已经上传成功,所以语句的错误并不妨碍文件上传。

$query = "insert into FILE_CONTENT(SORT_ID,SUBJECT,CONTENT,SEND_TIME,ATTACHMENT_ID,ATTACHMENT_NAME,ATTACHMENT_DESC,USER_ID,CONTENT_NO,CREATER) values ($SORT_ID,'$SUBJECT','','$SEND_TIME','$ATTACHMENT_ID','$ATTACHMENT_NAME','$ATTACHMENT_DESC','','$CONTENT_NO','" . $_SESSION["LOGIN_USER_ID"] . "')";

sort_id可控所以这里也是一个insert注入


file_content表中sort_id的是int(11),所以要使用字符截断控制长度,但是由于没有回显和过滤了一些函数,需要找到二次注入点或者找到一个可以显示sort_id的地方,由于还要写论文(毕业重要),时间问题就没有继续寻找。

失败的文件写入

搜索file_put_contents,文件general/workflow/document_list/input_form/form6.php没有校验权限,并将$MAINDOC_ID写入到29.txt

而变量$MAINDOC_ID可以结合变量覆盖漏洞来传入,因为在gateway.php中的引入过程中,引入了inc/common.inc.php

结果是因为strip_tags的处理,无法输入php标签,此处利用失败

文件包含

<?php

ob_start();
include_once "inc/session.php";
include_once "inc/conn.php";
include_once "inc/utility_org.php";

if ($P != "") {
    if (preg_match("/[^a-z0-9;]+/i", $P)) {
        echo _("非法参数");
        exit();
    }

    session_id($P);
    session_start();
    session_write_close();
    if (($_SESSION["LOGIN_USER_ID"] == "") || ($_SESSION["LOGIN_UID"] == "")) {
        echo _("RELOGIN");
        exit();
    }
}

if ($json) {
    $json = stripcslashes($json);
    $json = (array) json_decode($json);

    foreach ($json as $key => $val ) {
        if ($key == "data") {
            $val = (array) $val;

            foreach ($val as $keys => $value ) {
                $keys = $value;
            }
        }

        if ($key == "url") {
            $url = $val;
        }
    }

    if ($url != "") {
        if (substr($url, 0, 1) == "/") {
            $url = substr($url, 1);
        }

        if ((strpos($url, "general/") !== false) || (strpos($url, "ispirit/") !== false) || (strpos($url, "module/") !== false)) {
            include_once $url;
        }
    }

    exit();
}

?>


?>

$P进行了是否为空、正则校验以及当前用户是否登录,只需要使$P为空即可,在下面的if $json分支中使用了include_once $url,所以只要在传入的json数据中使URl参数中包含ispirit/general/module/再跳转目录到包含的文件即可进行任意文件包含。

payload:json={"url":"xxx"}

v11测试:

参考

文笔不好,内容某个方面或许偏颇,不足之处欢迎师傅前辈们指点和纠正,感激不尽。

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