Inyección SQL
Las inyecciones SQL representan una gran amenaza para los modelos de bases de datos relacionales y la información que almacenan. Por ello, es fundamental contar con una protección completa frente a estos accesos externos no autorizados que se aprovechan de vulnerabilidades de seguridad.
¿Qué es una inyección SQL?
Una inyección SQL (del inglés SQL injection) se define como la explotación de una vulnerabilidad en las bases de datos relacionales que utilizan el lenguaje de consulta SQL. El atacante se aprovecha de aquellas entradas de usuario que no están debidamente filtradas y que contienen metacaracteres como el guion doble, las comillas o el punto y coma. Estos caracteres representan funciones especiales para el intérprete de SQL y permiten la influencia externa sobre las instrucciones ejecutadas. Es común que las inyecciones SQL se realicen en conjunto con programas PGP y ASP, que dependen de interfaces más antiguas. Allí, algunas de las entradas no cuentan con el correcto enmascaramiento, convirtiéndose así en el blanco perfecto para estos ataques.
Mediante el uso intencionado de caracteres especiales, un usuario no autorizado puede inyectar comandos SQL adicionales y manipular las entradas de forma que pueda modificar, eliminar o leer datos. En los casos más graves, un atacante incluso puede obtener acceso a la línea de comandos del sistema que ejecuta las órdenes, y con ello, al servidor de bases de datos completo.
Ejemplos de inyecciones SQL: así funcionan los ataques a las bases de datos
Debido a que los servidores de bases de datos pueden ser rastreados fácilmente, de la misma forma que también es fácil ejecutar ataques de inyección SQL, este método se ha convertido rápidamente en uno de los preferidos por cibercriminales alrededor del mundo. Estos actúan con diferentes patrones de ataque y se aprovechan, sobre todo, de las vulnerabilidades tradicionales y actuales de las aplicaciones que participan en el proceso de gestión de datos. A continuación, y para explicar cómo funcionan estos ataques, se presentan a modo de ejemplo los dos métodos más típicos.
Ejemplo 1: acceso a través de una entrada del usuario mal enmascarada
Por lo general, antes de que un usuario pueda acceder a una base de datos necesita autenticarse. Para este propósito existen scripts que, por ejemplo, crean un formulario de inicio de sesión compuesto por un nombre de usuario y una contraseña. Los usuarios rellenan el formulario y, a continuación, el script comprueba si existen entradas correspondientes en la base de datos. De forma predeterminada, la base de datos contiene una tabla llamada users
con las columnas username
y password
. Para cualquier aplicación web, las líneas de comando (pseudocódigo) que permiten el acceso al servidor web serían:
uname = request.POST['username']
passwd = request.POST['password']
sql = "SELECT id FROM users WHERE username='" + uname + "' AND password='" + passwd + "'"
database.execute(sql)
pythonUn atacante tiene ahora la posibilidad de manipular intencionadamente el campo de contraseña mediante una inyección SQL, introduciendo por ejemplo password' OR 1='1
, lo que daría lugar a la siguiente consulta SQL:
sql = "SELECT id FROM users WHERE username='' AND password='password' OR 1='1'"
pythonAsí, el atacante obtiene acceso al registro completo de usuarios en la base de datos, pues la contraseña siempre es verdadera (1=‘1’). Ahora se puede registrar como administrador y puede realizar cambios en las entradas. El campo de nombre de usuario también puede manipularse de la misma forma.
Ejemplo 2: espionaje de datos a través de la manipulación de ID
Solicitar información de una base de datos con un identificador es un método práctico y habitual, así como una potencial puerta de entrada para un ataque de inyección SQL. Por ejemplo, el identificador transmitido en una URL permite al servidor web identificar qué información debe recuperar de la base de datos. El script PHP para ello se verá de la siguiente manera:
<?php
$mysqli = new mysqli("localhost", "nombre_usuario", "contraseña", "base_de_datos");
$id = intval($_GET['id']);
$result = $mysqli->query("SELECT * FROM tabla WHERE id=$id");
while ($row = $result->fetch_assoc()) {
echo print_r($row, true);
}
?>
phpLa URL esperada tiene el formato .../script.php?id=22
. En ese caso, se consultaría el registro de la tabla con la ID “22”. Sin embargo, si una persona externa tiene la posibilidad de manipular esa URL y envía al servidor web la solicitud .../script.php?id=22+OR+1=1
, la consulta resultante hará que se lean todos los datos de la tabla:
SELECT * FROM tabla WHERE id=22 OR 1=1;
sql¿Cómo identifican los ciberdelincuentes las bases de datos vulnerables?
En principio, cualquier página web o aplicación que utilice bases de datos SQL sin consultas preparadas (prepared statements) u otros mecanismos de protección puede ser vulnerable a inyecciones SQL. Las vulnerabilidades descubiertas no permanecen en secreto mucho tiempo en el vasto mundo de Internet; de hecho, existen páginas que recopilan fallos de seguridad actuales y muestran incluso cómo encontrar proyectos web vulnerables a través de búsquedas en Google. Si una página devuelve mensajes de error SQL detallados, los ciberdelincuentes pueden aprovechar esa información para detectar posibles puntos débiles. Por ejemplo, basta con añadir una comilla simple al final de una URL con un parámetro de ID (como en el siguiente ejemplo):
[Domainname].com/news.php?id=5‘
Un simple apóstrofe en el identificador del URL hace que una página web vulnerable arroje un mensaje de error como este:
Query failed: You have an error in your SQL Syntax …
En español: “Error en la solicitud: la sintaxis de tu SQL es incorrecta…”. Con métodos similares también se pueden revelar el número de columnas, los nombres de tablas y columnas, la versión de SQL e incluso nombres de usuario y contraseñas. Además, existen herramientas con las que se puede automatizar tanto la búsqueda como las posteriores inyecciones SQL.
Cómo proteger tu base de datos de una inyección SQL
Existen diferentes medidas para prevenir ataques de inyección SQL y proteger la base de datos. Para ello es necesario familiarizarse con todos los componentes involucrados: el servidor, las aplicaciones individuales y el sistema de gestión de base de datos.
Paso 1: supervisar las modificaciones automáticas en las aplicaciones
Cuando se procesan entradas procedentes de aplicaciones externas o integradas, es fundamental validar y filtrar los valores enviados para evitar ataques de inyecciones SQL.
1. Comprobar los tipos de datos
Cada dato introducido debe coincidir con el tipo esperado. Por ejemplo, si se espera un número entero, una validación sencilla en PHP podría verse así:
if (filter_var($input, FILTER_VALIDATE_INT) === false) {
throw new InvalidArgumentException("Entrada no válida");
}
phpSe deben aplicar comprobaciones similares para cadenas de texto, fechas u otros formatos específicos.
2. Filtrar caracteres especiales
Los caracteres especiales pueden provocar vulnerabilidades, especialmente en contextos SQL o HTML. Una forma segura de tratarlos es usar htmlspecialchars()
para entradas HTML y PDO::quote()
para consultas SQL.
3. Evitar mensajes de error detallados
No se deben mostrar mensajes de error que revelen información técnica sobre la base de datos o el sistema. En su lugar, es preferible mostrar un mensaje genérico como este:
echo "Se ha producido un error. Por favor, inténtalo de nuevo más tarde.";
error_log("Se ha producido un error inesperado. Consulta el registro del sistema para más detalles.");
php4. Usar consultas preparadas (prepared statements)
Una forma segura de evitar inyecciones SQL es mediante el uso de consultas preparadas (o prepared statements), como ya se ha mencionado. Este método separa las instrucciones SQL de los parámetros, impidiendo así la ejecución de código malicioso. Un ejemplo en PHP con PDO (PHP Data Objects) sería:
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$stmt->bindParam(':id', $user_id, PDO::PARAM_INT);
$stmt->execute();
phpEl sistema de gestión de bases de datos se encarga automáticamente de procesar los datos de forma segura.
Paso 2: proporcionar una protección completa al servidor
Lógicamente, la seguridad del servidor en el que se ejecuta el sistema de gestión de base de datos juega un papel fundamental en la prevención de ataques de inyección SQL. La primera medida de seguridad es el endurecimiento del sistema operativo de acuerdo con el siguiente patrón:
- Instala y activa únicamente aquellas aplicaciones y servicios que son realmente relevantes para la base de datos.
- Elimina todas las cuentas de usuario que no sean necesarias.
- Asegúrate de instalar todas las actualizaciones relevantes para el sistema y el programa.
- Aplica el principio de mínimo privilegio para asegurarte de que los usuarios y servicios solo tengan los permisos estrictamente necesarios.
Según los requisitos de seguridad de tu proyecto web, conviene considerar también medidas de protección adicionales:
- Sistemas de detección de intrusos (IDS) y sistemas de prevención de intrusos (IPS): estos sistemas utilizan distintos métodos de detección para identificar ataques al servidor en una fase temprana, emitir alertas y, en el caso de los IPS, aplicar automáticamente medidas de respuesta.
- Application Layer Gateway (ALG): una pasarela de nivel de aplicación o ALG supervisa y filtra el tráfico de datos entre las aplicaciones y los navegadores web directamente en la capa de aplicación.
- Cortafuegos de aplicaciones web (WAF): un WAF protege específicamente las aplicaciones web contra las inyecciones SQL y Cross Site Scripting (XSS), bloqueando o neutralizando solicitudes sospechosas.
- Enfoque Zero Trust: este enfoque de seguridad moderno garantiza que todo acceso sea verificado y validado, independientemente de su origen, antes de ser autorizado.
- Reglas de cortafuegos y segmentación de red: estas medidas son fundamentales para reducir la superficie de ataque a largo plazo.
- Auditorías de seguridad TI periódicas y pruebas de penetración: estas acciones ayudan a identificar y corregir vulnerabilidades antes de que puedan ser explotadas.
Paso 3: blindar la base de datos y utilizar códigos más seguros
De la misma forma que un sistema operativo, una base de datos debería estar libre de factores externos irrelevantes y ser actualizada regularmente. Para ello es recomendable desactivar todos los servicios y cuentas de usuario inactivos, así como eliminar todos los procedimientos almacenados que no sean necesarios. Configura una cuenta de base de datos que esté destinada únicamente para el acceso desde la web y que tenga derechos de acceso mínimos.
Siguiendo las buenas prácticas de las consultas preparadas, se recomienda encarecidamente no utilizar el módulo mysql
de PHP (eliminado desde PHP 7) y optar en su lugar por mysqli
o PDO. De este modo, también puedes proteger tu aplicación con código más seguro. Una consulta segura con mysqli
se vería, por ejemplo, así:**
$mysqli = new mysqli("localhost", "usuario", "contraseña", "base_de_datos");
if ($mysqli->connect_error) die("Conexión fallida");
$stmt = $mysqli->prepare("SELECT password FROM users WHERE username = ?");
$stmt->bind_param("s", $_POST['username']);
$stmt->execute();
$stmt->bind_result($hashedPassword);
if ($stmt->fetch() && password_verify($_POST['password'], $hashedPassword)) {
echo "Inicio de sesión correcto";
} else {
echo "Credenciales incorrectas";
}
$stmt->close();
$mysqli->close();
phpAdemás, nunca se deben guardar las contraseñas directamente en la base de datos ni consultarlas en texto plano. En su lugar, utiliza un método de hash seguro como password_hash()
combinado con password_verify()
para proteger adecuadamente las credenciales. Una implementación segura podría verse así:
$mysqli = new mysqli("localhost", "usuario", "contraseña", "base_de_datos");
$stmt = $mysqli->prepare("SELECT password FROM users WHERE username = ?");
$stmt->bind_param("s", $_POST['username']);
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();
if ($row && password_verify($_POST['password'], $row['password'])) {
echo "¡Inicio de sesión realizado con éxito!";
} else {
echo "Nombre de usuario o contraseña incorrectos.";
}
phpBobby Tables: la inyección SQL explicada con humor en forma de cómic
En la página bobby-tables.com se aborda el tema de las entradas de usuario inseguras en bases de datos a través de un webcómic de xkcd. El cómic muestra a una madre que recibe una llamada del colegio de su hijo, cariñosamente apodado “el pequeño Bobby Tables”. Al preguntarle si su hijo se llama realmente Robert'); DROP TABLE Students;--
, ella responde que sí, y entonces le explican el motivo de la llamada: al intentar crear un registro para Robert en la base de datos de alumnos, se eliminó todo el contenido de la tabla. La madre, lejos de disculparse, simplemente expresa su esperanza de que el colegio haya aprendido la lección y que, en el futuro, controlen las entradas que se introducen en la base de datos.
El cómic ilustra de forma clara y divertida las consecuencias potencialmente desastrosas de no validar las entradas de usuario en sistemas que utilizan bases de datos.