Java内存马前置知识

javaweb基础知识

javaweb(Container)三大件

Servlet

Servlet 技术是 Web 开发的原点,Tomcat 和 Jetty 这样的 Web 容器,负责加载和运行 Servlet

Java内存马前置知识_第1张图片

什么是Servlet

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

请求处理的过程

Java内存马前置知识_第2张图片

客户端发起一个http请求,比如get或者post类型。
Servlet容器接收到请求,根据请求信息,封装成HttpServletRequest和HttpServletResponse对象。
Servlet容器调用HttpServlet的init()方法,init方法只在第一次请求的时候被调用。
Servlet容器调用service()方法。
service()方法根据请求类型,这里是get类型,分别调用doGet或者doPost方法,这里调用doGet方法。(这个地方一般需要重写方法) 在doXXX方法中是我们自己写的业务逻辑 业务逻辑处理完成之后,返回给Servlet容器,然后容器将结果返回给客户端。
容器关闭时候,会调用destory方法
生命周期
  1. 服务器启动时(web.xml中配置load-on-startup=1,默认为0)或者第一次请求该servlet时,就会初始化一个Servlet对象,也就是会执行初始化方法init(ServletConfig conf)。
  2. servlet对象去处理所有客户端请求,在service(ServletRequest req,ServletResponse res)方法中执行
  3. 服务器关闭时,销毁这个servlet对象,执行destroy()方法。
  4. 由JVM进行垃圾回收。

Filter

什么是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 就可以阻止某些非法的访问请求。

Java内存马前置知识_第3张图片

生命周期

与servlet一样,Filter的创建和销毁也由web容器负责。 web 应用程序启动时,web 服务器将创建Filter 的实例对象,并调用其init方法,读取web.xml配置,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作(filter对象只会创建一次,init方法也只会执行一次)。开发人员通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。

Filter对象创建后会驻留在内存,当web应用移除或服务器停止时才销毁。在Web容器卸载 Filter 对象之前被调用。该方法在Filter的生命周期中仅执行一次。在这个方法中,可以释放过滤器使用的资源。

注意:有时候,filter不止一个,这样就会形成filter链。(根据实际的代码场景逻辑进行判断,从而进行绕过)

Listener

什么是Listener

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

注意:javaweb中有九大内置对象 :request,response,pageContext,session,application,out,page,config,exception,需要导入相应的包才可以使用
  • ServletContextListener:对Servlet上下文的创建和销毁进行监听;
  • ServletContextAttributeListener:监听Servlet上下文属性的添加、删除和替换;
  • HttpSessionListener:对Session的创建和销毁进行监听。Session的销毁有两种情况,一个中
  • Session超时,还有一种是通过调用Session对象的invalidate()方法使session失效。
  • HttpSessionAttributeListener:对Session对象中属性的添加、删除和替换进行监听;
  • ServletRequestListener:对请求对象的初始化和销毁进行监听;
  • ServletRequestAttributeListener:对请求对象属性的添加、删除和替换进行监听。

Tomcat

简介

tomcat是http服务器+servlet容器。

Tomcat 作为Servlet容器,将http请求文本接收并解析,然后封装成HttpServletRequest类型的request对象,传递给servlet;同时会将响应的信息封装为HttpServletResponse类型的response对象,然后将response交给tomcat,tomcat就会将其变成响应文本的格式发送给浏览器

Java内存马前置知识_第4张图片

架构设计

一个 WEB 服务器 + 一个 Servlet 容器

需要处理网络的连接与 Servlet 的管理,因此,Tomcat 设计了两个核心组件来实现这两个功能,分别是连接器和容器,连接器用来处理外部网络连接,容器用来处理内部 Servlet

Tomcat 设计了 4 种容器,分别是 EngineHostContextWrapper。要注意的是这 4 种容器不是平行关系,属于父子关系,

Java内存马前置知识_第5张图片

一个 Tomcat 代表一个 Server 服务器,一个 Server 服务器可以包含多个 Service 服务,Tomcat 默认的 Service 服务是 Catalina,而一个 Service 服务可以包含多个连接器,因为 Tomcat 支持多种网络协议,包括 HTTP/1.1、HTTP/2、AJP 等等,一个 Service 服务还会包括一个容器,容器外部会有一层 Engine 引擎所包裹,负责与处理连接器的请求与响应,连接器与容器之间通过 ServletRequest 和 ServletResponse 对象进行交流。

