重逢——Java安全之反射

robots

 

Java反射可谓是Java安全中的基础中的基础,你可以在各个代码的角落中看到他的身影,既然在江湖上碰见这么多次,那么一定要找机会好好了解一下。对于Java反射的爱恨情仇给我一包烟再给我一瓶酒我可以讲上一天,别的不吹,关于反射我也讲不出花来,也就是总结、整理、思考目前互联网上一些大佬的知识,今天我们先把反射知识捋一捋,希望本文能更好的让你理解和记住反射操作,并在以后的安全研究中能够举一反三熟练运用,为之后rmi、反序列化等知识打下基础。

 

0x01 反射的概念

0x1 反射介绍

反射(Reflection) 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说“自审”,并能直接操作程序的内部属性和方法。在程序运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态的获取信息以及动态调用对象的方法的功能称为 java 的反射机制。

0x2 为什么存在反射

其实从官方定义中就能找到其存在的价值,在运行时获得程序或程序集中每一个类型成员成员的信息,从而动态的创建、修改、调用、获取其属性,而不需要事先知道运行的对象是谁。划重点:在运行时而不是编译时

在正向开发领域反射最广泛的用途就是开发各种通用框架,比如

  • Spring 框架的 IOC 基于反射创建对象和设置依赖属性。
  • Spring MVC 的请求调用对应方法,也是通过反射。
  • JDBC 的 Class#forName(String className) 方法,也是使用反射。

在安全领域反射最广泛的用途就是构造利用链,触发命令执行

  • 反序列化中的利用链构造
  • 动态获取或执行任意类中的属性或方法
  • 动态代理的底层原理是反射技术
  • rmi反序列化也涉及到反射操作

 

0x02 反射深层次理解

为了更好的理解反射,我们向反射深一层次的知识点进发,当然这里不是反射的底层实现原理(我也没写相关的技术点分析),只是帮助你更好的理解反射机制。我们首先要介绍一个概念Class

0x1 Class概念

Class是个名字比较特殊的类,这点确定无疑(很容易和class混在一起),我们在JDK中可以看到相关定义,在Java反射中,这个类起到了关键的作用,这是因为Class类中定义了很多保存类的信息。从定义上来看我们在Java中每一个类都会有一个Class类实例与之一一对应(这种关系属于一对一)。


有很多小伙伴们经常把Class和class混为一谈,但实际上概念差距很大

Class类是由class关键字修饰的一个特殊的类,因为class本身是个关键字,我们在定义类的时候都会用到。在这好好掰扯掰扯类、class以及Class的关系。
类(class)是一个语法层次上的概念,可以理解为一个抽象概念,比如我们通常所说的String类,其实说的是那个抽象的字符串类型
实例是指根据抽象的class定义在运行时声明的一段内存区间,该内存区间可以按照class的定义进行合法的访问。我们这里的Class Instance就是一个实例。

我之前画了一个类与Class对应关系图我这里搬过来讲一讲,下图是String类和Class对象的对应关系,可以看出每个类对应的Class Instance中保存着这个类的相关信息。String类不是实例,它是一个class,只是可以用一个Class的实例来描述。

看到这个图class和Class的关系也就不言而喻了,这种一一对应关系,体现的非常完美。class代表语法概念,Class代表JDK中提供的Class数据结构。

0x2 Class在反射中的作用

Class主要用于反射,正是因为该类实例保存了对应类中的方法属性信息,我们的反射调用才能正常进行(我感觉在学反射前先把这块给理清楚),一般反射时进行的操作是获取要反射类的Class对象,从而获取类型元数据(metadata),比如字段、属性、构造器、方法等,获取后并调用。

通过上图可以清晰的看出对应Class对象的关系,我们在反射的时候其实主要运用了a2这个对象。在Class类中定义了很多和反射有关的方法和属性:

public final class Class<T> implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement {
public static Class<?> forName(String className){}
public Field getField(String name){}
public Method getMethod(String name, Class<?>... parameterTypes){}
public Method[] getMethods() throws SecurityException {}
public Method[] getDeclaredMethods() throws SecurityException {}
public Constructor<T> getConstructor(Class<?>... parameterTypes){}
public Field[] getDeclaredFields() throws SecurityException {}
......
}

0x3 举例说明其重要性

我们都知道class是在JVM第一次读取到他的类型时,将其加载进内存的,每加载一种class,JVM就为其创建一个Class类型的实例,并关联起来。注意:这里的Class类型是一个名叫Class的class,前面说过。如下所示

