The Common Object Request Broker Architecture (CORBA) is a standard defined by the Object Management Group (OMG). It is a platform independent RPC framework which predates standards such as SOAP and gRPC. In a distributed setting, CORBA uses the Internet InterORB Protocol (IIOP) protocol to communicate between endpoints. In a default installation of IBM WebSphere has CORBA services running on TCP ports 2809, 9100, 9402 and 9403. Prior to invoking service methods, the call is intercepted by Interceptor classes. Of interest to us is the TxServerInterceptor class.
```java
public void receive_request(ServerRequestInfo sri) {
// ...snip...
if (TxProperties.SINGLE_PROCESS) {
propagationContext = TxInterceptorHelper.demarshalContext(serviceContext.context_data, (ORB)((LocalObject)sri)._orb()); // <------------
contextType = TxInterceptorHelper.determineContextType(propagationContext);
}
// ...snip...
}
public static final PropagationContext demarshalContext(byte[] bytes, ORB orb) {
// ...snip...
propContext.implementation_specific_data = inputStream.read_any(); // <-------
// ...snip...
}
public Any read_any() {
// ...snip...
any.read_value(this.encoderStream, typeCodeImpl); // <------------------------
return any;
}
private Object simpleReadObjectInternal(Class paramClass, String paramString) throws ClassNotFoundException, IOException {
// ...snip...
return readSerializable(paramClass, objectStreamClass, paramString); // <------
// ...snip...
}
private Object readSerializable(Class paramClass, ObjectStreamClass paramObjectStreamClass, String paramString) throws IOException, ClassNotFoundException {
// ...snip...
return inputObjectClassDesc(serializable, paramObjectStreamClass); // <-------
}
private Object inputObjectClassDesc(Object paramObject, ObjectStreamClass paramObjectStreamClass) throws IOException, ClassNotFoundException {
ObjectStreamClass objectStreamClass = processClassHierarchy(paramObject, paramObjectStreamClass);
return inputObjectUsingClassDesc(paramObject, objectStreamClass); // <--------
}
Object inputObjectUsingClassDesc(Object paramObject, ObjectStreamClass paramObjectStreamClass) throws IOException, ClassNotFoundException {
// ...snip...
ObjectStreamClass objectStreamClass = this.readObjectOSC;
this.readObjectOSC = paramObjectStreamClass;
paramObjectStreamClass.readObjectMethod.invoke(paramObject, this.readObjectArglist); // <--- calls readObject()
this.readObjectOSC = objectStreamClass;
return true;
}
```
receive_request() is called when TxServerInterceptor intercepts a call and demarshalContext() is used to retrieve a ServiceContext from a byte stream that can be controlled by the attacker. Arbitrary objects embedded within this byte stream are extracted by calling read_any() which eventually results in the readObject() method of the embedded object class being called.
#### The RCE Gadget (CVE-2020-4450)
Despite having the ability to deserialize any object, achieving RCE is not trivial. This is because the IBM Java SDK has implemented significant mitigations against deserialization attacks.
The mitigations relevant to this vulnerability are:
- It has a restricted ClassLoader to only provide necessary classes at runtime.
- The Xalan's TemplatesImpl TemplatesImpl class is no longer serializable. This class is often used in public gadget chains that rely on a single arbitrary method invocation.
- The IBM SDK does not use the Oracle JDK’s implementation of the Java Naming and Directory Interface (JNDI). As a result, it is not vulnerable to remote class loading via RMI/LDAP.
In his submission, tint0 provided a gadget that bypasses these mitigations. This gadget uses the WSIFPort_EJB class as an entry point.
```java
public class WSIFPort_EJB
extends WSIFDefaultPort
implements Serializable
{
// ...snip...
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
ois.defaultReadObject();
if (this.separatedEJBRefs) {
Object objHome = ois.readObject();
if (objHome != null && objHome instanceof HomeHandle) {
HomeHandle homeHandle = (HomeHandle)objHome;
this.fieldEjbHome = homeHandle.getEJBHome();
}
Object obj = ois.readObject();
if (obj != null && obj instanceof Handle) {
Handle handle = (Handle)obj;
this.fieldEjbObject = handle.getEJBObject(); // <----------------------
}
}
}
}
public EJBObject getEJBObject() throws RemoteException {
// ...snip...
home = (EJBHome)PortableRemoteObject.narrow(ctx.lookup(this.homeJNDIName), homeClass); // <----
// ...snip...
Method fbpk = findFindByPrimaryKey(homeClass); // <---- returns findFindByPrimaryKey() method
this.object = (EJBObject)fbpk.invoke(home, new Object[] { this.key }); <-- $proxy.findFindByPrimaryKey(Serializable $arg)
}
```
This class does a few interesting things inside the getEJBObject() method. Let us start by looking at the JNDI lookup call.
```java
com.sun.jndi.rmi.registry.RegistryContext#lookup
com.sun.jndi.rmi.registry.RegistryContext#decodeObject
javax.naming.spi.NamingManager#getObjectInstance
org.apache.aries.jndi.OSGiObjectFactoryBuilder#getObjectInstance
org.apache.aries.jndi.ObjectFactoryHelper#getObjectInstance
org.apache.aries.jndi.ObjectFactoryHelper#getObjectInstanceViaContextDotObjectFactories
protected Object getObjectInstanceViaContextDotObjectFactories(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment, Attributes attrs) throws Exception {
Object result = null;
String factories = (String)environment.get("java.naming.factory.object");
if (factories != null && factories.length() > 0) {
String[] candidates = factories.split(":");
ClassLoader cl = (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
public ClassLoader run() {
return Thread.currentThread().getContextClassLoader();
}
});
for (String cand : candidates) {
ObjectFactory factory = null;
try {
Class<ObjectFactory> clz = cl.loadClass(cand);
factory = (ObjectFactory)clz.newInstance();
} catch (Exception e) {
if (logger.isLoggable(Level.FINE)) logger.log(Level.FINE, "Exception instantiating factory: " + e);
}
if (logger.isLoggable(Level.FINE)) logger.log(Level.FINE, "cand=" + cand + " factory=" + factory);
if (factory != null) {
if (factory instanceof DirObjectFactory) {
if (logger.isLoggable(Level.FINE)) logger.log(Level.FINE, "its a DirObjectFactory");
DirObjectFactory dirFactory = (DirObjectFactory)factory;
result = dirFactory.getObjectInstance(obj, name, nameCtx, environment, attrs);
}
else {
if (logger.isLoggable(Level.FINE)) logger.log(Level.FINE, "its an ObjectFactory");
result = factory.getObjectInstance(obj, name, nameCtx, environment);
}
}
if (result != null && result != obj)
break;
}
} if (logger.isLoggable(Level.FINE)) logger.log(Level.FINE, "result = " + result);
return (result == null) ? obj : result;
}
```
As shown, getObjectInstanceViaContextDotObjectFactories() can result in the invocation of getObjectInstance of any ObjectFactory class. One such class which is relevant to this gadget is WSIFServiceObjectFactory:
```java
public Object getObjectInstance(Object obj, Name name, Context context, Hashtable env) throws Exception {
Trc.entry(this, obj, name, context, env);
if (obj instanceof Reference && obj != null) {
Reference ref = (Reference)obj;
if (ref.getClassName().equals(WSIFServiceRef.class.getName())) {
String wsdlLoc = resolveString(ref.get("wsdlLoc"));
String serviceNS = resolveString(ref.get("serviceNS"));
String serviceName = resolveString(ref.get("serviceName"));
String portTypeNS = resolveString(ref.get("portTypeNS"));
String portTypeName = resolveString(ref.get("portTypeName"));
if (wsdlLoc != null) {
WSIFServiceFactory factory = WSIFServiceFactory.newInstance();
WSIFService service = factory.getService(wsdlLoc, serviceNS, serviceName, portTypeNS, portTypeName);
Trc.exit(service);
return service;
}
} else if (ref.getClassName().equals(WSIFServiceStubRef.class.getName())) {
String wsdlLoc = resolveString(ref.get("wsdlLoc"));
String serviceNS = resolveString(ref.get("serviceNS"));
String serviceName = resolveString(ref.get("serviceName"));
String portTypeNS = resolveString(ref.get("portTypeNS"));
String portTypeName = resolveString(ref.get("portTypeName"));
String preferredPort = resolveString(ref.get("preferredPort"));
String className = resolveString(ref.get("className"));
if (wsdlLoc != null) {
WSIFServiceFactory factory = WSIFServiceFactory.newInstance();
WSIFService service = factory.getService(wsdlLoc, serviceNS, serviceName, portTypeNS, portTypeName); // <----
Class iface = Class.forName(className, true, Thread.currentThread().getContextClassLoader());
Object stub = service.getStub(preferredPort, iface);
Trc.exit(stub);
return stub;
}
}
}
}
```
The getObjectInstance() call will initialize a Web Services Invocation Framework (WSIF) service from a URL pointing to a remote XML definition that can be controlled by the attacker. In this specific scenario, the service’s className is set to javax.el.ELProcessor, and a java:operation element is defined that maps findByPrimaryKey() to the eval() Java method. For further details on WSIF services, please refer to this website.
The getObjectInstance() call returns a WSIFClientProxy Java proxy object. This proxy object will invoke eval() on an instance of ELProcessor when findByPrimaryKey() is called. Recall that we already control the argument this.key via deserialization. This results in RCE via Expression Language injection. The following video demonstrates this PoC in action.
暂无评论