Guía completa de seguridad en PHP para desarrolladores

php Guía completa de seguridad en PHP para desarrolladores

Guía completa de seguridad en PHP para desarrolladores

PHP sigue siendo el motor de millones de aplicaciones web. Esta guía concentra las prácticas esenciales y ejemplos prácticos para minimizar vulnerabilidades comunes: inyección SQL, XSS, CSRF, sesiones inseguras, y malas configuraciones. Aquí verás qué hacer y el por qué, con snippets listos para usar.

A quién va dirigida

Desarrolladores PHP con conocimientos básicos-intermedios que quieren endurecer aplicaciones propias o de clientes, y equipos que preparan apps para producción.

Modelo de amenazas (rápido)

  • Atacantes remotos que explotan entradas sin validar (SQLi, XSS).
  • Usuarios autenticados que intentan escalar privilegios o forzar CSRF.
  • Exposición de secretos por mala configuración (.env en repo, errores visibles).

1) Validación y saneamiento de entrada

Siempre trata datos externos como maliciosos. Valida por tipo y rango; sanea al salida según el contexto.

  • Validación en servidor: filter_var, ctype_* o librerías de validación.
  • Sanea para HTML con htmlspecialchars; para atributos JS/URL usa funciones específicas.
// Validar email
$email = $_POST['email'] ?? '';
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    throw new RuntimeException('Email inválido');
}

// Evitar XSS al mostrar
echo htmlspecialchars($username, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');

2) Acceso a la base de datos: PDO y consultas preparadas

Nunca concatenes valores en SQL. Usa consultas preparadas y tipos cuando sea posible.

$pdo = new PDO($dsn, $user, $pass, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);

$stmt = $pdo->prepare('SELECT id, name FROM users WHERE email = :email');
$stmt->execute([':email' => $email]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);

Si necesitas consultas dinámicas (columnas, ORDER BY), valida y whitelist los valores antes de interpolarlos.

3) Autenticación y hashing de contraseñas

Usa password_hash/password_verify. No inventes tu propio algoritmo.

// Registrar
$hash = password_hash($plainPassword, PASSWORD_DEFAULT);
// Verificar
if (password_verify($inputPassword, $hash)) {
    // OK
}

Re-hash automático si cambian costos:

if (password_needs_rehash($hash, PASSWORD_DEFAULT)) {
    $newHash = password_hash($plainPassword, PASSWORD_DEFAULT);
    // Guardar nuevo hash
}

4) Gestión segura de sesiones

Configura cookies de sesión y regenera ID al autenticar.

// Antes de session_start()
ini_set('session.use_strict_mode', 1);
session_set_cookie_params([
    'lifetime' => 0,
    'path' => '/',
    'domain' => 'tu-dominio.com',
    'secure' => true,
    'httponly' => true,
    'samesite' => 'Lax',
]);
session_start();

// Al iniciar sesión correctamente
session_regenerate_id(true);
$_SESSION['user_id'] = $userId;

Evita almacenar datos sensibles sin cifrado en la sesión y limita su contenido.

5) CSRF: tokens y cabeceras

Protege formularios y endpoints mutables. Para APIs con tokens (Bearer) CSRF no aplica igual; para cookies sí.

// Generar token
if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

// En formulario
echo '';

// Verificar
if (!hash_equals($_SESSION['csrf_token'] ?? '', $_POST['csrf_token'] ?? '')) {
    http_response_code(403);
    exit('CSRF token inválido');
}

6) XSS: escapar según contexto

Escapa para HTML (htmlspecialchars), para atributos, CSS y JS usa estrategias específicas. Nunca uses addslashes para esto.

  • Para valores en HTML: htmlspecialchars(..., ENT_QUOTES, 'UTF-8')
  • Para JSON en
// Inyectar datos en JS de forma segura
$data = ['name' => $user['name']];
echo '<script>window.APP_DATA = ' . json_encode($data, JSON_HEX_TAG | JSON_HEX_AMP | JSON_UNESCAPED_UNICODE) . '</script>';

7) Subida de archivos

Valida extensión, MIME type y tamaño; no confíes en el MIME reportado por el cliente. Guarda fuera del webroot o renombra archivos y quita permisos ejecutables.

$allowed = ['image/png','image/jpeg'];
if (!in_array($_FILES['avatar']['type'], $allowed, true)) {
    throw new RuntimeException('Tipo no permitido');
}
// Usar finfo para verificar MIME real
$finfo = new finfo(FILEINFO_MIME_TYPE);
mime = $finfo->file($_FILES['avatar']['tmp_name']);
if (!in_array($mime, $allowed, true)) { /* rechazar */ }

