SQL Injection es un tipo de ataque a la base de datos de una aplicación donde un atacante inyecta código SQL malicioso en los parámetros de consulta, obteniendo acceso no autorizado a los datos o la posibilidad de modificarlos. Según OWASP (2025), SQL Injection sigue siendo una de las vulnerabilidades críticas capaz de llevar a un compromiso completo de la base de datos. La inyección de código SQL permite al atacante leer, modificar y eliminar registros, y en algunos casos, obtener acceso al sistema operativo del servidor.
Puntos clave
SQL Injection es una vulnerabilidad que ocurre cuando una aplicación construye consultas SQL mediante concatenación de cadenas con datos del usuario. Un atacante pasa una cadena especialmente diseñada en un parámetro de consulta que cambia la estructura del comando SQL. En lugar de ser datos, la cadena inyectada se convierte en parte del código SQL, permitiendo al atacante ejecutar consultas arbitrarias contra la base de datos. Según el Informe de Filtraciones de Datos de Verizon (2025), SQL Injection está presente en el 8% de todas las filtraciones de datos investigadas.
A pesar de ser una vulnerabilidad conocida (mencionada por primera vez a finales de los 90), SQL Injection sigue apareciendo en aplicaciones modernas. La razón es el error humano: los desarrolladores escriben código con concatenación de cadenas, el código heredado no se refactoriza y los frameworks ORM se usan incorrectamente (por ejemplo, consultas raw con interpolación de cadenas). Según Veracode (2026), aproximadamente el 14% de todas las aplicaciones escaneadas contienen al menos una vulnerabilidad SQLi.
Una inyección SQL exitosa le da al atacante una amplia gama de capacidades: leer cualquier tabla de la base de datos, incluidos hashes de contraseñas y datos personales de usuarios; modificar y eliminar registros; ejecutar operaciones administrativas (DROP TABLE, TRUNCATE); y en algunas configuraciones, ejecución remota de comandos mediante xp_cmdshell (MSSQL) o INTO OUTFILE (MySQL). Las consecuencias van desde la filtración de datos de usuario hasta la pérdida total del control del sistema.
Las inyecciones SQL se clasifican según cómo se extraen los datos de la base de datos. La elección del método depende de cómo la aplicación maneja los resultados de las consultas y los mensajes de error. La clasificación de OWASP identifica tres tipos principales: In-band (datos extraídos a través del mismo canal), Inferential/Blind (inferencias lógicas) y Out-of-band (datos transmitidos a través de otro canal).
| Tipo | Método de extracción | Complejidad | Frecuencia |
|---|---|---|---|
| In-band (clásico) | Directamente a través del resultado de la consulta | Baja | Alta |
| Blind SQLi | Inferencias lógicas a partir de respuestas del servidor | Alta | Media |
| Out-of-band | A través de un canal externo (DNS, HTTP) | Media | Baja |
El tipo más común. Un atacante inyecta código SQL en un parámetro de consulta, y el resultado de la inyección es directamente visible en la respuesta del servidor. Dos subtipos: Error-based (a través de mensajes de error de la BD) y UNION-based (a través del operador UNION SELECT). Error-based utiliza información de los mensajes de error, como un error de sintaxis de MySQL que puede revelar el nombre de la tabla o la estructura de la consulta. UNION-based permite combinar resultados de consultas legítimas con datos de otras tablas de la base de datos.
Se utiliza cuando la aplicación no muestra los resultados de la consulta ni los mensajes de error. Un atacante hace preguntas de sí/no enviando consultas con condiciones lógicas y analizando las diferencias en las respuestas del servidor (por ejemplo, tiempo de respuesta o contenido de la página). Time-based Blind SQLi utiliza funciones de retardo (SLEEP, WAITFOR DELAY) para confirmar condiciones — si la página tarda más en cargarse, la condición es verdadera. Este método es muy lento — extraer un solo registro puede llevar horas.
# Ejemplo de Blind SQL Injection (basada en tiempo)
# Si SQLi es vulnerable, SLEEP(2) se ejecuta si la condición se cumple
import requests
import time
payload = "' OR IF(SUBSTRING((SELECT password FROM users LIMIT 1),1,1)='a',SLEEP(2),0) -- "
url = "http://example.com/user?id=1" + payload
start = time.time()
response = requests.get(url)
elapsed = time.time() - start
# Si la respuesta llega después de >2 segundos — la primera letra de la contraseña es 'a'
print(f"Time: {elapsed:.2f}s - First letter is 'a'" if elapsed > 2 else "First letter is not 'a'")
Los datos se transmiten no a través de la respuesta HTTP sino mediante canales alternativos: consultas DNS, peticiones HTTP a un servidor externo, SMTP. Se utiliza cuando la aplicación no devuelve resultados de consultas ni muestra errores. MySQL admite la función LOAD_FILE(), que puede iniciar una consulta DNS, y MSSQL tiene xp_dirtree para enviar datos a un servidor SMB remoto. Out-of-band SQL Injection es efectivo pero requiere configuración adicional del servidor atacante y funciones específicas de la BD.
El mecanismo de SQL Injection se basa en que SQL utiliza comillas para literales de cadena. Si una aplicación inserta la entrada del usuario directamente en una consulta SQL sin escaparla, un atacante puede “cerrar” la cadena y añadir código SQL arbitrario. Por ejemplo, en la consulta SELECT * FROM users WHERE name = '$input', insertar ' OR '1'='1 la transforma en SELECT * FROM users WHERE name = '' OR '1'='1', que devuelve todos los usuarios.
Considere un formulario de inicio de sesión con la consulta SELECT * FROM users WHERE username = '$user' AND password = '$pass'. Si un atacante introduce admin' -- en el campo de usuario y deja la contraseña en blanco, la consulta resultante se convierte en SELECT * FROM users WHERE username = 'admin' -- ' AND password = ''. Los caracteres -- comentan el resto de la consulta, desactivando la verificación de la contraseña. El servidor devuelve el registro del usuario admin, y el atacante inicia sesión sin conocer la contraseña.
# Ejemplo de SQL Injection — Evasión de autenticación
# CÓDIGO VULNERABLE: concatenación directa de cadenas
def login_vulnerable(username, password):
query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
cursor.execute(query)
return cursor.fetchone() is not None
# username = "admin' --" anula la verificación de la contraseña
# VERSIÓN SEGURA: consulta parametrizada
def login_secure(username, password):
query = "SELECT * FROM users WHERE username = %s AND password = %s"
cursor.execute(query, (username, password))
return cursor.fetchone() is not None
El operador UNION SELECT permite combinar resultados de dos consultas SELECT. Si un atacante encuentra un parámetro vulnerable, añade UNION SELECT con una consulta de otra tabla. Por ejemplo: ' UNION SELECT username, password FROM admins --. La condición de éxito es que el número de columnas en ambas consultas debe coincidir. El número de columnas se determina mediante ORDER BY (insertando ' ORDER BY 1--, luego 2, 3... hasta que ocurra un error). Conociendo el número de columnas, el atacante inserta UNION SELECT con el mismo número de campos.
Las aplicaciones móviles se enfrentan a SQL Injection tanto en el lado del servidor (API) como en el lado del cliente — en bases de datos locales (SQLite, Realm). Si bien la SQLi del lado del servidor en las APIs móviles es similar a las aplicaciones web, las bases de datos locales crean un vector adicional. Si una aplicación almacena datos en SQLite y ejecuta consultas con concatenación de cadenas, los datos maliciosos que entran en la base de datos local a través de la API pueden desencadenar SQLi durante el procesamiento posterior.
La base de datos local SQLite en un dispositivo también es vulnerable a SQL Injection si la aplicación construye consultas concatenando cadenas. Los Content Providers en Android y Core Data en iOS utilizan parametrización por defecto, pero las consultas raw requieren atención del desarrollador. SQLite no admite múltiples consultas separadas por punto y coma, lo que limita las opciones del atacante pero no protege contra la lectura de datos mediante condiciones WHERE. Utilice siempre selectionArgs en Android y NSPredicate con parámetros en iOS.
La API con la que se comunica una aplicación móvil es vulnerable como cualquier servidor web. Los desarrolladores móviles a menudo asumen que SQLi es solo un problema del backend, pero la vulnerabilidad ocurre en el endpoint de la API que acepta parámetros del cliente. La separación de responsabilidades no protege: si el desarrollador del backend olvidó parametrizar la consulta, la aplicación móvil del usuario se convierte en un vector de ataque. Exija al backend que utilice ORM o prepared statements.
// Ejemplo de SQL Injection en SQLite local en Android
// CÓDIGO VULNERABLE: concatenación directa
fun getUserVulnerable(db: SQLiteDatabase, userId: String): Cursor {
return db.rawQuery(
"SELECT * FROM users WHERE id = $userId", null
)
}
// VERSIÓN SEGURA: parametrización mediante selectionArgs
fun getUserSecure(db: SQLiteDatabase, userId: String): Cursor {
return db.rawQuery(
"SELECT * FROM users WHERE id = ?",
arrayOf(userId)
)
}
La protección contra SQL Injection se basa en un principio simple: nunca confíe en la entrada del usuario en consultas SQL. El único método fiable es la parametrización de consultas (prepared statements), donde el código SQL y los datos se pasan por separado. Todos los demás métodos — escapado, validación, WAF — son capas de seguridad adicionales pero no reemplazan la parametrización. Según OWASP (2025), la parametrización previene el 100% de los ataques de SQL Injection.
Al usar prepared statements, la consulta SQL primero se compila en el servidor de BD sin datos, y luego los valores de los parámetros se pasan por separado. La base de datos trata los parámetros como datos, no como código ejecutable. Incluso si un atacante pasa ' OR '1'='1, la base de datos lo interpreta como un valor de cadena, no como código SQL. Los prepared statements son compatibles con todos los lenguajes y frameworks modernos: PDO en PHP, PreparedStatement en Java, cursor.execute en Python.
Los ORM modernos (Hibernate, Entity Framework, SQLAlchemy, Room) utilizan automáticamente la parametrización al ejecutar consultas, a menos que el desarrollador cambie a consultas raw. Sin embargo, los ORM no protegen completamente: construcciones como @Query(value = "SELECT * FROM users WHERE name = :name", nativeQuery = true) en JPA requieren pasar parámetros mediante parámetros nombrados, no por concatenación. Los Query Builders (Knex, jOOQ) también parametrizan las consultas por defecto si no se utilizan métodos raw.
El escapado de caracteres especiales (mysql_real_escape_string) es un método obsoleto que no protege contra todos los tipos de SQL Injection. El problema: el escapado depende de la codificación y puede ser eludido al usar codificaciones multibyte (por ejemplo, GBK en sistemas asiáticos). Use el escapado solo en código heredado donde la parametrización sea imposible, y siempre en combinación con una validación estricta del tipo de datos de entrada.
| Método | Efectividad | Recomendación |
|---|---|---|
| Prepared Statements | 100% | Obligatorio para todas las consultas |
| ORM (uso correcto) | 99% | Recomendado |
| Escapado de cadenas | 70% (depende de la codificación) | Solo legacy |
| Validación de entrada (white-list) | 50% (solo números) | Adicional |
| WAF (Web Application Firewall) | 60% | Adicional |
La cuenta de la aplicación debe tener los privilegios mínimos necesarios: SELECT, INSERT, UPDATE, DELETE — solo en las tablas que la aplicación realmente requiere. Prohíba el uso de DROP, TRUNCATE, CREATE para la cuenta de la aplicación. Esto limita el daño incluso en caso de una inyección SQL exitosa: el atacante no podrá eliminar tablas ni ejecutar operaciones administrativas.
Las pruebas regulares de SQL Injection deben ser parte del pipeline de desarrollo seguro. Una combinación de análisis estático, escaneo dinámico y pruebas de penetración manuales ofrece los mejores resultados. Según el Informe de Ciberseguridad de Synopsys (2025), los escáneres automatizados encuentran hasta el 70% de las vulnerabilidades SQLi, pero los ataques Blind complejos requieren pruebas manuales.
Para aplicaciones móviles, también es importante el análisis de SQLite local: verifique todas las llamadas rawQuery, consultas ContentProvider y consultas Room con rawQuery. Herramientas: Android Studio Lint (detecta SQLi en SQLite), MobSF (Mobile Security Framework) para análisis estático y dinámico automático de APK/IPA. También se recomienda probar los endpoints de la API mediante sqlmap con intercepción de proxy del tráfico de la aplicación móvil.
Preguntas frecuentes
SQL Injection ataca bases de datos relacionales a través de consultas SQL. NoSQL Injection afecta a bases de datos no relacionales (MongoDB, Couchbase) a través de sus operadores de consulta ($gte, $ne, $where). En MongoDB, la inyección es posible si la aplicación construye un documento BSON a partir de una cadena JSON. Los mecanismos de protección son similares: parametrización y validación de tipos.
Encuentre todos los lugares donde las consultas SQL se forman mediante concatenación de cadenas con datos del usuario. Busque patrones como "SELECT ... WHERE id = " + userId o f"UPDATE ... SET name = '{name}'". Cada línea de este tipo es una posible inyección SQL. Reemplácelas todas con consultas parametrizadas o prepared statements.
Los frameworks ORM protegen automáticamente solo si utiliza sus métodos Query Builder y parámetros nombrados. Si utiliza consultas raw (nativeQuery en JPA, rawQuery en Room), la protección no funciona — debe pasar los parámetros a través de expresiones preparadas, no mediante concatenación de cadenas.
Second-Order SQL Injection es un ataque donde datos maliciosos se almacenan en la base de datos como seguros, pero luego se utilizan en otra consulta sin escapado. Por ejemplo, un atacante se registra con un nombre de usuario como ' OR '1'='1. Los datos se guardan como una cadena — no hay ataque en el registro. Pero si otra consulta utiliza el nombre de usuario en SQL sin parametrización, la inyección se activa.
NoSQL Injection puede ser potencialmente más peligroso debido a la menor conciencia de los desarrolladores. Los desarrolladores conocen SQL Injection y la mayoría usa ORM, pero pocos conocen NoSQL Injection. En MongoDB, una consulta mal formada puede devolver todos los documentos de una colección. La protección es la misma — prepared statements (parametrización BSON) y validación estricta de entrada.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también