Skip to content

Commit aefb1a2

Browse files
committed
feat: support servletFilterMapping for TomcatFilterProbe
1 parent 9e3babc commit aefb1a2

35 files changed

+347
-211
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.reajason.javaweb.probe.payload;
2+
3+
import com.reajason.javaweb.GenerationException;
4+
import com.reajason.javaweb.Server;
5+
import com.reajason.javaweb.buddy.TargetJreVersionVisitorWrapper;
6+
import com.reajason.javaweb.probe.payload.filter.*;
7+
import com.reajason.javaweb.utils.CommonUtil;
8+
import net.bytebuddy.ByteBuddy;
9+
import net.bytebuddy.dynamic.DynamicType;
10+
import org.apache.commons.codec.binary.Base64;
11+
12+
/**
13+
* @author ReaJason
14+
* @since 2026/1/11
15+
*/
16+
public class FilterProbeFactory {
17+
18+
public static String getBase64ByServer(String server) {
19+
try (DynamicType.Unloaded<?> unloaded = new ByteBuddy()
20+
.redefine(getFilterClass(server))
21+
.visit(TargetJreVersionVisitorWrapper.DEFAULT)
22+
.name(CommonUtil.generateClassName()).make()) {
23+
return Base64.encodeBase64String(unloaded.getBytes());
24+
}
25+
}
26+
27+
28+
private static Class<?> getFilterClass(String server) {
29+
switch (server) {
30+
case Server.Tomcat:
31+
case Server.JBoss:
32+
case Server.BES:
33+
case Server.TongWeb:
34+
return TomcatFilterProbe.class;
35+
case Server.Jetty:
36+
return JettyFilterProbe.class;
37+
case Server.Apusic:
38+
return ApusicFilterProbe.class;
39+
case Server.GlassFish:
40+
case Server.InforSuite:
41+
return GlassFishFilterProbe.class;
42+
case Server.WebSphere:
43+
return WebSphereFilterProbe.class;
44+
case Server.WebLogic:
45+
return WebLogicFilterProbe.class;
46+
case Server.Undertow:
47+
return UndertowFilterProbe.class;
48+
default:
49+
throw new GenerationException("filterProbe not supported for server: " + server);
50+
}
51+
}
52+
}

generator/src/main/java/com/reajason/javaweb/probe/payload/filter/TomcatFilterProbe.java

