Files
OnAPB-Carrere_Demartin/misc/onapb-security-hardening.md
T
2026-06-04 14:50:19 -03:00

9.7 KiB

Blueprint de Seguridad — onapb.com

Generado por: The Architect
Fecha: 2026-04-01
Para ejecutar con: Claude Code en el repositorio del sistema


Contexto

Este documento es una instrucción completa y autónoma para que Claude Code implemente todas las mejoras de seguridad detectadas en el sitio onapb.com.

  • Stack: Laravel (PHP 8.4.11) + LiteSpeed + Hostinger
  • Framework detectado: Laravel (confirmado por cookies laravel-session y XSRF-TOKEN)
  • Dev responsable: único desarrollador con acceso completo al repositorio

Ejecutá cada paso en orden. No saltes pasos. Al final, hacé deploy a producción.


CLAUDE.md para este proyecto

Sos el implementador de seguridad del sitio onapb.com.
Tu tarea es implementar los cambios de seguridad descritos en este archivo en orden.
El stack es Laravel. No rompas funcionalidad existente.
Al terminar cada paso, confirmá que los tests pasen antes de avanzar.

Orden de implementación

PASO 1 — Middleware de Security Headers

Problema: Faltan 5 headers de seguridad críticos:

  • Strict-Transport-Security (HSTS)
  • X-Frame-Options
  • X-Content-Type-Options
  • X-XSS-Protection
  • Content-Security-Policy (actualmente solo tiene upgrade-insecure-requests)

Qué hacer:

  1. Crear el archivo app/Http/Middleware/SecurityHeaders.php:
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class SecurityHeaders
{
    public function handle(Request $request, Closure $next)
    {
        $response = $next($request);

        $response->headers->set('X-Frame-Options', 'SAMEORIGIN');
        $response->headers->set('X-Content-Type-Options', 'nosniff');
        $response->headers->set('X-XSS-Protection', '1; mode=block');
        $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
        $response->headers->set(
            'Permissions-Policy',
            'camera=(), microphone=(), geolocation=()'
        );
        $response->headers->set(
            'Strict-Transport-Security',
            'max-age=31536000; includeSubDomains; preload'
        );
        $response->headers->set(
            'Content-Security-Policy',
            "default-src 'self'; " .
            "script-src 'self' 'unsafe-inline' 'unsafe-eval'; " .
            "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; " .
            "font-src 'self' data: https://fonts.gstatic.com; " .
            "img-src 'self' data: https:; " .
            "connect-src 'self'; " .
            "frame-ancestors 'self';"
        );

        return $response;
    }
}
  1. Registrar el middleware en app/Http/Kernel.php, dentro de $middlewareGroups['web'], al final del array:
\App\Http\Middleware\SecurityHeaders::class,
  1. Verificar: hacer php artisan route:list y luego una request curl local para confirmar que los headers aparecen.

PASO 2 — Ocultar versión de PHP y stack

Problema: El header X-Powered-By: PHP/8.4.11 y Server: LiteSpeed revelan el stack exacto.

Qué hacer:

  1. En el archivo public/.htaccess, agregar al inicio (después de <IfModule mod_rewrite.c> o antes):
# Ocultar headers de servidor
<IfModule mod_headers.c>
    Header unset X-Powered-By
    Header always unset X-Powered-By
    Header unset Server
</IfModule>
  1. Si tenés acceso a php.ini (en Hostinger hPanel → PHP → php.ini personalizado):
expose_php = Off
  1. Si usás un archivo php.ini en la raíz del proyecto, agregar esa línea ahí también.

PASO 3 — Forzar HTTPS y HSTS en .htaccess

Problema: El sitio no sirve headers HSTS. El HSTS del Paso 1 solo actúa cuando ya estás en HTTPS, pero si alguien entra por HTTP no hay redirect forzado a nivel servidor.

Qué hacer:

En public/.htaccess, asegurar que exista este bloque (Laravel suele tenerlo pero verificar):

<IfModule mod_rewrite.c>
    RewriteEngine On

    # Forzar HTTPS
    RewriteCond %{HTTPS} off
    RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

    # Resto de reglas de Laravel...
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [L]
</IfModule>

PASO 4 — Proteger archivo .env y rutas sensibles

Problema: Laravel ya protege .env por defecto, pero verificar que las rutas de storage no sean accesibles.

Qué hacer:

En public/.htaccess, agregar:

# Bloquear acceso a archivos sensibles
<FilesMatch "\.(env|log|git|gitignore|htpasswd|DS_Store)$">
    Order allow,deny
    Deny from all
</FilesMatch>

# Bloquear acceso directo a /storage si está symlinkado
<IfModule mod_rewrite.c>
    RewriteRule ^storage/app/(.*)$ - [F,L]
</IfModule>

PASO 5 — Mejorar robots.txt

Problema: El robots.txt actual tiene Disallow: "" (vacío), lo que permite que todos los bots indexen todo, incluidas rutas como /recuperar y rutas de admin.

Qué hacer:

Reemplazar el contenido de public/robots.txt (o crearlo si no existe):

