Tu sitio PHP en la mira: por qué los hackers aman la seguridad débil
\n\nImagina: pasaste semanas desarrollando un blog genial o una tienda online en PHP. El sitio funciona, los visitantes están contentos. Pero un "hermoso" día entras al panel de administración y está vacío. La base de datos ha sido eliminada. O, peor aún, en la página principal aparece publicidad de un casino y todos los usuarios son redirigidos a un sitio de phishing.
\n\n¿Te suena familiar? Lamentablemente, para muchos desarrolladores PHP principiantes (y no tan principiantes) esto es una realidad. Lo más molesto es que el 90% de estos ataques ocurren debido a dos vulnerabilidades aparentemente simples: inyecciones SQL y cross-site scripting (XSS). Los hackers no descifran algoritmos de encriptación complejos; simplemente se aprovechan de la falta de atención de los programadores.
\n\nEn este artículo no daremos conferencias aburridas. Analizaremos cómo funcionan estos ataques "desde dentro" y, lo más importante, aprenderemos a poner barreras sólidas. Descubrirás cómo hacer que tu código PHP sea un hueso duro de roer para los atacantes. ¡Allá vamos!
\n\nInyecciones SQL: cuando la base de datos habla con la voz del hacker
\n\nInyección SQL (SQL injection) es un tipo de ataque en el que un atacante "inyecta" código SQL malicioso en una consulta a la base de datos. Si tu aplicación no filtra los datos de entrada, el hacker puede hacer que la base de datos ejecute cualquier comando: desde robar contraseñas hasta eliminar tablas por completo.
\n\n¿Cómo se ve el código vulnerable?
\n\nImagina un formulario de inicio de sesión simple. El código de un principiante a menudo se ve así:
\n\n<?php\n$login = $_POST['login'];\n$password = $_POST['password'];\n\n// ¡PELIGROSO! Sustitución directa en la consulta\n$query = "SELECT * FROM users WHERE login = '$login' AND password = '$password'";\n$result = mysqli_query($link, $query);\n?>\n\nParece lógico. Pero, ¿qué sucede si el atacante ingresa esta cadena en el campo de login?
\n\n' OR '1'='1\n\nLa consulta se convierte en:
\n\nSELECT * FROM users WHERE login = '' OR '1'='1' AND password = '...'\n\nLa condición '1'='1' siempre es verdadera. ¡Felicidades, el hacker acaba de iniciar sesión sin contraseña! Y si ingresa '; DROP TABLE users; --, tu tabla de usuarios desaparecerá para siempre.
Escape vs. Consultas preparadas: ¿qué elegir?
\n\nMuchos tutoriales antiguos recomiendan usar la función mysqli_real_escape_string(). Sí, escapa caracteres especiales, pero eso es cosa del pasado. Este método tiene desventajas:
- \n
- Es fácil olvidar aplicar la función a cada variable. \n
- No protege contra todos los tipos de inyecciones (por ejemplo, al trabajar con LIKE). \n
- El código se vuelve desordenado y difícil de mantener. \n
La forma moderna y correcta son las consultas preparadas (prepared statements) a través de PDO o MySQLi. Separan la lógica de la consulta de los datos. La base de datos primero "compila" la plantilla de la consulta y luego inserta los datos. Incluso si el hacker envía código malicioso, se tratará como una cadena normal, no como parte del comando SQL.
\n\nEscribiendo código seguro con PDO
\n\nAsí es como debería verse una consulta segura a la base de datos usando PDO (PHP Data Objects):
\n\n<?php\n// Conexión a la BD\n$dsn = 'mysql:host=localhost;dbname=mydb;charset=utf8mb4';\n$pdo = new PDO($dsn, 'user', 'password');\n\n// Configuración del modo de errores (IMPORTANTE para depuración, pero en producción lo quitamos)\n$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);\n\n// Preparación de la consulta (plantilla)\n$stmt = $pdo->prepare("SELECT * FROM users WHERE login = :login AND password = :password");\n\n// Vinculación de parámetros y ejecución\n$stmt->execute([\n ':login' => $_POST['login'],\n ':password' => $_POST['password']\n]);\n\n$user = $stmt->fetch();\n\nif ($user) {\n echo "¡Bienvenido!";\n} else {\n echo "Usuario o contraseña incorrectos";\n}\n?>\n\nAc