0x01 环境搭建
华夏ERP基于SpringBoot框架和SaaS模式,可以算作是国内人气较高的一款ERP项目,看网上已经公开了漏洞,本次对此框架代码进行源码审计。
源码下载路径:
https://github.com/jishenghua/jshERP
这里的版本为v2.3,直接拖入IDEA加载,Maven下载所需的jar包,点击run即可
0x02 Filter 审计
使用@WebInitParam配置多个InitParam,使某些页面不被拦截。
@WebInitParam 标注通常不单独使用,而是配合@WebServlet 或者@WebFilter 使用。它的作业是为 Servlet 或者过滤器指定初始化参数
这里使用 @WebInitParam 注解配置多个 name,对 .css#.js#.jpg#.png#.gif#.ico,/user/login#/user/registerUser#/v2/api-docs资源请求的时候不会进行拦截。
doFilter方法是具体的实现:
如果登陆了会得到一个session,从session中取出的user字段,如果不为空,则代表已登陆,不拦截,继续调用下一个doFilter
如果未登陆,会判断url中是否含有doc.html,register.html,login.html,不拦截
ignoredList是css,js等字符串列表,通过正则表达式判断是否存在url中,如果存在则不拦截
private static boolean verify(ListignoredList, String url) {
for (String regex : ignoredList) {
Pattern pattern = Pattern.compile(regexPrefix + regex + regexSuffix);
Matcher matcher = pattern.matcher(url);
if (matcher.matches()) {
return true;
}
}
return false;
}
最后一个if,allowUrls是/user/login等url,判断url是否以这些开头,如果是则不拦截。如果这四个if都没进去,则重定向到login.html
读完这个filter我们可以明确几点:
1.requestUrl中如果存在/doc.html,/register.html,/login.html字段就可以绕过认证请求
2.传入verify()方法进行判断的时候,正确的使用应该选择endsWith()来判断url是否以.css、.png这些资源后缀结尾,但是上图代码只是使用正则表达式判断.css,.png名字存在即可,导致传入诸如:../a.css/../,也可以绕过认证请求
3.使用startsWith()方法来判断url是否以/user/login,/user/registerUser等字符开头的时候,可以使用目录穿越来骗过判断,导致可以绕过认证请求
4.并没有对传入的参数处理的filter,在Filter过滤器中没有看到xss过滤,sql注入等恶意字符的过滤。
0x03 SQL注入
在readme.md文件中,看项目总体框架,确定使用了mybatis
Mybatis的SQL语句可以基于注解的方式写在类方法上面,更多的是以xml的方式写到xml文件。Mybatis中SQL语句需要我们自己手动编写或者用generator自动生成。对于 mybatis 的 SQL 注入,我们可以直接在 mapper_xml 文件夹内进行全局搜索 $ 以及 like,in 以及 order by。
直接去找 UserMapperEx.xml,因为这个业务点我认为是非常非常直接的,显山露水的,找可控点更为容易。在 UserMapperEx.xml 里面搜索 like 这一关键字
我们去到接口:UserMapperEx.java
对应的 Service 接口
很明显在 /addUser 这个情况下是可以触发的
跟踪代码流程:
Usermapperex.xml--->>usermapperex.java---->>userservice.java---->>usercontoller.java
0x04 权限校验绕过
在审计准备阶段对拦截器@WebFilter进行分析的时候,发现存在权限绕过的情况,来验证第一种情况:
requestUrl中如果存在/doc.html,/register.html,/login.html字段就可以绕过认证请求
只要我们的请求url中有/doc.html,/register.html,/login.html字就可以绕过认证,payload可以这样写:/doc.html/../,/register.html/../,/login.html/../
这样就可以去访问任意接口拿到数据了
资源加白的问题在前面已经说过了,我们现在直接来复现一遍
当我们没有处于登录态的时候,发包/访问时得到的是一个 302 的重定向回显,如图
前文说到,这几种请求是不会被拦截的:/doc.html,/register.html,/login.html
所以我们构造如下 payload
/login.html/../home.html
成功 Bypass
资源加白
同上面是一样的,因为没有做严格的 endsWith() 的判断
payload 如下
/1.css/../home.html
URL 加白
同样的攻击手段
/user/login/../../home.html
这个攻击不如前两种好用,它要求你知道文件的路径
0x05 越权漏洞
发现这个业务逻辑的代码是写在 Service 层里面的,跟进一下 Service 层的代码
明显看到这里只禁止重置admin超管密码,但是并没有对当前重置密码的用户身份做判断,导致存在越权重置他人密码
越权删除用户
先去看 deleteUser 对应的接口
接收参数ids,使用逗号,分割,调用userMapperEx.batDeleteOrUpdateUser()方法将ids参数拼接进sql语句进行删除,这里没有对当前执行删除用户操作的用户身份做判断,甚至没有禁止删除超级管理员,导致存在越权删除任意用户信息(包括管理员)
但是问题就出在 doc.html 中,这个接口文档在被未授权读取之后,能够看到所有的 URL,配合之前的未授权可以进行删库的操作,但是可利用性并不大。
我们先用 admin 的账户抓一个 deleteUser 的包
再用我们普通用户权限去抓包,替换 Session,替换 ID,成功删除 admin 的账户。这里的水平越权与垂直越权都是存在的。
再登录,就是用户不存在了
越权修改用户信息
去到 Service 层,这里有一个 checkUserNameAndLoginName(ue); 的语句,跟进去看一下
checkUserNameAndLoginName() 做了一个什么业务呢,它进行 UserName 与 loignName 是否为空的判断
我们先看 loginName 是否为空的判断
再看 userName
由于用户名和登录名的校验逻辑一样,这里只拿登录名检查来说,首先通过getLoginName()获取id对应的登录名,再通过getUserListByUserNameOrLoginName()方法,将登录名作为参数拼接进sql语句中获取user列表,从列表中取出id与前端传入的id进行比较,相同就可以更新数据。同样没有对当前执行更新用户数据的操作的用户身份做判断
0x06 参考
https://www.cnblogs.com/bmjoker/p/14856437.html
https://drun1baby.top/2022/09/30/Java-%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1%E4%B9%8B%E5%8D%8E%E5%A4%8F-ERP-CMS-V2.3/
免责声明:
「由于传播、利用本公众号虫洞小窝所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,本公众号及作者不为此承担任何责任,一旦造成后果请自行承担!」
虫洞小窝
bugbounty or redteam
原文始发于微信公众号(虫洞小窝):华夏erp代码审计
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论