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序列化
<?php
class 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.个人的理解,序列化的结果是字符串,对象当然不能直接序列化.
<?php
class 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
Contents
TC_OBJECT - 0x73
TC_CLASSDESC - 0x72
className
Length - 43 - 0x00 2b
Value - com.individuals.javaSecurity.myclass.Person - 0x636f6d2e696e646976696475616c732e6a61766153656375726974792e6d79636c6173732e506572736f6e
serialVersionUID - 0xf9 30 f2 ab 12 b1 36 32
newHandle 0x00 7e 00 00
classDescFlags - 0x03 - SC_WRITE_METHOD | SC_SERIALIZABLE
fieldCount - 2 - 0x00 02
Fields
0:
Int - I - 0x49
fieldName
Length - 3 - 0x00 03
Value - age - 0x616765
1:
Object - L - 0x4c
fieldName
Length - 4 - 0x00 04
Value - name - 0x6e616d65
className1
TC_STRING - 0x74
newHandle 0x00 7e 00 01
Length - 18 - 0x00 12
Value - Ljava/lang/String; - 0x4c6a6176612f6c616e672f537472696e673b
classAnnotations
TC_ENDBLOCKDATA - 0x78
superClassDesc
TC_NULL - 0x70
newHandle 0x00 7e 00 02
classdata
com.individuals.javaSecurity.myclass.Person
values
age
(int)123 - 0x00 00 00 7b
name
(object)
TC_STRING - 0x74
newHandle 0x00 7e 00 03
Length - 3 - 0x00 03
Value - lda - 0x6c6461
objectAnnotation
TC_STRING - 0x74
newHandle 0x00 7e 00 04
Length - 16 - 0x00 10
Value - this is a object - 0x746869732069732061206f626a656374
TC_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
Contents
TC_OBJECT - 0x73
TC_CLASSDESC - 0x72
className
Length - 43 - 0x00 2b
Value - com.individuals.javaSecurity.myclass.Person - 0x636f6d2e696e646976696475616c732e6a61766153656375726974792e6d79636c6173732e506572736f6e
serialVersionUID - 0xf9 30 f2 ab 12 b1 36 32
newHandle 0x00 7e 00 00
classDescFlags - 0x03 - SC_WRITE_METHOD | SC_SERIALIZABLE
fieldCount - 2 - 0x00 02
Fields
0:
Int - I - 0x49
fieldName
Length - 3 - 0x00 03
Value - age - 0x616765
1:
Object - L - 0x4c
fieldName
Length - 4 - 0x00 04
Value - name - 0x6e616d65
className1
TC_STRING - 0x74
newHandle 0x00 7e 00 01
Length - 18 - 0x00 12
Value - Ljava/lang/String; - 0x4c6a6176612f6c616e672f537472696e673b
classAnnotations
TC_ENDBLOCKDATA - 0x78
superClassDesc
TC_NULL - 0x70
newHandle 0x00 7e 00 02
classdata
com.individuals.javaSecurity.myclass.Person
values
age
(int)123 - 0x00 00 00 7b
name
(object)
TC_STRING - 0x74
newHandle 0x00 7e 00 03
Length - 3 - 0x00 03
Value - lda - 0x6c6461
objectAnnotation
TC_STRING - 0x74
newHandle 0x00 7e 00 04
Length - 16 - 0x00 10
Value - this is a object - 0x746869732069732061206f626a656374
TC_ENDBLOCKDATA - 0x78
标签:java,String,基础,class,0x00,01java,序列化,public
From: https://www.cnblogs.com/LSF2456/p/18641569