|
原文链接:fastjson1224的TemplatesImpl链反序列化分析
fastjson的序列化与反序列化
首先,分析fastjson的反序列化前我们先来了解一下fastjson,我们新建一个User类:
- public class User {
- public Long id;
- public String name;
- public Long getId() {
- System.out.println("getid="+this.id);
- return id;
- }
- public void setId(Long id) {
- this.id = id;
- System.out.println("setid="+this.id);
- }
- public String getName() {
- System.out.println("getname="+this.name);
- return name;
- }
- public void setName(String name) {
- System.out.println("setname="+this.name);
- this.name = name;
- }
- }
复制代码
fastjson提供了三种反序列化的方法,分别是:
- String jsonString = "{"id":2,"name":"guest"}\n";
- // 反序列化代码示例1
- Object ob1 = JSON.parse(jsonString);
- System.out.println("ob1="+ob1);
- // 反序列化代码示例2
- Object ob2 = JSON.parseObject(jsonString);
- System.out.println("ob2="+ob2);
- // 反序列化代码示例3
- Object ob3 = JSON.parseObject(jsonString,User.class);
- System.out.println("ob3="+ob3);
复制代码
那么这三个方法有什么区别呢,接下来,我们在代码中set一下参数的值,运行一遍看看:
- public class main {
- public static void main(String[] args) {
- User guestUser = new User();
- guestUser.setId(2L);
- guestUser.setName("guest");
- // fastjson提供toJsonString接口实现序列化
- // fastjson提供parseObject来分别实现反序列化
- // 序列化示例
- String jsonString = JSON.toJSONString(guestUser);
- System.out.println("序列化数据="+jsonString);
- ...
- }
- }
复制代码
运行后发现ob1和ob2返回的是JSONObject,而ob3则是实际的类对象,并且ob3调用了User类中全部set方法,ob1与ob2则是set、get方法均未调用,那么接下来我们就来看看他们在调用set与get方法的区别
我们新写一段代码,首先传入User类的一句序列化后的字符串,而后使用三种方法来反序列化它,看看分别调用了什么方法
- String parseString = "{"@type":"User","id":2,"name":"guest"}";
- System.out.println("parse方法的调用结果");
- Object ob11 = JSON.parse(parseString);
- System.out.println("ob11="+ob11);
- System.out.println("----------------------");
- System.out.println("parseObject方法的调用结果");
- Object ob12 = JSON.parseObject(parseString);
- System.out.println("ob12="+ob12);
- System.out.println("----------------------");
- System.out.println("parseObject(xx,class)方法的调用结果");
- Object ob13 = JSON.parseObject(parseString,User.class);
- System.out.println("ob13="+ob13);
复制代码
结果: 
我们可以发现,parse和parseObject都返回了全部的set方法,并且parseObject返回了全部的get方法,parseObject(xx,class)返回了全部的set方法,那么我们是否可以得出结论,全部的方法都可以调用set方法呢?那么接下来我们就来看看调用到的set、get方法分别存在什么限制,这其实就是fastjson的特性
fastjson特性
首先,我们来看set与get方法的限制:
- // com/alibaba/fastjson/util/JavaBeanInfo.java
- public class JavaBeanInfo{
- public static JavaBeanInfo build(Class clazz, Type type, PropertyNamingStrategy propertyNamingStrategy) {
- /**
- * 判断set方法函数
- */
- for (Method method : methods) { //
- int ordinal = 0, serialzeFeatures = 0, parserFeatures = 0;
- String methodName = method.getName();
- // 长度比4大
- if (methodName.length() < 4) {
- continue;
- }
- // 非静态方法
- if (Modifier.isStatic(method.getModifiers())) {
- continue;
- }
- // 返回类型不能是void或者当前类
- // support builder set
- if (!(method.getReturnType().equals(Void.TYPE) || method.getReturnType().equals(method.getDeclaringClass()))) {
- continue;
- }
- Class[] types = method.getParameterTypes();
- // 不能有传入的参数
- if (types.length != 1) {
- continue;
- }
- // annotation = null,跳过
- JSONField annotation = method.getAnnotation(JSONField.class);
- if (annotation == null) {
- annotation = TypeUtils.getSuperMethodAnnotation(clazz, method);
- }
- if (annotation != null) {
- // ......略过
- }
- // 以set为开头
- if (!methodName.startsWith("set")) {
- continue;
- }
- char c3 = methodName.charAt(3);
- String propertyName;
- // 第4个字母大写
- if (Character.isUpperCase(c3) //
- || c3 > 512 // for unicode method name
- ) {
- if (TypeUtils.compatibleWithJavaBean) {
- propertyName = TypeUtils.decapitalize(methodName.substring(3));
- } else {
- propertyName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
- }
- } else if (c3 == '_') {
- propertyName = methodName.substring(4);
- } else if (c3 == 'f') {
- propertyName = methodName.substring(3);
- } else if (methodName.length() >= 5 && Character.isUpperCase(methodName.charAt(4))) {
- propertyName = TypeUtils.decapitalize(methodName.substring(3));
- } else {
- continue;
- }
- Field field = TypeUtils.getField(clazz, propertyName, declaredFields);
- if (field == null && types[0] == boolean.class) {
- String isFieldName = "is" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
- field = TypeUtils.getField(clazz, isFieldName, declaredFields);
- }
- // filed = null,跳过
- JSONField fieldAnnotation = null;
- if (field != null) {
- // ......
- }
- // propertyNamingStrategy = null,跳过
- if (propertyNamingStrategy != null) {
- // ......
- }
- add(fieldList, new FieldInfo(propertyName, method, field, clazz, type, ordinal, serialzeFeatures, parserFeatures,
- annotation, fieldAnnotation, null));
- }
- /**
- * 判断get方法函数体:
- */
- for (Method method : clazz.getMethods()) { // getter methods
- String methodName = method.getName();
- // 长度不能小于4
- if (methodName.length() < 4) {
- continue;
- }
- // 不能是静态方法
- if (Modifier.isStatic(method.getModifiers())) {
- continue;
- }
- // 以get开头并且第4位是个大写字母
- if (methodName.startsWith("get") && Character.isUpperCase(methodName.charAt(3))) {
- // 不能有传入的参数
- if (method.getParameterTypes().length != 0) {
- continue;
- }
- // 返回值继承于Collection Map AtomicBoolean AtomicInteger AtomicLong之一
- if (Collection.class.isAssignableFrom(method.getReturnType()) //
- || Map.class.isAssignableFrom(method.getReturnType()) //
- || AtomicBoolean.class == method.getReturnType() //
- || AtomicInteger.class == method.getReturnType() //
- || AtomicLong.class == method.getReturnType() //
- ) {
- // .........
- // 对get方法进行处理的函数,略过
- }
复制代码
所以,根据以上代码,我们可以总结出调用到set、get方法的限制:- 方法名长度大于4且以set开头,且第四个字母要是大写
- 非静态方法
- 返回类型为void或当前类
- 参数个数为1个
- 方法名长度大于等于4,且以get开头且第4个字母为大写
- 非静态方法
- 无传入参数
- 返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong
并且,parseobject()这个方法中多了一步toJSON操作,会调用全部的get方法:
 
这样,上面调用到的set、get方法就全部说通了。
下面我们看看fastjson的另一种特性,就是@type这个参数,开发者本意是序列化时使用SerializerFeature.WriteClassName会写入类型信息,从而使反序列化时不会丢失类型信息,并且反序列化时自动进行类型识别,但是这就造成了一个问题,type参数可以指定反序列化任意的类,然后去调用set、get方法,我们来看看实际的代码是如何运作的
在序列化是写入类型信息,此时的序列化后的字符串就有@type参数:

我们在看看输入@type参数,他的反序列化结果:
 
看似这种情况很正常,反序列化使用到set、get方法恢复数据,但是在可以调用任意类的情况下,这就很恐怖了,如果set或者get方法中有可利用的点的情况下,就会造成RCE。
jdk7u21TemplatesImpl链
说到fastjson的TemplatesImpl链,就不得不提jdk7u21的TemplatesImpl链,fastjson的这种利用方法其实和jdk7u21的TemplatesImpl链很像,jdk7u21由以下代码出发RCE
- import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
- public class jdk7u21_nekoc {
- public static void main(String[] args) throws Exception {
- TemplatesImpl calc = (TemplatesImpl) Gadgets.createTemplatesImpl("/System/Applications/Calculator.app/Contents/MacOS/Calculator");
- calc.getOutputProperties();
- }
- }
复制代码
gadegets类是在ysoserial里拔出来的,跟进触发漏洞的calc.getOutputProperties();方法,发现最终由obj.newinstance触发恶意代码,由于newinstance示例化会默认触发static及构造方法,所以payload可以写在这两个中的其一,并且,这里注意,getOutputProperties方法正好符合fastjson中对get方法名字的限制。
我们这里不再具体分析jdk7u21,我们来跟进看一下该链子的一些限制条件,跟进代码:
return newTransformer().getOutputProperties();
首先,我们发现:
com/sun/org/apache/xalan/internal/xsltc/trax/TemplatesImpl.java中,存在两个限制条件:
- public class TemplatesImpl {
- private Translet getTransletInstance()
- throws TransformerConfigurationException {
- try {
- // 第一个条件 _name不为null
- if (_name == null) return null;
- // 第二个条件 _class不为null,进到defineTransletClasses里
- if (_class == null) defineTransletClasses();
- // ......
- }
- }
复制代码
第一个条件就是_name不为null,第二个条件是_class不为null,这样才能进到defineTransletClasses方法,我们继续来看看defineTransletClasses方法里边的判断:
- private void defineTransletClasses()
- throws TransformerConfigurationException {
- // 第三个条件:_bytecodes不为null
- if (_bytecodes == null) {
- ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
- throw new TransformerConfigurationException(err.toString());
- }
- // 7u21
- TransletClassLoader loader = (TransletClassLoader)
- AccessController.doPrivileged(new PrivilegedAction() {
- public Object run() {
- // 这里应该是第四个条件,这里7u21和高版本的7u80有区别
- return new TransletClassLoader(ObjectFactory.findClassLoader());
- }
- });
- // 7u80
- TransletClassLoader loader = (TransletClassLoader)
- AccessController.doPrivileged(new PrivilegedAction() {
- public Object run() {
- // 这里就是第四个限制条件,_tfactory参数存在一个getExternalExtensionsMap方法
- return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
- }
- });
- try {
- final int classCount = _bytecodes.length;
- _class = new Class[classCount];
- if (classCount > 1) {
- _auxClasses = new Hashtable();
- }
- for (int i = 0; i < classCount; i++) {
- _class[i] = loader.defineClass(_bytecodes[i]);
- final Class superClass = _class[i].getSuperclass();
- // Check if this is the main class
- // 第五个限制条件:_bytecodes必须是ABSTRACT_TRANSLET子类
- if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
- _transletIndex = i;
- }
- else {
- _auxClasses.put(_class[i].getName(), _class[i]);
- }
- }
- if (_transletIndex < 0) {
- ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
- throw new TransformerConfigurationException(err.toString());
- }
- }
- catch(Exception e){
- //...
- }
- }
- }
复制代码
我们跟进代码首先会发现第三个限制条件,即_bytecodes不为null,继续向下看,这里的代码版本不同,内容就不一样,在7u21中,不存在有关_tfactory的限制,在7u80中,就存在该限制,这里要求_tfactory参数存在一个getExternalExtensionsMap方法,为了版本通用,所以在poc里加上对tfactory的限制条件,继续向下看,我们可以看到第五个条件,即_bytecodes必须是ABSTRACT_TRANSLET子类,并且,payload要写在_bytecode对应的类的静态方法或者构造方法里,这样,我们就集齐了全部的限制条件,小结一下就是:- name != null
- _class == null
- _bytecodes != null
- _tfactory需要有一个getExternalExtensionsMap方法
- _bytecodes的类必须是ABSTRACT_TRANSLET的子类
- payload要在_bytecode对应的类的静态方法或者构造方法里
1224 TemplatesImpl链
jdk7u21在fastjson1224的应用还需要一点点条件,用parseObject()时,必须用JSON.parseObject(jsonString, Feature.SupportNonPublicField);这样的格式,必须要有Feature.SupportNonPublicField参数才可以,payload中有部分参数是private属性,需要在这里设置一下才能被接受;使用parse()时,需要JSON.parse(jsonString,Feature.SupportNonPublicField); 这样的格式。
在编写payload时,需要用到javasist类,这里不再描述该类。
最终的部分payload:
 
看到最后的代码可能会有些疑问,这里的_tfactory为什么是空而不是一个有getExternalExtensionsMap方法的类对象?_OutputProperties是怎么调用到getOutputProperties方法的?
我们来看看这两个问题的解答。
首先是第一个,_tfactory为什么是空而不是一个有getExternalExtensionsMap方法的类对象呢,我们跟一下代码,不难发现,解析这几个参数的时候,如果发现参数值为空对象,就会新建一个该参数应有的格式的对象实例,将其赋值给该参数
 


那么,_tfactory应有的格式为啥是它呢?咱们看一下定义就知道了
 
所以,_tfactory其实本身就是TransformerFactoryImpl类的对象,赋值为空就系那个回家了一样,完全是可以的。
接下来我们看看第二个问题,_OutputProperties是怎么调用到getOutputProperties方法的呢,我们跟进代码就可以看到,在处理参数的时候,代码会将_OutputProperties前面的下划线替换为空,此时反序列化时就回去调用他的get方法,即getOutputProperties,所以其实poc生成中得_OutputProperties有没有下划线都是可以的,这样我们就在fastjson1224版本中完成了TemplatesImpl链的分析。
|
|