¿Explotando la vulnerabilidad de MD5 en este formulario de PHP?

43

He estado practicando en temas relacionados con la seguridad y encontré este problema que no entiendo en absoluto. Recibe un formulario con una entrada llamada pass , y este es el código que debe omitir:

<?php
error_reporting(0);
session_save_path('/home/mawekl/sessions/');
session_start();
echo '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>';
echo 'This task is as hard to beat as the castle which you can see on the bottom.<br>';
echo '<pre>';
include('./ascii.txt');
echo '</pre><br>';

$_SESSION['admin_level']=1;

if (!isset($_POST['pass']) || md5($_POST['pass'])!='castle')
{
    echo '<b>Wrong or empty password.</b><br>';
    $_SESSION['admin_level']=0;
}

Si ingresa a la sentencia if final, pierde (debe hacerlo para que $_SESSION['admin_level'] permanezca en 1 ).

Cualquier ayuda es apreciada, gracias!

Clarificación:

No puedo editar el código que publiqué. Es un desafío. Todo lo que puedo hacer es enviar una contraseña a través de una entrada cuyo nombre es "pasar". Sí, sé que se supone que md5 devuelve una cadena larga de 32 caracteres. Ese es el reto.

    
pregunta Tom 11.06.2016 - 22:32
fuente

7 respuestas

59

Intenta enviar una solicitud HEAD.

Supongo que con ascii.txt incluido, la salida de la secuencia de comandos es un poco más de 4096 bytes, un valor común de output_buffering . Una vez que el script haya escrito output_buffering bytes, debe vaciar el búfer de salida antes de continuar.

Normalmente, esto funciona bien y la secuencia de comandos continúa, pero si el tipo de solicitud es HEAD, no hay ningún lugar para la salida y la ejecución se detiene, es de esperar que en medio del mensaje "contraseña incorrecta", lo que significa que admin_level nunca se establezca de nuevo a 0.

Si ascii.txt es un archivo que puedes controlar, tendrás que modificar el tamaño para que los números se excedan en output_buffering al escribir el mensaje de "contraseña incorrecta".

Aquí hay un artículo sobre esta técnica . Es posible que no siempre sea aplicable según la versión / configuración de PHP, pero el último ejemplo en el documento es tan similar a este problema que esperaría que fuera la solución prevista.

    
respondido por el gabedwrds 12.06.2016 - 07:02
fuente
27

La falla de seguridad no está en MD5 en este caso. Y la idea no es obtener if (!isset($_POST['pass']) || md5($_POST['pass'])!='castle') para evaluar a favor del hacker. La vulnerabilidad aparece cuando se bloquea el script. Los privilegios de administrador no son condicionales. No importa lo que el usuario obtenga su permiso elevado al establecer 1 en $_SESSION['admin_level'] . PHP es de procedimiento. Por lo tanto, si puede realizar un error en la siguiente declaración IF, el permiso no solo está establecido sino que permanece persistente. Ahora está disponible para el resto del programa tal como está almacenado en la sesión. Incluso si la secuencia de comandos se bloquea y errores. Por lo tanto, si la instrucción IF solo existe durante un inicio de sesión, el atacante puede visitar todos los demás scripts restringidos como un administrador de nivel establecido en 1.

Así que la línea de tiempo de los eventos:

  • El atacante coloca datos incorrectos o un desbordamiento de datos en la solicitud POST
  • Secuencias de comandos
  • El script le da a admin_level 1 que se almacena en la sesión
  • Bombas de script en evaluación
  • El atacante va a otra página / script que busca para ver si $_SESSION['admin_level'] es 1

Esto podría haberse evitado si el script incluyera el valor de la sesión en la declaración IF:

if (!isset($_POST['pass']) || md5($_POST['pass'])!='castle')
{
    echo '<b>Wrong or empty password.</b><br>';
    $_SESSION['admin_level']=0;
} else {
    $_SESSION['admin_level']=1;
}

Sin embargo, aún queda mucho más por hacer para proteger este código.

    
respondido por el Bacon Brad 12.06.2016 - 03:13
fuente
17

He visto un desafío similar en alguna parte. Intenta enviar algo como 'QNKCDZO' como entrada. md5('QNKCDZO') es '0e830400451993494058024219903391' (note el prefijo 0e en el hash que indica la notación científica de un número) y dado que el código usa el operador != (en lugar del tipo sensible !== ) PHP (¿versiones anteriores solamente? ) en realidad lo lanzará a un número antes de comparar. Obviamente, ambos evaluarán a 0 y usted ganará.

