La inyección SQL es cuando el texto que escribes en un cuadro de búsqueda se convierte en «parte de un comando» para la base de datos. Aquí tienes cómo funciona y cómo prevenirla de forma fiable (sin pasos de ataque).
Por qué ocurre (el mecanismo)
La causa raíz es construir SQL por concatenación de cadenas. Mezcla la entrada directamente en la sentencia y puede cruzar la «frontera del valor» y actuar como comando.
✗ Concatenación de cadenas (peligroso)
Entrada concatenada en la sentencia → la entrada puede leerse como parte del comando
✓ Marcadores de posición (seguro)
Valores pasados a las ranuras ? / $1 como datos → siempre siguen siendo valores
El daño es «todo lo que esa conexión a la BD pueda hacer». Si la BD interna es alcanzable, es una causa clásica de fugas masivas, junto a RCE y SSRF.
Defensa
Usa marcadores de posición / sentencias preparadas (lo más importante)
No construyas SQL por concatenación. Pasa los valores mediante ? / parámetros con nombre como 'datos'. Esto por sí solo elimina casi toda la clase.
Apóyate en tu ORM / constructor de consultas
La mayoría de los ORMs usan marcadores de posición por defecto. Cuanto menos SQL en bruto escribas a mano, más seguro. Si debes escribir SQL en bruto, parametrízalo siempre.
Usuario de BD con privilegios mínimos
No concedas al usuario de BD de la aplicación derechos innecesarios (DROP, otras tablas, operaciones de administración). Aunque haya brecha, contén el daño.
Validación de entrada como complemento
Las comprobaciones de tipo/longitud/formato ayudan, pero no las conviertas en tu defensa principal; complementan a los marcadores de posición.
La visión de este sitio: deja de construir SQL en bruto a mano para empezar
La SQLi se conoce desde hace mucho tiempo y aun así sigue ocurriendo, porque es «cómodo» mezclar la entrada en la sentencia. La postura de este sitio es clara: nunca crees un lugar donde los valores se concatenen como cadenas dentro del SQL. Haz de los marcadores de posición o de un ORM el valor predeterminado y esta vulnerabilidad queda prevenida por diseño. No dependas del escape manual.
Cuidado: los marcadores de posición solo llevan valores
Los marcadores de posición no lo cubren todo. Solo llevan valores, no la estructura de la consulta, como los nombres de tabla, los nombres de columna o la dirección del ORDER BY (asc/desc).
Cuando necesitas esos elementos dinámicos (por ejemplo, dejar que un usuario elija una «clave de ordenación» o «columna de filtro»), no metas la entrada en el SQL: selecciona el nombre real de una lista de permitidos predefinida. La «columna de ordenación» y la «columna de filtro» son los puntos ciegos clásicos donde la gente asume que los marcadores de posición los cubren.
La entrada no son solo los campos de formulario
Si crees que solo hay que tener cuidado con los cuadros de búsqueda y los formularios de inicio de sesión, se te escaparán casos. Los valores que pueden acabar en el SQL llegan por muchas vías.
- Cabeceras HTTP (
User-Agent,X-Forwarded-For, etc.) y cookies - Valores dentro del JSON que recibe una API, o leídos de archivos
- Valores guardados una vez en la base de datos y que otro proceso mete después en SQL
Lo último se llama inyección SQL de segundo orden. Aunque usaras marcadores de posición al guardar, funciona cuando otro proceso concatena después como cadena el valor guardado. No supongas «viene de mi propia base de datos, así que es seguro»: usa marcadores de posición en todos los sitios donde se construye SQL.
El artículo de este sitio sobre la suplantación de X-Forwarded-For describe un caso en el que llegaron cadenas de sondeo en una cabecera, pero la inyección SQL no funcionó porque el valor se pasaba con un marcador de posición.
En qué se diferencia de términos parecidos
Los fallos con «inyección» en el nombre comparten una cosa: la entrada se lee como comandos de otro lenguaje. La diferencia es dónde acaba la entrada.
| Término | Dónde acaba la entrada | Daño principal | Solución de raíz |
|---|---|---|---|
| Inyección SQL | Comandos a la base de datos (SQL) | Leer, alterar o borrar datos | Marcadores de posición |
| XSS | HTML o JavaScript que se ejecuta en el navegador del usuario | Abuso de la sesión del usuario, contenido falso en la página | Escape en la salida y CSP |
| Inyección de comandos del SO | Comandos que ejecuta la shell del servidor | Ejecutar cualquier comando en el servidor (RCE) | Usar APIs que no pasan por la shell y pasan los argumentos por separado |
La inyección SQL y el XSS suelen mencionarse juntos, pero el daño cae en sitios distintos: la inyección SQL afecta a la base de datos del servidor, el XSS al navegador del usuario.
Cómo se llaman los marcadores de posición en los principales lenguajes
Todos los lenguajes principales ofrecen una forma de pasar los valores por separado. Úsalo como guía de qué función usar en tu stack.
| Lenguaje / biblioteca | Función que usar |
|---|---|
| Java (JDBC) | PreparedStatement con métodos para fijar valores como setString |
| PHP (PDO) | Prepara la sentencia con prepare y pasa los valores a execute |
| Python (DB-API) | Pasa los valores como segundo argumento de execute (no los incrustes en la cadena SQL) |
| Node.js (pg y otros) | Símbolos numerados como $1, con los valores pasados en un array aparte |
| C# (.NET) | Añade los valores a los Parameters del comando |
En cualquier lenguaje, en el momento en que incrustas valores en el SQL con plantillas de texto o +, esta protección deja de funcionar.
Casos reales: grandes fugas que empezaron con inyección SQL en un producto
La inyección SQL se conoce desde hace mucho tiempo y aun así sigue causando fugas importantes. Los casos recientes no suelen empezar en código escrito por la propia organización, sino en un producto que usaba.
| Caso | Qué ocurrió |
|---|---|
| MOVEit Transfer (2023) | Se explotó una inyección SQL en un producto de transferencia de archivos (CVE-2023-34362) antes de que existiera un parche, y se robaron datos de más de 2.700 organizaciones, muchas de ellas a través de proveedores (→ la brecha masiva de datos de MOVEit) |
| Metabase (2026) | Se explotó una inyección SQL sin autenticación en una herramienta de análisis (CVE-2026-72898) antes del parche, y varias empresas comunicaron fugas de datos de clientes (→ explotación de Metabase y fugas, en inglés) |
| Núcleo de WordPress (2026) | Combinada con otro fallo, una inyección SQL sin autenticación podía llevar a RCE (→ CVE-2026-63030 explicada, en inglés) |
Tras MOVEit, las agencias estadounidenses CISA y FBI emitieron en marzo de 2024 una alerta que instaba a los fabricantes de software a eliminar la inyección SQL, y señalaba las consultas parametrizadas (marcadores de posición) como la solución. En el «CWE Top 25» de MITRE publicado en diciembre de 2025, la inyección SQL (CWE-89) ocupa el segundo lugar.
Qué hacer depende de tu papel
| Tu papel | Lo primero que hay que hacer |
|---|---|
| Escribes SQL (desarrollador) | Deja de concatenar cadenas; usa marcadores de posición o un ORM. Revisa cómo se usan las funciones de SQL en bruto |
| Gestionas productos o software de código abierto (operador) | Comprueba los CVE y el estado KEV de los productos que gestionas y actualiza a versiones corregidas. Reduce las pantallas de administración accesibles desde internet |
| Ambos | Da al usuario de base de datos de la aplicación privilegios mínimos, limitados a las tablas y columnas que necesita |
Cómo comprobar tu propio entorno
No necesitas escribir cadenas de ataque para comprobarlo. El código y la configuración te dicen lo que necesitas.
Busca SQL construido a partir de cadenas
Busca en tu código líneas donde aparezcan palabras clave de SQL (SELECT, WHERE, etc.) junto con concatenación de cadenas o plantillas de texto. Comprueba también que no se concatene ninguna entrada en funciones de SQL en bruto ($queryRawUnsafe, whereRaw, DB::raw, raw() y similares).
Anota lo que puede hacer el usuario de la base de datos
Comprueba a qué tablas puede acceder el usuario de base de datos de la aplicación y qué puede hacer en ellas (SHOW GRANTS en MySQL, information_schema.role_table_grants en PostgreSQL). Quita los permisos de borrado que no necesite y cualquier acceso a tablas de otras aplicaciones.
Compara los productos que usas con los datos de vulnerabilidades
Usa la consulta de CVE / KEV para ver las vulnerabilidades conocidas de los productos que gestionas y si se están explotando. Para las dependencias, osv-scanner las revisa todas de una vez.
Asegúrate de que los errores SQL no se muestran en pantalla
En producción, confirma que los mensajes de error de la base de datos no se muestran tal cual a los usuarios. Guarda los errores detallados solo en los registros.
Busca señales en tus registros
Comprueba si los errores de sintaxis de la base de datos han aumentado de repente, y si tu WAF o tus registros de acceso muestran una serie de peticiones desde el mismo origen con cadenas que parecen SQL. Si es así, revisa primero el código de esa ruta.
Fuentes (primarias)
- OWASP, «SQL Injection Prevention Cheat Sheet» (en inglés) — cheatsheetseries.owasp.org (marcadores de posición, precauciones con los procedimientos almacenados, validación de entrada con lista de permitidos, privilegios mínimos, no depender del escape)
- CISA y FBI, «Secure by Design Alert: Eliminating SQL Injection Vulnerabilities in Software» (25 de marzo de 2024, en inglés) — cisa.gov
- MITRE, «2025 CWE Top 25 Most Dangerous Software Weaknesses» (diciembre de 2025, en inglés) — cwe.mitre.org (CWE-89 en segundo lugar)
- IPA, «How to Secure Your Web Site» — ipa.go.jp (en japonés; soluciones fundamentales frente a mitigaciones)
Sigue leyendo
- Glosario: Qué es RCE · Qué es XSS · Qué es el OWASP Top 10
- Explicación: la suplantación de X-Forwarded-For y la configuración de proxies de confianza
- Fundamentos: Fundamentos de seguridad