Goby社区第 2 篇漏洞分析文章
全文共:6856 字 预计阅读时间:18 分钟
01 概述
近些年,Weblogic 反序列化漏洞一直围绕着反序列化的触发点进行漏洞挖掘,事实上还有很多存在反序列化但无法实时利用的点,在大家平时的漏洞挖掘中容易忽略。在行业内也有一些关于“后反序列化”的进一步讨论,这些看似无法利用的漏洞,其实可以通过一些后续的技巧完成稳定的利用效果。例如,进行 bind() 或 rebind() 操作后,并没有触发漏洞,此时可以尝试其他方法如 lookup()、lookupLink() 等触发漏洞。
**通过这种思路我们发现了两个 Weblogic 的后反序列化漏洞(CVE-2023-21931、CVE-2023-21839),获得了 Oracle 的官方确认。本文以这两个 Weblogic 漏洞为例,分享"后反序列化漏洞"的利用思路。**我们相信还有很多这类的漏洞在未来会逐渐被挖掘出来,希望本篇文章能够给大家一些启发。
02 后反序列化漏洞
Weblogic 反序列化漏洞挖掘思路是利用 readObject()、readResolve()、readExternal() 等反序列化方法对恶意序列化数据进行操作,以达到攻击目的。常规的漏洞思路重点关注 Weblogic 在反序列化过程中进行恶意攻击,而忽略了反序列化完成后的操作。后反序列化漏洞挖掘的思路重点关注 Weblogic 完成反序列化过程后,在达到某个时机或执行操作后触发的漏洞攻击。
在 Weblogic 中,如果进行 bind() 或 rebind() 操作后,并没有触发漏洞,此时可以尝试其他方法如 lookup()、lookupLink() 等触发漏洞。
本文将以 lookup() 方法作为漏洞触发点,对 Weblogic 后反序列化漏洞的攻击过程进行分析和漏洞实例展示。
03 lookup
通过跟踪调用堆栈,我们发现 lookup() 的流程如下:
Weblogic 在接收到请求后,通过 BasicServerRef 类中的 invoke() 方法解析传入数据。
通过 _invoke() 方法,Weblogic 根据传入的方法名 resolve_any 执行的 resolve_any() 方法。
在 resolve_any() 方法中,通过 resolveObject() 方法对传入的绑定命名进行解析。
在 resolveObject() 方法中,根据上下文信息调用其中的 lookup() 方法。
根据上下文中的信息,经过在 WLContextImpl、WLEventContextImpl、 WLEventContextImpl 、RootNamingNode、ServerNamingNode 、BasicNamingNode 类中一系列的 lookup() 方法调用,实现 BasicNamingNode 类中的 resolveObject() 方法调用。
由于传入 resolveObject() 方法中的 obj 不是 NamingNode 类的实例,且 mode 的值默认为 1,所以会调用 WLNamingManager 类中的 getObjectInstance() 方法。
最终,可以看到 WLNamingManager 类的 getObjectInstance() 方法根据传入的对象接口类型,调用对象中的 getReferent() 方法,完成漏洞触发点的 lookup() 方法调用。实际上这两个 CVE 漏洞都是通过 getObjectInstance() 的两个分支触发的。
04 CVE-2023-21931
CVE-2023-21931 的漏洞触发点在 WLNamingManager 类的 getObjectInstance() 方法中,当传入的 boundObject 对象是 LinkRef 的实现类时,则调用传入对象 boundObject 的 getLinkName() 方法,并通过 lookup() 方法对 getLinkName() 方法返回的 linkAddrType 地址进行远程 JNDI 加载。在实例化 LinkRef 类时,可以通过类中的构造方法给 linkAddrType 传入一个 JNDI 地址。这样,我们就可以调用 lookup() 方法对自定义的 JNDI 地址进行远程加载,达到攻击的目的。
`package weblogic.jndi.internal;``public final class WLNamingManager {` `public static Object getObjectInstance(Object boundObject, Name name, Context ctx, Hashtable env) throws NamingException {` `if (boundObject instanceof ClassTypeOpaqueReference) {` `......` `} else if (boundObject instanceof LinkRef) {` `String linkName = ((LinkRef)boundObject).getLinkName();` `InitialContext ic = null;` `try {` `ic = new InitialContext(env);` `boundObject = ic.lookup(linkName); // 漏洞触发点` `} catch (NamingException var15) {` `......` `} finally {......}` `}` `}``}`
漏洞 JNDI 地址构造在 LinkRef 这个类中,LinkRef 是 Java 的一个原生类。通过 LinkRef 类中的构造方法,我们可以控制变量 linkAddrType 的值, 再通过 getLinkName() 方法将 linkAddrType 作为字符串返回。
`package javax.naming;``public class LinkRef extends Reference {` `static final String linkClassName = LinkRef.class.getName();` `static final String linkAddrType = <span data-raw-text="" "="" data-textnode-index-1681866646750="276" data-index-1681866646750="2786" data-textnode-notemoji-index-1681866646750="2786" class="character">"LinkAddress<span data-raw-text="" "="" data-textnode-index-1681866646750="276" data-index-1681866646750="2798" data-textnode-notemoji-index-1681866646750="2798" class="character">";`` ` `public LinkRef(Name linkName) {` `super(linkClassName, new StringRefAddr(linkAddrType, linkName.toString()));` `}`` ` `public LinkRef(String linkName) {` `super(linkClassName, new StringRefAddr(linkAddrType, linkName));` `}`` ` `public String getLinkName() throws NamingException {` `if (className != null && className.equals(linkClassName)) {` `RefAddr addr = get(linkAddrType);` `if (addr != null && addr instanceof StringRefAddr) {` `return (String)((StringRefAddr)addr).getContent();` `}` `}` `throw new MalformedLinkException();` `}``}`
在上述过程中,rebind() 和 lookup() 方法的反序列化过程并未执行恶意操作,而是在完成反序列化之后,通过调用类 WLNamingManager 中 getObjectInstance() 方法的 lookup() 才触发漏洞,进行远程恶意加载 JNDI 地址操作的。
我们在 Goby 中已经集成了 CVE-2023-21931 漏洞,并加入了回显和反弹 shell 的功能。演示效果如下:
05 CVE-2023-21839
ForeignOpaqueReference 是 OpaqueReference 接口的实现类。在 ForeignOpaqueReference 类中声明了两个私有变量:jndiEnvironment 和 remoteJNDIName,同时声明了两个构造方法,在有参构造方法中接收 env 和 remoteJNDIName,并分别赋值给了上面的两个私有类变量。
ForeignOpaqueReference 类的 getReferent() 方法是 OpaqueReference 接口的实现方法,在 getReferent() 方法中,retVal = context.lookup(this.remoteJNDIName); 对本类 remoteJNDIName 变量中的 JNDI 地址进行远程加载,导致了反序列化漏洞。
`package weblogic.jndi.internal;``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 {` `Hashtable properties = this.decrypt();` `context = new InitialContext(properties);` `}` `Object retVal;` `try {` `retVal = context.lookup(this.remoteJNDIName); // 漏洞点` `} finally {` `context.close();` `}` `return retVal;` `}` `......``}`
getReferent() 调用分析
`package weblogic.jndi;``public interface OpaqueReference {` `Object getReferent(Name var1, Context var2) throws NamingException;` `String toString();``}`
OpaqueReference 接口有两个抽象方法:getReferent() 和 toString();
ForeignOpaqueReference 类的 getReferent() 方法调用在 WLNamingManager 类中。
在 WLNamingManager 类的 getObjectInstance() 方法中,当传入的 boundObject 对象实现了 OpaqueReference 接口时,则会调用该对象的 getReferent() 方法,即 boundObject = ((OpaqueReference)boundObject).getReferent(name, ctx);。
正如上方提到的 ForeignOpaqueReference 类实现了 OpaqueReference 接口,因此会调用该类中的 getReferent() 方法,导致反序列化代码执行漏洞。
`package weblogic.jndi.internal;``public final class WLNamingManager {` `public static Object getObjectInstance(Object boundObject, Name name, Context ctx, Hashtable env) throws NamingException {` `if (boundObject instanceof ClassTypeOpaqueReference) {` `......` `} else if (boundObject instanceof OpaqueReference) {` `boundObject = ((OpaqueReference)boundObject).getReferent(name, ctx);` `} else if (boundObject instanceof LinkRef) {` `...` `}` `}``}`
CVE-2023-21931 漏洞原理相同,CVE-2023-21839 也是在反序列化过程中没有进行恶意操作,而是完成反序列化过程后执行了漏洞类 ForeignOpaqueReference 中 getReferent() 方法中的 lookup() 才触发的漏洞。
在 Goby 中,我们已经集成了 CVE-2023-21839 漏洞,并添加了回显以及反弹 Shell 的功能。以下是演示效果:
06 时间线
CVE-2023-21931
2022 年 8 月 12 日 漏洞提交官方
2022 年 8 月 19 日 漏洞官方确认
2023 年 4 月 18 日 漏洞官方修复
CVE-2023-21839
2022 年 7 月 31 日 漏洞提交官方
2022 年 8 月 5 日 漏洞官方确认
2023 年 1 月 16 日 漏洞官方修复
07 研究环境
Vulfocus Weblogic 环境
`docker pull vulfocus/vcpe-1.0-a-oracle-weblogic:12.2.1.2.0-jdk-release``docker pull vulfocus/vcpe-1.0-a-oracle-weblogic:12.2.1.1.0-jdk-release``docker pull vulfocus/vcpe-1.0-a-oracle-weblogic:12.2.1.3.0-jdk-release``docker pull vulfocus/vcpe-1.0-a-oracle-weblogic:12.2.1.4.0-jdk-release``docker pull vulfocus/vcpe-1.0-a-oracle-weblogic:12.2.1.0.0-jdk-release``docker pull vulfocus/vcpe-1.0-a-oracle-weblogic:14.1.1.0.0-jdk-release``docker pull vulfocus/vcpe-1.0-a-oracle-weblogic:12.1.2.0.0-jdk-release``docker pull vulfocus/vcpe-1.0-a-oracle-weblogic:12.1.3.0.0-jdk-release``docker pull vulfocus/vcpe-1.0-a-oracle-weblogic:10.3.6.0-jdk-release`
08 参考
1. Java“后反序列化漏洞”利用思路 - Ruilin (http://rui0.cn/archives/1338)
2. Ruil1n/after-deserialization-attack: Java After-Deserialization Attack (https://github.com/Ruil1n/after\-deserialization\-attack)
本文中演示的漏洞与功能适配 Goby 版本:Beta 2.4.7,已支持Goby红队版、漏扫版扫描验证。最新版本下载体验:https://gobysec.net/
最新 Goby 使用技巧分享**:**
• su18 | Shell中的王者-JAVAWEB内存马【认知篇】
• su18 | Goby利用内存马的一些技术细节【技术篇】
• 14m3ta7k | 跨越语言的艺术:Weblogic序列化漏洞与IIOP协议
更多 >> 技术分享
Goby 欢迎表哥/表姐们加入我们的社区大家庭,一起交流技术、生活趣事、奇闻八卦,结交无数白帽好友。
也欢迎投稿到 Goby(Goby 介绍/扫描/口令爆破/漏洞利用/插件开发/ PoC 编写/ IP 库使用场景/ Webshell /漏洞分析 等文章均可),审核通过后可奖励 Goby 红队版,快来加入微信群体验吧~~~
微信群:公众号发暗号“加群”,参与积分商城、抽奖等众多有趣的活动