Lines changed: 58 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -13,84 +13,92 @@ public class TomcatFilterProbe {
1313

1414
@Override
1515
public String toString() {
16-
String msg = "";
16+
StringBuilder msg = new StringBuilder();
1717
Map<String, List<Map<String, String>>> allFiltersData = new LinkedHashMap<String, List<Map<String, String>>>();
1818
Set<Object> contexts = null;
1919
try {
2020
contexts = getContext();
2121
} catch (Throwable throwable) {
22-
msg += "context error: " + getErrorMessage(throwable);
22+
msg.append("context error: ").append(getErrorMessage(throwable));
2323
}
2424
if (contexts == null || contexts.isEmpty()) {
25-
msg += "context not found\n";
25+
msg.append("context not found\n");
2626
} else {
2727
for (Object context : contexts) {
2828
String contextRoot = getContextRoot(context);
29-
List<Map<String, String>> filters = collectFiltersData(context);
30-
allFiltersData.put(contextRoot, filters);
29+
try {
30+
List<Map<String, String>> filters = collectFiltersData(context);
31+
allFiltersData.put(contextRoot, filters);
32+
} catch (Throwable e) {
33+
msg.append(contextRoot).append(" failed ").append(getErrorMessage(e)).append("\n");
34+
}
3135
}
32-
msg += formatFiltersData(allFiltersData);
36+
msg.append(formatFiltersData(allFiltersData));
3337
}
34-
return msg;
38+
return msg.toString();
3539
}
3640

37-
private List<Map<String, String>> collectFiltersData(Object context) {
41+
@SuppressWarnings("unchecked")
42+
private List<Map<String, String>> collectFiltersData(Object context) throws Exception {
3843
Map<String, Map<String, Object>> aggregatedData = new LinkedHashMap<>();
3944

40-
try {
41-
Object[] filterMaps = (Object[]) invokeMethod(context, "findFilterMaps");
42-
if (filterMaps == null || filterMaps.length == 0) return Collections.emptyList();
45+
Object[] filterMaps = (Object[]) invokeMethod(context, "findFilterMaps");
46+
if (filterMaps == null || filterMaps.length == 0) return Collections.emptyList();
4347

44-
Object[] filterDefs = (Object[]) invokeMethod(context, "findFilterDefs");
48+
Object[] filterDefs = (Object[]) invokeMethod(context, "findFilterDefs");
4549

46-
for (Object fm : filterMaps) {
47-
String name = (String) invokeMethod(fm, "getFilterName");
48-
if (name == null) continue;
49-
if (!aggregatedData.containsKey(name)) {
50-
String filterClass = "N/A";
51-
if (filterDefs != null) {
52-
for (Object def : filterDefs) {
53-
if (!name.equals(invokeMethod(def, "getFilterName"))) continue;
54-
String cls = (String) invokeMethod(def, "getFilterClass");
55-
if (cls == null) {
56-
Object config = invokeMethod(context, "findFilterConfig", new Class[]{String.class}, new Object[]{name});
57-
Object filter = config != null ? invokeMethod(config, "getFilter") : null;
58-
if (filter != null) cls = filter.getClass().getName();
59-
}
60-
if (cls != null) filterClass = cls;
61-
break;
62-
}
50+
for (Object fm : filterMaps) {
51+
String name = (String) invokeMethod(fm, "getFilterName");
52+
if (name == null) continue;
53+
if (!aggregatedData.containsKey(name)) {
54+
String filterClass = "N/A";
55+
if (filterDefs != null) {
56+
Object filterDef = invokeMethod(context, "findFilterDef", new Class[]{String.class}, new Object[]{name});
57+
filterClass = (String) invokeMethod(filterDef, "getFilterClass");
58+
if (filterClass == null) {
59+
Object filterConfig = invokeMethod(context, "findFilterConfig", new Class[]{String.class}, new Object[]{name});
60+
Object filter = invokeMethod(filterConfig, "getFilter");
61+
if (filter != null) filterClass = filter.getClass().getName();
6362
}
64-
Map<String, Object> info = new HashMap<>();
65-
info.put("filterName", name);
66-
info.put("filterClass", filterClass);
67-
info.put("urlPatterns", new LinkedHashSet<String>());
68-
info.put("servletNames", new LinkedHashSet<String>());
69-
aggregatedData.put(name, info);
7063
}
71-
Map<String, Object> info = aggregatedData.get(name);
72-
String[] urls = null;
73-
try {
74-
urls = (String[]) invokeMethod(fm, "getURLPatterns");
75-
} catch (Exception e) {
76-
try {
77-
Object urlPattern = getFieldValue(fm, "urlPattern");
78-
if (urlPattern instanceof String) {
79-
urls = new String[] { (String) urlPattern };
80-
}
81-
} catch (Exception ignored) {
82-
}
64+
Map<String, Object> info = new HashMap<>();
65+
info.put("filterName", name);
66+
info.put("filterClass", filterClass);
67+
info.put("urlPatterns", new LinkedHashSet<String>());
68+
info.put("servletNames", new LinkedHashSet<String>());
69+
aggregatedData.put(name, info);
70+
}
71+
Map<String, Object> info = aggregatedData.get(name);
72+
String[] urls = null;
73+
try {
74+
urls = (String[]) invokeMethod(fm, "getURLPatterns");
75+
} catch (Exception e) {
76+
Object urlPattern = invokeMethod(fm, "getURLPattern");
77+
if (urlPattern instanceof String) {
78+
urls = new String[]{(String) urlPattern};
8379
}
84-
if (urls != null) ((Set<String>) info.get("urlPatterns")).addAll(Arrays.asList(urls));
8580
}
86-
} catch (Exception ignored) {}
81+
if (urls != null) ((Set<String>) info.get("urlPatterns")).addAll(Arrays.asList(urls));
82+
String[] servletNames = null;
83+
try {
84+
servletNames = (String[]) invokeMethod(fm, "getServletNames");
85+
} catch (Exception e) {
86+
Object servletName = invokeMethod(fm, "getServletName");
87+
if (servletName instanceof String) {
88+
servletNames = new String[]{(String) servletName};
89+
}
90+
}
91+
if (servletNames != null) ((Set<String>) info.get("servletNames")).addAll(Arrays.asList(servletNames));
92+
}
8793
List<Map<String, String>> result = new ArrayList<>();
8894
for (Map<String, Object> entry : aggregatedData.values()) {
8995
Map<String, String> finalInfo = new HashMap<>();
9096
finalInfo.put("filterName", (String) entry.get("filterName"));
9197
finalInfo.put("filterClass", (String) entry.get("filterClass"));
9298
Set<?> urls = (Set<?>) entry.get("urlPatterns");
9399
finalInfo.put("urlPatterns", urls.isEmpty() ? "" : urls.toString());
100+
Set<?> servletNames = (Set<?>) entry.get("servletNames");
101+
finalInfo.put("servletNames", servletNames.isEmpty() ? "" : servletNames.toString());
94102
result.add(finalInfo);
95103
}
96104
return result;
@@ -113,6 +121,7 @@ private String formatFiltersData(Map<String, List<Map<String, String>>> allFilte
113121
appendIfPresent(output, "", info.get("filterName"), "");
114122
appendIfPresent(output, " -> ", info.get("filterClass"), "");
115123
appendIfPresent(output, " -> URL:", info.get("urlPatterns"), "");
124+
appendIfPresent(output, " -> Servlet:", info.get("servletNames"), "");
116125
output.append("\n");
117126
}
118127
}

generator/src/main/java/com/reajason/javaweb/utils/CommonUtil.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,9 @@ public static String getPackageName(String className) {
9696
return className.substring(0, className.lastIndexOf("."));
9797
}
9898

99-
public static String generateShellClassName() {
100-
return getRandomPackageName() + ".ErrorHandler";
99+
public static String generateClassName() {
100+
String randomString = getRandomString(5);
101+
return getRandomPackageName() + ".Error" + randomString.substring(0, 1).toUpperCase() + randomString.substring(1).toLowerCase() + "Handler";
101102
}
102103

103104
public static String generateInjectorClassName() {

generator/src/test/java/com/reajason/javaweb/memshell/generator/CustomShellGeneratorTest.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ class CustomShellGeneratorTest {
3030
void testListener() {
3131
byte[] bytes = new ByteBuddy()
3232
.redefine(CommandListener.class)
33-
.name(CommonUtil.generateShellClassName()).make().getBytes();
34-
String className = CommonUtil.generateShellClassName();
33+
.name(CommonUtil.generateClassName()).make().getBytes();
34+
String className = CommonUtil.generateClassName();
3535
ShellConfig shellConfig = ShellConfig.builder()
3636
.server(Server.Tomcat)
3737
.shellType(ShellType.LISTENER)
@@ -52,8 +52,8 @@ void testListener() {
5252
void testFilter() {
5353
byte[] bytes = new ByteBuddy()
5454
.subclass(Object.class)
55-
.name(CommonUtil.generateShellClassName()).make().getBytes();
56-
String className = CommonUtil.generateShellClassName();
55+
.name(CommonUtil.generateClassName()).make().getBytes();
56+
String className = CommonUtil.generateClassName();
5757
ShellConfig shellConfig = ShellConfig.builder()
5858
.shellType(ShellType.FILTER)
5959
.build();
@@ -72,8 +72,8 @@ void testFilter() {
7272
void testValue() {
7373
byte[] bytes = new ByteBuddy()
7474
.redefine(GodzillaValve.class)
75-
.name(CommonUtil.generateShellClassName()).make().getBytes();
76-
String className = CommonUtil.generateShellClassName();
75+
.name(CommonUtil.generateClassName()).make().getBytes();
76+
String className = CommonUtil.generateClassName();
7777
ShellConfig shellConfig = ShellConfig.builder()
7878
.server(Server.BES)
7979
.shellType(ShellType.VALVE)

generator/src/test/java/com/reajason/javaweb/memshell/generator/GodzillaGeneratorTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ void generate() {
3333
.build();
3434
GodzillaConfig godzillaConfig = GodzillaConfig.builder()
3535
.shellClass(GodzillaServlet.class)
36-
.shellClassName(CommonUtil.generateShellClassName())
36+
.shellClassName(CommonUtil.generateClassName())
3737
.pass("pass")
3838
.key("key")
3939
.headerName("User-Agent")

integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertion.java

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,7 @@
4040
import static com.reajason.javaweb.memshell.ShellTool.*;
4141
import static com.reajason.javaweb.utils.CommonUtil.INJECTOR_CLASS_NAMES;
4242
import static com.reajason.javaweb.utils.CommonUtil.getRandomString;
43-
import static org.hamcrest.CoreMatchers.anyOf;
44-
import static org.hamcrest.CoreMatchers.containsString;
43+
import static org.hamcrest.CoreMatchers.*;
4544
import static org.hamcrest.MatcherAssert.assertThat;
4645
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
4746
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -53,13 +52,13 @@
5352
@Slf4j
5453
public class ShellAssertion {
5554

56-
public static void shellInjectIsOk(String url, String server, String shellType, String shellTool, int targetJdkVersion, Packers packer) {
57-
shellInjectIsOk(url, server, shellType, shellTool, targetJdkVersion, packer, null);
55+
public static MemShellResult shellInjectIsOk(String url, String server, String shellType, String shellTool, int targetJdkVersion, Packers packer) {
56+
return shellInjectIsOk(url, server, shellType, shellTool, targetJdkVersion, packer, null);
5857
}
5958

6059
@SneakyThrows
61-
public static void shellInjectIsOk(String url, String server, String shellType, String shellTool, int targetJdkVersion, Packers packer, GenericContainer<?> container) {
62-
shellInjectIsOk(url, server, shellType, shellTool, targetJdkVersion, packer, container, null);
60+
public static MemShellResult shellInjectIsOk(String url, String server, String shellType, String shellTool, int targetJdkVersion, Packers packer, GenericContainer<?> container) {
61+
return shellInjectIsOk(url, server, shellType, shellTool, targetJdkVersion, packer, container, null);
6362
}
6463

6564
@SneakyThrows
@@ -85,12 +84,12 @@ public static Pair<String, String> getUrls(String url, String shellType, String
8584
}
8685

8786
@SneakyThrows
88-
public static void shellInjectIsOk(String url, String server, String shellType, String shellTool, int targetJdkVersion, Packers packer, GenericContainer<?> appContainer, GenericContainer<?> pythonContainer) {
89-
shellInjectIsOk(url, server, null, shellType, shellTool, targetJdkVersion, packer, appContainer, pythonContainer);
87+
public static MemShellResult shellInjectIsOk(String url, String server, String shellType, String shellTool, int targetJdkVersion, Packers packer, GenericContainer<?> appContainer, GenericContainer<?> pythonContainer) {
88+
return shellInjectIsOk(url, server, null, shellType, shellTool, targetJdkVersion, packer, appContainer, pythonContainer);
9089
}
9190

9291
@SneakyThrows
93-
public static void shellInjectIsOk(String url, String server, String serverVersion, String shellType, String shellTool,
92+
public static MemShellResult shellInjectIsOk(String url, String server, String serverVersion, String shellType, String shellTool,
9493
int targetJdkVersion, Packers packer,
9594
GenericContainer<?> appContainer, GenericContainer<?> pythonContainer) {
9695
Pair<String, String> urls = getUrls(url, shellType, shellTool, packer);
@@ -104,6 +103,8 @@ public static void shellInjectIsOk(String url, String server, String serverVersi
104103
packerResultAndInject(generateResult, url, shellTool, shellType, packer, appContainer);
105104

106105
assertShellIsOk(generateResult, shellUrl, shellTool, shellType, appContainer, pythonContainer);
106+
107+
return generateResult;
107108
}
108109

109110
@SneakyThrows
@@ -492,4 +493,11 @@ public static void testProbeInject(String url, String server, String serverVersi
492493
public static void testProbeInject(String url, String server, String shellType, int targetJdkVersion) {
493494
testProbeInject(url, server, null, shellType, targetJdkVersion);
494495
}
496+
497+
public static void assertFilterProbeIsRight(String filterInfos) {
498+
assertThat(filterInfos, allOf(
499+
containsString("urlMappingTestFilter -> UrlMappingTestFilter -> URL:[/b64, /test]"),
500+
containsString("servletNameTestFilter -> ServletNameTestFilter -> Servlet:[b64, biginteger]")
501+
));
502+
}
495503
}

integration-test/src/test/java/com/reajason/javaweb/integration/probe/DetectionTool.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public class DetectionTool {
1818
public static String getBase64Class(Class<?> clazz) {
1919
return Base64.encodeBase64String(new ByteBuddy()
2020
.redefine(clazz)
21-
.name(CommonUtil.generateShellClassName())
21+
.name(CommonUtil.generateClassName())
2222
.visit(TargetJreVersionVisitorWrapper.DEFAULT)
2323
.make().getBytes());
2424
}

integration-test/src/test/java/com/reajason/javaweb/integration/probe/jbossas/Jboss423ContainerTest.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22

33
import com.reajason.javaweb.Server;
44
import com.reajason.javaweb.integration.ProbeAssertion;
5+
import com.reajason.javaweb.integration.ShellAssertion;
56
import com.reajason.javaweb.integration.VulTool;
67
import com.reajason.javaweb.integration.probe.DetectionTool;
8+
import com.reajason.javaweb.memshell.MemShellResult;
79
import com.reajason.javaweb.memshell.ShellTool;
810
import com.reajason.javaweb.memshell.ShellType;
911
import com.reajason.javaweb.packer.Packers;
12+
import com.reajason.javaweb.probe.payload.FilterProbeFactory;
1013
import com.reajason.javaweb.utils.CommonUtil;
1114
import lombok.SneakyThrows;
1215
import lombok.extern.slf4j.Slf4j;
@@ -83,20 +86,17 @@ void testBytecodeReqParamResponseBody() {
8386
@Test
8487
void testFilterProbe() {
8588
String url = getUrl(container);
86-
String data = VulTool.post(url + "/b64", DetectionTool.getTomcatFilterProbe());
87-
System.out.println(data);
88-
assertThat(data, anyOf(
89-
containsString("Context: ")
90-
));
89+
String data = VulTool.post(url + "/b64", FilterProbeFactory.getBase64ByServer(Server.JBoss));
90+
ShellAssertion.assertFilterProbeIsRight(data);
9191
}
9292

9393
@Test
9494
void testFilterFirstInject() {
9595
String url = getUrl(container);
96-
shellInjectIsOk(url, Server.JBoss, ShellType.FILTER, ShellTool.Command, Opcodes.V1_6, Packers.BigInteger, container);
96+
MemShellResult memShellResult = shellInjectIsOk(url, Server.JBoss, ShellType.FILTER, ShellTool.Command, Opcodes.V1_6, Packers.BigInteger, container);
9797
String data = VulTool.post(url + "/b64", DetectionTool.getTomcatFilterProbe());
9898
List<String> filter = ProbeAssertion.getFiltersForContext(data, "/app");
9999
String filterName = ProbeAssertion.extractFilterName(filter.get(0));
100-
assertThat(filterName, anyOf(startsWith(CommonUtil.getWebPackageNameForServer(Server.JBoss))));
100+
assertEquals(filterName, memShellResult.getShellClassName());
101101
}
102102
}

0 commit comments

Comments
 (0)