5 errores críticos que todos cometen con PHP (y cómo solucionarlos)

php 5 errores críticos que todos cometen con PHP (y cómo solucionarlos)

5 errores críticos que todos cometen con PHP (y cómo solucionarlos)

PHP es flexible y rápido para prototipar, pero esa flexibilidad trae trampas. Aquí tienes cinco errores frecuentes—con ejemplos reales, por qué son peligrosos y cómo arreglarlos correctamente.

1) Concatenar consultas SQL en lugar de usar consultas preparadas (inyección SQL)

Por qué importa: la inyección SQL sigue siendo una de las vulnerabilidades más explotadas. Usar concatenación permite que datos del usuario modifiquen la consulta.

Mal (vulnerable):

<?php
// Usuario controla 'id'
$id = $_GET['id'];
$sql = "SELECT * FROM users WHERE id = $id";
$rows = $pdo->query($sql)->fetchAll();
?>

Bien (PDO con prepared statements):

<?php
$id = $_GET['id'];
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute([':id' => $id]);
$rows = $stmt->fetchAll();
?>

Por qué funciona: las consultas preparadas separan código y datos; el driver se encarga de escapar y bindear valores correctamente.

2) Almacenar contraseñas en texto plano o usar hashing débil

Por qué importa: si tu base de datos se filtra, las contraseñas deben ser inútiles para el atacante.

Mal: guardar la contraseña tal cual o usar md5/sha1.

<?php
// NO
$stored = $_POST['password'];
// o
$hash = md5($_POST['password']);
?>

Bien (password_hash / password_verify):

<?php
// Al registrar
$hash = password_hash($_POST['password'], PASSWORD_DEFAULT);
// Al autenticar
if (password_verify($_POST['password'], $hash_from_db)) {
    // contraseña correcta
}
?>

Por qué funciona: password_hash aplica un algoritmo fuerte y sal único; password_verify maneja la comparación segura y soporta rehash cuando cambias el algoritmo.

3) No escapar salida (XSS)

Por qué importa: mostrar datos del usuario sin escapar permite inyectar scripts que roban cookies o realizan acciones en su nombre.

Mal:

<?php
echo '<div>Usuario: ' . $user['name'] . '</div>';
?>

Bien (escape contextual con htmlspecialchars):

<?php
echo '<div>Usuario: ' . htmlspecialchars($user['name'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</div>';
?>

Por qué funciona: escapar convierte < > " ' en entidades seguras. Usa escaping contextual (HTML, attribute, JS, URL) según dónde insertes el dato.

4) Manejo inseguro de sesiones (session fixation / IDs predecibles)

Por qué importa: sesiones mal protegidas permiten secuestrar cuentas. Problemas comunes: no regenerar ID al login, no limitar cookies, no usar HTTPS/secure flags.

Mejor configuración y prácticas:

<?php
// Al inicio de la app (antes de session_start)
session_set_cookie_params([
    'lifetime' => 0,
    'path' => '/',
    'domain' => 'example.com',
    'secure' => true, // solo HTTPS
    'httponly' => true, // no accesible por JS
    'samesite' => 'Lax',
]);
session_start();

// Cuando el usuario hace login:
session_regenerate_id(true); // evita fixation
$_SESSION['user_id'] = $userId;
?>

Por qué funciona: secure/httponly/samesite ayudan a proteger la cookie; regenerar ID evita que un atacante reutilice el ID anterior.

5) Validación insuficiente en uploads y deserialización insegura

Por qué importa: subir archivos sin validar puede llevar a RCE (ejecutar scripts), y unserialize() con datos no confiables permite object injection.

Mal:

<?php
move_uploaded_file($_FILES['f']['tmp_name'], '/var/www/html/uploads/' . $_FILES['f']['name']);
// o
$data = unserialize($_POST['data']);
?>

Bien:

<?php
// Validar upload
$allowed = ['image/png', 'image/jpeg'];
$finfo = new finfo(FILEINFO_MIME_TYPE);
$type = $finfo->file($_FILES['f']['tmp_name']);
if (!in_array($type, $allowed, true)) {
    throw new RuntimeException('Tipo de archivo no permitido');
}
$ext = $type === 'image/png' ? '.png' : '.jpg';
$target = sprintf('/var/www/uploads/%s%s', bin2hex(random_bytes(16)), $ext);
move_uploaded_file($_FILES['f']['tmp_name'], $target);

// Evitar unserialize: usar json
$data = json_decode($_POST['data'], true);
?>

Por qué funciona: comprobar MIME y generar nombres aleatorios evita sobreescrituras y ejecución. Usar JSON evita la ejecución de objetos mágicos en clase deserializada.

Buenas prácticas transversales

  • Configura display_errors = off en producción y registra errores en ficheros seguros.
  • Principio de mínimo privilegio en la BD: cuenta DB por app con permisos limitados.
  • Usa frameworks modernos que te dan protecciones por defecto (CSRF tokens, escaping, routing).
  • Revisa dependencias: Composer audit / SensioLabs Security Checker (o herramientas actuales).

Implementar estas correcciones protegerá tu aplicación frente a los vectores más comunes. Siguiente paso: prueba cada cambio con casos adversos (pentest básico) y añade CI que ejecute comprobaciones de seguridad automatizadas para que estas buenas prácticas no se pierdan con el tiempo.

Comentarios
¿Quieres comentar?

Inicia sesión con Telegram para participar en la conversación


Comentarios (0)

Aún no hay comentarios. ¡Sé el primero en comentar!

Iniciar Sesión