public final class Class {
    private Class() {}
}

以String类为例,当JVM加载String类时,它首先读取String.class文件到内存,然后,为String类创建一个Class实例并关联起来:

Class cls = new Class(String);

这个Class实例是JVM内部创建的,如果我们查看JDK源码,可以发现Class类的构造方法是private,只有JVM能创建Class实例,我们自己的Java程序是无法创建Class实例的,并且JVM持有的每个Class实例都指向一个数据类型(class或interface),一个Class实例包含了该class的所有完整信息:

由于JVM为每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个Class实例,我们就可以通过这个Class实例获取到该实例对应的class的所有信息。

0x4 Class实例获取方式

既然类对应的Class对象这么重要那么怎么去获取呢?总结了四种方式以及他们的注意事项,但是在这之前我首先介绍下java中的初始化(在部分获取类Class对象操作的时候会初始化类)。

1. 类初始化

  • 类初始化时机:类第一次加载到内存时会触发类初始化,并且只进行一次(前提是被同一类加载器加载)
  • 类初始化行为:1、准备阶段虚拟机会给静态变量分配内存并初始化为零。2、初始化阶段执行类构造器(cinit),收集类初始化代码和static{}代码变成cinit函数,并执行。

2. Class对象获取方式

// 1.通过字符串获取Class对象,这个字符串必须带上完整路径名,进行类初始化
Class studentClass = Class.forName("com.test.reflection.Student");
// 2.通过类的class属性,不进行类初始化
Class studentClass2 = Student.class;
// 3.通过对象的getClass()函数
Student studentObject = new Student();
Class studentClass3 = studentObject.getClass();
// 4.通过classloader获取,不进行类初始化
ClassLoader  classLoader = this.getClass().getClassLoader();
Class  clazz5 = classLoader.loadClass("com.test.reflection.Student");
  • 第一种方法是通过类的全路径字符串获取 Class 对象,不需要事先import导入
  • 第二种方法有限制条件:需要导入类的包;
  • 第三种方法已经有了 Student 对象,不再需要反射。
  • 第四种方法首先要获取个classLoader实例,之后再通过字符串获取Class对象。

因为Class.forName有重载函数,重点针对这个获取方式展开讲解,主要是理清楚这两个函数的区别

Class<?> forName(String name)
Class<?> forName(String name, **boolean** initialize, ClassLoader loader)

引用p神总结的知识点,在代码层面的关系是

Class.forName(className)
Class.forName(className, true, currentLoader)

第一个参数是类名;第二个参数表示是否初始化;第三个参数为classLoader。关于classLoader和初始化我打算单独开篇文章学习记录。最后再明确一点类初始化对象初始化是不同的概念,Class的获取一般与目标类的初始化有关,和目标对象初始化无关。

0x5 Class获取与初始化关系测试

关于Class实例获取初始化过程我们可以用个例子测试下

//Animal.java
public class Animal {
    private static String name = getName();
    static {
        System.out.println("父类静态代码块");
    }
    {
        System.out.println("父类代码块");
    }
    public Animal() {
        System.out.println("父类构造函数");
    }
    private static String getName() {
        System.out.println("父类静态变量");
        return null;
    }
}
//Monkey.java
public class Monkey extends Animal{
    {
        System.out.println("子类代码块");
    }
    static {
        System.out.println("子类静态代码块");
    }
    private static String name = getName1();
    public Monkey() {
        System.out.println("子类构造方法");
    }

    private static String getName1() {
        System.out.println("子类静态变量");
        return null;
    }
}
//test.java
public class test {
    public void get() throws ClassNotFoundException {
//        Class<?> a = Monkey.class;
        Class<?> a = Class.forName("Monkey");
//        Monkey mk = new Monkey();
//        Class<?> a = mk.getClass();
//        ClassLoader  classLoader = this.getClass().getClassLoader();
//        Class<?> a = classLoader.loadClass("Monkey");
    }
    public static void main(String[] args) throws ClassNotFoundException {
        test t = new test();
        t.get();
    }
}

 

0x03 反射关键技术

Java类的成员包括以下三类:成员变量、成员方法、构造方法。那么反射技术就是讲每个类型都看作是类,

0x1 创造实例

反射概念本身并不是很难,主要是在进行实践的时候会遇到各种各样的问题,比如在创建实例的时候就会分很多种情况,我梳理了下大概有以下几种

  • 1.公有无参构造方法
  • 2.公有含参构造方法
  • 3.私有构造方法

