Jimureport1.7.8越权漏洞代码分析及修复代码分析(CVE-2024-44893)
follycat Lv3

漏洞代码分析及漏洞成因

漏洞成因

拦截器中对于Token验证的代码中存在逻辑漏洞。

漏洞代码分析

首先,这个项目使用了Spring MVC的拦截器。

可以找到拦截器的代码段位于这个项目的jimureport-spring-boot-starter.jar中的org.jeecg.modules.jmreport.config.firewall.interceptor.JimuReportTokenInterceptor包。

以下是preHandle拦截器的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
} else {
String var4 = d.i(request.getRequestURI().substring(request.getContextPath().length()));
log.debug("JimuReportInterceptor check requestPath = " + var4);
int var5 = 500;
if (n.a(var4)) {
log.error("请注意,请求地址有xss攻击风险!" + var4);
this.backError(response, "请求地址有xss攻击风险!", var5);
return false;
} else {
String var6 = this.jmBaseConfig.getCustomPrePath();
log.debug("customPrePath: {}", var6);
if (j.d(var6) && !var6.startsWith("/")) {
var6 = "/" + var6;
}

request.setAttribute("customPrePath", var6);
HandlerMethod var7 = (HandlerMethod)handler;
Method var8 = var7.getMethod();
if (var4.contains("/jmreport/shareView/")) {
return true;
} else {
JimuNoLoginRequired var9 = (JimuNoLoginRequired)var8.getAnnotation(JimuNoLoginRequired.class);
if (j.d(var9)) {
return true;
} else {
boolean var10 = false;

try {
var10 = this.verifyToken(request);
} catch (Exception var14) {
}

if (!var10) {
if (this.jimuReportShareService.isSharingEffective(var4, request)) {
return true;
} else {
String var16 = request.getParameter("previousPage");
if (j.d(var16)) {
if (this.jimuReportShareService.isShareingToken(var4, request)) {
return true;
} else {
log.error("分享链接失效或分享token不匹配(" + request.getMethod() + "):" + var4);
this.backError(response, "分享链接失效或分享token不匹配,禁止钻取!", var5);
return false;
}
} else {
log.error("Token校验失败!请求无权限(" + request.getMethod() + "):" + var4);
this.backError(response, "Token校验失败,无权限访问!", var5);
return false;
}
}
} else {
b var15 = (b)var8.getAnnotation(b.class);
if (var15 != null) {
String[] var11 = var15.a();
String[] var12 = this.jimuTokenClient.getRoles(request);
if (var12 == null || var12.length == 0) {
log.error("此接口需要角色权限,请联系管理员!请求无权限(" + request.getMethod() + "):" + var4);
if ("/jmreport/loadTableData".equals(var4)) {
var5 = GEN_TEST_DATA_CODE;
}

this.backError(response, NO_PERMISSION_PROMPT_MSG, var5);
return false;
}

boolean var13 = Arrays.stream(var12).anyMatch((code) -> {
return j.a(code, var11);
});
if (!var13) {
log.error("此接口需要角色权限,请联系管理员!请求无权限(" + request.getMethod() + "):" + var4);
if ("/jmreport/loadTableData".equals(var4)) {
var5 = GEN_TEST_DATA_CODE;
}

this.backError(response, NO_PERMISSION_PROMPT_MSG, var5);
return false;
}
}

return true;
}
}
}
}
}
}

漏洞代码位于为下面这段

其中var10为检查用户是否登录的检测结果,如果没有登录就会进入下面的代码中。

其中isSharingEffective是用于判断时间是否过期的代码,我们当前处于未登录状态,所以主要看else后面的代码段。

首先从get中获取previousPage这个参数,并通过j.d这个方法进行校验。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static String d(String var0) {
byte var1 = 3;
if (var0.length() < var1) {
return var0.toLowerCase();
} else {
StringBuilder var2 = new StringBuilder(var0);
int var3 = 0;

for(int var4 = 2; var4 < var0.length(); ++var4) {
if (Character.isUpperCase(var0.charAt(var4))) {
var2.insert(var4 + var3, "_");
++var3;
}
}

return var2.toString().toLowerCase();
}
}

j.d方法的这段代码主要用于字符串分割,小于3个长度的字符会转化为小写直接输出,大于3个长度的字符就会检测大写字符并加入_进行分割。

所以只需要previousPage参数不为空即可。

然后跟进到isShareingToken方法。

这个方法位于org.jeecg.modules.jmreport.desreport.service.a.f

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public boolean isShareingToken(String requestPath, HttpServletRequest request) {
String var3 = request.getHeader("JmReport-Share-Token");
String var4 = "";
if (j.c(var3)) {
var3 = request.getParameter("shareToken");
}

String var5 = request.getParameter("jmLink");
if (j.d(var5)) {
try {
byte[] var6 = Base64Utils.decodeFromString(var5);
String var7 = new String(var6);
String[] var8 = var7.split("\\|\\|");
if (ArrayUtils.isNotEmpty(var8) && var8.length == 2) {
var3 = var8[0];
var4 = var8[1];
}
} catch (IllegalArgumentException var9) {
a.error("解密失败:" + var9.getMessage());
a.error(var9.getMessage(), var9);
return false;
}
}

if (j.c(var3)) {
return false;
} else {
JimuReportShare var10 = this.jimuReportShareDao.getShareByShareToken(var3);
if (var10 != null) {
var10 = this.compareToDate(var10);
if (!"0".equals(var10.getStatus())) {
return false;
}
}

if (requestPath.startsWith("/jmreport/view")) {
if (!j.d(var4)) {
return false;
}

Long var11 = this.jimuReportLinkDao.selectLinkCountByLinkId(var4);
if (null != var11 && var11 > 0L) {
return true;
}
}

return true;
}
}

可以看到代码中先获取http头为JmReport-Share-Token的字符串,正常逻辑来说会判断其是否存在,如果不存在就获取名字为shareToken的参数。

但是在j函数中,有俩个.c方法,一个接收值类型为字符串,另一个为Object。

而var3类型就为String类型,所以会优先调用c(String var0)这个方法。

这样漏洞就产生了,当JmReport-Share-Token有参数时,其也会获取shareToken的值,并赋值给var3参数,造成参数覆盖。

并且当shareToken赋值为空时,并不会触发return false,会继续进入到else中,从而走到getShareByShareToken方法,

这个方法用于查询数据库中是否存在 shareToken。

如果shareToken查找结果为空,也就是var10为null时,由于没有对var10=null的处理方法,并且这个函数默认返回true,从而达到权限绕过的漏洞

漏洞修复代码分析

可以下载一个1.8.0的代码进行对比,看看厂家是如何修复这个漏洞的。

首先,他更改了j函数中的c方法,改为了OkConvertUtils.isEmpty

点进去看源码,其实可以发现这个方法的代码很眼熟,就是原来j函数中的接收Object参数类型的c方法。

然后,再往下看,可以看到下面的代码对var10=null的情况进行了处理,当var10=null时,使用了else将其返回值更改为false。

 Comments
Comment plugin failed to load
Loading comment plugin
Please fill in the required configuration items for Valine comment plugin
Powered by Hexo & Theme Keep
Unique Visitor Page View