天天看點

滲透測試-對新型記憶體馬webshell的研究

記憶體馬webshell-MemoryShell

  • ​​前言:你馬沒了​​
  • ​​利用JavaAgent技術發現并清除系統中的記憶體馬​​
  • ​​介紹​​
  • ​​安全行業主要讨論的記憶體馬主要分為以下幾種方式​​
  • ​​寫入測試​​
  • ​​Servlet API 提供的動态注冊機制​​
  • ​​Filter 記憶體馬​​
  • ​​使用 ServletContext 添加 Filter 記憶體馬的方法。​​
  • ​​Servlet 記憶體馬​​
  • ​​Listener 記憶體馬​​
  • ​​應用中可能調用的監聽器如下​​

前言:你馬沒了

利用JavaAgent技術發現并清除系統中的記憶體馬

Github:https://github.com/su18/MemoryShell

介紹

記憶體馬又名無檔案馬,見名知意,也就是無檔案落地的 webshell 技術,

是由于 webshell 特征識别、防篡改、目錄監控等等

針對 web 應用目錄或伺服器檔案防禦手段的介入,

導緻的檔案 shell 難以寫入和持久而衍生出的一種“概念型”木馬。

這種技術的核心思想非常簡單,一句話就能概括,

那就是對通路路徑映射及相關處理代碼的動态注冊。

這種動态注冊技術來源非常久遠,在安全行業裡也一直是不溫不火的狀态,

直到冰蠍的更新将 java agent 類型的記憶體馬重新帶入大衆視野

并且瞬間火爆起來。這種技術的爆紅除了概念新穎外,

也确實符合時代發展潮流,

現在針對 webshell 的清除和識别已經花樣百出,

大廠研發的使用​​

​分類、機率​

​​等等方式訓練的​

​機器學習算法模型​

​​,

基于​​

​神經網絡​

​​的​

​流量​

​​層面的​

​特征識别手段​

​​,

基本上都花式吊打正常檔案型 webshell。

如果你不會寫,不會繞,還僅僅使用網上下載下傳的 jsp ,那肯定是不行的。

記憶體馬搭上了

冰蠍和反序列化漏洞的快車,快速占領了人們的視野,成為了主流的 webshell 寫入方式。

作為 RASP 技術的使用者,

自然也要來研究和學習一下記憶體馬的思想、原理、添加方式,

并探究較好、較通用的防禦和清除方式。

安全行業主要讨論的記憶體馬主要分為以下幾種方式

• 動态注冊 servlet/filter/listener(使用 servlet-api 的具體實作)

• 動态注冊 interceptor/controller(使用架構如 spring/struts2)

• 動态注冊使用職責鍊設計模式的中間件、架構的實作(例如 Tomcat 的 Pipeline & Valve,Grizzly 的 FilterChain & Filter 等等)

• 使用 java agent 技術寫入位元組碼

寫入測試

Servlet API 提供的動态注冊機制

2013 年,國際大站 p2j 就釋出了這種特性的一種使用方法:

滲透測試-對新型記憶體馬webshell的研究

​Servlet、Listener、Filter ​

​由 javax.servlet.ServletContext 去加載

無論是使用 xml 配置檔案

還是使用 Annotation 注解配置

均由 Web 容器進行初始化,

讀取其中的配置屬性,然後向容器中進行注冊。Servlet 3.0 API 允許使 ServletContext 用動态進行注冊

在 Web 容器初始化的時候(即建立ServletContext 對象的時候)進行動态注冊。

可以看到 ServletContext 提供了 add*/create* 方法來實作動态注冊的功能。

滲透測試-對新型記憶體馬webshell的研究

在不同的容器中,實作有所不同,這裡僅以 Tomcat 為例調試,其他中間件在代碼中有部分實作。

Filter 記憶體馬

Filter 我們稱之為過濾器,

是 Java 中最常見也最實用的技術之一,

通常被用來​​

​處理靜态 web 資源​

​​、​

​通路權限控制​

​​、​

​記錄日志​

​​等附加功能等等。

一次請求進入到伺服器後,将先由 Filter 對使用者請求進行​​

​預處理​

​,再交給 Servlet。

通常情況下,Filter 配置在​

​配置檔案​

​​和​

​注解​

​中,在其他代碼中如果想要完成注冊,主要有以下幾種方式:

1. 使用 ServletContext 的 addFilter/createFilter 方法注冊;

2. 使用 ServletContextListener 的 contextInitialized 方法在伺服器啟動時注冊
(将會在 Listener 中進行描述);

3.      

使用 ServletContext 添加 Filter 記憶體馬的方法。

看一下 createFilter 方法

按照注釋,這個類用來在調用 ​​

​addFilter ​

​​向 ServletContext ​

​執行個體化​

​一個指定的 Filter 類。

滲透測試-對新型記憶體馬webshell的研究

這個類還約定了一個事情,那就是

如果這個 ServletContext 傳遞給 ServletContextListener 的 ServletContextListener.contextInitialized 方法,

