Inyección SQL en PHP: cómo protegerte en 2025

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: ¿por qué las inyecciones SQL siguen siendo relevantes?

\n\n

Imagina: tu sitio web funciona como un reloj, los usuarios se registran, dejan comentarios, realizan pedidos. Y de repente, la base de datos termina en manos de un atacante. No porque los hackers sean genios, sino porque alguien olvidó poner un signo de interrogación en una consulta SQL.

\n\n

Las inyecciones SQL no son una amenaza arcaica de los años 2000. En 2024, OWASP aún las sitúa entre las 3 vulnerabilidades más peligrosas en aplicaciones web. Y la pregunta "How can I prevent SQL injection in PHP?" en Stack Overflow supera los 1.5 millones de visitas, y sigue creciendo. Esto significa que el problema sigue vigente.

\n\n

En este artículo, veremos cómo cerrar la puerta de forma definitiva a las inyecciones SQL en PHP en 2025. Sin magia, sin frameworks complejos, solo código limpio y las mejores prácticas.

\n\n

¿Qué es una inyección SQL en términos sencillos?

\n\n

Una inyección SQL ocurre cuando la entrada del usuario se convierte en parte de un comando SQL, en lugar de ser solo un dato. Ejemplo de la vida real:

\n\n
<?php\n// CÓDIGO MALO — nunca hagas esto\n$username = $_POST['username'];\n$query = "SELECT * FROM users WHERE username = '$username'";\n$result = mysqli_query($conn, $query);\n?>
\n\n

Si un atacante ingresa en el campo username la cadena ' OR '1'='1, la consulta se convierte en:

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

Esto devuelve todos los usuarios. Y si ingresa '; DROP TABLE users; --, podría perder toda la tabla. ¿Da miedo? Claro que sí.

\n\n

Mejores prácticas para protegerte de inyecciones SQL en PHP 2025

\n\n

1. Usa PDO con consultas preparadas (es el estándar)

\n\n

PDO (PHP Data Objects) es una interfaz universal para trabajar con bases de datos. Su principal ventaja: separa la lógica SQL de los datos. Escribes la consulta con marcadores de posición (placeholders) y PDO se encarga del escape seguro.

\n\n
<?php\n// CÓDIGO BUENO — usa PDO\n$dsn = 'mysql:host=localhost;dbname=testdb;charset=utf8mb4';\n$pdo = new PDO($dsn, $user, $pass, [\n    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,\n    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,\n    PDO::ATTR_EMULATE_PREPARES => false, // desactivamos la emulación\n]);\n\n$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');\n$stmt->execute(['email' => $_POST['email']]);\n$user = $stmt->fetch();\n?>
\n\n

¿Por qué funciona? La base de datos recibe la estructura de la consulta por separado de los datos. Incluso si el usuario ingresa SQL malicioso, se interpretará como una cadena de texto normal, no como parte del comando.

\n\n

2. Mysqli con consultas preparadas (alternativa)

\n\n

Si solo trabajas con MySQL, puedes usar mysqli, pero siempre con consultas preparadas, no con escape mediante mysqli_real_escape_string().

\n\n
<?php\n$mysqli = new mysqli('localhost', 'user', 'pass', 'testdb');\n$stmt = $mysqli->prepare('INSERT INTO comments (user_id, text) VALUES (?, ?)');\n$stmt->bind_param('is', $userId, $commentText);\n$stmt->execute();\n?>
\n\n

Importante: en 2025, mysqli_real_escape_string() sigue existiendo, pero confiar en él para protegerte es una mala idea. Solo escapa cadenas, pero no protege contra inyecciones en campos numéricos o en operadores LIKE.

\n\n

3. Filtrado y validación: segunda línea de defensa

\n\n

Las consultas preparadas son la base. Pero añade filtrado por tipo de dato. Si esperas un número, verifica que sea un número. Si esperas un email, verifica el formato.

\n\n
<?php\n$id = filter_input(INPUT_POST, 'user_id', FILTER_VALIDATE_INT);\nif ($id === false) {\n    throw new InvalidArgumentException('El ID debe ser un número entero');\n}\n\n$stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id');\n$stmt->execute(['id' => $id]);\n?>
\n\n

Combinando

Blogs

Book Recommendations