Las in­ye­c­cio­nes SQL re­pre­se­n­tan una gran amenaza para los modelos de bases de datos re­la­cio­na­les y la in­fo­r­ma­ción que almacenan. Por ello, es fu­n­da­me­n­tal contar con una pro­te­c­ción completa frente a estos accesos externos no au­to­ri­za­dos que se apro­ve­chan de vu­l­ne­ra­bi­li­da­des de seguridad.

¿Qué es una inyección SQL?

Una inyección SQL (del inglés SQL injection) se define como la ex­plo­ta­ción de una vu­l­ne­ra­bi­li­dad en las bases de datos re­la­cio­na­les que utilizan el lenguaje de consulta SQL. El atacante se aprovecha de aquellas entradas de usuario que no están de­bi­da­me­n­te filtradas y que contienen me­ta­ca­ra­c­te­res como el guion doble, las comillas o el punto y coma. Estos ca­ra­c­te­res re­pre­se­n­tan funciones es­pe­cia­les para el in­té­r­pre­te de SQL y permiten la in­flue­n­cia externa sobre las in­s­tru­c­cio­nes eje­cu­ta­das. Es común que las in­ye­c­cio­nes SQL se realicen en conjunto con programas PGP y ASP, que dependen de in­te­r­fa­ces más antiguas. Allí, algunas de las entradas no cuentan con el correcto en­ma­s­ca­ra­mie­n­to, co­n­vi­r­tié­n­do­se así en el blanco perfecto para estos ataques.

Mediante el uso in­te­n­cio­na­do de ca­ra­c­te­res es­pe­cia­les, un usuario no au­to­ri­za­do puede inyectar comandos SQL adi­cio­na­les 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 in­ye­c­cio­nes SQL: así funcionan los ataques a las bases de datos

Debido a que los se­r­vi­do­res de bases de datos pueden ser ra­s­trea­dos fá­ci­l­me­n­te, de la misma forma que también es fácil ejecutar ataques de inyección SQL, este método se ha co­n­ve­r­ti­do rá­pi­da­me­n­te en uno de los pre­fe­ri­dos por ci­be­r­cri­mi­na­les alrededor del mundo. Estos actúan con di­fe­re­n­tes patrones de ataque y se apro­ve­chan, sobre todo, de las vu­l­ne­ra­bi­li­da­des tra­di­cio­na­les y actuales de las apli­ca­cio­nes que pa­r­ti­ci­pan en el proceso de gestión de datos. A co­n­ti­nua­ció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 en­ma­s­ca­ra­da

Por lo general, antes de que un usuario pueda acceder a una base de datos necesita au­te­n­ti­car­se. Para este propósito existen scripts que, por ejemplo, crean un fo­r­mu­la­rio de inicio de sesión compuesto por un nombre de usuario y una co­n­tra­se­ña. Los usuarios rellenan el fo­r­mu­la­rio y, a co­n­ti­nua­ción, el script comprueba si existen entradas co­rre­s­po­n­die­n­tes en la base de datos. De forma pre­de­te­r­mi­na­da, la base de datos contiene una tabla llamada users con las columnas username y password. Para cualquier apli­ca­ción web, las líneas de comando (pseu­do­có­di­go) 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)
python

Un atacante tiene ahora la po­si­bi­li­dad de manipular in­te­n­cio­na­da­me­n­te el campo de co­n­tra­se­ña mediante una inyección SQL, in­tro­du­cie­n­do 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'"
python

Así, el atacante obtiene acceso al registro completo de usuarios en la base de datos, pues la co­n­tra­se­ña siempre es verdadera (1=‘1’). Ahora se puede registrar como ad­mi­ni­s­tra­dor y puede realizar cambios en las entradas. El campo de nombre de usuario también puede ma­ni­pu­lar­se de la misma forma.

Ejemplo 2: espionaje de datos a través de la ma­ni­pu­la­ción de ID

Solicitar in­fo­r­ma­ción de una base de datos con un ide­n­ti­fi­ca­dor 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 ide­n­ti­fi­ca­dor tra­n­s­mi­ti­do en una URL permite al servidor web ide­n­ti­fi­car qué in­fo­r­ma­ció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);
    }
?>
php

