Ir al contenido principal

Inyección SQL

Qué es la inyección SQL

Una aplicación recibe datos de un formulario y los usa para consultar una base. Hay un error grave si mezcla esos datos con el texto de la consulta: la base puede interpretar una parte del dato como si fuera una instrucción SQL.

A ese problema se lo llama inyección SQL (SQLi). No hace falta que quien usa el sistema conozca la contraseña de la base: alcanza con que el programa arme mal la consulta.

Cómo se produce

Supongamos que un login busca una cuenta por nombre de usuario. Si el programa pega el texto recibido dentro de la consulta, podría construir algo así:

$sql = "SELECT id, usuario FROM usuarios WHERE usuario = '$usuario'";

Si alguien escribe un valor que cambia la condición —por ejemplo ' OR '1'='1' -- —, la consulta resultante podría quedar así:

SELECT id, usuario FROM usuarios WHERE usuario = '' OR '1'='1' -- '
Una entrada maliciosa se pega dentro de una consulta vulnerable y altera su condición 1. Texto recibido del formulario ' OR '1'='1' -- 2. Consulta armada pegando el texto SELECT id, usuario FROM usuarios WHERE usuario = '' OR '1'='1' -- La condición cambia: el dato pasó a formar parte del SQL.
En la consulta vulnerable, el texto ingresado modifica la condición del WHERE.

La condición '1'='1' siempre es verdadera y el resto queda comentado. La consulta puede devolver cuentas que no correspondían. El problema no está en que SQL “se equivoque”: el programa dejó que un dato alterara la instrucción.

La defensa: consultas preparadas

La regla es no concatenar datos del usuario en una consulta. Con PDO se escribe primero la consulta con un marcador y se envía el valor por separado:

$consulta = $bd->prepare(
    'SELECT id, usuario FROM usuarios WHERE usuario = :usuario'
);
$consulta->execute([':usuario' => $usuario]);
La consulta SQL fija y el texto del usuario viajan por separado; el valor no puede cambiar la instrucción 1. Consulta fija, con un marcador ... WHERE usuario = :usuario 2. Valor enviado aparte ' OR '1'='1' -- El valor se compara como dato La estructura de la consulta no cambia.
Con una consulta preparada, la entrada se trata como un valor completo; no puede convertirse en una instrucción.

El marcador :usuario indica dónde va el dato. Al pasarlo a execute(), se trata como un valor y no como parte del código SQL. También se puede usar un marcador posicional ?:

$consulta = $bd->prepare(
    'SELECT id, usuario FROM usuarios WHERE usuario = ?'
);
$consulta->execute([$usuario]);

Las dos formas sirven. Los nombres hacen más fácil reconocer cada valor; los signos ? son más breves, pero dependen del orden. En ambos casos, la consulta queda fija y los valores se envían aparte.

La regla práctica

  • Si una consulta incluye un valor recibido de una persona, usar prepare() y execute().
  • No armar consultas pegando texto con concatenación.
  • No confiar en que el formulario o la validación impidan por sí solos una inyección.
  • Usar un usuario de base de datos con los permisos mínimos necesarios: así se limita el daño si otro error aparece.

En la Unidad 4, las operaciones del CRUD aplican esta regla desde PHP. Allí se explica cómo conectar el programa con MariaDB y leer el resultado de cada consulta.

Resumen

  • La inyección SQL aparece cuando un dato puede cambiar la consulta que ejecuta el programa.
  • La defensa principal es usar consultas preparadas: prepare() para la consulta y execute() para enviar los valores aparte.
  • La validación y las consultas preparadas resuelven problemas distintos.
  • Escapar caracteres a mano no reemplaza las consultas preparadas.