該方法 即 未在 web.xml 或 web-fragment.xml 中聲明,

也未使用 javax.servlet.annotation.WebListener 進行注釋,

則會抛出 UnsupportedOperationException 異常,這個約定其實是非常重要的一點。

接下來看 addFilter 方法

ServletContext 中有三個重載方法,

分别接收字元串類型的 filterName

以及 Filter 對象/className 字元串/Filter 子類的 Class 對象,

提供不同場景下添加 filter 的功能,

這些方法均傳回 FilterRegistration.Dynamic 實際上就是 FilterRegistration 對象。

addFilter 方法實際上就是動态添加 filter 的最核心和關鍵的方法,

但是這個類中同樣約定了 UnsupportedOperationException 異常。

由于 Servlet API 隻是提供接口定義,

具體的實作還要看具體的容器,

那我們首先以 Tomcat 7.0.96 為例,看一下具體的實作細節。

相關實作方法在 org.apache.catalina.core.ApplicationContext#addFilter 中。

滲透測試-對新型記憶體馬webshell的研究

可以看到,這個方法建立了一個 FilterDef 對象,

将 filterName、filterClass、filter 對象初始化進去,

使用 StandardContext 的 addFilterDef 方法将建立的 FilterDef 儲存在了 StandardContext 中的一個 Hashmap filterDefs 中,

然後 new 了一個 ApplicationFilterRegistration 對象并且傳回,

并沒有将這個 Filter 放到 FilterChain 中,

單純調用這個方法不會完成自定義 Filter 的注冊。

并且這個方法判斷了一個狀态标記,如果程式以及處于運作狀态中,則不能添加 Filter。

直接操縱 FilterChain 呢?

FilterChain 在 Tomcat 中的實作是 org.apache.catalina.core.ApplicationFilterChain,

這個類提供了一個 addFilter 方法添加 Filter,

這個方法接受一個 ApplicationFilterConfig 對象,将其放在 this.filters 中。

答案是可以,但是沒用,因為對于每次請求需要執行的 FilterChain 都是動态取得的。

那Tomcat 是如何處理一次請求對應的 FilterChain 的呢?

在 ApplicationFilterFactory 的 createFilterChain 方法中,可以看到流程如下:

• 在 context 中擷取 filterMaps,并周遊比對 url 位址和請求是否比對;

• 如果比對則在 context 中根據 filterMaps 中的 filterName 查找對應的 filterConfig;

• 如果擷取到 filterConfig,則将其加入到 filterChain 中

• 後續将會循環 filterChain 中的全部 filterConfig,通過 getFilter 方法擷取 Filter 并執行 Filter 的 doFilter 方法。      

通過上述流程可以知道,每次請求的 FilterChain 是動态比對擷取和生成的,

如果想添加一個 Filter ,需要在 StandardContext 中 filterMaps 中添加 FilterMap,

在 filterConfigs 中添加 ApplicationFilterConfig。這樣程式建立時就可以找到添加的 Filter 了。

在之前的 ApplicationContext 的 addFilter 中将 filter 初始化存在了 StandardContext 的 filterDefs 中,那後面又是如何添加在其他參數中的呢?

在 StandardContext 的 filterStart 方法中生成了 filterConfigs

滲透測試-對新型記憶體馬webshell的研究

在 ApplicationFilterRegistration 的 addMappingForUrlPatterns 中生成了 filterMaps。

滲透測試-對新型記憶體馬webshell的研究

而這兩者的資訊都是從 filterDefs 中的對象擷取的。

在了解了上述邏輯後,在應用程式中動态的添加一個 filter 的思路就清晰了:

• 調用 ApplicationContext 的 addFilter 方法建立 filterDefs 對象,需要反射修改應用程式的運作狀态,加完之後再改回來;

• 調用 StandardContext 的 filterStart 方法生成 filterConfigs;

• 調用 ApplicationFilterRegistration 的 addMappingForUrlPatterns 生成 filterMaps;

• 為了相容某些特殊情況,将我們加入的 filter 放在 filterMaps 的第一位,
可以自己修改 HashMap 中的順序,
也可以在自己調用 StandardContext 的 addFilterMapBefore 直接加在 filterMaps 的第一位。      

基于以上思路的實作在 threedr3am 師傅的 文章 中有實作代碼,

既然知道了需要修改的關鍵位置,那就沒有必要調用方法去改,直接用反射加進去就好了,

其中中間還有很多小細節可以變化。

寫一個 demo 模拟一下動态添加一個 filter 的過程。

首先我們有一個 IndexServlet,如果請求參數有 id 的話,則列印在頁面上。

滲透測試-對新型記憶體馬webshell的研究

現在我們想實作在程式運作過程中動态添加一個 filter ,

提供将 id 參數的數字值 + 3 的功能(随便瞎想的功能。)

具體代碼放在了 org.su18.memshell.web.servlet.AddTomcatFilterServlet 中