假如有用户访问一个 URL,比如图中的http://user.shopping.com:8080/order/buy,Tomcat 如何将这个 URL 定位到一个 Servlet 呢?

  1. 首先根据协议和端口号确定 Service 和 Engine。Tomcat 默认的 HTTP 连接器监听 8080 端口、默认的 AJP 连接器监听 8009 端口。上面例子中的 URL 访问的是 8080 端口,因此这个请求会被 HTTP 连接器接收,而一个连接器是属于一个 Service 组件的,这样 Service 组件就确定了。我们还知道一个 Service 组件里除了有多个连接器,还有一个容器组件,具体来说就是一个 Engine 容器,因此 Service 确定了也就意味着 Engine 也确定了。
  2. 根据域名选定 Host。 Service 和 Engine 确定后,Mapper 组件通过 URL 中的域名去查找相应的 Host 容器,比如例子中的 URL 访问的域名是user.shopping.com,因此 Mapper 会找到 Host2 这个容器。
  3. 根据 URL 路径找到 Context 组件。 Host 确定以后,Mapper 根据 URL 的路径来匹配相应的 Web 应用的路径,比如例子中访问的是 /order,因此找到了 Context4 这个 Context 容器。
  4. 根据 URL 路径找到 Wrapper(Servlet)。 Context 确定后,Mapper 再根据 web.xml 中配置的 Servlet 映射路径来找到具体的 Wrapper 和 Servlet。

Java内存马前置知识_第6张图片

java反射

java的四个基本特征是:封装,继承,多态和反射

在研究java安全的时候,大多数需要用到反射这个特性,因为,大部分情况下是不允许以正常的方式去实例化对象的。

java反射:在运行状态中,对于任意一个类,都能够知道这个类的属性和方法。
对于任意一个对象,都能够调用他的属性和方法

所以我们需要用反射的方式去实例化对象进行操作。

反射的本质相当于,直接从内存中去加载jvm编译过的,已经有的class对象,然后反向调用。

Java内存马前置知识_第7张图片

双亲委派机制,可以参考jvm的分析

当一个类加载器收到了类加载的请求的时候,它不会直接去加载这个类,而是把这个请求委托给父加载器加载。只有当父加载器无法加载这个类的时候,他才会尝试去加载这个类
  • BootstrapClassLoader是启动类加载器,由 C 语言实现,用来加载 JVM启动时所需要的核心类,比如rt.jarresources.jar等。
  • ExtClassLoader是扩展类加载器,用来加载\jre\lib\ext目录下 JAR 包。
  • AppClassLoader是系统类加载器,用来加载 classpath下的类,应用程序默认用它来加载类。
  • 自定义类加载器,用来加载自定义路径下的类。

Java内存马前置知识_第8张图片

但是Tomcat却打破了双亲委派机制

Tomcat打破双亲委派的做法,我们称之为热加载

Tomcat 本质是通过一个后台线程做周期性的任务,定期检测类文件的变化,如果有变化就重新加载类。

Tomcat 的自定义类加载器 WebAppClassLoader打破了双亲委托机制,它首先自己尝试去加载某个类,如果找不到再代理给父类加载器,其目的是优先加载 Web 应用自己定义的类。具体实现就是重写 ClassLoader的两个方法:findClassloadClass

