SQL Injection con #PHP, explicación y contramedidas

David Galisteo Cantero

¡Hola! Hoy vamos a ver cómo protegernos en parte de las inyecciones SQL cuando estamos desarrollando aplicaciones en #PHP.

Las inyecciones SQL, aunque no son recientes, siguen estando en la primera posición del TOP 10 OWASP, por lo que debemos estar debidamente concienciados sobre sus peligros.

En esta entrada vamos a “simular” un login vulnerable con PHP, y veremos qué medidas podríamos tomar para eliminar la vulnerabilidad.

Supongamos un típico formulario de acceso, con un campo ‘user’ y otro ‘password’.

<form action="/login.php" method="post">	
	<input type="text" name="user" placeholder="Nombre de Usuario">
	<input type="password" name="password" placeholder="Contraseña">
	<input type="submit" value="Entrar">
</form>

Como vemos, es lo más sencillo posible.

Si nos fijamos, el ‘action’ del formulario apunta al fichero ‘login.php’, que vemos a continuación:

$usuario = $_POST["user"]
$password = $_POST["password"]
 
$consulta = "SELECT user, password FROM usuarios WHERE user = '".$usuario."' AND password = '".$password."'";
$resultadoConsulta = db_query($consulta);
 
if(numeroResultados($resultadoConsulta) > 0){
   //login ok
}else{
   //login fail
}

Lo primero que hacemos es obtener el usuario y la contraseña que nos llega por POST, es recomendable usar este método y no ‘GET’ para evitar que los parámetros enviados desde el formulario aparezcan en la url.

A continuación construimos la consulta con los parámetros obtenidos y la ejecutamos, como apunte, la función ‘db_query’ es la encargada de ejecutar la consulta a la base de datos, la implementación de la misma, así como la conexión a la base de datos, no ha sido llevada a cabo en esta entrada.

La función ‘numeroResultados’ devuelve el número de tuplas que retorna la consulta SQL, tampoco se ha implementado.

LLegamos al punto importante, como podéis comprobar, si la consulta devuelve más de 0 tuplas, quiere decir que hay un usuario y contraseña con esa clave, por lo que el login se da como válido, y es aquí donde viene el problema.

Para conseguir que el flujo de ejecución entre en el condicional, solo nos basta con hacer que la consulta devuelva más de un resultado, por ejemplo con los campos del formulario siguientes:

user: admin’ or ‘1’=’1′ —
password: (lo que queramos)

La comilla después de admin, cierra la cadena de texto, si no conocemos el nombre de usuario, en este caso admin, también nos valdría poner solo la ‘, ya que la segunda condición se va a cumplir siempre, por último, los caracteres ‘–‘ indican que lo que viene a continuación de los mismos es un comentario, por lo que no será tenido en cuenta.

Así esta consulta nos va a devolver TODOS los usuarios, por lo que el número de tuplas será, claramente, mayor que 0. Ya hemos conseguido saltarnos el proceso de login.

Como contramedidas, lo primero que se nos ocurre es escapar la cadena, la cual veremos más adelante, sin embargo hay otra, una vez obtengamos el password de la base de datos, comprobemos que se corresponde con el que nos viene como parámetro:

$usuario = $_POST["user"]
$password = $_POST["password"]
 
$consulta = "SELECT user, password FROM usuarios WHERE user = '".$usuario."' AND password = '".$password."'";
$resultadoConsulta = db_query($consulta);
 
if(numeroResultados($resultadoConsulta) > 0){
   $tupla = tupla($resultadoConsulta);
   if($tupla["password"] == $password){
      //login ok
   }else{
      //login fail
   }
}else{
   //login fail
}

Hay que hacer mención a la función ‘tupla’, que nos devolverá la tupla devuelta por la consulta SQL, si hay más de una, nos devuelve la primera (en un login es imposible que haya más de un usuario con el mismo campo ‘usuario’ y ‘password’).

Comprobamos el password que nos llega de la base de datos con el que nos llega del formulario.

Pero NO acaba aquí el peligro, ¿qué tal si por alguna razón, el atacante consigue tener acceso a nuestro código fuente? Ya sea por alguna vulnerabilidad en el servidor, o por que tenemos un fichero de backup indexable por google.

Si se da esta circunstancia, no es necesario mucho esfuerzo para que el atacante consiga pasar el login, si nos fijamos, la consulta SQL retorna dos valores, ‘user’ y ‘password’, ahora, echad un ojo al valor de los siguientes campos del formulario:

user: nombreInventado’ UNION SELECT ‘admin’ as user, ‘123’ as password —
password: 123

¿Ya veis por donde voy? La consulta SQL quedaría tal que así:

$query = "SELECT user, password FROM usuarios WHERE user = 'nombreInventado' UNION SELECT 'admin' as user, '123' as password --";  

Y con ‘–‘ volvemos a obviar todo lo que queda a continuación, por lo que no se tiene en cuenta para la consulta.

Esta última, antes del UNION, NO devolverá nada, ya que probablemente no haya un usuario ‘nombreInventado’, pero la parte que viene detrás del UNION SI que arrojará resultados, una tupla con el campo ‘user’ con admin, y el campo ‘password’ con 123.

Ahora, ¿cual era la condición para entrar a la sección de código del login correcto? Que el password devuelto por la consulta, y el que se pasa por el formulario sean iguales, ¿Se cumple?, tú mismo verás que sí, el atacante llega al extremo de “inventar” la contraseña.

Escapando cadenas en PHP

Como bien se apuntó anteriormente, como contramedida, podemos intentar escapar las cadenas que nos llegan desde el formulario, para intentar eliminar caracteres como comillas, guiones, barras… etc, todo lo que pueda dar pie a una inyección.

Podemos usar la función de PHP mysqli_real_scape_string, con esta función, nos ahorraremos algún que otro disgusto.

Sin embargo, NADA es totalmente eficaz, hay que tenerlo en cuenta, siempre debemos estar adoptando medidas de seguridad para nuestra información.

Espero que os haya sido de ayuda.

HackSaludos!

Publicado el 30-09-2014

Compartelo!

Deja un comentario

Comentanos

*
Ir arriba de la pagina