普通通路時,會将 id 的值列印出來

滲透測試-對新型記憶體馬webshell的研究

通路添加 filter。

滲透測試-對新型記憶體馬webshell的研究

再次通路,id 參數會被加三。

滲透測試-對新型記憶體馬webshell的研究

Servlet 記憶體馬

Servlet 是 Server Applet(伺服器端小程式)的縮寫,用來讀取用戶端發送的資料,處理并傳回結果。也是最常見的 Java 技術之一。

與 Filter 相同,本小節也僅僅讨論使用 ServletContext 的相關方法添加 Servlet。

還是首先來看一下實作類 ApplicationContext 的 addServlet 方法。

滲透測試-對新型記憶體馬webshell的研究

與上一小節看到的 addFilter 方法十分類似。

那麼我們面臨同樣的問題,在一次通路到達 Tomcat 時,是如何比對到具體的 Servlet 的?

這個過程簡單一點,隻有兩部走:

• ApplicationServletRegistration 的 addMapping 方法調用 StandardContext#addServletMapping 方法,
在 mapper 中添加 URL 路徑與 Wrapper 對象的映射(Wrapper 通過 this.children 中根據 name 擷取)

• 同時在 servletMappings 中添加 URL 路徑與 name 的映射。      

直接調用相關方法進行添加,當然是用反射直接寫入也可以,有一些邏輯較為複雜。

測試代碼在 org.su18.memshell.web.servlet.AddTomcatServlet 中,

通路這個 servlet 會在程式中生成一個新的 Servlet :/su18。

滲透測試-對新型記憶體馬webshell的研究

看一下效果。

滲透測試-對新型記憶體馬webshell的研究

Listener 記憶體馬

Servlet 和 Filter 是程式員常接觸的兩個技術,

是以在網絡上對于之前兩小節的讨論較多,

對于 Listener 的讨論較少。但實際上這個點還是有很多師傅關注到了。

Listener 可以譯為監聽器,監聽器用來監聽對象或者流程的建立與銷毀,通過 Listener,可以自動觸發一些操作,

是以依靠它也可以完成記憶體馬的實作。

先來了解一下 Listener 是幹什麼的,看一下 Servlet API 中的注釋。

滲透測試-對新型記憶體馬webshell的研究

應用中可能調用的監聽器如下

.servlet.http.HttpSessionListener:對 Session 整體狀态的監聽

• javax.servlet.http.HttpSessionAttributeListener:對 Session 屬性的監聽      

可以看到 Listener 也是為一次通路的請求或生命周期進行服務的,

在上述每個不同的接口中,都提供了不同的方法,用來在監聽的對象發生改變時進行觸發。

而這些類接口,實際上都是 java.util.EventListener 的子接口。

這裡我們看到,在 ServletRequestListener 接口中,提供了兩個方法在 request 請求建立和銷毀時進行處理,比較适合我們用來做記憶體馬。

滲透測試-對新型記憶體馬webshell的研究

除了這個 Listener,其他的 Listener 在某些情況下也可以觸發作為記憶體馬的實作.

ServletRequestListener 提供兩個方法:

requestInitialized 和 requestDestroyed,

兩個方法均接收 ServletRequestEvent 作為參數,

ServletRequestEvent 中又儲存了 ServletContext 對象和 ServletRequest 對象,

是以在通路請求過程中我們可以在 request 建立和銷毀時實作自己的惡意代碼,完成記憶體馬的實作。

滲透測試-對新型記憶體馬webshell的研究

Tomcat 中 EventListeners 存放在

StandardContext 的 applicationEventListenersObjects 屬性中,

同樣可以使用 StandardContext 的相關 add 方法添加。

我們還是實作一個簡單的功能,在 requestDestroyed 方法中擷取 response 對象,

向頁面原本輸出多寫出一個字元串。正常通路時:

滲透測試-對新型記憶體馬webshell的研究

添加 Listener,可以看到,

由于我們是在 requestDestroyed 中植入惡意邏輯,那麼在本次請求中就已經生效了:

滲透測試-對新型記憶體馬webshell的研究

通路之前的路徑也生效了:

滲透測試-對新型記憶體馬webshell的研究

除了 EventListener,Tomcat 還存在了一個 LifecycleListener ,

當然也肯定有可以用來觸發的實作類,但是用起來一定是不如 ServletRequestListener

由于在 ServletRequestListener 中可以擷取到 ServletRequestEvent,

這其中又存了很多東西,ServletContext/StandardContext 都可以擷取到,那玩法就變得更多了。

在 requestInitialized 中監聽,如果通路到了某個特定的 URL,

或這次請求中包含某些特征(可以拿到 request 對象,随便怎麼定義),

則新起一個線程去 StandardContext 中注冊一個 Filter,可以實作某些惡意功能。

在 requestDestroyed 中再起一個新線程 sleep 一定時間後将我們添加的 Filter 解除安裝掉。

有了一個真正的動态後門,隻有用的時候才回去注冊它,用完就删。