Bueno, primero vamos a ver qué estamos protegiendo, o mejor dicho, de quién. Al principio de este nivel ya mencionamos las inyecciones SQL, uno de los ataques más populares y destructivos contra bases de datos. El ataque funciona así: el atacante manda código SQL malicioso en tu consulta para "engañarla" y obtener acceso a datos a los que no debería acceder. Ahora vamos a ver esto con más detalle.
Ejemplo de inyección SQL
Supón que tienes una aplicación web con un simple input de nombre de usuario y contraseña. El backend ejecuta una consulta SQL para comprobar si el usuario existe en la base de datos:
SELECT * FROM users WHERE username = 'admin' AND password = 'password123';
Esta consulta se ve perfecta... mientras el usuario mete datos correctos. Pero ¿qué pasa si alguien mete esto?
- Nombre de usuario:
admin' -- - Contraseña: (dejamos este campo vacío)
Al final la consulta se convierte en algo así:
SELECT * FROM users WHERE username = 'admin' -- AND password = 'password123';
Fíjate en los caracteres --, que en SQL marcan el inicio de un comentario. Todo lo que va después se ignora. Como resultado, la comprobación de la contraseña se salta y el atacante entra como administrador.
Consecuencias potenciales de las inyecciones SQL
Una inyección SQL puede tener consecuencias horribles:
- Obtener acceso no autorizado a los datos. Por ejemplo, el atacante puede ver datos confidenciales como contraseñas de usuarios.
- Eliminar o modificar datos. Alguien puede borrar toda una tabla o liarla parda.
- Ejecutar código SQL arbitrario. Imagina que el atacante lanza el comando
DROP DATABASE... Sí, suena a pesadilla.
¡Pero no somos de los que se asustan fácil! PostgreSQL nos da un montón de herramientas para protegernos de estos ataques.
¿Cómo protegerse? Métodos para prevenir inyecciones SQL
- Uso de consultas preparadas (
PREPAREyEXECUTE)
Las consultas preparadas (prepared statements) son como una receta probada para tu código SQL. Funcionan así: "preparas" la consulta SQL una vez y luego le pasas los datos aparte. Así es imposible meter código malicioso.
Aquí tienes un ejemplo de cómo hacerlo bien:
Prepara la consulta usando PREPARE.
PREPARE user_login (text, text) AS
SELECT *
FROM users
WHERE username = $1 AND password = $2;
La consulta con dos parámetros $1 y $2 espera que los valores reales se pasen después.
Usa EXECUTE para ejecutar la consulta.
EXECUTE user_login('admin', 'password123');
En este caso, PostgreSQL escapa automáticamente todos los datos del usuario, evitando la inyección de código SQL malicioso.
Ventajas de este enfoque:
- No se puede hacer una inyección SQL, porque los parámetros se tratan como datos normales, no como parte del código SQL.
- Las consultas son más seguras y se ejecutan más rápido gracias al caché del plan de ejecución.
- Consultas parametrizadas (Parameterized Queries)
Este método es especialmente popular en apps hechas en lenguajes como Python o Java. En vez de trabajar a mano con PREPARE y EXECUTE, puedes usar librerías o un ORM que gestionan los parámetros automáticamente.
Ejemplo con Python y la librería psycopg2:
import psycopg2
connection = psycopg2.connect(
dbname="tu_db",
user="tu_usuario",
password="tu_contraseña",
host="localhost",
port="5432"
)
cursor = connection.cursor()
# Uso de una consulta parametrizada
username = "admin"
password = "password123"
query = "SELECT * FROM users WHERE username = %s AND password = %s"
cursor.execute(query, (username, password))
# ¡Tus datos están totalmente seguros!
result = cursor.fetchall()
print(result)
Fíjate en el %s en la consulta SQL — ahí es donde se mete el parámetro. La librería psycopg2 se encarga de pasar los datos de forma segura.
- Validación de los datos de entrada
Si vas a pasar datos que mete el usuario, asegúrate de que cumplen lo que esperas. Por ejemplo:
- Para datos de texto, usa expresiones regulares para comprobar que no tienen caracteres prohibidos.
- Para datos numéricos, asegúrate de que realmente son números.
Ejemplo en Python:
import re
username = input("Introduce el nombre de usuario: ")
# Permitimos solo letras, números y guiones bajos
if re.match(r"^\w+$", username):
print("Nombre de usuario correcto")
else:
print("¡Nombre de usuario peligroso!")
- Uso de privilegios mínimos
Asegúrate de que los roles que usas para ejecutar consultas tengan solo los privilegios mínimos necesarios. Por ejemplo, no des acceso a DROP TABLE o ALTER TABLE si no hace falta.
- Registro de acciones sospechosas
Puedes usar parámetros de PostgreSQL para monitorizar la actividad de los usuarios:
log_statement = 'all'— registra todas las consultas.log_connections = on— registra todas las conexiones a la base de datos.
Estos parámetros ayudan a detectar acciones potencialmente peligrosas.
Ejemplos de implementación
Ejemplo 1: Uso de una consulta preparada en SQL
-- Creamos una consulta preparada
PREPARE check_credentials (text, text) AS
SELECT * FROM users WHERE username = $1 AND password = $2;
-- Ejecutamos la consulta con parámetros seguros
EXECUTE check_credentials('admin', 'password123');
Ejemplo 2: Consulta parametrizada en Python
query = "UPDATE users SET last_login = NOW() WHERE username = %s"
username = "admin"
cursor.execute(query, (username,))
Recomendaciones de seguridad
Valida siempre los datos de entrada. Nunca confíes en los datos que vienen del usuario.
Usa solo consultas preparadas o parametrizadas. Estos métodos son tu principal barrera contra las inyecciones SQL.
Asigna roles con los privilegios mínimos necesarios. Así minimizas el daño si un atacante consigue acceso.
Configura el registro y revisa los logs regularmente. Mantente al tanto de la actividad en tu base de datos.
Las inyecciones SQL son, claro, un bicho feo, pero usando consultas preparadas, parametrizadas y siguiendo buenas prácticas, vas a proteger tu base de datos y podrás dormir tranquilo por las noches, sin miedo a que alguien "accidentalmente" borre todas tus tablas. PostgreSQL te da todas las herramientas para esto, ¡así que dale caña!
GO TO FULL VERSION