1. 公有无参构造方法

class.newInstance()

上面的Monkey类就可以使用该方法创建实例

Monkey mk = Monkey.class.newInstance();

调用Class.newInstance()的局限是,它只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。

2. 公有含参构造方法

同时这种方式也可以获取公有无参构造方法

Constructor getConstructor(Class<?>... parameterTypes);
Constructor[] getConstructors();

我们以java.lang.ProcessBuilder为例,它的构造方法为公有含参

使用下面的方式进行实例化

Class clazz = Class.forName("java.lang.ProcessBuilder"); 
ProcessBuilder processbuilder = ((ProcessBuilder)clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}}));
processbuilder.start();

利用完全反射的方式编写代码,所有操作通过Class对象和Method对象实现,或许看到这里有很多的疑问,具体的分析将在调用方法中讲解。

Class clazz = Class.forName("java.lang.ProcessBuilder");
Method getConstructorMethod = clazz.getClass().getMethod("getConstructor",new Class[]{Class[].class});
Object b = getConstructorMethod.invoke(clazz,new Object[]{new Class[]{String[].class}});
Method newInstanceMethod = b.getClass().getMethod("newInstance",new Class[]{Object[].class});
Object d = newInstanceMethod.invoke(b,new Object[]{new String[][]{{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}}});
Method startMethod = d.getClass().getMethod("start",new Class[]{});
startMethod.invoke(d,new Object[]{});

3. 私有构造方法

同时这种方式也可以获取公有方法

Constructor getDeclaredConstructor(Class<?>... parameterTypes);
Constructor[] getDeclaredConstructors();
Class clazz = Class.forName("java.lang.Runtime"); 
Constructor m = clazz.getDeclaredConstructor(); 
m.setAccessible(true); 
clazz.getMethod("exec", String.class).invoke(m.newInstance(), "/System/Applications/Calculator.app/Contents/MacOS/Calculator");

注意:调用非public的Constructor时,必须首先通过setAccessible(true)设置允许访问。setAccessible(true)可能会失败。

0x2 操作字段

1. 函数定义

  • 1.Field getField(name):根据字段名获取某个public的field(包括父类)
  • 2.Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
  • 3.Field[] getFields():获取所有public的field(包括父类)
  • 4.Field[] getDeclaredFields():获取当前类的所有field(不包括父类)

可以通过上面的四种方法获取成员变量对象,下面就是要对成员变量对象做一些操作,目前支持的函数为

  • 1.getName():返回字段名称,例如,”name”;
  • 2.getType():返回字段类型,也是一个Class实例,例如,String.class;
  • 3.getModifiers():返回字段的修饰符,它是一个int,不同的bit表示不同的含义。
  • 4.setAccessible(): 设置变量为public
  • 5.set(): 设置字段值
  • 6.get():获取字段值

2. 具体使用

下面结合实例看下到底是怎么使用的

class Student extends Person {
    public int score;
    private int grade;

}

class Person {
    public String name;
    public Person(String name) {
        this.name = name;
    }
}

public class Main {
    public static void main(String[] args) throws Exception {
        Student stdObj = new Student("Teddy");
        Class stdClass = Student.class;
        // 获取public字段"score":
        Field score = stdClass.getField("score");
        // 获取继承的public字段"name":
        Field name = stdClass.getField("name");
        // 获取private字段"grade":
        Field grade = stdClass.getDeclaredField("grade");
        // 设置private为public访问权限
        grade.setAccessible(true);
        // 设置字段
        grade.set(stdObj,1);
        // 获取字段值
        grade.get(stdObj);
        // 获取字段名
        grade.getName();
        // 获取字段类型
        grade.getType();
        // 获取字段的修饰符
        int m = f.getModifiers();
        Modifier.isFinal(m); // true
        Modifier.isPublic(m); // false
        Modifier.isProtected(m); // false
        Modifier.isPrivate(m); // true
        Modifier.isStatic(m); // false
    }
}

3. 问题

虽然看起来分析的很全面,但是还是少了一种情况,先把问题抛在这,回过头看获取field的四个方法,好像就是没有获取父类私有变量,怎样获取到父类中的私有变量?关于这个问题还有个兄弟问题,会在下一小节出现,但解决方法都是相同的,让我们拭目以待。

0x3 操作非构造方法

