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)

Breve y directo: estos cinco fallos aparecen con frecuencia en apps PHP y causan desde vulnerabilidades hasta fallos en producción. Para cada uno verás por qué es peligroso, un ejemplo 'malo' y la corrección recomendada.

1) Concatenar entradas en consultas SQL (inyección SQL)

Por qué duele: concatenar parámetros en queries permite a un atacante modificar la consulta y acceder o destruir datos.

// MAL: vulnerable a SQL injection
$u = $_POST['user'];
$q = "SELECT * FROM users WHERE username = '" . $u . "' AND active = 1";
$res = $db->query($q);

Solución: usa consultas preparadas (PDO) y nunca confíes en el input.

// BIEN: PDO con prepared statements
$stmt = $pdo->prepare('SELECT * FROM users WHERE username = :u AND active = 1');
$stmt->execute([':u' => $_POST['user']]);
$user = $stmt->fetch();

Por qué: los parámetros se envían por separado y el driver se encarga del escape correcto.

2) Almacenar contraseñas con MD5/SHA1 o texto plano

Por qué duele: MD5/SHA1 son rápidos y no están diseñados para contraseñas; son vulnerables a ataques por diccionario y rainbow tables.

// MAL
$hash = md5($password);
// Y peor: guardar $password directamente en la DB

Solución: usa password_hash() y password_verify(), que aplican bcrypt/argon2 y manejan el salt automáticamente.

// BIEN
$hash = password_hash($password, PASSWORD_DEFAULT);
// Al verificar
if (password_verify($candidate, $hash)) {
    // OK
}

Consejo: revisa password_needs_rehash() tras un cambio de algoritmo y actualiza hashes en el login.

3) Mostrar errores en producción

Por qué duele: mostrar stack traces o errores revela rutas, consultas y configuración del servidor a atacantes.

// MAL: en producción
ini_set('display_errors', 1);
error_reporting(E_ALL);

Solución: desactiva display_errors y registra errores en archivos o sistemas de logging. Detecta entorno (env) y ajusta comportamiento.

// BIEN: entorno controlado
if ($env === 'production') {
    ini_set('display_errors', 0);
    ini_set('log_errors', 1);
    ini_set('error_log', '/var/log/app/php_errors.log');
}

// Manejo simple de excepciones
set_exception_handler(function (Throwable $e) {
    error_log($e->getMessage() . " in " . $e->getFile() . ':' . $e->getLine());
    http_response_code(500);
    echo 'Ocurrió un error. Intente más tarde.'; // mensaje genérico
});

Mejor aún: integra Monolog u otro logger y envía alertas en errores críticos.

4) Gestión insegura de sesiones (session fixation)

Por qué duele: si no renuevas el ID tras autenticación, un atacante que fije una sesión puede secuestrarla.

// MAL
// Inicio de sesión sin regenerar
session_start();
// ... verificar credenciales
$_SESSION['user_id'] = $id;

Solución: configura cookies seguras y regenera ID en login/logout.

// BIEN
session_set_cookie_params([
    'lifetime' => 0,
    'path' => '/',
    'domain' => 'tudominio.com',
    'secure' => true,      // HTTPS obligatorio
    'httponly' => true,    // evita acceso JS
    'samesite' => 'Lax',   // o 'Strict' según caso
]);
session_start();

// Tras autenticación correcta
session_regenerate_id(true); // destruye el viejo ID
$_SESSION['user_id'] = $id;

Adicional: invalida sesión en logout, limita intentos de login y considera usar tokens de renovación.

5) Manejo inseguro de uploads

Por qué duele: confiar en el nombre o en el MIME reportado por el navegador puede permitir subir archivos maliciosos (.php) que se ejecuten en el servidor.

// MAL
move_uploaded_file($_FILES['f']['tmp_name'], '/var/www/html/uploads/' . $_FILES['f']['name']);

Solución: valida tamaño, tipo real con finfo, guarda fuera del webroot o fuerza headers para descartar ejecución, y genera nombres únicos.

// BIEN
$max = 5 * 1024 * 1024; // 5 MB
if ($_FILES['f']['error'] !== UPLOAD_ERR_OK) {
    throw new RuntimeException('Upload error');
}
if ($_FILES['f']['size'] > $max) {
    throw new RuntimeException('Archivo demasiado grande');
}
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($_FILES['f']['tmp_name']);
$allowed = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'application/pdf' => 'pdf'];
if (!isset($allowed[$mime])) {
    throw new RuntimeException('Tipo de archivo no permitido');
}
$ext = $allowed[$mime];
$targetDir = '/var/www/private_uploads/'; // fuera del webroot
$filename = bin2hex(random_bytes(16)) . '.' . $ext;
if (!move_uploaded_file($_FILES['f']['tmp_name'], $targetDir . $filename)) {
    throw new RuntimeException('No se pudo guardar el archivo');
}

Además: evita ejecutar archivos desde el directorio de uploads y aplica permisos restrictivos.

Buenas prácticas transversales (rápido)

  • Usa PDO/ORMs con parametrización.
  • Aplica password_hash y revisa rehashing.
  • Controla errores según entorno y registra todo en logs centralizados.
  • Configura cookies de sesión seguras y usa HTTPS obligatorio.
  • Valida y trata todos los inputs del cliente como maliciosos por defecto.

Consejo avanzado: incorpora análisis estático (PHPStan, Psalm), pruebas de seguridad en CI (dependabot, composer audit), y revisiones periódicas de dependencias. Si ya aplicas lo anterior, el siguiente paso es realizar un pentest automatizado y probar remediaciones en un entorno staging.

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