Magic In Java API 学习记录
2023-9-2 13:11:11 Author: xz.aliyun.com(查看原文) 阅读量:2 收藏

前言

这篇文章主要是对yemoliR1ckyZ在KCON2023中的议题《Magic In Java API》的一次学习记录

对应的slide可以在https://github.com/knownsec/KCon/tree/master/2023中获取

概述

该议题的结构如下

  1. API的介绍
  2. 该API在各个框架或者组件能够的影响细节
  3. 在RASP中利用该API的小技巧
  4. 根据各个组件的修复方式总结了一些有效的防御措施

API介绍

议题中API指代的就是sun.print.UnixPrintServiceLookup这个类

这是一个用于打印服务注册的功能的接口

一个用于查找可用的打印机服务

public static void main(String[] args) throws IOException {
    // 获取 UnixPrintServiceLookup 实例
    PrintServiceLookup lookup = (PrintServiceLookup) PrintServiceLookup.lookupDefaultPrintService();

    // 使用 UnixPrintServiceLookup 查找所有可用的打印服务
    PrintService[] printServices = lookup.getPrintServices();

    if (printServices.length == 0) {
        System.out.println("No printers available.");
    } else {
        System.out.println("Available printers:");
        for (PrintService printer : printServices) {
            System.out.println(printer.getName());
        }
    }
}

在windows中对应的功能是在Win32PrintServiceLookup中实现的

public static void main(String[] args) {
    // 获取 PrintServiceLookup 实例
    // 获取所有可用的打印服务
    PrintService[] printServices = PrintServiceLookup.lookupPrintServices(null, null);

    if (printServices.length == 0) {
        System.out.println("No printers available.");
    } else {
        System.out.println("Available printers:");
        for (PrintService printer : printServices) {
            System.out.println(printer.getName());
        }
    }
}

UnixPrintServiceLookup初始化的时候

创建了一个PrinterChangeListener监听器,是一个线程类,不断的更新服务

过程中存在有可能利用的点getAllPrinterNamesBSD方法中

存在有命令执行的位置,传入的参数是在lpcAllCom数组中的其中一个元素

如果我们能够控制传入的参数就能够达到RCE的目的

之后就是从修改类属性的”不同方法“的角度来阐明该API可能导入RCE的情况

  1. 想要更改类的属性值,我们常用的结合反序列化可以通过java中的反射机制进行类属性值的更改,这个想法在很多其他的常见反序列化利用中很常见,但是可惜的是,在UnixPrintServiceLookup类并没有实现Serializable接口

但是跟进过dubbo rpc框架的CVE也能够知道,在类似hession这类远程调用协议,在序列化和反序列化的过程中可以反序列化没有实现Serializable接口的类

  1. 可能在恢复bean类的过程中会直接使用反射来恢复类属性值

available protocols

在前面更改类属性值得其中第一个方法也就是通过反序列化过程中的反射机制来进行类属性值得更改

前面只提到了在dubbo使用hession的时候可以在没有实现Serializable接口得情况下进行属性值的更改

在原slide中作者总结了常见的序列化协议是否支持没有实现Serializable接口

也主要就是hession类和json处理类的几个协议

Hession

在图中,可以利用的hession版本是在3.x才可以

Hession 3.x

我们简单跟进一下具体的能够进行利用的原理(即为什么可以支持没有实现Serializable接口的类的反序列化和是怎样通过反射来修改类属性

这里我们默认是对Java对象进行反序列化,因为在hessian的反序列化的过程中将会针对不同的类型选择不同的序列化器

针对Java对象,默认是使用JavaDeserializer这个反序列化器

  1. 首先是在Hessian2Input#readObject方法中

    根据tag位的不同选择不同的处理方法

  1. 而在Hessian2Input#readObjectDefinition中主要是将反序列化的类中所有存在的类名给保存在了_classDefs属性中

  1. 之后又重新回到了Hessian2Input#readObject中进行类对象的实例化

  1. 这里并没有显示的指明反序列化的类,进而通过序列化工厂类的反序列化逻辑进行反序列化

通过type确定使用默认的JavaDeserializer反序列化器

  1. 来到了JavaDeserializer#readObject中进行对象的实例化

  1. JavaDeserializer#instantiate中存在有调用构造方法的操作

  1. 在得到了反序列化类的实例之后,将其传入JavaDeserializer#readObject的一个同名方法中进行类属性的反序列化

对于不同的属性的类型,将会选择不同的反序列化器进行处理

这里我们象征性的看一下String类型的反序列过程

这里是通过反射进行恢复属性值

总结一下,上面也就是在hessian反序列化的过程中可能调用构造方法和反射恢复属性值的原理

至于hessian中的可以序列化和反序列化不实现Serializable接口的类,我们可以在SerializerFactory#getDefaultSerializer中找到出处

在抛出IllegalStateException异常需要满足条件if (!Serializable.class.isAssignableFrom(cl) && !this._isAllowNonSerializable)如果没有实现Serializable接口,也可以选择将属性_isAllowNonSerializable设置为true

hessianOutput.getSerializerFactory().setAllowNonSerializable(true);

Hessian 4.x

在4.x的版本中在前面的默认的反序列化器直接简单的设置为JavaDeserializer的基础上进行了一个限制

具体在SerializerFactory#getDefaultSerializer这个方法中,这个方法是用来返回一个没有被直接匹配的序列化器的一个默认的序列化器

这里的逻辑为:

  1. 如果_defaultSerializer不为null,就返回这个序列化器进行类对象的序列化操作,如果为null则执行 2 操作
  2. 如果没有实现Serializable接口的情况下同时没有开启_isAllowNonSerializable将会抛出异常,否则执行 3 操作
  3. 如果启动了不安全的序列化器,并且目标类中没有名为writeReplace的无参方法,就构造一个UnsafeSerializer返回
  4. 如果上面的都不满足就会返回3.x版本中的默认的JavaSerializer序列化器

而在API的触发点 -- 构造方法的入口的位置instantiate方法中

在3.x版本中将会在这里调用构造方法,结合之后的反射修改属性值控制参数能够达到RCE

而在这里4.x版本,使用的是UnsafeDeserializer#instantiate方法

使用了allocateInstance这个native方法实例化一个被序列化的对象,但是这里不会执行构造函数,也就不能够利用该API

在属性值的恢复也使用的是Unsafe类中的API

a case

构造一个简单的JavaBean,观察构造函数是否调用

import java.io.Serializable;

public class ObjectTest implements Serializable {
    private String cmd;

    public ObjectTest() {
        System.out.println("construct...");
    }

    public String getCmd() {
        return cmd;
    }

    public void setCmd(String cmd) {
        this.cmd = cmd;
    }
}

分别使用3.x 4.x版本的hessian进行序列化和反序列化操作

Hessian-lite

这个协议是在alibaba用在Dubbo这个RPC框架中的一个内置版本

维护了一些常见的黑名单

这也是导致CVE-2022-39198的sink点,我之前也分析过这个链子

https://xz.aliyun.com/t/11961

sofa hessian

这个也和hessian-lite类似,都是在原始hessian的基础上的修改版本,也内置了一个黑名单

和原始的hessian类似,在4.x版本中同样使用了Unsafe的API,将不会进行构造方法的触发

总结

这里主要是针对hessian和其衍生的协议的角度详细学习了hessian中能够调用构造方法的原理,同时也对hessian协议有了更深的理解


文章来源: https://xz.aliyun.com/t/12818
如有侵权请联系:admin#unsafe.sh