问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
ADSelfService Plus未授权RCE漏洞(CVE-2021-40539)利用与分析
漏洞分析
睡一觉就把漏洞给分析了
#### 题外话 故事要从一段中式英语对话开始说起,起先是看到这是个危险程度严重的新漏洞,然后疯狂在官网找相应漏洞版本的应用找不到,就更有意思了(谁知道暂时没客服回应我,可能是因为美国时间客服正在睡觉吧) [](1) 于是睡了一觉,早上看到邮件。于是就兴奋的开始了漏洞复现,顺便细心分析了一波(好心人已公开链接,不过是免费版) [](2) #### 步入正题 漏洞描述 ---- ZOHO ManageEngine ADSelfService Plus是美国卓豪(ZOHO)公司的针对 Active Directory 和云应用程序的集成式自助密码管理和单点登录解决方案。 [](3) Zoho ManageEngine ADSelfService Plus 6113版本及更早版本存在授权问题漏洞,该漏洞源于软件很容易绕过REST API认证,从而导致远程代码执行。 漏洞利用与分析 ------- 应用搭建安装后的效果如下(首次及默认账号密码为admin\\admin): [](4) 根据官方修复资料来看,先去 `ManageEngineADSFrameworkJava.jar`中 `com.manageengine.ads.fw.api.RestAPIUtil`类看看验证绕过漏洞的修复前后 ```java 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进行`/.`和`/..`的过滤和多余/的删除 [](5) 根据 <https://github.com/projectdiscovery/nuclei-templates/blob/77c3dc36ac7df4c04e3ff7cd97f5f63ec8dc7311/cves/2021/CVE-2021-40539.yaml> 的描述,尝试发送验证是否存在身份验证绕过的漏洞。经测试url路径前以下字符均可绕过身份验证 ```java /./ /./// ///./// /.//./// /../ ``` 返回包表明路径遍历请求实际上绕过了认证过程 [](6) 接下来利用`/./`的身份验证绕过实现任意及恶意文件的上传,通过跟踪漏洞中所利用到的文件上传的接口,找到 `AdventNetADSMClient.jar`中的 `LogonCustomization`类,类中的`unspecified`方法( 原本`unspecified`是添加智能卡相关配置参数的方法)存在与文件上传相关的函数,对`unspecified`方法进行分析,确定了达到文件上传条件所需要的参数 [](7) [](8) 试着跟进`sCAction.addSmartCardConfig()`看看是如何写文件的,`sCAction.addSmartCardConfig()` —> `mapping.findForward(addSmartCardConfig()` —> `FileActionHandler.getFileFromRequest()`,结果是直接将form下要上传的文件内容以form中`filename`参数命名方式直接写进了`应用启动程序bin`目录下,还以为会稍微有点过滤 [](9) [](10) 那这边我先上传个编译后的弹计算器的`class`文件,以备后续使用 [](11) 接着在`AdventNetADSMClient.jar`下`ConnectionAction`类的`openSSLTool`方法,可不需要过滤接受http请求下的`action`参数值,如果等于`generateCSR`即执行`SSLUtil.createCSR()` [](12) 跟踪`SSLUtil.createCSR()`,映入眼帘的是keytool这个工具的执行和执行keytool所必需的参数,这边看到`keysize`和`validity`这两个参数值可通过http传入可控且没受任何过滤,将拼接进`keyCmd`的keytool命令执行语句中,最后执行`runCommand(keyCmd.toString());` ```java 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`执行了该命令 [](13) `import com.adventnet.sym.adsm.common.server.util.RunCmd;` [](14) 刚刚说到`keysize`和`validity`这两个参数值可通过http传入可控且没受任何过滤,那么我们可以通过这两个参数进行keytool中有效命令的注入。该程序自带keytool,那么看看其在创建密钥时可用到的options,在可用options可以看到`-providerclass`和`-providerpath`两个可选参数以执行二进制文件即上面所传入的恶意`class文件` [](15) 发送以下poc去执行刚刚上传恶意class文件内容的jsp文件 [](16) 原先任意文件上传默认上传在bin目录下,而keytool也是在当前目录下,那么`就可以通过keytool的命令注入有效参数以实现来加载任意二进制的java类即class类` [](17) #### 总结 将以上每个漏洞利用的过程结合在一块就是通过`/./`绕过`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>
发表于 2021-11-12 16:57:59
阅读 ( 11104 )
分类:
漏洞分析
1 推荐
收藏
0 条评论
请先
登录
后评论
w1nk1
12 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!