La URL esperada tiene el formato .../script.php?id=22. En ese caso, se co­n­su­l­ta­ría el registro de la tabla con la ID “22”. Sin embargo, si una persona externa tiene la po­si­bi­li­dad de manipular esa URL y envía al servidor web la solicitud .../script.php?id=22+OR+1=1, la consulta re­su­l­ta­n­te hará que se lean todos los datos de la tabla:

SELECT * FROM tabla WHERE id=22 OR 1=1;
sql

¿Cómo ide­n­ti­fi­can los ci­be­r­de­li­n­cue­n­tes las bases de datos vu­l­ne­ra­bles?

En principio, cualquier página web o apli­ca­ción que utilice bases de datos SQL sin consultas pre­pa­ra­das (prepared sta­te­me­nts) u otros me­ca­ni­s­mos de pro­te­c­ción puede ser vu­l­ne­ra­ble a in­ye­c­cio­nes SQL. Las vu­l­ne­ra­bi­li­da­des de­s­cu­bie­r­tas no pe­r­ma­ne­cen 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 vu­l­ne­ra­bles a través de búsquedas en Google. Si una página devuelve mensajes de error SQL de­ta­lla­dos, los ci­be­r­de­li­n­cue­n­tes pueden apro­ve­char esa in­fo­r­ma­ció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 ide­n­ti­fi­ca­dor del URL hace que una página web vu­l­ne­ra­ble 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 in­co­rre­c­ta…”. 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 co­n­tra­se­ñas. Además, existen he­rra­mie­n­tas con las que se puede au­to­ma­ti­zar tanto la búsqueda como las po­s­te­rio­res in­ye­c­cio­nes SQL.

Cómo proteger tu base de datos de una inyección SQL

Existen di­fe­re­n­tes medidas para prevenir ataques de inyección SQL y proteger la base de datos. Para ello es necesario fa­mi­lia­ri­zar­se con todos los co­m­po­ne­n­tes in­vo­lu­cra­dos: el servidor, las apli­ca­cio­nes in­di­vi­dua­les y el sistema de gestión de base de datos.

Paso 1: su­pe­r­vi­sar las mo­di­fi­ca­cio­nes au­to­má­ti­cas en las apli­ca­cio­nes

Cuando se procesan entradas pro­ce­de­n­tes de apli­ca­cio­nes externas o in­te­gra­das, es fu­n­da­me­n­tal validar y filtrar los valores enviados para evitar ataques de in­ye­c­cio­nes SQL.

1. Comprobar los tipos de datos

Cada dato in­tro­du­ci­do debe coincidir con el tipo esperado. Por ejemplo, si se espera un número entero, una va­li­da­ción sencilla en PHP podría verse así:

if (filter_var($input, FILTER_VALIDATE_INT) === false) {
    throw new InvalidArgumentException("Entrada no válida");
}
php

Se deben aplicar co­m­pro­ba­cio­nes similares para cadenas de texto, fechas u otros formatos es­pe­cí­fi­cos.

2. Filtrar ca­ra­c­te­res es­pe­cia­les

Los ca­ra­c­te­res es­pe­cia­les pueden provocar vu­l­ne­ra­bi­li­da­des, es­pe­cia­l­me­n­te 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 de­ta­lla­dos

No se deben mostrar mensajes de error que revelen in­fo­r­ma­ción técnica sobre la base de datos o el sistema. En su lugar, es pre­fe­ri­ble 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.");
php

4. Usar consultas pre­pa­ra­das (prepared sta­te­me­nts)

Una forma segura de evitar in­ye­c­cio­nes SQL es mediante el uso de consultas pre­pa­ra­das (o prepared sta­te­me­nts), como ya se ha me­n­cio­na­do. Este método separa las in­s­tru­c­cio­nes SQL de los pa­rá­me­tros, im­pi­die­n­do 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();
php

El sistema de gestión de bases de datos se encarga au­to­má­ti­ca­me­n­te de procesar los datos de forma segura.

Paso 2: pro­po­r­cio­nar una pro­te­c­ción completa al servidor