public Class findClass(String name) throws ClassNotFoundException {
    ...

    Class clazz = null;
    try {
            //1. 先在 Web 应用目录下查找类
            clazz = findClassInternal(name);
    }  catch (RuntimeException e) {
           throw e;
       }

    if (clazz == null) {
    try {
            //2. 如果在本地目录没有找到,交给父加载器去查找
            clazz = super.findClass(name);
    }  catch (RuntimeException e) {
           throw e;
       }

    //3. 如果父类也没找到,抛出 ClassNotFoundException
    if (clazz == null) {
        throw new ClassNotFoundException(name);
     }

    return clazz;
}

更多细节,请看这个师傅的文章:java - Tomcat 架构原理解析到架构设计借鉴 - 个人文章 - SegmentFault 思否

我们返回来继续说java反射

反射功能可以在运行时(动态):

  1. 获取一个类的所有成员变量和方法
  2. 创建一个类的对象:
  • a.获取对象成员变量&赋值
  • b.调用对象的方法
  • c.判断对象所属的类

常见的java反射获取对象的方式有这么几种:

  • 类名.class,如:com.anbai.sec.classloader.TestHelloWorld.class
  • Class.forName("com.anbai.sec.classloader.TestHelloWorld")
  • classLoader.loadClass("com.anbai.sec.classloader.TestHelloWorld");

例如,注入一个servlet型的内存马,我们需要使用反射机制来获取当前的context,然后将恶意的servlet(wrapper)添加到当前的context的children中。主要过程如下:

①获取 目标类型的Class对象

②通过 Class 对象分别获取Constructor类对象、Method类对象 & Field 类对象

③通过 Constructor类对象、Method类对象 & Field类对象分别获取类的构造函数、方法&属性的具体信息,并进行后续操作

java instrumentation

在java中,只有被JVM加载后的类才能被调用,或者在需要时通过反射通知JVM加载.

Instrumentation是Java提供的一个来自JVM的接口,该接口提供了一系列查看和操作Java类定义的方法

例如修改类的字节码、向classLoader的classpath下加入jar文件等。使得开发者可以通过Java语言来操作和监控JVM内部的一些状态,进而实现Java程序的监控分析,甚至实现一些特殊功能(如AOP、热部署)。

第一次有人请求Instrumentation的实例时,它会创建一个代理JAR文件,然后查找其自身进程的PID,并将其附加到JVM中

Java agent是一种特殊的Java程序(Jar文件),它是Instrumentation的客户端。与普通Java程序通过main方法启动不同,agent并不是一个可以单独启动的程序,而必须依附在一个Java应用程序(JVM)上,与它运行在同一个进程中,通过Instrumentation API与虚拟机交互。

在注入内存马的过程中,我们可以利用java instrumentation机制,动态的修改已加载到内存中的类里的方法,进而注入恶意的代码。

Java内存马前置知识_第9张图片

简单demo

在Example下新建两个类

Bird.java:
public class Bird {
    public void say()
    {
        System.out.println("bird is gone.");
    }
}

然后把编译后的Bird.class复制出来,放到一个文件夹下。然后把Bird.java再改成如下:

Bird.java:
public class Bird {
    public void say()
    {
        System.out.println("bird say hello");
    }
}
Main.java:
public class Main {
    public static void main(String[] args) throws Exception {
        // TODO Auto-generated method stub
        while(true)
        {
            Bird bird=new Bird();
            bird.say();
            Thread.sleep(3000);
        }    
    }
}

把整个工程打包成可执行jar包Example.jar,放到之前那个文件夹。

在工程Agent中新建2个类:

AgentEntry.java:
public class AgentEntry { 
   public static void agentmain(String agentArgs, Instrumentation inst) 
           throws ClassNotFoundException, UnmodifiableClassException, 
           InterruptedException { 
       inst.addTransformer(new Transformer (), true); 
        Class[] loadedClasses = inst.getAllLoadedClasses();
        for (Class c : loadedClasses) {
            if (c.getName().equals("Bird")) {
                try {
                    inst.retransformClasses(c);
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
       System.out.println("Class changed!"); 
   } 
}
Transformer.java:
public class Transformer implements ClassFileTransformer { 
   static byte[] mergeByteArray(byte[]... byteArray) {
       int totalLength = 0;
       for(int i = 0; i < byteArray.length; i ++) {
           if(byteArray[i] == null) {
               continue;
           }
           totalLength += byteArray[i].length;
       }
       byte[] result = new byte[totalLength];
       int cur = 0;
       for(int i = 0; i < byteArray.length; i++) {
           if(byteArray[i] == null) {
               continue;
           }
           System.arraycopy(byteArray[i], 0, result, cur, byteArray[i].length);
           cur += byteArray[i].length;
       }
       return result;
   }
   public static byte[] getBytesFromFile(String fileName) { 
       try { 
           byte[] result=new byte[] {};
           InputStream is = new FileInputStream(new File(fileName)); 
           byte[] bytes = new byte[1024];
           int num = 0;
           while ((num = is.read(bytes)) != -1) {
               result=mergeByteArray(result,Arrays.copyOfRange(bytes, 0, num));       
           }
           is.close();
           return result; 
       } catch (Exception e) { 
           e.printStackTrace();
           return null;
       } 
   } 
   public byte[] transform(ClassLoader classLoader, String className, Class c, 
           ProtectionDomain pd, byte[] b) throws IllegalClassFormatException { 
       if (!className.equals("Bird")) { 
           return null; 
       } 
       return getBytesFromFile("d:/Bird.class"); 
   } 
}

这个也要打包成jar包,但是直接打包肯定是打包不了的

首先在SRC下创建META-INF文件夹然后再常见文件MANIFEST.MF,内容如下

MAINFEST.MF:
Manifest-Version: 1.0 
Agent-Class: AgentEntry
Can-Retransform-Classes: true

然后把Agent工程打包为Agent.jar.

然后新建run工程中,在其中新建1个类:

Attach.java:
public class Attach {
    public static void main(String[] args) throws Exception {
        VirtualMachine vm = null;
        List listAfter = null;
        List listBefore = null;
        listBefore = VirtualMachine.list();
        while (true) {
            try {
                listAfter = VirtualMachine.list();
                if (listAfter.size() <= 0)
                    continue;
                for (VirtualMachineDescriptor vmd : listAfter) {
                    vm = VirtualMachine.attach(vmd);
                    listBefore.add(vmd);
                    System.out.println("i find a vm,agent.jar was injected.");
                    Thread.sleep(1000);
                    if (null != vm) {
                        vm.loadAgent("agent.jar");
                        vm.detach();
                    }
                }
                break;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

//import com.sun.tools.attach.VirtualMachineDescriptor;
//import com.sun.tools.attach.VirtualMachine;
//这里import的是这两个包,导入其他包会出错,同时还要把 java/lib下的tools.jar放到lib 移到项目中来,否则会报错

报错解决方法参考:“程序包com.sun.tools.javac.util不存在” 问题解决_java: 程序包sun.font不存在-CSDN博客

之后,打包成可执行jar包run.jar,放到该文件夹下。

先后执行

java -jar Example.jar
java -jar run.jar

就会有这样的情况

Java内存马前置知识_第10张图片

我们就实现了在动态运行中改变方法体。

动态注册之Servlet+Filter+Listener

  • xml文件注册
  • 注解注册(Servlet 3.0 +)
  • ServletContext动态注册

你可能感兴趣的:(java,开发语言)