User-agent: *
Disallow: /admin
Disallow: /recuperar
Disallow: /api/
Disallow: /storage/
Disallow: /vendor/
Allow: /

Sitemap: https://onapb.com/sitemap.xml

PASO 6 — Agregar security.txt

Problema: No existe /well-known/security.txt. Es una buena práctica de divulgación responsable.

Qué hacer:

  1. Crear el directorio public/.well-known/
  2. Crear el archivo public/.well-known/security.txt:
Contact: mailto:admin@onapb.com
Expires: 2027-04-01T00:00:00Z
Preferred-Languages: es, en
Canonical: https://onapb.com/.well-known/security.txt

(Reemplazar admin@onapb.com con el email real del responsable de seguridad.)


PASO 7 — Rate limiting en rutas críticas

Problema: No se detectó rate limiting explícito. Rutas como /recuperar (recuperación de contraseña) son vectores de ataque por fuerza bruta y enumeración de usuarios.

Qué hacer:

En routes/web.php o en el middleware de rutas, asegurar rate limiting en rutas sensibles:

Route::middleware(['throttle:5,1'])->group(function () {
    Route::post('/recuperar', [PasswordController::class, 'sendResetLink']);
    Route::post('/login', [AuthController::class, 'login']);
});

El 5,1 significa 5 intentos por minuto. Ajustar según la lógica del negocio.

Si el proyecto usa Laravel Breeze o Fortify, ya tiene esto incorporado — verificar que esté habilitado en config/fortify.php:

'limiters' => [
    'login' => 'login',
],

PASO 8 — Configurar Content Security Policy estricta (opcional pero recomendado)

Problema: La CSP del Paso 1 usa 'unsafe-inline' y 'unsafe-eval' como fallback seguro. Si el proyecto no necesita eval, endurecerla.

Qué hacer:

Una vez que el sitio esté corriendo con el middleware del Paso 1, revisar la consola del navegador por errores de CSP. Si no hay errores de scripts inline, cambiar en SecurityHeaders.php:

"script-src 'self';" // quitar unsafe-inline y unsafe-eval

Esto requiere que todos los scripts estén en archivos externos, no inline.


Pasos que requieren acción MANUAL (fuera del repositorio)

Estos cambios no se hacen en el código — son configuraciones del servidor/DNS. Documentarlos para hacerlos en paralelo:

A — Cerrar puerto 3306 (CRÍTICO — hacer YA)

  • En Hostinger hPanel → Seguridad → Firewall
  • Bloquear acceso externo al puerto 3306 (MySQL)
  • Solo debe aceptar conexiones desde 127.0.0.1 (localhost)
  • Verificar en .env: DB_HOST=127.0.0.1

B — Deshabilitar FTP (puerto 21)

  • En Hostinger hPanel → Archivos → Administrador FTP
  • Deshabilitar FTP o cambiar a SFTP (puerto 22)

C — Migrar DNS a Cloudflare (recomendado para WAF)

  • Crear cuenta en cloudflare.com
  • Agregar el dominio onapb.com
  • Cambiar los nameservers en Registrar.eu (registrar actual) a los de Cloudflare
  • Habilitar WAF en plan gratuito
  • Habilitar DNSSEC en Cloudflare (un click)
  • Habilitar proxy (nube naranja) para ocultar IP real

D — Configurar DMARC + endurecer SPF

En el panel DNS (Hostinger o Cloudflare):

Cambiar SPF existente (~all-all):

v=spf1 include:_spf.mail.hostinger.com -all

Agregar registro DMARC (nuevo TXT en _dmarc.onapb.com):

v=DMARC1; p=quarantine; rua=mailto:admin@onapb.com; fo=1

E — Verificar DKIM

En Hostinger hPanel → Email → Configuración de dominio → habilitar DKIM si no está activo.


Orden de deploy

Una vez implementados los pasos de código (1 al 8):

  1. Correr tests: php artisan test
  2. Verificar que la app levanta: php artisan serve + revisar consola del browser
  3. Hacer commit con mensaje: security: add security headers middleware and hardening
  4. Push a rama de staging si existe, sino directo a main
  5. Deploy a producción (según el proceso actual del proyecto)
  6. Verificar headers en producción: usar securityheaders.com con onapb.com
  7. Verificar que el sitio funciona 100% antes de cerrar puertos (paso manual A)

Verificación post-deploy

Correr estos checks para confirmar que todo quedó bien:

Check Herramienta URL
Headers HTTP Security Headers securityheaders.com
SSL/TLS SSL Labs ssllabs.com/ssltest
HSTS hstspreload.org hstspreload.org
Reputación IP VirusTotal virustotal.com
Puertos Shodan shodan.io

Resultado esperado

Al completar todos los pasos, el sitio debe:

  • Obtener calificación A en securityheaders.com (actualmente F)
  • No exponer versión de PHP ni stack en headers
  • Tener HSTS activo con preload
  • No tener puertos de base de datos expuestos a internet
  • Tener protección WAF básica via Cloudflare
  • Tener autenticación de email (SPF + DKIM + DMARC)