睡一觉就把漏洞给分析了
题外话
故事要从一段中式英语对话开始说起,起先是看到这是个危险程度严重的新漏洞,然后疯狂在官网找相应漏洞版本的应用找不到,就更有意思了(谁知道暂时没客服回应我,可能是因为美国时间客服正在睡觉吧)
于是睡了一觉,早上看到邮件。于是就兴奋的开始了漏洞复现,顺便细心分析了一波(好心人已公开链接,不过是免费版)
步入正题
漏洞描述
ZOHO ManageEngine ADSelfService Plus是美国卓豪(ZOHO)公司的针对 Active Directory 和云应用程序的集成式自助密码管理和单点登录解决方案。
Zoho ManageEngine ADSelfService Plus 6113版本及更早版本存在授权问题漏洞,该漏洞源于软件很容易绕过REST API认证,从而导致远程代码执行。
漏洞利用与分析
应用搭建安装后的效果如下(首次及默认账号密码为admin\admin):
根据官方修复资料来看,先去 ManageEngineADSFrameworkJava.jar
中 com.manageengine.ads.fw.api.RestAPIUtil
类看看验证绕过漏洞的修复前后
public static boolean isRestAPIRequest(HttpServletRequest request, JSONObject filterParams)
{
String restApiUrlPattern = "/RestAPI/.*";
try
{
restApiUrlPattern = filterParams.optString("API_URL_PATTERN", restApiUrlPattern);
}
catch (Exception ex)
{
out.log(Level.INFO, "Unable to get API_URL_PATTERN.", ex);
}
String reqURI = request.getRequestURI();
String contextPath = request.getContextPath() != null ? request.getContextPath() : "";
reqURI = reqURI.replace(contextPath, "");
reqURI = reqURI.replace("//", "/");
return Pattern.matches(restApiUrlPattern, reqURI);
}
修复后的reqURI将通过getNormalizedURI
方法对url进行/.
和/..
的过滤和多余/的删除
根据 https://github.com/projectdiscovery/nuclei-templates/blob/77c3dc36ac7df4c04e3ff7cd97f5f63ec8dc7311/cves/2021/CVE-2021-40539.yaml 的描述,尝试发送验证是否存在身份验证绕过的漏洞。经测试url路径前以下字符均可绕过身份验证
/./
/.///
///.///
/.//.///
/../
返回包表明路径遍历请求实际上绕过了认证过程
接下来利用/./
的身份验证绕过实现任意及恶意文件的上传,通过跟踪漏洞中所利用到的文件上传的接口,找到 AdventNetADSMClient.jar
中的 LogonCustomization
类,类中的unspecified
方法( 原本unspecified
是添加智能卡相关配置参数的方法)存在与文件上传相关的函数,对unspecified
方法进行分析,确定了达到文件上传条件所需要的参数
试着跟进sCAction.addSmartCardConfig()
看看是如何写文件的,sCAction.addSmartCardConfig()
—> mapping.findForward(addSmartCardConfig()
—> FileActionHandler.getFileFromRequest()
,结果是直接将form下要上传的文件内容以form中filename
参数命名方式直接写进了应用启动程序bin
目录下,还以为会稍微有点过滤
那这边我先上传个编译后的弹计算器的class
文件,以备后续使用
接着在AdventNetADSMClient.jar
下ConnectionAction
类的openSSLTool
方法,可不需要过滤接受http请求下的action
参数值,如果等于generateCSR
即执行SSLUtil.createCSR()
跟踪SSLUtil.createCSR()
,映入眼帘的是keytool这个工具的执行和执行keytool所必需的参数,这边看到keysize
和validity
这两个参数值可通过http传入可控且没受任何过滤,将拼接进keyCmd
的keytool命令执行语句中,最后执行runCommand(keyCmd.toString());
public class SSLUtil
{
private static Logger logger = Logger.getLogger(SSLUtil.class.getName());
public static void createCSR(HttpServletRequest request)
throws Exception
{
JSONObject sslParams = new JSONObject();
sslParams.put("COMMON_NAME", request.getParameter("NAME"));
sslParams.put("SAN_NAME", request.getParameter("SAN_NAME"));
sslParams.put("OU", request.getParameter("OU"));
sslParams.put("ORGANIZATION", request.getParameter("ORGANIZATION"));
sslParams.put("LOCALITY", request.getParameter("LOCALITY"));
sslParams.put("STATE", request.getParameter("STATE"));
sslParams.put("COUNTRY_CODE", request.getParameter("COUNTRY_CODE"));
sslParams.put("PASSWORD", request.getParameter("PASSWORD"));
sslParams.put("VALIDITY", request.getParameter("VALIDITY"));
sslParams.put("KEY_LENGTH", request.getParameter("KEY_LENGTH"));
JSONObject csrStatus = createCSR(sslParams);
if (csrStatus.has("eStatus")) {
request.setAttribute("status", customizeError(csrStatus.optString("eStatus", null)));
} else {
request.setAttribute("status", "success");
}
}
public static JSONObject createCSR(JSONObject sslSettings)
throws Exception
{
String name = "\"" + sslSettings.optString("COMMON_NAME", null) + "\"";
String pass = sslSettings.optString("PASSWORD", null);
pass = ClientUtil.keyToolEscape(pass);
String password = "\"" + pass + "\"";
logger.log(Level.INFO, "Keystore will be created for " + name);
File keyFile = new File("..\\jre\\bin\\SelfService.keystore");
if (keyFile.exists())
{
File bckFile = new File("..\\jre\\bin\\SelfService_" + System.currentTimeMillis() + ".keystore");
keyFile.renameTo(bckFile);
}
StringBuilder keyCmd = new StringBuilder("..\\jre\\bin\\keytool.exe -J-Duser.language=en -genkey -alias tomcat -sigalg SHA256withRSA -keyalg RSA -keypass ");
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);
}
String validity = sslSettings.optString("VALIDITY", null);
if ((validity != null) && (!validity.equals(""))) {
keyCmd.append(" -validity ").append(validity);
}
String san_name = sslSettings.optString("SAN_NAME", null);
keyCmd.append(" -dName \"CN=").append(ClientUtil.keyToolEscape(sslSettings.optString("COMMON_NAME", null)));
keyCmd.append(", OU= ").append(ClientUtil.keyToolEscape(sslSettings.optString("OU", null)));
keyCmd.append(", O=").append(ClientUtil.keyToolEscape(sslSettings.optString("ORGANIZATION", null)));
keyCmd.append(", L=").append(ClientUtil.keyToolEscape(sslSettings.optString("LOCALITY", null)));
keyCmd.append(", S=").append(ClientUtil.keyToolEscape(sslSettings.optString("STATE", null)));
keyCmd.append(", C=").append(ClientUtil.keyToolEscape(sslSettings.optString("COUNTRY_CODE", null)));
keyCmd.append("\" -keystore ..\\jre\\bin\\SelfService.keystore");
if ((san_name != null) && (!san_name.equals("")))
{
keyCmd.append(" -ext SAN=");
String[] san_name_arr = san_name.split(",");
for (int i = 0; i < san_name_arr.length; i++)
{
if (i != 0) {
keyCmd.append(",");
}
keyCmd.append("dns:" + ClientUtil.keyToolEscape(san_name_arr[i]));
}
}
JSONObject jStatus = new JSONObject();
String status = runCommand(keyCmd.toString());
看到runCommand(keyCmd.toString());
,顾名思义毫无疑问就是直接执行命令,跟踪runCommand
—> RunCmd
—> super(cmd)
寻其父类 runRuntimeExec()
,很明显通过Runtime.getRuntime
执行了该命令
import com.adventnet.sym.adsm.common.server.util.RunCmd;
刚刚说到keysize
和validity
这两个参数值可通过http传入可控且没受任何过滤,那么我们可以通过这两个参数进行keytool中有效命令的注入。该程序自带keytool,那么看看其在创建密钥时可用到的options,在可用options可以看到-providerclass
和-providerpath
两个可选参数以执行二进制文件即上面所传入的恶意class文件
发送以下poc去执行刚刚上传恶意class文件内容的jsp文件
原先任意文件上传默认上传在bin目录下,而keytool也是在当前目录下,那么就可以通过keytool的命令注入有效参数以实现来加载任意二进制的java类即class类
总结
将以上每个漏洞利用的过程结合在一块就是通过/./
绕过RestAPI
的身份验证以上传任意文件到程序启动bin
目录下,接着利用keytool命令
的执行拼接有效参数,指向任意class类以实现任意代码的执行
修复建议
- 目前厂商已发布升级补丁以修复漏洞,补丁获取链接:https://www.manageengine.com/products/self-service-password/service-pack.html
- 这是官方文档中给出的危险检测工具,则是通过日志报错信息、相应路径下是否含有key和证书、不常规web路径下是否含有jsp文件去检测是否被入侵
https://www.manageengine.com/products/self-service-password/kb/how-to-fix-authentication-bypass-vulnerability-in-REST-API.html
- 本文作者: w1nk1
- 本文来源: 奇安信攻防社区
- 原文链接: https://forum.butian.net/share/876
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!