1. 函数定义

  • 1.Method getMethod(name, Class…):获取某个public的Method(包括父类)
  • 2.Method getDeclaredMethod(name, Class…):获取当前类的某个Method(不包括父类)
  • 3.Method[] getMethods():获取所有public的Method(包括父类)
  • 4.Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)

获取Method对象之后,可以调用如下函数:

  • 1.getName():返回方法名称,例如:”getScore”;
  • 2.getReturnType():返回方法返回值类型,也是一个Class实例,例如:String.class;
  • 3.getParameterTypes():返回方法的参数类型,是一个Class数组,例如:{String.class, int.class};
  • 4.getModifiers():返回方法的修饰符,它是一个int,不同的bit表示不同的含义。
  • 5.invoke(object,new Object[]{}) 调用执行方法
  • 6.setAccessible() 设置private函数为public属性

2. 具体使用

使用方法

class Student extends Person {
    public int getScore(String type) {
        return 99;
    }
    private int getGrade(int year) {
        return 1;
    }
}
class Person {
    public String getName() {
        return "Person";
    }
}
public class Main {
    public static void main(String[] args) throws Exception {
        Class stdClass = Student.class;
        // 获取public方法getScore,参数为String:
        Method getScore = stdClass.getMethod("getScore", String.class);
        // 获取继承的public方法getName,无参数:
        Method getName = stdClass.getMethod("getName");
        // 获取private方法getGrade,参数为int:
        Method getGrade = stdClass.getDeclaredMethod("getGrade", int.class);
        // 返回方法名
        getGrade.getName();
        // 返回参数类型 Class[] 数组
        getGrade.getParameterTypes();
        // 返回方法返回值类型 Class 实例
        getGrade.getReturnType();
        // 调用private方法
        getGrade.setAccessiable(true);
        getGrade.invoke(1);
    }
}

3. 难点1解析

关于getMethod和invoke第二个参数关系的理解。
我们拿Student类做个示范

class Student extends Person {
    public int getScore(String type) {
        return 99;
    }
    private int getGrade(int year) {
        return 1;
    }
}

getScore这个方法的参数类型为String类,那么在getMethod时候就要填充String对应的Class对象,在invoke的时候填充String对象
用一个关系图表示

3. 难点2解析

在getMethod和invoke方法中有个我认为比较容易混淆的知识点,我们先看下getMethod和invoke方法的定义

public Method getMethod(String name, Class<?>... parameterTypes)
public Object invoke(Object obj, Object... args)

一个是Class<?>… ,一个是Object…,那么对于这个…和Class类理解有多深反射运用的就有多熟练。Class我们已经讲过,下面重点分析下这个…

定义:可变长参数,这个位置可以传入任意个该类型参数,简单来说就是个数组。

举个简单的栗子

public static void testPoints(Integer... itgr){
    if(itgr.length==0){
        System.out.println("没有Integer参数传入!");
    }else if(itgr.length==1){
        System.out.println("1个Integer参数传入!");
    }else{
        System.out.println("the input string is-->");
        for(int i=0;i<itgr.length;i++){
            System.out.println("第"+(i+1)+"个Integer参数是"+itgr[i]+";");
        }
    }
}

testPoints(7);  
testPoints(7,9,11); //等价于下面的调用
testPoints(new Integer[]{7,9,11}); 

//输出
//1个Integer参数传入!  
//the input string is-->  
//第1个Integer参数是7;  
//第2个Integer参数是9;  
//第3个Integer参数是11;    
//the input string is-->  
//第1个Integer参数是7;  
//第2个Integer参数是9;  
//第3个Integer参数是11;

再回到之前的创造实例章节,我为了运用getMethod和invoke函数调用公有含参构造方法,代码如下

Class clazz = Class.forName("java.lang.ProcessBuilder");
Method getConstructorMethod = clazz.getClass().getMethod("getConstructor",new Class[]{Class[].class});//因为...特性这里也可传入Class[].class效果相同
Object b = getConstructorMethod.invoke(clazz,new Object[]{new Class[]{String[].class}});
Method newInstanceMethod = b.getClass().getMethod("newInstance",new Class[]{Object[].class});
Object d = newInstanceMethod.invoke(b,new Object[]{new String[][]{{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}}});
Method startMethod = d.getClass().getMethod("start",new Class[]{});
startMethod.invoke(d,new Object[]{});

看到这个代码大概率有几个问题

  • 1.第三行为什么在invoke的时候填入的参数为new Object[]{new Class[]{String[].class}},看起来感觉复杂而冗余,必须要这样吗?
  • 2.代码中充满着各种new Class[]、new Object[]、new String[]、Class[].class、String[].class,怎么理解他们的含义及关联关系?

