Dev.to Security 🔐 Cybersecurity 👁 0 📖 3 min read

SQL Injection en PHP: cómo prevenirlo con consultas preparadas (guía rápida)

La inyección SQL (SQL Injection) es una de las amenazas más comunes y peligrosas para una aplicación web: el atacante manipula las consultas SQL que ejecuta tu código para leer, modificar o borrar información que no le c

La inyección SQL (SQL Injection) es una de las amenazas más comunes y peligrosas para una aplicación web: el atacante manipula las consultas SQL que ejecuta tu código para leer, modificar o borrar información que no le corresponde. Puede comprometer la integridad de los datos, exponer información sensible y dañar la reputación de tu sitio. En PHP se evita con pocas costumbres, y la principal es no concatenar nunca datos del usuario dentro de una consulta.

Cómo funciona el ataque

Cuando los datos del usuario se insertan directamente en una consulta SQL, el atacante puede alterar su lógica. Este login es vulnerable:

$username = $_POST['user'];
$password = $_POST['pass'];

// Consulta vulnerable
$sql = "SELECT * FROM seg_usuario WHERE username = '$username' AND password = '$password'";
$result = $conn->query($sql);

Si el atacante envía admin como usuario y ' OR '1'='1 como contraseña, la consulta resultante es:

SELECT * FROM seg_usuario WHERE username = 'admin' AND password = '' OR '1'='1'

La condición '1'='1' siempre es verdadera, así que se concede el acceso sin credenciales válidas. En el artículo original hay además otro ejemplo con un formulario de búsqueda, donde la causa es la misma: datos del usuario pegados directamente en el SQL.

La defensa: consultas preparadas

La mejor forma de prevenirlo es separar siempre la consulta de los datos. Con consultas preparadas, el motor recibe primero la estructura de la consulta y después los valores, que nunca se interpretan como SQL. Con MySQLi:

$stmt = $conn->prepare("SELECT * FROM seg_usuario WHERE username = ? AND password = ?");
$stmt->bind_param("ss", $username, $password);
$stmt->execute();
$result = $stmt->get_result();

Y con PDO, usando parámetros con nombre:

$stmt = $pdo->prepare("SELECT * FROM seg_usuario WHERE username = :username AND password = :password");
$stmt->execute(['username' => $username, 'password' => $password]);

Dos aclaraciones. Los parámetros sustituyen valores, no nombres de tablas ni de columnas: si alguno de esos tiene que ser dinámico, valídalo contra una lista de valores permitidos. Y en un login real las contraseñas no se guardan en claro, se usan password_hash() y password_verify(); aquí se mantiene la comparación directa solo para que el ejemplo sea corto.

Capas adicionales

Las consultas preparadas son la defensa principal, pero conviene combinarlas con otras medidas:

  • Validar la entrada: comprueba que el dato tiene el formato esperado. Es un complemento, no un sustituto de las consultas preparadas.
if (!preg_match('/^[a-zA-Z0-9_]{3,20}$/', $username)) {
    die("Nombre de usuario inválido");
}
  • No mostrar errores SQL al usuario: los mensajes de error pueden revelar la estructura de tu base de datos. Registra el detalle en el log y muestra un mensaje genérico:
error_log($conn->error);
echo "Ha ocurrido un error. Inténtalo de nuevo.";
  • Dar permisos mínimos a la cuenta de la aplicación: no todos los usuarios de la base de datos necesitan acceso total. Una cuenta que solo puede hacer SELECT e INSERT limita el daño si algo falla.
  • Mantener todo actualizado: PHP, el servidor web y el gestor de base de datos, para corregir vulnerabilidades conocidas.

Conclusión

Para prevenir la inyección SQL en PHP, usa consultas preparadas siempre, valida la entrada como apoyo, no muestres errores internos y limita los permisos de la base de datos.

Si quieres el ejemplo completo y la explicación detallada, está en el artículo original en Todo se Aprende.

📰 Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.