最近,看到網上有安全研究人員發布了一篇文章,描述了一款在 tomcat的全新內存馬,地址為:https://www.iculture.cc/forum-post/19128.html,該內存馬基于 websocket協議,有別于目前已知的見的filter型內存馬、servlet 型內存馬,該類型的網馬從形式上還有功能上都是非常新穎的,且根據該作者描述,目前常規的內存馬掃描工具(如memshell scanner)無法檢測出該類型的內存馬。為此,筆者對該內存馬的原理進行了分析,并在此提出了一種檢測該內存馬的思路,可以幫助安全人員檢測出此種類型內存馬,從而降低業務系統安全隱患。

websocket 內存馬原理

Tomcat 服務器在啟動時會通過WsSci中的 WsServerContainer 將 classpath 注解下帶有@ServerEndpoint的類加入到 websocket 服務中。如:

通過調試可以看到添加位置如下:

對該段代碼進行分析,注冊 websocket 服務步驟如下,

首先要初始化一個WsServerContainer

通過掃描 classpath 下的帶注解的類并加入一個 iterator 之中,

然后遍歷該列表,針對每個元素,然后創建一個 ServerEndpointConfig ,然后通過 addEndpoint 將該類加入到websocket服務之中。

至此,就將一個ws節點加入的服務中,根據這個思路,攻擊者也可以利用這種方式在運行時動態注入ws節點,注入的思路與此一致,該類型內存馬作者也給出了代碼,此處不再過多敘述。

檢測思路

既然是內存馬,那在內存中肯定會有實體,所以只要找到存儲該實體的位置,就能檢測出此類內存馬。

下面通過動態調試分析該實體的具體存儲位置,前面提到,最終是通過 addEndpoint 函數將該內存馬實體加入到ws服務中,于是跟進該函數,

在 addEndpoint 中,通過(WsServerContainer.ExactPathMatch)this.configExactMatchMap.put(path, newMatch)加入到一個名為configExactMatchMap的 Map 之中,進一步查看該成員變量定義:

是一個Map的類型,通過調試可知,該 map 的 key 就是對應 ws 服務的 url, 后面的WsServerContainer.ExactPathMatch則存放了ws服務的實體,該類定義如下:

其中的 config 變量就是之前掃描注解時生成的實體類。

于是,檢測思路就呼之欲出了:找到對應 context 的 configExactMatchMap, 里面保存了所有注冊的 ws 服務,每個服務就是該map中的一個元素。

筆者寫了一個簡單的 jsp 檢測腳本,訪問該 jsp 就能打印出所有的 ws 服務。

<%@ page import="org.apache.tomcat.websocket.server.WsServerContainer" %>
<%@ page import="javax.websocket.server.ServerContainer" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.util.Set" %>
<%@ page import="java.util.Iterator" %>
<%@ page import="javax.websocket.server.ServerEndpointConfig" %><%-- Created by IntelliJ IDEA. --%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
  // 通過 request 的 context 獲取 ServerContainer
  WsServerContainer wsServerContainer = (WsServerContainer) request.getServletContext().getAttribute(ServerContainer.class.getName());
  // 利用反射獲取 WsServerContainer 類中的私有變量 configExactMatchMap
  Class obj = Class.forName("org.apache.tomcat.websocket.server.WsServerContainer");
  Field field = obj.getDeclaredField("configExactMatchMap");
  field.setAccessible(true);
  Map configExactMatchMap = (Map) field.get(wsServerContainer);
  // 遍歷configExactMatchMap, 打印所有注冊的 websocket 服務
  Set keyset = configExactMatchMap.keySet();
  Iterator iterator = keyset.iterator();
  while (iterator.hasNext()){
    String key = iterator.next();
    Object object = wsServerContainer.findMapping(key);
    Class wsMappingResultObj = Class.forName("org.apache.tomcat.websocket.server.WsMappingResult");
    Field configField = wsMappingResultObj.getDeclaredField("config");
    configField.setAccessible(true);
    ServerEndpointConfig config1 = (ServerEndpointConfig)configField.get(object);
    Class clazz = config1.getEndpointClass();
    // 打印 ws 服務 url, 對應的 class
    out.println(String.format("websocket name:%s,  websocket class: %s", key, clazz.getName()));
  }
  // 如果參數帶name, 刪除該服務,名字為name參數值
  if(request.getParameter("name")!= null){
    configExactMatchMap.remove(request.getParameter("name"));
    out.println(String.format("delete ws service: %s", request.getParameter("name")));
  }
%>

效果如下:

如果想要刪除一個ws服務,直接將該實體從map中移除即可,

configExactMatchMap.remove(request.getParameter("name"));

通過該jsp就是在后面加入?name=websocket服務名字即可:

再此訪問檢測工具,該服務已經刪掉:

總結

本文通過分析該新型內存馬原理,提出了對應的檢測思路,并實現了一個簡陋的內存馬檢測代碼和內存馬刪除代碼,希望該方法能夠幫助有此需求的同行安全運維人員,也希望能夠拋磚引玉,開展更為深入的討論。