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.
¿Quieres comentar?
Inicia sesión con Telegram para participar en la conversación