Imagina que un formulario de contacto tan sencillo como el de tu web pudiera convertirse en la puerta de entrada para que alguien borre tu base de datos completa o, peor aún, acceda a los datos de tus clientes.

La inyección SQL (SQL Injection) sigue siendo, año tras año, una de las vulnerabilidades más explotadas en aplicaciones web según OWASP, y la razón es sencilla: muchísimos proyectos —incluso webs profesionales— construyen sus consultas SQL concatenando texto sin ningún tipo de protección.

La buena noticia es que blindar tu web contra este tipo de ataques no requiere reescribir el proyecto ni ser un experto en ciberseguridad. Con PHP y MySQL, las herramientas que ya usas a diario, puedes implementar una protección sólida en muy poco tiempo.

En esta guía verás qué es exactamente la inyección SQL, el error que la provoca, cómo solucionarlo paso a paso y qué buenas prácticas adicionales deberías aplicar si gestionas o desarrollas una web con base de datos.

¿Qué es la inyección SQL y por qué sigue siendo un problema en 2026?

La inyección SQL ocurre cuando un atacante introduce código SQL malicioso a través de un campo de entrada —un formulario de login, un buscador, un parámetro en la URL— y ese código termina ejecutándose directamente en tu base de datos porque la aplicación no distingue entre «datos» y «instrucciones».

A pesar de ser una vulnerabilidad conocida desde hace más de veinte años, sigue apareciendo en auditorías de seguridad porque muchos proyectos PHP heredados —o desarrollados con prisa— siguen construyendo las consultas concatenando variables directamente, sin ningún filtro intermedio.

El error más común: concatenar variables en la consulta

Así es como se ve, en la práctica, una consulta vulnerable:

<?php
// ❌ CÓDIGO VULNERABLE — nunca hagas esto
$usuario = $_POST['usuario'];
$clave   = $_POST['clave'];

$consulta  = "SELECT * FROM usuarios WHERE usuario = '$usuario' AND clave = '$clave'";
$resultado = mysqli_query($conexion, $consulta);
?>

Con este código, un atacante podría escribir en el campo «usuario» algo como admin' OR '1'='1 y conseguir acceder sin necesidad de conocer ninguna contraseña real. La aplicación interpreta esa entrada como parte de la lógica SQL, no como un simple dato.

La solución: prepared statements (consultas preparadas)

Un prepared statement separa la estructura de la consulta SQL de los datos que introduce el usuario. Primero se define la consulta con marcadores de posición y, después, los valores reales se envían por separado, sin que puedan alterar la instrucción SQL original. Esto hace prácticamente imposible que una entrada maliciosa cambie el comportamiento de la consulta.

Con PDO (la opción recomendada):

<?php
// ✅ CÓDIGO SEGURO con PDO y prepared statements
$pdo = new PDO(
    'mysql:host=localhost;dbname=mi_base;charset=utf8mb4',
    'usuario_bd',
    'contraseña_bd',
    [PDO::ATTR_EMULATE_PREPARES => false] // Desactiva la emulación: MySQL gestiona la consulta de forma nativa
);

$stmt = $pdo->prepare('SELECT id, nombre FROM usuarios WHERE usuario = :usuario AND clave_hash = :clave_hash');
$stmt->execute([
    ':usuario'    => $_POST['usuario'],
    ':clave_hash' => $claveHash, // Obtenido tras verificar con password_verify()
]);
$resultado = $stmt->fetchAll();
?>

Un detalle importante que muchos desarrolladores pasan por alto: PDO emula los prepared statements por defecto en algunos drivers, lo que puede reabrir ciertos escenarios de riesgo. Por eso conviene desactivar explícitamente esa emulación con PDO::ATTR_EMULATE_PREPARES en false, como se ve en el ejemplo anterior, para que MySQL gestione la consulta de forma nativa.

¿Y si trabajas con mysqli?

Si tu proyecto usa la extensión mysqli en lugar de PDO, el principio es exactamente el mismo:

<?php
// ✅ CÓDIGO SEGURO con mysqli y prepared statements
$stmt = $conexion->prepare('SELECT id, nombre FROM usuarios WHERE usuario = ? AND clave_hash = ?');
$stmt->bind_param('ss', $_POST['usuario'], $claveHash);
$stmt->execute();
$resultado = $stmt->get_result();
?>

Lo esencial en ambos casos es lo mismo: los datos del usuario nunca se escriben directamente dentro del texto de la consulta.

Buenas prácticas adicionales para blindar tu base de datos

Los prepared statements son la base, pero una protección completa incluye varias capas más:

  • Aplica el principio de mínimo privilegio. El usuario de base de datos que utiliza tu web no debería tener permisos de administrador como DROP o GRANT. Crea un usuario específico con solo los permisos que realmente necesita (SELECT, INSERT, UPDATE).
  • Valida el tipo y formato de cada campo. Un ID debe ser numérico, un email debe tener formato de email. Esta validación se suma a los prepared statements, no los sustituye.
  • Hashea las contraseñas correctamente. Usa siempre password_hash() y password_verify(); nunca MD5, SHA1 ni, por supuesto, texto plano.
  • Desactiva la visualización de errores en producción. Un mensaje de error de MySQL en pantalla puede revelar la estructura de tus tablas a un atacante. Configura display_errors en Off y registra los errores en un log privado.
  • Mantén PHP y las librerías del proyecto actualizadas. Buena parte de las vulnerabilidades conocidas ya tienen parche; el riesgo está en los proyectos que nunca se actualizan.

Cómo saber si tu web ya podría ser vulnerable

Algunas señales de alerta habituales: formularios o buscadores antiguos que nunca se han revisado, mensajes de error de MySQL visibles para el usuario, código heredado con funciones ya eliminadas de PHP como mysql_query(), o una web que lleva años sin ningún tipo de mantenimiento técnico.

La seguridad no es un lujo

La seguridad no es un lujo reservado a las grandes empresas: una sola consulta sin proteger puede comprometer toda tu base de datos y, con ella, la confianza de tus clientes. Revisar y blindar tus consultas SQL es una de las inversiones de mantenimiento más rentables que puedes hacer en tu web.

Si no estás seguro de si tu proyecto tiene este tipo de vulnerabilidades, puedo hacer una revisión de seguridad y ayudarte a corregirlas. Hablamos sin compromiso.