代码审计-通达OA任意文件上传配合文件包含
任意文件上传的漏洞点在ispirit/im/upload.php这个文件里面
$P = $_POST["P"];
if (isset($P) || ($P != "")) { //$p变量必须存在,文件才会继续执行
ob_start();
include_once "inc/session.php";
session_id($P);
session_start();
session_write_close();
}
else {
include_once "./auth.php";
}
$TYPE = $_POST["TYPE"];
$DEST_UID = $_POST["DEST_UID"];
$dataBack = array();
if (($DEST_UID != "") && !td_verify_ids($ids)) { //如果$DEST_UID不为空和传入的参数为数字和逗号进行下一步,可以追踪下td_verify_ids函数
$dataBack = array("status" => 0, "content" => "-ERR " . _("接收方ID无效"));
echo json_encode(data2utf8($dataBack));
exit();
}
if (strpos($DEST_UID, ",") !== false) { //判断$DEST_UID里是否有逗号,有逗号则不执行代码,没有则强制转化为整数
}
else {
$DEST_UID = intval($DEST_UID);
}
if ($DEST_UID == 0) { //$DEST_UID等于0且不为2则执行下列代码
if ($UPLOAD_MODE != 2) {
$dataBack = array("status" => 0, "content" => "-ERR " . _("接收方ID无效"));
echo json_encode(data2utf8($dataBack));
exit();
}
}
$MODULE = "im";
if (1 <= count($_FILES)) { //当传入一个或多个文件时且$UPLOAD_MODE等于1时,执行下列代码
if ($UPLOAD_MODE == "1") {
if (strlen(urldecode($_FILES["ATTACHMENT"]["name"])) != strlen($_FILES["ATTACHMENT"]["name"])) {
//使用了URL解码函数并比较解码前后的文件名长度,如果不相等则证明该文件进行了URL编码。
$_FILES["ATTACHMENT"]["name"] = urldecode($_FILES["ATTACHMENT"]["name"]);
//将文件名替换成解码后的文件名
}
}
$ATTACHMENTS = upload("ATTACHMENT", $MODULE, false);
//漏洞产生函数:upload,追踪upload
if (!is_array($ATTACHMENTS)) {
$dataBack = array("status" => 0, "content" => "-ERR " . $ATTACHMENTS);
echo json_encode(data2utf8($dataBack));
exit();
}
ob_end_clean();
$ATTACHMENT_ID = substr($ATTACHMENTS["ID"], 0, -1);
$ATTACHMENT_NAME = substr($ATTACHMENTS["NAME"], 0, -1);
if ($TYPE == "mobile") {
$ATTACHMENT_NAME = td_iconv(urldecode($ATTACHMENT_NAME), "utf-8", MYOA_CHARSET);
}
}
else {
$dataBack = array("status" => 0, "content" => "-ERR " . _("无文件上传"));
echo json_encode(data2utf8($dataBack));
exit();
}
$FILE_SIZE = attach_size($ATTACHMENT_ID, $ATTACHMENT_NAME, $MODULE);
if (!$FILE_SIZE) {
$dataBack = array("status" => 0, "content" => "-ERR " . _("文件上传失败"));
echo json_encode(data2utf8($dataBack));
exit();
}
if ($UPLOAD_MODE == "1") {
if (is_thumbable($ATTACHMENT_NAME)) {
$FILE_PATH = attach_real_path($ATTACHMENT_ID, $ATTACHMENT_NAME, $MODULE);
$THUMB_FILE_PATH = substr($FILE_PATH, 0, strlen($FILE_PATH) - strlen($ATTACHMENT_NAME)) . "thumb_" . $ATTACHMENT_NAME;
CreateThumb($FILE_PATH, 320, 240, $THUMB_FILE_PATH);
}
$P_VER = (is_numeric($P_VER) ? intval($P_VER) : 0);
$MSG_CATE = $_POST["MSG_CATE"];
if ($MSG_CATE == "file") {
$CONTENT = "[fm]" . $ATTACHMENT_ID . "|" . $ATTACHMENT_NAME . "|" . $FILE_SIZE . "[/fm]";
}
else if ($MSG_CATE == "image") {
$CONTENT = "[im]" . $ATTACHMENT_ID . "|" . $ATTACHMENT_NAME . "|" . $FILE_SIZE . "[/im]";
}
else {
$DURATION = intval($DURATION);
$CONTENT = "[vm]" . $ATTACHMENT_ID . "|" . $ATTACHMENT_NAME . "|" . $DURATION . "[/vm]";
}
$AID = 0;
$POS = strpos($ATTACHMENT_ID, "@");
if ($POS !== false) {
$AID = intval(substr($ATTACHMENT_ID, 0, $POS));
}
$query = "INSERT INTO im_offline_file (TIME,SRC_UID,DEST_UID,FILE_NAME,FILE_SIZE,FLAG,AID) values ('" . date("Y-m-d H:i:s") . "','" . $_SESSION["LOGIN_UID"] . "','$DEST_UID','*" . $ATTACHMENT_ID . "." . $ATTACHMENT_NAME . "','$FILE_SIZE','0','$AID')";
$cursor = exequery(TD::conn(), $query);
$FILE_ID = mysql_insert_id();
if ($cursor === false) {
$dataBack = array("status" => 0, "content" => "-ERR " . _("数据库操作失败"));
echo json_encode(data2utf8($dataBack));
exit();
}
$dataBack = array("status" => 1, "content" => $CONTENT, "file_id" => $FILE_ID);
echo json_encode(data2utf8($dataBack));
exit();
}
else if ($UPLOAD_MODE == "2") {
$DURATION = intval($_POST["DURATION"]);
$CONTENT = "[vm]" . $ATTACHMENT_ID . "|" . $ATTACHMENT_NAME . "|" . $DURATION . "[/vm]";
$query = "INSERT INTO WEIXUN_SHARE (UID, CONTENT, ADDTIME) VALUES ('" . $_SESSION["LOGIN_UID"] . "', '" . $CONTENT . "', '" . time() . "')";
$cursor = exequery(TD::conn(), $query);
echo "+OK " . $CONTENT;
}
else if ($UPLOAD_MODE == "3") {
if (is_thumbable($ATTACHMENT_NAME)) {
$FILE_PATH = attach_real_path($ATTACHMENT_ID, $ATTACHMENT_NAME, $MODULE);
$THUMB_FILE_PATH = substr($FILE_PATH, 0, strlen($FILE_PATH) - strlen($ATTACHMENT_NAME)) . "thumb_" . $ATTACHMENT_NAME;
CreateThumb($FILE_PATH, 320, 240, $THUMB_FILE_PATH);
}
echo "+OK " . $ATTACHMENT_ID;
}
else {
$CONTENT = "[fm]" . $ATTACHMENT_ID . "|" . $ATTACHMENT_NAME . "|" . $FILE_SIZE . "[/fm]";
$msg_id = send_msg($_SESSION["LOGIN_UID"], $DEST_UID, 1, $CONTENT, "", 2);
$query = "insert into IM_OFFLINE_FILE (TIME,SRC_UID,DEST_UID,FILE_NAME,FILE_SIZE,FLAG) values ('" . date("Y-m-d H:i:s") . "','" . $_SESSION["LOGIN_UID"] . "','$DEST_UID','*" . $ATTACHMENT_ID . "." . $ATTACHMENT_NAME . "','$FILE_SIZE','0')";
$cursor = exequery(TD::conn(), $query);
$FILE_ID = mysql_insert_id();
if ($cursor === false) {
echo "-ERR " . _("数据库操作失败");
exit();
}
if ($FILE_ID == 0) {
echo "-ERR " . _("数据库操作失败2");
exit();
}
echo "+OK ," . $FILE_ID . "," . $msg_id;
exit();
}
function upload($PREFIX, $MODULE, $OUTPUT)
{
if (strstr($MODULE, "/") || strstr($MODULE, "\\")) {
//判断$MODULE里是否有斜杠和反斜杠且$OUTPUT为false就进行下一步
if (!$OUTPUT) {
return _("参数含有非法字符。");
}
Message(_("错误"), _("参数含有非法字符。"));
exit();
}
$ATTACHMENTS = array("ID" => "", "NAME" => "");
//初始化$ATTACHMENTS数组,并存入名为ID和NAME的key
reset($_FILES);
//让$_FILES内部的指针执行第一个元素
foreach ($_FILES as $KEY => $ATTACHMENT ) {
if (($ATTACHMENT["error"] == 4) || (($KEY != $PREFIX) && (substr($KEY, 0, strlen($PREFIX) + 1) != $PREFIX . "_"))) {
continue;
}
$data_charset = (isset($_GET["data_charset"]) ? $_GET["data_charset"] : (isset($_POST["data_charset"]) ? $_POST["data_charset"] : ""));
$ATTACH_NAME = ($data_charset != "" ? td_iconv($ATTACHMENT["name"], $data_charset, MYOA_CHARSET) : $ATTACHMENT["name"]);
$ATTACH_SIZE = $ATTACHMENT["size"];
$ATTACH_ERROR = $ATTACHMENT["error"];
$ATTACH_FILE = $ATTACHMENT["tmp_name"];
$ERROR_DESC = "";
if ($ATTACH_ERROR == UPLOAD_ERR_OK) {
if (!is_uploadable($ATTACH_NAME)) {
$ERROR_DESC = sprintf(_("禁止上传后缀名为[%s]的文件"), substr($ATTACH_NAME, strrpos($ATTACH_NAME, ".") + 1));
}
$encode = mb_detect_encoding($ATTACH_NAME, array("ASCII", "UTF-8", "GB2312", "GBK", "BIG5"));
if ($encode != "UTF-8") {
$ATTACH_NAME_UTF8 = mb_convert_encoding($ATTACH_NAME, "utf-8", MYOA_CHARSET);
}
else {
$ATTACH_NAME_UTF8 = $ATTACH_NAME;
}
if (preg_match("/[\':<>?]|\/|\\\\|\"|\|/u", $ATTACH_NAME_UTF8)) {
$ERROR_DESC = sprintf(_("文件名[%s]包含[/\'\":*?<>|]等非法字符"), $ATTACH_NAME);
}
if ($ATTACH_SIZE == 0) {
$ERROR_DESC = sprintf(_("文件[%s]大小为0字节"), $ATTACH_NAME);
}
if ($ERROR_DESC == "") {
$ATTACH_NAME = str_replace("'", "", $ATTACH_NAME);
$ATTACH_ID = add_attach($ATTACH_FILE, $ATTACH_NAME, $MODULE);
if ($ATTACH_ID === false) {
$ERROR_DESC = sprintf(_("文件[%s]上传失败"), $ATTACH_NAME);
}
else {
$ATTACHMENTS["ID"] .= $ATTACH_ID . ",";
$ATTACHMENTS["NAME"] .= $ATTACH_NAME . "*";
}
}
@unlink($ATTACH_FILE);
}
else if ($ATTACH_ERROR == UPLOAD_ERR_INI_SIZE) {
$ERROR_DESC = sprintf(_("文件[%s]的大小超过了系统限制(%s)"), $ATTACH_NAME, ini_get("upload_max_filesize"));
}
else if ($ATTACH_ERROR == UPLOAD_ERR_FORM_SIZE) {
$ERROR_DESC = sprintf(_("文件[%s]的大小超过了表单限制"), $ATTACH_NAME);
}
else if ($ATTACH_ERROR == UPLOAD_ERR_PARTIAL) {
$ERROR_DESC = sprintf(_("文件[%s]上传不完整"), $ATTACH_NAME);
}
else if ($ATTACH_ERROR == UPLOAD_ERR_NO_TMP_DIR) {
$ERROR_DESC = sprintf(_("文件[%s]上传失败:找不到临时文件夹"), $ATTACH_NAME);
}
else if ($ATTACH_ERROR == UPLOAD_ERR_CANT_WRITE) {
$ERROR_DESC = sprintf(_("文件[%s]写入失败"), $ATTACH_NAME);
}
else {
$ERROR_DESC = sprintf(_("未知错误[代码:%s]"), $ATTACH_ERROR);
}
if ($ERROR_DESC != "") {
if (!$OUTPUT) {
delete_attach($ATTACHMENTS["ID"], $ATTACHMENTS["NAME"], $MODULE);
return $ERROR_DESC;
}
else {
Message(_("错误"), $ERROR_DESC);
}
}
}
return $ATTACHMENTS;
}
由此可知利用漏洞需满足四个条件
1. P=1
2、UPLOAD_MODE=1
3、DEST_UID=1
4、表单名为ATTACHMENT
但是文件上传的路径并不在目录根路径,所以还需要利用文件包含漏洞来执行代码
文件包含漏洞在ispirit/interface/gateway.php文件
点击查看代码
ob_start();
include_once "inc/session.php";
include_once "inc/conn.php";
include_once "inc/utility_org.php";
//$P变量不为空则进行正则匹配
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();
}
}
//如果$json存在则继续执行
if ($json) {
//去除反斜杠
$json = stripcslashes($json);
//转换为数组
$json = (array) json_decode($json);
//进行数组循环
foreach ($json as $key => $val ) {
//如果键为data,则$val转化为数组
if ($key == "data") {
$val = (array) $val;
//$val数组进行循环
foreach ($val as $keys => $value ) {
$keys = $value;
}
}
//如果键为url,则将其值赋值给$url
if ($key == "url") {
$url = $val;
}
}
//如果$url不为空
if ($url != "") {
//如果$url开头为斜杠,则去除斜杠
if (substr($url, 0, 1) == "/") {
$url = substr($url, 1);
}
//如果$url包含特定字符串("general/"、"ispirit/"或"module/"),则包含对应文件
if ((strpos($url, "general/") !== false) || (strpos($url, "ispirit/") !== false) || (strpos($url, "module/") !== false)) {
include_once $url;
}
}
exit();
}
由上代码可知,实现文件包含漏洞要满足两个条件
json为true
url包含特定字符串("general/"、"ispirit/"或"module/")