Consultas preparadas de PDO: ¿100% de protección contra inyecciones SQL?

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: la pregunta que atormenta a todo desarrollador PHP

\n\n

Si alguna vez has escrito consultas SQL en PHP, seguramente has escuchado el mantra: "Usa PDO y prepared statements — y estarás seguro". Pero, ¿es realmente así? En Stack Overflow, la pregunta "Are PDO prepared statements sufficient to prevent SQL injection?" ha acumulado miles de visitas y decenas de acalorados debates. Y no es para menos.

\n\n

En este artículo no solo daremos una respuesta corta de "sí" o "no". Nos meteremos bajo el capó de PDO, analizaremos cómo funcionan exactamente las consultas preparadas y descubriremos en qué casos incluso PDO puede fallarte. ¿Listos? Allá vamos.

\n\n

¿Qué es una inyección SQL y por qué es peligrosa?

\n\n

Antes de hablar de protección, recordemos al enemigo en persona. La inyección SQL es un ataque en el que un atacante "inyecta" código SQL malicioso a través de datos de entrada. Un clásico:

\n\n
// Código malo: nunca lo hagas así\n$username = $_POST['username'];\n$query = "SELECT * FROM users WHERE username = '$username'";\n$result = mysqli_query($conn, $query);\n
\n\n

Si el atacante introduce admin' OR '1'='1, la consulta se convierte en:

\n\n
SELECT * FROM users WHERE username = 'admin' OR '1'='1'\n
\n\n

¡Voilà! Acceso a datos sin contraseña. ¿Y si introduce '; DROP TABLE users; --? Adiós, tabla de usuarios.

\n\n

PDO y consultas preparadas: ¿cómo funciona?

\n\n

PDO (PHP Data Objects) ofrece un mecanismo de prepared statements (expresiones preparadas). La idea es simple: separas la estructura de la consulta de los datos. En lugar de insertar variables directamente en la cadena, usas marcadores de posición:

\n\n
// Código seguro con PDO\n$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");\n$stmt->execute([':username' => $_POST['username']]);\n
\n\n

Pero, ¿qué sucede bajo el capó? Este es el punto clave que muchos pasan por alto.

\n\n

Modos de emulación vs. prepared statements nativos

\n\n

PDO puede funcionar en dos modos:

\n\n
    \n
  • Modo de emulación (por defecto en versiones antiguas de PHP) — PDO escapa los datos por sí mismo y los inserta en la consulta del lado de PHP. Sigue siendo seguro, pero teóricamente vulnerable si el escape falla (por ejemplo, al trabajar con codificaciones no estándar).
  • \n
  • Modo nativo — PDO envía al servidor de BD dos mensajes: primero la plantilla de la consulta, luego los datos por separado. El servidor procesa los datos como valores, no como código. Esto es absolutamente seguro.
  • \n
\n\n

Para activar el modo nativo, desactiva la emulación:

\n\n
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);\n
\n\n

Ahora la consulta se envía al servidor en dos etapas. Incluso si el atacante introduce ' OR '1'='1, el servidor MySQL o PostgreSQL simplemente lo interpretará como un valor de cadena, no como parte del código SQL. ¡Magia!

\n\n

Pero, ¿es todo tan perfecto? Problemas ocultos de PDO

\n\n

Volvamos a la pregunta de Stack Overflow. La respuesta es "sí, los prepared statements de PDO son suficientes" — pero con salvedades. Estas son situaciones en las que la protección puede fallar.

\n\n

1. Identificadores dinámicos (nombres de tablas, columnas)

\n\n

Los prepared statements solo funcionan para valores (datos). Si insertas el nombre de una tabla o columna, los marcadores de posición no ayudan:

\n\n
// ¡Peligroso! El nombre de la tabla no se puede pasar mediante un marcador de posición\n$stmt = $pdo->prepare("SELECT * FROM :table WHERE id = :id");\n$stmt->execute([':table' => $_GET['table'], ':id' => 1]); // ¡Error!\n
\n\n

PDO simplemente insertará el nombre de la tabla como una cadena y la consulta fallará. Si usas emulación, el atacante podría introducir users; DROP TABLE users; --. Solución: lista blanca de valores permitidos.

\n\n

2. Consultas LIKE y caracteres comodín

\n\n

Si usas

Blogs

Book Recommendations