Inicio > Cursos > SQL y Bases de Datos > Seguridad y SQL Injection
SQL y Bases de DatosSQL en ProducciónLección 17 de 20

Seguridad y SQL Injection

La inyección SQL es una de las vulnerabilidades más peligrosas y aún muy común. Ocurre cuando datos del usuario se concatenan directamente en una query: SELECT * FROM usuarios WHERE nombre = " + input. Si el usuario escribe " OR 1=1 --, la query devuelve TODOS los usuarios. Con input malicioso se puede robar toda la base de datos o borrarla.

Los prepared statements (consultas parametrizadas) son la solución definitiva. En lugar de concatenar, usas placeholders ($1, ?, :nombre) y el valor se pasa separado. La base de datos sabe que ese valor es dato, nunca código SQL. Nunca concatenes input del usuario en queries SQL, sin excepción.

-- VULNERABILIDAD: concatenar input del usuario directamente
-- Usuario malicioso escribe: ' OR '1'='1
-- La query resultante sería:
-- SELECT * FROM users WHERE email = '' OR '1'='1'
-- Devuelve TODOS los usuarios aunque no existan

-- Peor aún: '; DROP TABLE users; --
-- SELECT * FROM users WHERE email = ''; DROP TABLE users; --'
-- ¡¡DESTRUYE la tabla!!

-- SOLUCIÓN: Prepared Statements (consultas parametrizadas)
-- El parámetro NUNCA se interpreta como SQL, siempre como dato

-- Node.js con librería pg (PostgreSQL)
-- const { rows } = await pool.query(
--   'SELECT * FROM users WHERE email = $1 AND password_hash = $2',
--   [email, passwordHash]  -- $1 y $2 son parámetros seguros
-- );

-- Python con psycopg2
-- cursor.execute(
--   "SELECT * FROM users WHERE email = %s",
--   (email,)  -- La coma hace que sea una tupla
-- );

-- GRANT y REVOKE: control de permisos
-- Crear usuario de solo lectura para reportes
CREATE USER usuario_reportes WITH PASSWORD 'contraseña_segura';

-- Solo permisos de SELECT en tablas específicas
GRANT CONNECT ON DATABASE miapp TO usuario_reportes;
GRANT USAGE ON SCHEMA public TO usuario_reportes;
GRANT SELECT ON users, orders, products TO usuario_reportes;

-- Usuario de aplicación: solo lo que necesita
CREATE USER usuario_app WITH PASSWORD 'otra_contraseña';
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO usuario_app;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO usuario_app;
-- NO dar TRUNCATE, DROP, CREATE

-- Revocar permisos
REVOKE DELETE ON orders FROM usuario_app;

-- Row Level Security (RLS): cada usuario solo ve sus datos
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY users_own_orders ON orders
  FOR ALL TO usuario_app
  USING (user_id = current_setting('app.user_id')::INTEGER);
ConsejoUsa el principio de mínimo privilegio: cada usuario de la base de datos debe tener solo los permisos estrictamente necesarios. El usuario de tu aplicación web no debería poder hacer DROP TABLE ni acceder a otras bases de datos del servidor.
← PostgreSQL AvanzadoORMs: Prisma y Sequelize →

Practica esta lección

Resuelve los ejercicios del curso de SQL y Bases de Datos en la app, gratis y con corrección automática.

Empezar a practicar