前言
近期想补完yso的所有链子,然后做一部分总结,之前只跟了CC/CB/JDK的链,打存在漏洞的组件的时候CC5/CB/CC6/Jdk7u21/Jdk8u2都比较好用,所以跟链子的目的除了知道链子本身,还要发散链子可拓宽可复用的点、以及链子之间的一些联系、以及在实战中合理用链
Groovy1
此链用到了动态代理(即CC1的前半部分),后半部分调用
MethodClosure#call
来执行任意类方法,同时用到了Groovy中"".execute
的特性来执行命令
前置
MethodClosure
这个类是方法闭包,相当于调用方法的一个封装,有点类似于MethodUtil这样的反射封装类,其类关系如下
该类中两个可以执行任意类方法的方法。
call
该方法是继承Closure的,底层还是通过Method.invoke
来执行,具体的封装细节就不多说了,和其他方法封装类差不多,只要知道方法名、方法所属类、参数,就可以实现一个方法的调用。注意其参数Object... args
,所以我们传参要多套一层new Object[]{args}
,否则会出现调用其他参数类型的重载方法的情况,因为底层获取方法(知道方法名)是根据参数类型来匹配的
doCall
这个就比较间接,细节就不多说了,看下参数的对应关系,这里的getOwner
获取的是this.owner
,对应构造方法的第一个参数,代表方法所属的类对象
所以我们可以通过如下方式调用一个方法
1 | MethodClosure mc = new MethodClosure(Runtime.getRuntime(), "exec"); |
不带参的call
在Groovy中,支持以下执行命令的方式,也就是说,执行命令可以直接String.excute()
1 | 'whoami'.execute() |
对应到java中的处理类是org.codehaus.groovy.runtime.ProcessGroovyMethods
注意,这个execute
方法有很多个重载,和实际的exec
方法参数相匹配,所以也支持new String[]{}
的参数形式
结合前面说的MethodClosure#call
,我们可以通过如下方式执行命令
1 | MethodClosure mc = new MethodClosure(new String[]{"open", "/System/Applications/Calculator.app"}, "execute"); |
可以发现,这里的call
方法相较于之前的不需要参数,增加了我们连接链子的灵活性。
ConvertedClosure
该类的类图如下:
可以看到其继承的父类实现了InvocationHandler
,所以该类可以用来作为动态代理的handler,这点很关键。
再看一下其构造方法和父类中定义的invoke
方法
前面的plugin部分不用管,这里的checkMethod
是判断method所属的类对象是否是Object,再跟进一下子类重写的invokeCustom方法
可以看到getDelegate()
获取的就是构造方法传入的Closure,调用Closure.call
来执行。
也就是说这个handler在内部是委托给Closure.call
去执行处理了。这样和前面的MethodClosure就接起来了。
有了一个可控的handler链,自然会想到复用CC1的AnnotationInvocationHandler
来作为source了。
利用链
1 | AnnotationInvocationHandler.readObject() |
这里也就体现了不带参call
的作用,因为entrySet()方法是无参的,因此参数传递不下来,所以执行的call只能是不带参的。如果把这条链用在带groovy依赖的XStream中,就可以调用带参的了。
- Source:sun.reflect.annotation.AnnotationInvocationHandler#readObject() -> Map#entrySet
- Chain:org.codehaus.groovy.runtime.ConvertedClosure#invoke()
- Sink:org.codehaus.groovy.runtime.MethodClosure#call()
POC
依赖:
- groovy核心包即可
1 | public class Groovy1 { |
Hibernate
前置
BasicPropertyAccessor - 调用任意getter
该类的类图如下
PropertyAccessor
接口是一个定义了获取类属性值策略的类,其中有两个方法getGetter
和getSetter
。传入两个参数,Class对象和要获取的属性名。返回org.hibernate.property.Getter
和 org.hibernate.property.Setter
对象
而BasicPropertyAccessor
是该接口的一个标准实现类。其中定义了两个内部类BasicSetter
和BasicGetter
,就是getGetter
和getSetter
方法需要返回的子类
这个关注一下BasicGetter
,根据构造方法的参数列表和get
方法,可以知道构造方法接受类、方法、属性名三个参数,通过这三个参数可以获取到一个属性确切的getter。而get
方法则是对某个具有该属性的类调用其对应getter
回看BasicPropertyAccessor
,其实现了getGetter
方法,而该方法继续调用createGetter -> getGetterOrNull
来获取,具体看一下getGetterOrNull
,其接受类、属性名两个参数,根据这两个参数以及getter方法的命名逻辑,可以获取该类属性的getter
继续看一下getterMethod
方法。处理逻辑很简单:
- 调用 Class 的
getDeclaredMethods
方法获取全部方法 - getter 方法不应该有参数,如果 Method 的参数类型数量不等于0,则跳过
- 如果方法类型是 BRIDGE,则跳过
- 获取方法名,如果以 get 或 is 开头,则可能为 getter 方法,减掉前缀后进行字符串的对比,在
Introspector.decapitalize()
方法中还进行的首字母大小写的处理。所以这里是根据属性名匹配的getter,因此如果有一些getter不按常理出牌,比如TemplatesImpl里面的一些getter,那就没办法获取到了。
所以这里我们就可以通过BasicPropertyAccessor
获取BasicGetter
,调用BasicGetter#get()
触发任意getter的调用,这里最常用的就是两条原生的getter链
TemplatesImpl#getOutputProperties
JdbcRowSetImpl#getDatabaseMetaData
所以我们的目标就是找到哪里调用了Getter#get
AbstractComponentTuplizer - 寻找Chain
抽象类org.hibernate.tuple.component.AbstractComponentTuplizer
定义了getPropertyValue(s)
和setPropertyValue(s)
用于Getter
和Setter
的调用
其成员变量getters
用于保存一个类的getter数组,所以这里可以作为我们的连接点。该抽象类有PojoComponentTuplizer
子类,虽然重写了getPropertyValues
方法但没有破坏逻辑。
那么我们继续找下一个连接点,看哪里调用了getPropertyValues
或getPropertyValue
可以看到org.hibernate.type.ComponentType#getHashCode
方法中有调用,并且ComponentType
的其他方法中也有调用
TypedValue - 回溯hashCode
org.hibernate.engine.spi.TypedValue
类是一个 final class,用来映射一个 Object 的值和对应的 Hibernate type。Hibernate 中定义了一个自己的类型接口org.hibernate.type.Hibernate.Type
,用来定义 Java 类型和一个或多个 JDBC 类型之间的映射
ComponentType
是Type
类的实现类,可以被保存在TypedValue
中作为映射目标Type。
TypedValue
类的readObject
方法中,调用了initTransients
方法
该方法中定义了一个ValueHolder
赋值给成员变量hashCode。而ValueHolder
构造方法接受个ValueHolder$DeferredInitializer
,该类重写了initialize
方法,可以看到,调用了type.getHashCode( value );
,且type和value作为成员变量都是可控的,这样就可以顺利的传递给ComponentType#getHashCode()
方法
哪里又调用DeferredInitializer#initialize
方法呢?注意到hashCode()
方法
看到这里基本上链子就成了,也就是hashCode -> ValueHolder#getValue -> DeferredInitializer#initialize
所以这是一条需要hashCode链来连接的后半部分链。hashCode可太多了,复用CC6的即可。HashMap#hashCode
版本差异
在hibernate不同的版本中,以4/5版本作为区分,出现了部分类的更替,影响了链子的部分地方,在ysoserial中也可以看到。具体如下:
- get链替换
- 版本4:BasicPropertyAccessor$BasicGetter#get()
- 版本5:GetterMethodImpl.get()
- getPropertyValue链替换
- 版本4/5:PojoComponentTuplizer#getPropertyValue()
- 版本3:用org.hibernate.tuple.entity.EntityEntityModeToTuplizerMapping对PojoComponentTuplizer进行封装。由于版本3过于久远,这里就不写在POC里了。
利用链
1 | HashMap.readObject() |
- Source:
HashMap#readObject() -> ValueHolder#hashCode()
- Chain:
ValueHolder#getValue() -> ValueHolder$DeferredInitializer.initialize()
ComponentTyp#getHashCode()
PojoComponentTuplizer#getPropertyValue()
BasicPropertyAccessor$BasicGetter#get() / GetterMethodImpl#get()
- Sink:任意有属性对应的getter
TemplatesImpl#getOutputProperties()
JdbcRowSetImpl#getDatabaseMetaData()
POC
依赖:
- org.hibernate:hibernate-core:4.3.11.Final
- org.jboss.logging:jboss-logging:3.3.0.Final
- org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec:1.0.0.Final
- dom4j:dom4j:1.6.1
Hibernate1
注意一下链子间参数的传递,保证最后的执行
1 | public class Hibernate1 { |
Hibernate2
JdbcRowSetImpl的触发方式
1 | public class Hibernate2 { |
后续
- 如何减少依赖?
可以发现hibernate这条链子的依赖十分复杂,不仅依赖原来hibernate的包,还依赖了jboss的相关包以及其他两个包,利用起来不是很优雅,所以还得看看有什么办法可以减少依赖,像无依赖的CB链一样,可能我们还需要替换掉多余依赖的部分链子来减少依赖。
Spring
前置
ReflectionUtils
这是Spring中内置的一个反射工具类,我们可以通过下面的方式来执行任意类方法
1 | Method method = ReflectionUtils.findMethod(Class objCls, String methodName); |
findMethod
:根据已知类的方法名寻找无参方法,特别注意只能是无参的。
invokeMethod
:调用任意类的一个无参public方法
MethodInvokeTypeProvider
这是一个内部类org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider
,实现了TypeProvider
,有readObject
方法,看一下
可以发现其中有我们上面说的findMethod
和invokeMethod
,所以这里我们只需要控制
this.provider.getType()
为目标类,比如TemplatesImpl
this.methodName
为目标方法,如newTransformer
再看动态代理
动态代理大家肯定都不陌生,从CC1链就开始用了,AnnotationInvocationHandler
是动态代理Gadgets中最常用的一个。动态代理最直观的概念就是相当于一个拦截器,代理对象调用了被代理接口的方法后,会委托给handler#invoke去执行。而在执行的过程中,可能触发一些其他类方法的调用,比如CC1的,就是从AnnotationInvocationHandler#readObject -> proxy(Map)#entrySet -> AnnotationInvocationHandler#invoke -> 任意Map类的get方法
,实现了一个从HashMap到任意Map类之间的跳转,这对构造Gadgets的灵活性来说是十分有用的。
那动态代理的作用仅仅于此了吗?继续看一下下面两个分析
Proxy的作用
直接上例子
1 | // Test接口,被代理 |
这里用TestInvocationHandler代理了Test接口(有一个getTest)方法,现在我们测试一下获取代理对象proxy的所有方法
1 | System.out.println(Arrays.toString(proxy.getClass().getDeclaredMethods())); |
可以看到除了继承Object的方法外,还有Test接口的方法getTest
。
也就是说代理对象是持有被代理接口的所有方法的,且能够cast成被代理接口的
AnnotationInvocationHandler控制返回任意对象(被代理方法的子类)
还是上面的示例代码,这里我们让代理对象调用getTest
方法
1 | System.out.println(proxy.getTest()); |
会出现如下报错
invoke方法返回的是个HashMap,而我们getTest方法返回的是Test接口类型,所以无法cast。很奇怪的一点是,代理类调用方法转到handler#invoke方法处理,这之间并不是完全失去联系的
也就是说,invoke方法的返回值并不能是任意的,最终是取决于被代理方法的返回值的。可以这么理解,代理类调用被代理方法,委托给hanlder#invoke执行,invoke执行完后返回,还是要经过被代理方法才能返回的,所以返回值是由被代理方法控制的
那说上面这些有啥意义呢?
我们再来看一下AnnotationInvocationHandler到底是干啥用的,前面说到AnnotationInvocationHandler#invoke可以连接任意Map类的get方法,但其实,本来的作用是返回成员变量memberValues
(Map)的对应键值的。
所以这里我们能够控制来返回回任意对象,结合前面说的,这里的”任意”还取决于被代理方法的返回值
所以再回到这条链子来,我们可以让前面说的this.provider.getType()
返回一个代理对象,其中代理了Templates接口和Type接口(为了能够cast成getType的返回值),而this.methodName
是newTransformer,但这样我们还是执行不了,因为在invokeMethod中是对代理对象调用的newTransformer方法。如果此时我们还能找到一个handler,这个handler中出现了method.invoke(obj,args)
的反射调用,而method
一般是被代理的方法,obj我们可控为TemplatesImpl
的话,那么我们就能够执行了(至于args,一般来说也是传入被代理方法的参数,但也不一定,对于这个链子来说不重要,提一下)
再提一下,如果已经理解前面的内容,就会理解这里为什么this.provider.getType()
不能直接返回TemplatesImpl了。
两个handler
ObjectFactoryDelegatingInvocationHandler
好巧不巧,在spring-beans
这个依赖包中,正好有一个handler类满足我们上面对handler的需求,这玩意是个内部类
org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler
this.objectFactory.getObject()
看一下ObjectFactory
接口的getObject
方法,返回的是一个泛型,那就没有前面说的返回值的限制了,可以返回任意对象,所以这里我们如法炮制this.objectFactory.getObject()
返回TemplatesImpl
就好了。
JdkDynamicAopProxy
这个又是另一个依赖包的东西了,是spring-aop
依赖包里的,因此构成了第二条链子Spring2。和前面的handler作用一样,但有一些细节不一样:
- 获取TemplatesImpl不需要AnnotationInvocationHandler了,只需设置
this.advised
即可 - 执行的方法可以是非public的,因为调用了
ReflectionUtils.makeAccessible(method);
这里我们设置this.advised
为AdvisedSupport
,调用其setTarget
方法来设置target和targetSource。
![image-20210916143904773](/Users/a861881/Library/Application Support/typora-user-images/image-20210916143904773.png)
利用链
1 | SerializableTypeWrapper$MethodInvokeTypeProvider.readObject() |
|
表示在这个方法中执行的语句,而不是继续深入,+
表示从这一层开始的另一条链子
这个链子不长,但是有3处用到动态代理的地方,比较有意思
- Source:
SerializableTypeWrapper$MethodInvokeTypeProvider.readObject()
- Chain:
ReflectionUtils#invokeMethod() -> Templates(Proxy)#newTransformer()
AutowireUtils$ObjectFactoryDelegatingInvocationHandler#invoke()
- Sink:任意无参public方法
TemplatesImpl#newTransformer()
JdbcRowSetImpl#getDatabaseMetaData()
POC
Spring1
依赖:
- org.springframework:spring-beans:4.1.4.RELEASE
- org.springframework:spring-core:4.1.4.RELEASE
- Jdk7 某个版本AnnotationInvocationHandler被修了
1 | public class Spring1 { |
Spring2
依赖:
- org.springframework:spring-beans:4.1.4.RELEASE
- org.springframework:spring-core:4.1.4.RELEASE
- Jdk7 某个版本AnnotationInvocationHandler被修了
1 | public class Spring2 { |