针对这两个问题我们来解答下

  • 1.如果我们在第三行填充new Class[]{String[].class},因为向上转型的原因,invoke会将传入的参数解析成String[].class 相当于把getConstructor的参数类型解析成了String[],那么肯定是不正确的;当传入看似复杂的new Object[]{new Class[]{String[].class}}之后,invoke将参数解析成new Class[]{String[].class},这就符合我们在getMethod时候创建的函数Class[].class参数类型;… 类型在反射时貌似只能用数组类型反射,这也决定了invoke参数必须是new Object[]{new Class[]{String[].class}};
  • 2.令人头大的类型种类,代码中的new实则是在创建对象,Class、Object、String在java都是class(类),都是可以通过new来创建实例的,new xxx[]{}是在创建类型为xxx的数组;这三者从关系上来讲Object是所有类的父类包括Class和String,String类和Class类实例有着某种关联关系,没有直接的类关系;xxx[].class 代表着xxx数组类型的Class对象,属于Class实例,Class[].class和String[].class是不同的Class实例他们之间没有关联关系,但是new Class[]和Class[].class、new String[]和String[].class通过getClass函数关联起来。

4. 问题

怎样获取父类中的私有变量问题的兄弟问题是怎样获取到父类中的私有方法?我们在下一小节解答疑惑。

0x4 获取父类私有方法及变量

1. 函数定义

在反射技术操作的最后总结如何获取父类私有方法及其变量。其实解决方法比较简单,就是通过反射获取父类对应的Class对象。

Class<?> superclass = sonClass.getSuperclass();

获取superclass对象后,按照需求调用以下函数

  • 1.Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
  • 2.Method getDeclaredMethod(name, Class…):获取当前类的某个Method(不包括父类)
  • 3.setAccessible(): 设置变量为public

其中setAccessible是必须被调用的为了修改其私有属性为public

2. 具体使用

祭出Student类

import java.lang.reflect.Field;
import java.lang.reflect.Method;

class Student extends Person {
}
class Person {
    private String DNA="blablabla";
    private String getName() {
        System.out.println("Person getName");
        return "Person";
    }
}
public class Main {
    public static void main(String[] args) throws Exception {
        Object stdObj = new Student();
        Class stdClass = stdObj.getClass();
        Class perClass = stdClass.getSuperclass();//获取父类
        Method getNameMethod = perClass.getDeclaredMethod("getName",new Class[]{});
        getNameMethod.setAccessible(true);//划重点
        getNameMethod.invoke(stdObj,new Object[]{});

        Field DNAField = perClass.getDeclaredField("DNA");
        DNAField.setAccessible(true);//划重点
        System.out.println(DNAField.get(stdObj));
    }
}

 

0x04 反射在安全中的运用

最后将学到的Java基础知识运用到安全上,关于反射也是从学Java安全时遇到了很多难以解释的问题,之后再去从代码中及原理上寻找解决答案。反射的一些定义和操作在上面介绍的也差不多了,打算在这节主要介绍下反射在安全中的一些运用,巩固下反射知识点。

0x1 反序列化链上的命令执行

在ysoserial中Transformer命令执行链就是运用的反射的知识,再次回顾下

final Transformer[] transformers = new Transformer[] {
    new ConstantTransformer(Runtime.class),
    new InvokerTransformer("getMethod", new Class[] {
        String.class, Class[].class }, new Object[] {
        "getRuntime", new Class[0] }),
    new InvokerTransformer("invoke", new Class[] {
        Object.class, Object[].class }, new Object[] {
        null, new Object[0] }),
    new InvokerTransformer("exec",
                           new Class[] { String.class }, execArgs),
    new ConstantTransformer(1) };

这段代码怎么来滴,我们一起推导下,Java命令执行语句为

Runtime.getRuntime().exec("calc")

InvokerTransformer函数实现有着一个特点,它封装了一个功能当调用tranform函数时会执行输入的Obj的指定方法和参数。源码中是这么写的,我们理解清楚这块的功能后就可以进行灵活运用了。

Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);

继续我们的推导,Runtime类本身是没有继承Serializable接口的所以不能被反序列化,那么为了构造反序列化poc我们采用了这种写法执行命令