Edit: ruakh tiene razón. Esto no funciona del todo, porque tanto el hash como la cadena 'castle' tendrían que parecerse a un número en PHP para que se produzca la conversión.

    
respondido por el YouniS Bensalah 12.06.2016 - 02:19
fuente
9

No he podido probar en PHP 5.5.9, y lo que voy a proponer no funciona en mi PHP 5.6.22 (pero podría haber cometido un error tonto).

La única solución que puedo comprender es si MD5 devuelve algo que se trata como un número . He buscado en Google y he encontrado algo que parece pertinente .

    
respondido por el LSerni 12.06.2016 - 01:08
fuente
4

Veo que aceptaste la solución de Gabedwrds (que es excelente, por cierto), pero quería publicar una solución potencial que haya usado en el pasado al hacer algunas prácticas interesantes de análisis. ( de ninguna manera soy un experto en ese campo, pero a veces soy el más hobbiest. )

Si se da cuenta, el script nunca establece ninguna política para manejar un aborto de usuario. De forma predeterminada (a menos que esté configurado de otra manera en el PHP ini), si un usuario interrumpe su conexión mientras se carga una página, la secuencia de comandos de PHP se cierra de inmediato y detiene todo el procesamiento. Debido a esto, si sincroniza la conexión correctamente, podría abortar la conexión entre $_SESSION['admin_level']=1; y la evaluación del condicional, lo que obligaría al servidor a salir del script inmediatamente, sin finalizar la evaluación condicional que causaría admin_level para restablecerse a cero.

Por supuesto, esto depende completamente de que la configuración de PHP no tenga ignore_user_abort=1 . Por lo tanto, si está configurado para continuar incluso si un usuario cancela, los privilegios aún se restablecerán.

Para aumentar su ventana de oportunidad para usar este exploit, podría aumentar el tamaño de su carga coercitiva / datos de pass . md5 tiene que hacer hash en trozos, por lo que al aumentar el tamaño de los datos de pass , hará que la función md5 dedique más tiempo a calcular el hash, lo que le dará una gran brecha de tiempo que tendrá que abortar. conexión.

    
respondido por el Spencer Doak 13.06.2016 - 23:11
fuente
2

Similar a lo que otros han dicho, PHP tiene un tamaño máximo de memoria. Si coloca datos POST que son JUSTOS lo suficientemente grandes para usar CASI toda la memoria de PHP, entonces podría fallar cuando llegue exactamente a esta línea si se queda sin memoria:

echo 'Contraseña incorrecta o vacía'

Esto se basa en tres principios:

1) Cuando las sesiones están habilitadas, PHP no vacía la salida, sino que lo almacena todo en la RAM hasta que la página termina de procesarse.

2) PHP tiene un tamaño máximo de RAM asignado para cada solicitud y, cuando se alcanza, bloquea su script.

3) Cuando se escriben nuevas líneas, usa más RAM, agotando la RAM restante.

Teniendo en cuenta los hechos anteriores, si coloca más datos POST de HTTP en su solicitud HTTP (por ejemplo, $ _POST ["data"]="maximumring"), es probable que pueda activar un error de memoria antes de $ _SESSION ["admin_level "] = 0 se ejecuta.

En un escenario de la vida real, esto es muy poco probable porque PHP también tiene un tamaño máximo de cuerpo POST, tamaño máximo de encabezado, y cualquier contenedor de servidor que lo aloje (nginx, apache, etc.) generalmente también tiene límites máximos. Probablemente alcanzaría todos estos límites máximos, pero si está mal configurado, es posible que obtenga $ _SESSION ["admin_level"] = 1 convirtiéndose efectivamente en administrador cuando vaya a cualquier otra página del sitio web usando este método.

Actualización: Otro método similar sería poner una contraseña grande para que la función md5 () falle mientras se procesa o algo, lo que nuevamente se bloquea porque se queda sin memoria. Depende de cómo md5 () funcione internamente. Es probable que se haga una COPIA del campo $ _POST ["pass"], por lo que si $ _POST ["pass"] fuera gigantesco, podría ser muy fácil hacer que esta copia sea la que utilice toda la RAM y la bloquee. .

    
respondido por el Jacob Beasley 13.06.2016 - 20:10
fuente
2

Suponiendo que pueda modificar el ./ascii.txt, pondría este texto dentro:

<?php

override_function('md5', '$a', 'return "castle";');

Y eso debería funcionar.

    
respondido por el peterpeterson 14.06.2016 - 13:49
fuente

Lea otras preguntas en las etiquetas