Ló­gi­ca­me­n­te, la seguridad del servidor en el que se ejecuta el sistema de gestión de base de datos juega un papel fu­n­da­me­n­tal en la pre­ve­n­ción de ataques de inyección SQL. La primera medida de seguridad es el en­du­re­ci­mie­n­to del sistema operativo de acuerdo con el siguiente patrón:

  • Instala y activa úni­ca­me­n­te aquellas apli­ca­cio­nes y servicios que son realmente re­le­va­n­tes para la base de datos.
  • Elimina todas las cuentas de usuario que no sean ne­ce­sa­rias.
  • Asegúrate de instalar todas las ac­tua­li­za­cio­nes re­le­va­n­tes para el sistema y el programa.
  • Aplica el principio de mínimo pri­vi­le­gio para ase­gu­rar­te de que los usuarios y servicios solo tengan los permisos es­tri­c­ta­me­n­te ne­ce­sa­rios.

Según los re­qui­si­tos de seguridad de tu proyecto web, conviene co­n­si­de­rar también medidas de pro­te­c­ción adi­cio­na­les:

  • Sistemas de detección de intrusos (IDS) y sistemas de pre­ve­n­ción de intrusos (IPS): estos sistemas utilizan distintos métodos de detección para ide­n­ti­fi­car ataques al servidor en una fase temprana, emitir alertas y, en el caso de los IPS, aplicar au­to­má­ti­ca­me­n­te medidas de respuesta.
  • Ap­pli­ca­tion Layer Gateway (ALG): una pasarela de nivel de apli­ca­ción o ALG supervisa y filtra el tráfico de datos entre las apli­ca­cio­nes y los na­ve­ga­do­res web di­re­c­ta­me­n­te en la capa de apli­ca­ción.
  • Co­r­ta­fue­gos de apli­ca­cio­nes web (WAF): un WAF protege es­pe­cí­fi­ca­me­n­te las apli­ca­cio­nes web contra las in­ye­c­cio­nes SQL y Cross Site Scripting (XSS), blo­quea­n­do o neu­tra­li­za­n­do so­li­ci­tu­des so­s­pe­cho­sas.
  • Enfoque Zero Trust: este enfoque de seguridad moderno garantiza que todo acceso sea ve­ri­fi­ca­do y validado, in­de­pe­n­die­n­te­me­n­te de su origen, antes de ser au­to­ri­za­do.
  • Reglas de co­r­ta­fue­gos y se­g­me­n­ta­ción de red: estas medidas son fu­n­da­me­n­ta­les para reducir la su­pe­r­fi­cie de ataque a largo plazo.
  • Au­di­to­rías de seguridad TI pe­rió­di­cas y pruebas de pe­ne­tra­ción: estas acciones ayudan a ide­n­ti­fi­car y corregir vu­l­ne­ra­bi­li­da­des antes de que puedan ser ex­plo­ta­das.

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 irre­le­va­n­tes y ser ac­tua­li­za­da re­gu­la­r­me­n­te. Para ello es re­co­me­n­da­ble des­ac­ti­var todos los servicios y cuentas de usuario inactivos, así como eliminar todos los pro­ce­di­mie­n­tos al­ma­ce­na­dos que no sean ne­ce­sa­rios. Configura una cuenta de base de datos que esté destinada úni­ca­me­n­te para el acceso desde la web y que tenga derechos de acceso mínimos.

Siguiendo las buenas prácticas de las consultas pre­pa­ra­das, se re­co­mie­n­da en­ca­re­ci­da­me­n­te 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 apli­ca­ció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();
php

Además, nunca se deben guardar las co­n­tra­se­ñas di­re­c­ta­me­n­te en la base de datos ni co­n­su­l­tar­las en texto plano. En su lugar, utiliza un método de hash seguro como password_hash() combinado con password_verify() para proteger ade­cua­da­me­n­te las cre­de­n­cia­les. Una im­ple­me­n­ta­ció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.";
}
php

Bobby 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, ca­ri­ño­sa­me­n­te apodado “el pequeño Bobby Tables”. Al pre­gu­n­tar­le 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 di­s­cu­l­par­se, si­m­ple­me­n­te expresa su esperanza de que el colegio haya aprendido la lección y que, en el futuro, controlen las entradas que se in­tro­du­cen en la base de datos.

El cómic ilustra de forma clara y divertida las co­n­se­cue­n­cias po­te­n­cia­l­me­n­te de­sas­tro­sas de no validar las entradas de usuario en sistemas que utilizan bases de datos.

Ir al menú principal