// Mover con nombre seguro
$target = sprintf('/var/www/uploads/%s.%s', bin2hex(random_bytes(12)), $ext);
move_uploaded_file($_FILES['avatar']['tmp_name'], $target);

8) Cabeceras HTTP de seguridad

Configura cabeceras recomendadas en tu aplicación o reverse proxy:

  • Content-Security-Policy
  • X-Frame-Options: DENY
  • Referrer-Policy: no-referrer-when-downgrade (o stricter)
  • Strict-Transport-Security para HTTPS
  • X-Content-Type-Options: nosniff
header("X-Frame-Options: DENY");
header("X-Content-Type-Options: nosniff");
header("Referrer-Policy: no-referrer");
header("Strict-Transport-Security: max-age=63072000; includeSubDomains; preload");
header("Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-...';");

9) Configuración y despliegue seguro

  • Desactiva display_errors en producción (display_errors = Off).
  • Configura errores en logs, no en salida al cliente.
  • No incluyas .env o config con secretos en el repo; usa variables de entorno y un gestor de secretos.
  • Limita permisos: PHP/NGINX debe tener solo los permisos necesarios sobre archivos.
  • Restringe acceso a /vendor, /storage y archivos sensibles mediante reglas del servidor web.

10) Dependencias y composición segura

Usa Composer con auditoría periódica. Comandos útiles:

$ composer audit
$ composer update --with-all-dependencies

Integra SCA (Software Composition Analysis) y mantén dependencias actualizadas. Revisa CVEs en paquetes que uses.

11) Logging, monitoreo y rate limiting

Registro estructurado (JSON) y monitoreo de accesos/errores facilitan detectar ataques. Aplica rate limiting en endpoints sensibles (login, reset de contraseña).

12) Pruebas y herramientas

  • Static analysis: PHPStan, Psalm (detectan tipos y patrones inseguros).
  • SAST/DAST: SonarQube, OWASP ZAP, Burp Suite para pruebas dinámicas.
  • Fuzzing y pruebas de carga para endpoints públicos.

13) Checklist rápido (impleméntalo en tu pipeline)

  • Usar PDO + prepared statements
  • Escapar todo output según contexto
  • CSRF tokens en formularios basados en cookie
  • session_regenerate_id tras login
  • password_hash / password_verify
  • Cabeceras HTTP de seguridad y HSTS
  • Composer audit y revisión de CVEs
  • Desactivar display_errors en producción

Fragmentos de configuración recomendada

.env (no subir al repo)

APP_ENV=production
APP_DEBUG=false
DB_DSN=mysql:host=127.0.0.1;dbname=app;charset=utf8mb4
DB_USER=app_user
DB_PASS=supersecret

php.ini (producción)

display_errors = Off
log_errors = On
error_log = /var/log/php_errors.log
session.cookie_secure = 1
session.cookie_httponly = 1

Errores comunes que verás en código heredado

  • SQL construido por concatenación.
  • Salida HTML sin escapar.
  • Mostrar errores detallados en producción.
  • Almacenar contraseñas en texto plano o con MD5/SHA1.
  • Subidas de archivo guardadas en webroot sin validación.

Implementación práctica: middleware simple para seguridad

Ejemplo de middleware (PSR-7/PSR-15) que añade cabeceras y fuerza HTTPS:

final class SecurityHeadersMiddleware implements MiddlewareInterface
{
    public function process(RequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $response = $handler->handle($request);

        return $response
            ->withHeader('X-Frame-Options', 'DENY')
            ->withHeader('X-Content-Type-Options', 'nosniff')
            ->withHeader('Referrer-Policy', 'no-referrer')
            ->withHeader('Strict-Transport-Security', 'max-age=63072000; includeSubDomains; preload')
            ->withHeader('Content-Security-Policy', "default-src 'self'");
    }
}

Instala esto en tu pipeline de middleware y comprueba en staging antes de producción.

Recursos y herramientas recomendadas

  • OWASP Top Ten y cheatsheets (referencia obligatoria)
  • PHPStan y Psalm (análisis estático)
  • Composer Audit, Snyk, GitHub Dependabot
  • OWASP ZAP, Burp Suite (pruebas dinámicas)

Si quieres, puedo generar un checklist imprimible o un scaffold mínimo (estructura de carpetas + middleware + ejemplo de login seguro) que puedes clonar e integrar en tus proyectos. Un consejo avanzado: integra análisis estático (PHPStan/Psalm) + SCA en tu CI y automatiza pruebas DAST periódicas para que la seguridad deje de ser un parche y pase a ser parte del flujo de desarrollo.

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