Class runtimeClass = Runtime.class;
Method m1 = runtimeClass.getMethod("getRuntime",new Class[]{});//new Class[]{}==new Class[0]
((Runtime)m1.invoke(null,new Object[]{})).exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator");

关于反射可以无限套娃的,我把之前的processBuilder命令执行反射调用改了改,变得更加复杂,当然搞懂之后反射也变得更好理解。

Class clazz = Class.forName("java.lang.ProcessBuilder");
Class classFath = clazz.getClass();
Class classFath2 = classFath.getClass();
Class classFath3 = classFath2.getClass();
Class classFath4 = classFath3.getClass();
Method a2 = classFath4.getMethod("getMethod",new Class[]{String.class,Class[].class});
Method t1 = (Method)a2.getClass().getMethod("invoke",new Class[]{Object.class,Object[].class});
Method a1 = (Method) t1.invoke(a2,new Object[]{classFath3,new Object[]{"getMethod",new Class[]{String.class,Class[].class}}});
Method a = (Method)a1.invoke(classFath2,new Object[]{"getMethod",new Class[]{String.class,Class[].class}});
Method getConstructorMethod = (Method) a.invoke(classFath,new Object[]{"getConstructor",new Class[]{Class[].class}});
Object b = getConstructorMethod.invoke(clazz,new Object[]{new Class[]{String[].class}});
Method newInstanceMethod = b.getClass().getMethod("newInstance",new Class[]{Object[].class});
Object d = newInstanceMethod.invoke(b,new Object[]{new String[][]{{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}}});
Method startMethod = d.getClass().getMethod("start",new Class[]{});
startMethod.invoke(d,new Object[]{});

通过动态调试发现,getClass不是我们想象中的那种无限向上追溯,而是获取到根Class对象后就会停止追溯,什么意思呢简单来说的,我们最多获取到图中a1这个对象。

0x2 IDEA 动态log实现

最近才发现IDEA断点有个Evaluate and log代码区,我们可以根据反射调用获取tomcat或者jetty web服务接受或发送请求响应包的全部内容。首先我们要自己搭建个tomcat web服务及调试环境,可参考链接

将断点断在ApplicationFilterChain::doFilter 72行

Class<?> reqClass = request.getClass();
java.lang.reflect.Field reqField = reqClass.getDeclaredField("request");
reqField.setAccessible(true);
org.apache.catalina.connector.Request req = (org.apache.catalina.connector.Request)reqField.get(request);

Class<?> req1Class = req.getClass();
java.lang.reflect.Field req1Field = req1Class.getDeclaredField("coyoteRequest");
req1Field.setAccessible(true);
org.apache.coyote.Request coyoteReqObj = (org.apache.coyote.Request)req1Field.get(req);

Class<?> coyoteReqClass = coyoteReqObj.getClass();
java.lang.reflect.Field coyoteReqField = coyoteReqClass.getDeclaredField("inputBuffer");
coyoteReqField.setAccessible(true);
org.apache.coyote.InputBuffer inputBufObj = (org.apache.coyote.InputBuffer)coyoteReqField.get(coyoteReqObj);

Class<?> inputBufClass = inputBufObj.getClass();
java.lang.reflect.Field byteBufField = inputBufClass.getDeclaredField("byteBuffer");
byteBufField.setAccessible(true);
java.nio.HeapByteBuffer byteBufObj = (java.nio.HeapByteBuffer)byteBufField.get(inputBufObj);

Class<?> byteBufClass = byteBufObj.getClass();
Class<?> byteBufFathClass = byteBufClass.getSuperclass();//获取父类
java.lang.reflect.Field hbField = byteBufFathClass.getDeclaredField("hb");
hbField.setAccessible(true);
byte[] hbObj = (byte[])hbField.get(byteBufObj);
new String(hbObj);

当然这部分完全可以这么写,但是当遇到一些特殊情况的时候就必须用到反射了

new String(((Http11InputBuffer) ((RequestFacade) request).request.coyoteRequest.inputBuffer).byteBuffer.hb);

打log有个小技巧,将Suspend勾掉,操作如下

看下效果

 

0x05 总结

将最近学习思考的东西总结了出来,关于Java反射后续还会跟进学习,不断丰富完善自己的知识体系。接下来一段时间打算把weblogic系列漏洞中涉及到的知识点总结梳理下,整理出一个从入门到放弃的weblogic学习路线。

 

参考文章

https://juejin.cn/post/6844904005294882830
https://zhuanlan.zhihu.com/p/86293659
https://www.zhihu.com/question/24304289

(完)