java反射的相关操作
一些重要的方法
-
获取类的⽅法: forName
-
实例化类对象的⽅法: newInstance
-
获取函数的⽅法: getMethod
-
执⾏函数的⽅法: invoke
// eg.反射获取任意类的任意方法并执行
import java.lang.reflect.Method;public class ReflectionExample {public static void main(String[] args) {try {// 获取类名Class<?> clazz = Class.forName("com.example.SomeClass");// 获取方法名和参数类型String methodName = "someMethod";Class<?>[] parameterTypes = {String.class, int.class};// 获取方法Method method = clazz.getMethod(methodName, parameterTypes);// 创建类的实例Object obj = clazz.newInstance();// 准备参数Object[] arguments = {"example", 123};// 执行方法Object result = method.invoke(obj, arguments);// 打印结果System.out.println("Method returned: " + result);} catch (ClassNotFoundException e) {e.printStackTrace();} catch (NoSuchMethodException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();} catch (java.lang.reflect.InvocationTargetException e) {e.printStackTrace();}}
}
forName
-
forName 不是获取“类”的唯⼀途径
-
obj.getClass() 如果上下⽂中存在某个类的实例 obj ,那么我们可以直接通过
obj.getClass() 来获取它的类 -
Test.class 如果你已经加载了某个类,只是想获取到它的 java.lang.Class 对象,那么就直接
拿它的 class 属性即可。这个⽅法其实不属于反射。 -
Class.forName 如果你知道某个类的名字,想获取到这个类,就可以使⽤ forName 来获取
-
-
forName的重载
-
forName(String name)和Class forName(String name, boolean initialize, ClassLoader loader)两个重载Class
-
ClassLoader loader就是⼀个“加载器”,一般是一个类的完整路径,如java.lang.Runtime
-
boolean initialize决定是否进行“类初始化”,forName(String name)默认initialize=true
-
-
关于类初始化的补充:下面代码的执行顺序为static{}, 构造函数的 super(),{},构造函数,static{}即为类初始化
public class TrainPrint {{System.out.printf("Empty block initial %s\n", this.getClass());}static {System.out.printf("Static initial %s\n", TrainPrint.class);}public TrainPrint() {super();System.out.printf("Initial %s\n", this.getClass());} }
newInstance
-
class.newInstance() 的作用就是调用这个类的无参构造函数,于是乎不成功是因为:
-
你使用的类没有无参构造函数
-
你使用的类构造函数是私有的,例如java.lang.Runtime,可以采用类的其他静态方法获取实例
-
newInstance的补充getConstructor
- Java和C++不同,C++的类必须有一个无参构造函数(显示定义或者编译器自动生成),而Java一但显示定义了任意构造函数,编译器就不会再自动生成无参构造函数,这就造成了一个问题,Java中的类可能没有无参构造函数也没有可获取实例的其他方法,此时就需要getConstructor获取有参构造函数
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(
Arrays.asList("calc.exe")));
);
关于类的私有方法
-
类的私有方法可以通过getDeclared 系列的反射调用,与普通的 getMethod 、 getConstructor 区别是:
-
getMethod 系列方法获取的是当前类中所有公共方法,包括从父类继承的方法
-
getDeclaredMethod 系列方法获取的是当前类中“声明”的方法,是实在写在这个类里的,包括私
有的方法,但从父类里继承来的就不包含了
Class clazz = Class.forName("java.lang.Runtime"); Constructor m = clazz.getDeclaredConstructor(); // setAccessible(true)修改作用域是必须得 m.setAccessible(true); clazz.getMethod("exec", String.class).invoke(m.newInstance(), "calc.exe");
-
反射的一些特性
-
无需import类
-
可以访问私有方法
不同语言的序列化对比
PHP序列化
<?phpclass Connection{protected $link;private $dsn, $username, $password;public function __construct($dsn, $username, $password){$this->dsn = $dsn;$this->username = $username;$this->password = $password;$this->connect();}private function connect(){$this->link = new PDO($this->dsn, $this->username, $this
>password);}}
- 这里的$link是一个对象,没有自定义__sleep函数时,$link序列化为null.个人的理解,序列化的结果是字符串,对象当然不能直接序列化.
<?phpclass Connection{protected $link;private $dsn, $username, $password;public function __construct($dsn, $username, $password){$this->dsn = $dsn;$this->username = $username;$this->password = $password;$this->connect();}private function connect(){$this->link = new PDO($this->dsn, $this->username, $this
>password);}public function __sleep(){return array('dsn', 'username', 'password');}public function __wakeup(){$this->connect();}
- 这里添加了一个__sleep,返回由属性组成的数组,又新添了一个__wakeup,这个wakeup完成了反序列化后对于$link的实例化
- P牛对于PHP反序列化的思考:__wakeup的作用在反序列化后,执行一些初始化操作。但其实我们很少利用序列化数据传递资源类型 的对象,而其他类型的对象,在反序列化的时候就已经赋予其值了。 所以你会发现,PHP的反序列化漏洞,很少是由__wakeup这个方法触发的,通常触发在析构函数 __destruct里。其实大部分PHP反序列化漏洞,都并不是由反序列化导致的,只是通过反序列化可以 控制对象的属性,进而在后续的代码中进行危险操作。
Java序列化
-
两个条件
- 实现 java.io.Serializable 接口
- 所有属性必须是可序列化的。如果有一个属性不是可序列化的,则该属性必须注明是短暂的
-
java对象序列化后和php不同,是字节码而非字符串
-
下述代码输出了序列化后的person类
package com.individuals.javaSecurity.myclass;import java.io.IOException;
import java.io.Serializable;public class Person implements Serializable {public String name;public int age;public Person(String name, int age) {this.name = name;this.age = age;}private void writeObject(java.io.ObjectOutputStream out) throws IOException {out.defaultWriteObject();out.writeObject("this is a object");}private void readObject(java.io.ObjectInputStream s)throws IOException, ClassNotFoundException {s.defaultReadObject();String message = (String) s.readObject();System.out.println(message);}
}
import java.io.*;public class SerializationUtils {// 序列化对象并转换为十六进制字符串public static String serializeObjectToHex(Serializable object) throws IOException {ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream)) {objectOutputStream.writeObject(object);}byte[] serializedBytes = byteArrayOutputStream.toByteArray();return bytesToHex(serializedBytes);}// 反序列化十六进制字符串回对象public static Object deserializeHexToObject(String hexString) throws IOException, ClassNotFoundException {byte[] bytes = hexStringToByteArray(hexString);try (ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(bytes))) {return objectInputStream.readObject();}}// 将字节数组转换为十六进制字符串public static String bytesToHex(byte[] bytes) {StringBuilder hexString = new StringBuilder();for (byte b : bytes) {hexString.append(String.format("%02X", b));}return hexString.toString();}// 将十六进制字符串转换为字节数组public static byte[] hexStringToByteArray(String s) {int len = s.length();byte[] data = new byte[len / 2];for (int i = 0; i < len; i += 2) {data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)+ Character.digit(s.charAt(i + 1), 16));}return data;}
}
import java.io.IOException;import static com.individuals.javaSecurity.utils.SerializationUtils.*;public class TestSer {public static void main(String[] args) throws IOException {Person person = new Person("lda",123);System.out.println(serializeObjectToHex(person));}
}
- java -jar SerializationDumper-v1.13.jar 序列化对象.值得注意的是我们在序列化时,写入的字符串"this is a object"在objectAnnotation中
STREAM_MAGIC - 0xac ed
STREAM_VERSION - 0x00 05
ContentsTC_OBJECT - 0x73TC_CLASSDESC - 0x72classNameLength - 43 - 0x00 2bValue - com.individuals.javaSecurity.myclass.Person - 0x636f6d2e696e646976696475616c732e6a61766153656375726974792e6d79636c6173732e506572736f6eserialVersionUID - 0xf9 30 f2 ab 12 b1 36 32newHandle 0x00 7e 00 00classDescFlags - 0x03 - SC_WRITE_METHOD | SC_SERIALIZABLEfieldCount - 2 - 0x00 02Fields0:Int - I - 0x49fieldNameLength - 3 - 0x00 03Value - age - 0x6167651:Object - L - 0x4cfieldNameLength - 4 - 0x00 04Value - name - 0x6e616d65className1TC_STRING - 0x74newHandle 0x00 7e 00 01Length - 18 - 0x00 12Value - Ljava/lang/String; - 0x4c6a6176612f6c616e672f537472696e673bclassAnnotationsTC_ENDBLOCKDATA - 0x78superClassDescTC_NULL - 0x70newHandle 0x00 7e 00 02classdatacom.individuals.javaSecurity.myclass.Personvaluesage(int)123 - 0x00 00 00 7bname(object)TC_STRING - 0x74newHandle 0x00 7e 00 03Length - 3 - 0x00 03Value - lda - 0x6c6461objectAnnotationTC_STRING - 0x74newHandle 0x00 7e 00 04Length - 16 - 0x00 10Value - this is a object - 0x746869732069732061206f626a656374TC_ENDBLOCKDATA - 0x78
- 尝试反序列化我们的序列化对象,可以看当初写入的字符串被打印出来了
import java.io.IOException;import static com.individuals.javaSecurity.utils.SerializationUtils.deserializeHexToObject;public class TestUnser {public static void main(String[] args) throws IOException, ClassNotFoundException {Person person = (Person)deserializeHexToObject("ACED00057372002B636F6D2E696E646976696475616C732E6A61766153656375726974792E6D79636C6173732E506572736F6EF930F2AB12B136320300024900036167654C00046E616D657400124C6A6176612F6C616E672F537472696E673B78700000007B7400036C6461740010746869732069732061206F626A65637478");}
}
- 这里借助gpt简单解释一下序列化对象中的objectAnnotation和 classAnnotations:
classAnnotations
classAnnotations 是与类相关的注解信息。在序列化过程中,Java会在序列化流中包括与类相关的注解信息,这些信息包括:类的元数据:例如类的名称、类的签名(包括字段和方法的签名)。
类的序列化版本UID:用于验证反序列化时类版本的一致性。
类的父类信息:如果类是从其他类继承而来,这些信息也会被包含在内。
objectAnnotation
objectAnnotation 是与对象相关的注解信息。在序列化过程中,Java会在序列化流中包括与对象相关的注解信息,这些信息包括:对象的字段值:对象的非静态和非瞬态字段的当前值。
引用的其他对象:如果对象包含对其他对象的引用,这些被引用对象也会被序列化。
对象的定制序列化数据:如果类实现了 writeObject 方法,这些方法中自定义序列化的数据也会被包含在 objectAnnotation 中。
Python反序列化
- Python反序列化和Java、PHP有个显著的区别,就是Python的反序列化过程实际上是在执行一个基于 栈的虚拟机。我们可以向栈上增、删对象,也可以执行一些指令,比如函数的执行等,甚至可以用这个 虚拟机执行一个完整的应用程序。 所以,Python的反序列化可以立即导致任意函数、命令执行漏洞,与需要gadget的PHP和Java相比更加 危险。
总结
- 总结一下,从危害上来看,Python的反序列化危害是最大的;从应用广度上来看,Java的反序列化是最 常被用到的;从反序列化的原理上来看,PHP和Java是类似又不尽相同的。
补充:SerializationDumper的使用
- SerializationDumper是一个分析序列化对象的工具
- 使用方法很简单:java -jar SerializationDumper-v1.13.jar ,后面可以直接加反序列化对象的十六进制串或者从文件读取
几个案例分析
- Java Object Serialization Specification: 6 - Object Serialization Stream Protocol (oracle.com) java序列化协议文档
- 下图为java序列化对象的结构分析图
public class Person implements Serializable {public String name;public int age;public Person(String name, int age) {this.name = name;this.age = age;}private void writeObject(java.io.ObjectOutputStream out) throws IOException {out.defaultWriteObject();out.writeObject("this is a object");}private void readObject(java.io.ObjectInputStream s)throws IOException, ClassNotFoundException {s.defaultReadObject();String message = (String) s.readObject();System.out.println(message);}
}
public class TestSer {public static void main(String[] args) throws IOException {Person person = new Person("lda",123);System.out.println(serializeObjectToHex(person));}
}
STREAM_MAGIC - 0xac ed
STREAM_VERSION - 0x00 05
ContentsTC_OBJECT - 0x73TC_CLASSDESC - 0x72classNameLength - 43 - 0x00 2bValue - com.individuals.javaSecurity.myclass.Person - 0x636f6d2e696e646976696475616c732e6a61766153656375726974792e6d79636c6173732e506572736f6eserialVersionUID - 0xf9 30 f2 ab 12 b1 36 32newHandle 0x00 7e 00 00classDescFlags - 0x03 - SC_WRITE_METHOD | SC_SERIALIZABLEfieldCount - 2 - 0x00 02Fields0:Int - I - 0x49fieldNameLength - 3 - 0x00 03Value - age - 0x6167651:Object - L - 0x4cfieldNameLength - 4 - 0x00 04Value - name - 0x6e616d65className1TC_STRING - 0x74newHandle 0x00 7e 00 01Length - 18 - 0x00 12Value - Ljava/lang/String; - 0x4c6a6176612f6c616e672f537472696e673bclassAnnotationsTC_ENDBLOCKDATA - 0x78superClassDescTC_NULL - 0x70newHandle 0x00 7e 00 02classdatacom.individuals.javaSecurity.myclass.Personvaluesage(int)123 - 0x00 00 00 7bname(object)TC_STRING - 0x74newHandle 0x00 7e 00 03Length - 3 - 0x00 03Value - lda - 0x6c6461objectAnnotationTC_STRING - 0x74newHandle 0x00 7e 00 04Length - 16 - 0x00 10Value - this is a object - 0x746869732069732061206f626a656374TC_ENDBLOCKDATA - 0x78