Implantación de Aplicaciones Web
Curso 2026/2027
mod_reqtimeout y
mod_evasiveEl bastionado o endurecimiento de sistemas (hardening) es el conjunto de técnicas, directivas de configuración y buenas prácticas aplicadas a un sistema informático para reducir al mínimo su superficie de exposición y mitigar posibles vulnerabilidades frente a ciberataques.
El servidor web Apache
HTTP Server (denominado comúnmente apache2 en
distribuciones GNU/Linux de la familia Debian y Ubuntu) es uno de los
servidores web más extendidos en Internet. Tras una instalación básica
por defecto, el servidor se entrega configurado para maximizar la
compatibilidad y facilitar una rápida puesta en marcha. No obstante,
esta configuración inicial incluye módulos innecesarios, cabeceras
informativas excesivas y directivas permisivas que resultan inseguras en
un entorno de producción real.
El objetivo de este documento es proporcionar a los estudiantes y administradores de sistemas una guía profesional, completa y realista para securizar un servidor web Apache. Las medidas descritas están alineadas con los estándares internacionales de referencia de la industria, incluyendo:
A la hora de diseñar y mantener la configuración de seguridad de un servidor web debemos guiarnos por cuatro principios clave:
root.| Vector de ataque | Descripción | Impacto |
|---|---|---|
| Reconocimiento (fingerprinting) | Identificación de versiones exactas de Apache, sistema operativo y lenguajes backend (PHP). | Facilita la búsqueda y ejecución de exploits públicos (CVEs). |
| Denegación de servicio (DoS / Slowloris) | Agotamiento de procesos e hilos de trabajo mediante el envío lento o masivo de cabeceras/peticiones. | Caída del servicio e indisponibilidad total para usuarios legítimos. |
| Fuga de información y path traversal | Acceso no autorizado a ficheros del sistema
(/etc/passwd), copias de seguridad (.bak,
.sql) o ficheros de configuración (.git,
.env). |
Filtración de credenciales de base de datos, código fuente y datos confidenciales. |
| Ejecución de web shells en subidas | Subida y posterior ejecución de scripts maliciosos
(.php, .phtml) a través de formularios de
subida de archivos. |
Ejecución remota de código (RCE) y toma de control del servidor. |
| Ataques de inyección web (SQLi, XSS, LFI) | Explotación de vulnerabilidades en el código de la aplicación web alojada. | Extracción de bases de datos, alteración de la web y robo de sesiones. |
| Ataques criptográficos a TLS/SSL | Uso de protocolos obsoletos (SSLv3, TLS 1.0, TLS 1.1) o cifrados vulnerables (RC4, 3DES, CBC). | Interceptación y descifrado de credenciales y datos en tránsito (Man-in-the-Middle). |
| Clickjacking / MIME-sniffing | Incrustación del sitio en marcos invisibles (iframes) o manipulación del tipo de contenido por el navegador. | Secuestro de clics, phishing y ejecución de scripts camuflados. |
En las distribuciones GNU/Linux de la familia Debian/Ubuntu, Apache
organiza su configuración en el directorio /etc/apache2/
siguiendo una arquitectura modular y jerárquica gestionada mediante
enlaces simbólicos:
/etc/apache2/
├── apache2.conf # Archivo de configuración maestro principal
├── ports.conf # Definición de puertos de escucha (Listen 80, 443)
├── envvars # Variables de entorno del proceso (usuario, grupo, logs)
├── magic # Firmas para detección de tipos MIME
├── conf-available/ # Fragmentos de configuración global disponibles
│ ├── security.conf # Parámetros de seguridad generales (ServerTokens, etc.)
│ └── security-headers.conf # Cabeceras de seguridad HTTP personalizadas
├── conf-enabled/ # Enlaces simbólicos a conf-available/ activos
├── mods-available/ # Módulos compilados disponibles en el sistema
├── mods-enabled/ # Enlaces simbólicos a mods-available/ activos
├── sites-available/ # Archivos de configuración de VirtualHosts disponibles
│ ├── 000-default.conf # VirtualHost HTTP por defecto (o catch-all)
│ └── default-ssl.conf # VirtualHost HTTPS por defecto
└── sites-enabled/ # Enlaces simbólicos a sites-available/ activos
Para gestionar configuraciones, módulos y sitios web de forma limpia y mantenible, se utilizan las herramientas nativas de Debian/Ubuntu:
# Gestión de sitios web (VirtualHosts)
sudo a2ensite <nombre-sitio>.conf
sudo a2dissite <nombre-sitio>.conf
# Gestión de módulos
sudo a2enmod <nombre-modulo>
sudo a2dismod <nombre-modulo>
# Gestión de fragmentos de configuración global
sudo a2enconf <nombre-configuracion>
sudo a2disconf <nombre-configuracion>
# Comprobación de sintaxis de la configuración (IMPRESCINDIBLE antes de reiniciar)
sudo apache2ctl configtest
# Recarga (sin corte de servicio) o reinicio del servicio
sudo systemctl reload apache2
sudo systemctl restart apache2[!IMPORTANT] Ejecuta siempre
sudo apache2ctl configtest(osudo apachectl -t) antes de recargar o reiniciar el servicio Apache. Si existe algún error sintáctico o una directiva incompatible, este comando te advertirá de inmediato y evitará una caída inesperada del servicio en producción.
El primer paso fundamental en cualquier procedimiento de bastionado consiste en mantener el sistema operativo y el software del servidor actualizados con los últimos parches de seguridad:
Para consultar la versión instalada de Apache y los módulos compilados de forma estática:
Cuando el sistema operativo arranca, el proceso maestro de Apache se
inicia como superusuario root con el único propósito de
enlazar los puertos privilegiados TCP 80 y 443. Inmediatamente después,
el servidor genera procesos hijos o hilos de trabajo (worker
threads) que se ejecutan bajo un usuario sin privilegios.
En Ubuntu y Debian, este usuario y grupo se configuran en
/etc/apache2/envvars:
Podemos verificar que los procesos de atención de peticiones se
ejecutan bajo www-data mediante el comando
ps:
La salida mostrará un único proceso principal perteneciente a
root y múltiples procesos trabajadores pertenecientes a
www-data.
[!CAUTION] Nunca cambies
APACHE_RUN_USERarootni permitas que los procesos hijos atiendan peticiones con privilegios de administración. Si un atacante lograra ejecutar código remoto a través de una vulnerabilidad web, obtendría acceso total e inmediato como superusuario a toda la máquina.
Un error clásico de administración consiste en otorgar permisos
777 o asignar la propiedad total del
DocumentRoot (/var/www/html) al usuario
www-data. Si el servidor web tiene permisos de escritura en
todos los archivos, un atacante que explote una vulnerabilidad en una
aplicación PHP podría sobrescribir ficheros .php, inyectar
puertas traseras (web shells) o modificar archivos de
configuración.
/etc/apache2)Los archivos de configuración de /etc/apache2 deben
pertenecer exclusivamente a root:root y no ser modificables
por el usuario www-data:
sudo chown -R root:root /etc/apache2
sudo find /etc/apache2 -type d -exec chmod 755 {} \;
sudo find /etc/apache2 -type f -exec chmod 644 {} \;[!WARNING] Protección estricta de claves privadas TLS/SSL: Las claves privadas criptográficas (
.key) nunca deben tener permisos644. Si almacenas certificados y claves privadas en/etc/ssl/privateo dentro de/etc/apache2/ssl/, asegúrate de que las claves privadas pertenezcan aroot:ssl-certoroot:rootcon permisos600o640, de modo que ningún usuario no autorizado del sistema pueda leerlas.
DocumentRoot)755
(rwxr-xr-x) o 750 en entornos con múltiples
usuarios locales.644
(rw-r--r--) o 640.root o un
usuario de despliegue administrativo (deploy),
perteneciendo al grupo www-data.uploads/ o media/)
deben tener permisos de escritura para www-data.# Establecer propietario y grupo
sudo chown -R root:www-data /var/www/html
# Ajustar permisos estándar para directorios y archivos
sudo find /var/www/html -type d -exec chmod 755 {} \;
sudo find /var/www/html -type f -exec chmod 644 {} \;
# Directorios de subida específicos que requieren escritura:
sudo chown -R www-data:www-data /var/www/html/uploads
sudo chmod -R 755 /var/www/html/uploadsUna de las técnicas más frecuentes de intrusión consiste en
aprovechar un formulario de subida de ficheros para subir un script PHP
malicioso (por ejemplo shell.php o c99.php) a
la carpeta uploads/ y solicitarlo vía HTTP para obtener una
terminal interactiva.
Aunque la carpeta tenga permisos de escritura para
www-data, debemos prohibir taxativamente a Apache que
ejecute cualquier tipo de script en ese directorio.
Añadimos la siguiente directiva dentro de la configuración del
VirtualHost o en
/etc/apache2/conf-available/security.conf:
# Bloquear la ejecución de scripts en directorios de subida de archivos
<Directory "/var/www/html/uploads">
# Desactivar ejecución de scripts CGI e includes
Options -ExecCGI -Includes
# Denegar acceso directo a cualquier extensión ejecutable
<FilesMatch "(?i)\.(php|phtml|phar|php3|php4|php5|php7|php8|cgi|pl|py|sh|jsp|asp|exe)$">
Require all denied
</FilesMatch>
# Forzar a Apache a tratar cualquier fichero restante como estático
ForceType application/octet-stream
<IfModule mod_php.c>
php_admin_flag engine off
</IfModule>
</Directory>Con esta regla, incluso si un atacante consigue subir un fichero
.php, cualquier intento de solicitarlo devolverá un error
403 Forbidden y el código nunca será ejecutado por el
servidor.
Por defecto, Apache incluye en la cabecera HTTP Server y
en los mensajes de error el nombre del servidor, la versión exacta de
Apache, el sistema operativo de base y las versiones de los módulos
instalados (por ejemplo:
Apache/2.4.58 (Ubuntu) OpenSSL/3.0.13 PHP/8.3.6). Esta
información facilita la fase de reconocimiento (fingerprinting)
a posibles atacantes.
| Configuración | Cabecera HTTP Server emitida |
Nivel de exposición |
|---|---|---|
| Por defecto (Insegura) | Server: Apache/2.4.58 (Ubuntu) mod_ssl/2.4.58 OpenSSL/3.0.13 PHP/8.3.6 |
Inseguro / Crítico (expone versiones de Apache, SO, OpenSSL y PHP) |
| Bastionada (Segura) | Server: Apache |
Seguro (mínimo permitido por el estándar HTTP) |
ServerTokens y ServerSignaturePara limitar al mínimo la información expuesta, editamos el archivo
/etc/apache2/conf-available/security.conf:
# Opciones disponibles para ServerTokens:
# Full -> Apache/2.4.58 (Ubuntu) PHP/8.3.6 mod_ssl/2.4.58
# OS -> Apache/2.4.58 (Ubuntu)
# Minor -> Apache/2.4
# Min -> Apache/2.4.58
# Prod -> Apache (Mínima información permitida por el estándar HTTP)
ServerTokens Prod
ServerSignature OffServerTokens Prod: Configura la
cabecera HTTP Server para que devuelva únicamente la
palabra genérica Apache, ocultando la versión exacta, la
distribución Linux y los módulos.ServerSignature Off: Desactiva el pie
de página que Apache genera en las páginas de error (404, 403, 500)
donde tradicionalmente se mostraba la versión, el nombre del host y el
puerto de escucha.X-Powered-ByLos intérpretes de backend como PHP añaden por defecto la cabecera
X-Powered-By: PHP/8.x.x. Debemos desactivarla en el archivo
de configuración php.ini correspondiente:
Adicionalmente, podemos forzar a Apache a eliminar cualquier cabecera
X-Powered-By residual generada por aplicaciones o proxies
inversos mediante mod_headers:
ErrorDocument)Las páginas de error estándar de Apache pueden revelar detalles sobre la estructura interna del servidor y rutas del sistema operativo. Es una buena práctica redirigir los errores comunes a documentos HTML genéricos y amigables:
# En /etc/apache2/conf-available/security.conf o dentro de cada VirtualHost
ErrorDocument 400 /errores/400.html
ErrorDocument 401 /errores/401.html
ErrorDocument 403 /errores/403.html
ErrorDocument 404 /errores/404.html
ErrorDocument 500 /errores/500.htmlLa página predeterminada de instalación
(/var/www/html/index.html) indica que el servidor acaba de
ser instalado y expone la estructura de archivos de Ubuntu/Debian.
Debemos sustituirla por el contenido real de la aplicación o
eliminarla:
El control de acceso al sistema de archivos es una de las defensas más críticas de Apache. Si no se restringe adecuadamente, un atacante podría solicitar archivos del sistema base fuera del directorio web a través de vulnerabilidades de cruce de directorios (Directory Traversal).
En el archivo principal /etc/apache2/apache2.conf,
debemos asegurarnos de que la directiva para la raíz del sistema de
ficheros (/) bloquee todo el tráfico por defecto:
Esta directiva implementa el principio de denegación por defecto
(deny all by default): ningún usuario web puede leer
absolutamente nada del disco salvo que se autorice explícitamente en un
bloque <Directory> posterior.
DocumentRootPara el directorio donde residen las páginas web públicas
(/var/www/html), configuramos permisos selectivos:
<Directory /var/www/html>
Options -Indexes -FollowSymLinks +SymLinksIfOwnerMatch -Includes -ExecCGI
AllowOverride None
Require all granted
</Directory>Analicemos en detalle cada opción de seguridad:
-Indexes: Desactiva el listado
automático de directorios. Si un usuario accede a una carpeta
que no contiene un fichero index.html o
index.php, Apache no mostrará el listado de archivos
contenidos, devolviendo un error 403 Forbidden. Esto
previene la enumeración de archivos de la aplicación.-FollowSymLinks +SymLinksIfOwnerMatch:
Desactiva el seguimiento incondicional de enlaces simbólicos. Con
SymLinksIfOwnerMatch, Apache solo seguirá un enlace
simbólico si el propietario del enlace coincide con el propietario del
archivo destino, evitando que un usuario web cree un enlace simbólico
que apunte a /etc/passwd o /etc/shadow.-Includes: Desactiva los Server
Side Includes (SSI), evitando la inyección de directivas del lado
del servidor en archivos HTML.-ExecCGI: Deshabilita la ejecución de
scripts CGI arbitrarios en este directorio.AllowOverride None: Deshabilita el uso
de archivos .htaccess. Los archivos .htaccess
reducen el rendimiento del servidor (Apache debe inspeccionar cada
directorio en cada petición) y permiten a usuarios locales anular
directivas de seguridad. Toda la configuración debe residir en los
archivos principales del servidor.[!WARNING] Si tu aplicación (por ejemplo, WordPress o Drupal) necesita reglas de reescritura de URLs mediante
.htaccess, limitaAllowOverrideúnicamente a lo necesario, comoAllowOverride FileInfo Limit Options=SymLinksIfOwnerMatchen lugar deAllowOverride All.
Es habitual que durante el despliegue de aplicaciones queden
expuestos archivos sensibles de control de versiones
(.git), variables de entorno (.env), volcados
de bases de datos (.sql) o copias de seguridad de editores
(.bak, .swp).
Podemos bloquear globalmente el acceso a todos estos patrones
añadiendo la siguiente configuración en
/etc/apache2/conf-available/security.conf:
# Bloquear acceso a archivos y directorios ocultos (.git, .env, .htaccess)
<FilesMatch "^\.">
Require all denied
</FilesMatch>
# Excepción explícita para el reto HTTP-01 de Let's Encrypt / Certbot (.well-known)
<Directory "/var/www/html/.well-known">
Require all granted
</Directory>
# Bloquear extensiones de archivos sensibles, volcados y copias de seguridad
<FilesMatch "(?i)\.(bak|config|sql|fla|psd|ini|log|sh|env|swp|dist|inc|yaml|yml|md|tpl)$">
Require all denied
</FilesMatch>Cuando un atacante escanea rangos de direcciones IP o envía
peticiones con una cabecera Host manipulada (Host
Header Injection), Apache recurre por defecto al primer VirtualHost
cargado en orden alfabético (generalmente
000-default.conf).
Para neutralizar escaneos masivos y evitar fugas de información, se
recomienda configurar un VirtualHost por defecto atrapa-todo
(catch-all) que capture cualquier petición dirigida
directamente a la dirección IP o con un nombre de dominio desconocido,
respondiendo de inmediato con un error 403 Forbidden o
400 Bad Request:
# /etc/apache2/sites-available/000-catchall.conf
<VirtualHost *:80>
ServerName default
ServerAlias *
# Denegar cualquier petición no dirigida a un dominio autorizado
<Location />
Require all denied
</Location>
ErrorLog ${APACHE_LOG_DIR}/catchall_error.log
CustomLog ${APACHE_LOG_DIR}/catchall_access.log combined
</VirtualHost>Posteriormente, habilitamos este sitio como el primero de la lista
con sudo a2ensite 000-catchall.conf. De este modo, los
VirtualHosts legítimos solo responderán cuando la cabecera
Host coincida exactamente con su ServerName o
ServerAlias configurado.
Cada módulo cargado en Apache consume memoria RAM y añade código que puede contener vulnerabilidades de seguridad potenciales. Siguiendo el principio de minimización, debemos auditar y desactivar todos los módulos que no sean indispensables.
mpm_event y PHP-FPMEn versiones modernas de Apache (2.4+) sobre Debian/Ubuntu, la
arquitectura recomendada consiste en utilizar el módulo de
multiprocesamiento mpm_event junto con
PHP-FPM a través de mod_proxy_fcgi, en
lugar del módulo obsoleto mpm_prefork con
libapache2-mod-php.
| Característica | mpm_prefork +
libapache2-mod-php (Obsoleto) |
mpm_event + PHP-FPM /
proxy_fcgi (Recomendado) |
|---|---|---|
| Modelo de concurrencia | Proceso pesado dedicado por conexión | Hilos asíncronos ligeros sin bloqueo |
| Consumo de memoria | Muy alto (duplica el intérprete PHP en cada proceso) | Mínimo y optimizado |
| Resistencia DoS/Slowloris | Muy baja (agotamiento rápido de procesos) | Muy alta (descriptores de socket no bloqueantes) |
| Aislamiento de seguridad | PHP se ejecuta en el mismo espacio que Apache | PHP aislado en un proceso/socket Unix independiente |
| Soporte HTTP/2 | No compatible | Soporte nativo completo |
Para migrar a mpm_event y PHP-FPM:
# Deshabilitar módulo PHP clásico y MPM prefork
sudo a2dismod php8.x mpm_prefork
# Habilitar MPM event, proxy FastCGI y configuración PHP-FPM
sudo a2enmod mpm_event proxy proxy_fcgi setenvif
sudo a2enconf php8.x-fpm
sudo systemctl restart apache2Para listar todos los módulos compilados y cargados actualmente en memoria:
A continuación se detallan los módulos que deben revisarse o desactivarse en la mayoría de entornos de producción:
| Módulo | Función | Riesgo de seguridad | Acción recomendada |
|---|---|---|---|
mod_autoindex |
Genera listados HTML de directorios. | Facilita la enumeración de archivos y carpetas del servidor. | Desactivar (a2dismod autoindex). |
mod_info |
Muestra la configuración completa del servidor en
/server-info. |
Fuga crítica de información y rutas internas. | Desactivar en producción
(a2dismod info). |
mod_status |
Muestra estadísticas y peticiones activas en
/server-status. |
Fuga de IPs de clientes, URLs consultadas y carga del sistema. | Restringir por IP o desactivar. |
mod_userdir |
Mapea URLs tipo /~usuario/ al directorio
/home/usuario/public_html. |
Facilita la enumeración de usuarios locales del sistema Linux. | Desactivar (a2dismod userdir). |
mod_cgi / mod_cgid |
Ejecuta scripts CGI tradicionales en
/cgi-bin/. |
Riesgo de ejecución de código y consumo excesivo de recursos. | Desactivar si se usa PHP-FPM
(a2dismod cgi cgid). |
mod_include |
Ejecuta directivas Server Side Includes (SSI). | Riesgo de inyección de código del lado del servidor. | Desactivar si no se usa
(a2dismod include). |
mod_dav / mod_dav_fs |
Protocolo WebDAV para gestión remota de archivos. | Riesgo de subida y modificación no autorizada de ficheros. | Desactivar si no se requiere
(a2dismod dav dav_fs). |
Para desactivar los módulos innecesarios de forma segura:
sudo a2dismod autoindex status info userdir cgi cgid include dav dav_fs
sudo systemctl restart apache2[!CAUTION] Si utilizas el módulo
mod_proxy, verifica que la directivaProxyRequests Offesté establecida a nivel global. DejarProxyRequests Onconvierte tu servidor en un Proxy Abierto (Open Proxy), permitiendo a ciberdelincuentes utilizar tu infraestructura como pasarela anónima para lanzar ataques a terceros.
mod_statusSi requieres mantener activo mod_status para
herramientas de monitorización interna (como Prometheus, Zabbix o
Nagios), debes restringir estrictamente el acceso para que solo responda
a peticiones desde localhost o desde la IP específica del
servidor de monitorización:
El protocolo HTTP define diversos métodos de petición
(GET, POST, HEAD,
PUT, DELETE, OPTIONS,
TRACE, PATCH, CONNECT). La
inmensa mayoría de sitios web estándar únicamente necesitan procesar
peticiones GET, POST y HEAD.
El método TRACE se diseñó para depuración de red: el
servidor web devuelve al cliente el contenido exacto de la petición
recibida. Sin embargo, este comportamiento permite a un atacante que
ejecute código JavaScript (vía XSS) eludir la protección de cookies
marcadas como HttpOnly mediante técnicas de Cross-Site
Tracing (XST).
Para desactivar el método TRACE en todo el servidor,
editamos /etc/apache2/conf-available/security.conf:
Podemos restringir de forma estricta los métodos admitidos en el
DocumentRoot mediante la directiva
<LimitExcept>:
<Directory /var/www/html>
# Si la aplicación web utiliza APIs o peticiones CORS, incluye OPTIONS
<LimitExcept GET POST HEAD OPTIONS>
Require all denied
</LimitExcept>
</Directory>Si la aplicación ofrece una API REST que requiere métodos como
PUT, DELETE o PATCH, dichos
métodos deben autorizarse exclusivamente dentro del bloque
<Location /api> correspondiente, manteniéndolos
bloqueados en el resto del sitio:
Las cabeceras de seguridad HTTP son instrucciones que el servidor web envía en la respuesta HTTP para obligar al navegador del cliente a activar mecanismos de protección integrados. Su correcta implementación mitiga ataques tan extendidos como el Clickjacking, Cross-Site Scripting (XSS), inyección de datos y MIME-sniffing.
Para gestionar cabeceras en Apache, es imprescindible habilitar el
módulo mod_headers:
A continuación, crearemos un archivo de configuración dedicado
/etc/apache2/conf-available/security-headers.conf:
# /etc/apache2/conf-available/security-headers.conf
<IfModule mod_headers.c>
# 1. HTTP Strict Transport Security (HSTS) - Solo sobre conexiones HTTPS
<If "%{HTTPS} == 'on'">
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
</If>
# 2. Protección contra Clickjacking
Header always set X-Frame-Options "SAMEORIGIN"
# 3. Protección contra MIME-Sniffing
Header always set X-Content-Type-Options "nosniff"
# 4. Política de Referrer
Header always set Referrer-Policy "strict-origin-when-cross-origin"
# 5. Restricción de APIs y Hardware del Navegador (Permissions-Policy)
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()"
# 6. Aislamiento de Origen Cruzado (COOP y CORP)
Header always set Cross-Origin-Opener-Policy "same-origin"
Header always set Cross-Origin-Resource-Policy "same-origin"
# 7. Content Security Policy (CSP)
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'self'; form-action 'self'; base-uri 'self';"
# 8. Desactivar el filtro XSS obsoleto y propenso a vulnerabilidades en navegadores antiguos
Header always set X-XSS-Protection "0"
# 9. Asegurar Cookies de Sesión (Inyectar Secure, HttpOnly y SameSite)
Header edit Set-Cookie ^(.*)$ "$1; Secure; HttpOnly; SameSite=Lax"
</IfModule>Habilitamos la configuración y recargamos Apache:
| Cabecera HTTP | Propósito y mitigación de seguridad |
|---|---|
| Strict-Transport-Security (HSTS) | Obliga al navegador a usar siempre HTTPS durante el tiempo indicado (31536000s = 1 año), impidiendo ataques de degradación SSL Stripping. |
| X-Frame-Options | Evita que el sitio sea incrustado en
<iframe> o <frame> externos,
neutralizando por completo ataques de Clickjacking. |
| X-Content-Type-Options | Fija el valor nosniff, impidiendo que el
navegador intente adivinar el tipo MIME de un fichero y ejecute scripts
camuflados como imágenes. |
| Referrer-Policy | Controla qué información de la URL de origen se envía
en la cabecera Referer al navegar hacia enlaces externos,
protegiendo tokens y rutas sensibles. |
| Permissions-Policy | Desactiva el acceso a APIs de hardware sensibles del cliente como la cámara, el micrófono, el sensor de geolocalización o pasarelas de pago. |
| Content-Security-Policy (CSP) | Especifica exactamente de qué orígenes autorizados el navegador puede cargar scripts, estilos, imágenes y fuentes. Máxima protección frente a XSS e inyecciones. |
| SameSite / Secure / HttpOnly en Set-Cookie | Protege las cookies de sesión contra robo mediante XSS
(HttpOnly), interceptación en tránsito
(Secure) y ataques CSRF (SameSite=Lax). |
curlPodemos comprobar rápidamente desde el terminal que nuestro servidor emite todas las cabeceras correctamente:
El cifrado en tránsito mediante el protocolo HTTPS es obligatorio en la web actual. No obstante, habilitar HTTPS no es suficiente por sí solo: es necesario garantizar que la configuración criptográfica utilice únicamente protocolos y algoritmos seguros.
# Módulos SSL, almacenamiento en caché de sesiones y soporte para HTTP/2
sudo a2enmod ssl
sudo a2enmod socache_shmcb
sudo a2enmod http2Debemos deshabilitar de forma categórica protocolos vulnerables como SSLv2, SSLv3, TLS 1.0 y TLS 1.1, permitiendo exclusivamente TLS 1.2 y TLS 1.3.
Editamos el archivo global de configuración SSL
/etc/apache2/mods-available/ssl.conf o la configuración de
nuestro VirtualHost HTTPS:
# Protocolos permitidos (Solo TLS 1.2 y TLS 1.3)
SSLProtocol -all +TLSv1.2 +TLSv1.3
# Cifrados seguros para TLS 1.2 (con secreto perfecto hacia adelante - PFS)
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
# Preferencia de cifrados (en OpenSSL moderno con TLS 1.3 se recomienda off)
SSLHonorCipherOrder off
# Desactivar compresión SSL para neutralizar ataques CRIME / BREACH
SSLCompression off
# Desactivar Session Tickets si no son estrictamente necesarios
SSLSessionTickets offEl grapado OCSP (OCSP stapling) mejora significativamente el rendimiento y la privacidad del usuario. En lugar de que cada navegador contacte con la Autoridad de Certificación (CA) para comprobar si el certificado ha sido revocado, el propio servidor web Apache consulta periódicamente el estado de revocación a la CA y adjunta la respuesta firmada directamente durante el saludo TLS (TLS Handshake).
Añadimos la siguiente configuración a nivel global (en
/etc/apache2/conf-available/ssl-stapling.conf):
<IfModule mod_ssl.c>
SSLUseStapling on
SSLStaplingCache "shmcb:${APACHE_RUN_DIR}/ssl_stapling(32768)"
SSLStaplingResponseTime 5
SSLStaplingResponderTimeout 5
SSLStaplingReturnUntrusted off
</IfModule>El protocolo HTTP/2 ofrece mejoras drásticas de rendimiento (multiplexación de peticiones sobre una única conexión TCP, compresión de cabeceras HPACK) y exige de forma obligatoria el uso de TLS moderno:
Todo el tráfico que llegue al puerto TCP 80 debe ser redirigido
inmediatamente al puerto seguro 443 con un código de estado
301 Moved Permanently:
Un servidor web Apache mal parametrizado puede ser fácilmente inutilizado mediante ataques de Denegación de Servicio basados en el agotamiento de descriptores de sockets, procesos y memoria (Slowloris, Slow HTTP POST o inundaciones de peticiones).
En /etc/apache2/apache2.conf, reducimos los tiempos de
espera excesivos por defecto:
# Tiempo máximo (en segundos) que Apache esperará para recibir una petición
Timeout 30
# Habilitar conexiones HTTP persistentes (Keep-Alive)
KeepAlive On
# Número máximo de peticiones por conexión persistente
MaxKeepAliveRequests 100
# Tiempo de espera (en segundos) para la siguiente petición en la misma conexión
KeepAliveTimeout 5Para prevenir desbordamientos de búfer y subidas masivas de datos que consuman el ancho de banda del servidor, establecemos límites estrictos:
# Limita el tamaño del cuerpo de la petición HTTP a 10 MB (10485760 bytes)
# Ajustar según las necesidades reales de subida de la aplicación
LimitRequestBody 10485760
# Limita el tamaño de la línea de petición HTTP (URI) a 4094 bytes
LimitRequestLine 4094
# Limita el número de campos en las cabeceras HTTP a 50
LimitRequestFields 50
# Limita el tamaño máximo de cada campo de cabecera a 8190 bytes
LimitRequestFieldSize 8190mod_reqtimeoutEl ataque Slowloris mantiene abiertas miles de
conexiones HTTP enviando cabeceras a una velocidad extremadamente lenta
(1 byte cada pocos segundos), agotando todos los hilos de Apache. El
módulo nativo mod_reqtimeout neutraliza esta técnica
exigiendo un tiempo máximo y una tasa mínima de transferencia de
datos:
Configuramos en
/etc/apache2/mods-available/reqtimeout.conf:
<IfModule mod_reqtimeout.c>
# Espera un máximo de 20s a 40s para recibir cabeceras; si la tasa es menor de 500 bytes/s, cancela la conexión
RequestReadTimeout header=20-40,MinRate=500 body=20,MinRate=500
</IfModule>mod_evasivemod_evasive proporciona protección frente a ataques de
fuerza bruta, escaneo masivo de URLs y ataques DoS HTTP en capa de
aplicación.
Configuramos
/etc/apache2/mods-available/evasive.conf:
<IfModule mod_evasive20.c>
# Tamaño de la tabla hash interna
DOSHashTableSize 3097
# Número máximo de peticiones a una misma página en el intervalo (por defecto 1s)
DOSPageCount 10
DOSPageInterval 1
# Número máximo de peticiones totales a un mismo sitio en el intervalo (por defecto 1s)
DOSSiteCount 100
DOSSiteInterval 1
# Tiempo de bloqueo en segundos para una IP infractora (recibirá error 403 Forbidden)
DOSBlockingPeriod 60
# Directorio para registrar bloqueos temporales
DOSLogDir "/var/log/mod_evasive"
# Lista blanca de IPs que no deben ser bloqueadas (por ejemplo localhost o monitorización)
DOSWhitelist 127.0.0.1
</IfModule>[!TIP] Ajuste realista de umbrales en
mod_evasive: En sitios web modernos con abundantes elementos estáticos (múltiples archivos CSS, JS, fuentes e imágenes), un único acceso de un usuario legítimo puede disparar más de 40 peticiones simultáneas. EstablecerDOSSiteCountpor debajo de 50 provocaría falsos positivos (bloqueo a usuarios legítimos). Valores deDOSPageCount 10yDOSSiteCount 100ofrecen un equilibrio robusto y realista.
Creamos el directorio de logs con los permisos adecuados:
Un WAF (Web Application Firewall) opera en la capa 7 del modelo OSI (capa de aplicación), inspeccionando en tiempo real tanto el tráfico HTTP entrante como las respuestas salientes del servidor.
Mientras que un cortafuegos tradicional (como UFW o iptables) solo filtra puertos e IPs, ModSecurity analiza el contenido de las peticiones para detectar y bloquear patrones de inyección SQL (SQLi), Cross-Site Scripting (XSS), inclusión de archivos locales y remotos (LFI/RFI), y ataques contra vulnerabilidades de día cero (0-days).
En Ubuntu y Debian instalamos el módulo oficial:
Por defecto, ModSecurity viene configurado en modo solo detección
(DetectionOnly). En entornos profesionales se recomienda
seguir este procedimiento:
SecRuleEngine DetectionOnly durante unos días para
registrar las peticiones legítimas y detectar posibles falsos
positivos.SecRuleRemoveById <ID_REGLA>.SecRuleEngine On.# Copiar el archivo de plantilla recomendado
sudo cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
# Habilitar el motor de bloqueo (SecRuleEngine On)
sudo sed -i 's/SecRuleEngine DetectionOnly/SecRuleEngine On/' /etc/modsecurity/modsecurity.confEl OWASP CRS es el conjunto de reglas de seguridad más reconocido y mantenido del mundo para proteger servidores web.
Instalamos el paquete del CRS en el sistema:
Verificamos en
/etc/apache2/mods-available/security2.conf que las reglas
del CRS se carguen adecuadamente:
<IfModule security2_module>
SecDataDir /var/cache/modsecurity
IncludeOptional /etc/modsecurity/*.conf
IncludeOptional /usr/share/modsecurity-crs/*.conf
IncludeOptional /usr/share/modsecurity-crs/rules/*.conf
</IfModule>Reiniciamos el servicio:
Cualquier ataque bloqueado quedará detalladamente registrado en el archivo de auditoría:
Fail2Ban es un sistema de prevención de intrusiones
(IPS) que monitoriza continuamente los archivos de registro
(logs) de Apache y del sistema operativo. Cuando detecta
múltiples intentos de acceso fallidos, escaneos de vulnerabilidades o
patrones anómalos procedentes de una misma dirección IP, añade
automáticamente una regla en el cortafuegos de red
(nftables o iptables) para bloquear el tráfico
de esa IP durante un tiempo determinado.
En lugar de modificar el archivo original
/etc/fail2ban/jail.conf, creamos una copia de configuración
personalizada en /etc/fail2ban/jail.local:
# /etc/fail2ban/jail.local
[DEFAULT]
# Tiempo de baneo por defecto: 1 hora (3600 segundos)
bantime = 3600
# Ventana de tiempo para contar reintentos: 10 minutos (600 segundos)
findtime = 600
# Número máximo de intentos antes del baneo
maxretry = 5
# IPs en lista blanca que nunca serán bloqueadas
ignoreip = 127.0.0.1/8 ::1 192.168.1.0/24
# 1. Protección contra fuerza bruta en autenticación básica HTTP
[apache-auth]
enabled = true
port = http,https
logpath = %(apache_error_log)s
# 2. Bloqueo de escáneres maliciosos y bots dañinos
[apache-badbots]
enabled = true
port = http,https
logpath = %(apache_access_log)s
bantime = 86400
maxretry = 1
# 3. Bloqueo de escaneos a scripts inexistentes (.php, .asp, .cgi)
[apache-noscript]
enabled = true
port = http,https
logpath = %(apache_error_log)s
maxretry = 3
# 4. Bloqueo de intentos de desbordamiento de búfer en URIs
[apache-overflows]
enabled = true
port = http,https
logpath = %(apache_error_log)s
maxretry = 2
# 5. Integración con ModSecurity (baneo automático de atacantes detectados por el WAF)
[apache-modsecurity]
enabled = true
port = http,https
logpath = /var/log/apache2/modsec_audit.log
maxretry = 2Iniciamos y habilitamos el servicio:
Para comprobar el estado de las jaulas y consultar las IPs actualmente bloqueadas:
Los registros de actividad (logs) constituyen la principal fuente de información forense ante un incidente de seguridad y son indispensables para la monitorización continua.
En entornos donde Apache se encuentra tras un balanceador de carga,
CDN o proxy inverso (como Cloudflare, HAProxy o Nginx), la IP registrada
por defecto sería la del propio proxy. Es fundamental registrar la IP
real del cliente activando el módulo mod_remoteip:
Configuramos en
/etc/apache2/conf-available/remoteip.conf:
<IfModule mod_remoteip.c>
RemoteIPHeader X-Forwarded-For
# Declarar rangos de confianza de tus proxies/balanceadores internos
RemoteIPInternalProxy 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 127.0.0.1
</IfModule>En /etc/apache2/apache2.conf, definimos un formato de
log enriquecido que incluya el tiempo de respuesta del servidor en
microsegundos (%D):
# Formato combinado avanzado incluyendo tiempo de proceso (%D) y cabeceras completas
LogFormat "%a %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\" %D" combined_secure
CustomLog ${APACHE_LOG_DIR}/access.log combined_secure
# Nivel de detalle de errores: en producción se recomienda "warn" o "notice"
LogLevel warn
ErrorLog ${APACHE_LOG_DIR}/error.logLos registros nunca deben ser legibles por usuarios no privilegiados del sistema:
sudo chown -R root:adm /var/log/apache2
sudo chmod 750 /var/log/apache2
sudo chmod 640 /var/log/apache2/*.loglogrotateVerificamos la configuración de rotación en
/etc/logrotate.d/apache2 para evitar el agotamiento del
espacio en disco y garantizar la retención adecuada de evidencias para
auditorías:
/var/log/apache2/*.log {
daily
missingok
rotate 30
compress
delaycompress
notifempty
create 640 root adm
sharedscripts
postrotate
/usr/sbin/apache2ctl graceful > /dev/null
endscript
}
Una vez aplicadas las directivas de bastionado, es imprescindible auditar y validar la eficacia de las medidas adoptadas utilizando herramientas profesionales.
| Herramienta | Tipo | Objetivo principal |
|---|---|---|
| curl / httpie | Línea de comandos (CLI) | Inspección de cabeceras HTTP y códigos de respuesta. |
| testssl.sh | Script bash local (CLI) | Análisis exhaustivo de protocolos y suites TLS/SSL. |
| Nikto | Escáner de seguridad | Detección de ficheros sensibles y vulnerabilidades web. |
| Nmap NSE | Escáner de red | Auditoría de puertos abiertos y métodos HTTP admitidos. |
| Lynis | Auditoría de host | Evaluación integral del bastionado del SO y Apache. |
| SecurityHeaders.com | Servicio web online | Calificación y diagnóstico de cabeceras HTTP. |
| SSL Labs (Qualys) | Servicio web online | Calificación A+ de la configuración y certificados SSL. |
curl# Inspeccionar cabeceras HTTP y validar ausencia de versiones
curl -I https://midominio.com
# Probar si el método TRACE está desactivado (debe devolver 403 o 405)
curl -X TRACE https://midominio.com
# Intentar acceder a un archivo sensible bloqueado (debe devolver 403 Forbidden)
curl -I https://midominio.com/.env
curl -I https://midominio.com/.git/config
curl -I https://midominio.com/backup.sqltestssl.shtestssl.sh es una herramienta de software libre que
evalúa cualquier servicio TLS para detectar cifrados obsoletos y
vulnerabilidades conocidas (Heartbleed, POODLE, ROBOT, DROWN,
CRIME):
# Descarga y ejecución de testssl
git clone --depth 1 https://github.com/drwetter/testssl.sh.git
./testssl.sh/testssl.sh https://midominio.comNiktoLynisA continuación se resume la lista de verificación completa que todo administrador de sistemas debe validar antes del paso a producción:
| Nº | Medida de bastionado | Directiva / Comando clave | Archivo afectado |
|---|---|---|---|
| 1 | Actualización periódica del SO | apt update && apt upgrade -y |
Sistema operativo |
| 2 | Proceso bajo usuario sin privilegios | APACHE_RUN_USER=www-data |
/etc/apache2/envvars |
| 3 | Permisos restrictivos en DocumentRoot | Directorios 755, Ficheros
644, Propietario root:www-data |
/var/www/html |
| 4 | Bloqueo de ejecución en subidas (uploads) | <Directory "/uploads"> ForceType application/octet-stream ... |
conf-available/security.conf |
| 5 | Ocultar versión del servidor | ServerTokens Prod y
ServerSignature Off |
conf-available/security.conf |
| 6 | Ocultar cabeceras backend PHP | expose_php = Off /
Header always unset X-Powered-By |
php.ini /
security-headers.conf |
| 7 | Páginas de error personalizadas | ErrorDocument 404 /errores/404.html |
conf-available/security.conf |
| 8 | Denegación global por defecto | <Directory /> Require all denied </Directory> |
apache2.conf |
| 9 | Desactivar listado de directorios | Options -Indexes -FollowSymLinks +SymLinksIfOwnerMatch |
apache2.conf o VirtualHost |
| 10 | Desactivar .htaccess innecesarios |
AllowOverride None |
apache2.conf |
| 11 | Bloqueo de ficheros sensibles y ocultos | <FilesMatch "^\."> Require all denied </FilesMatch> |
conf-available/security.conf |
| 12 | VirtualHost catch-all / por defecto | 000-catchall.conf con
Require all denied |
sites-available/000-catchall.conf |
| 13 | Arquitectura moderna con mpm_event |
a2enmod mpm_event proxy_fcgi y
php-fpm |
mods-enabled/ |
| 14 | Módulos mínimos e indispensables | a2dismod autoindex status info userdir cgi include dav |
mods-enabled/ |
| 15 | Desactivar método HTTP TRACE | TraceEnable Off |
conf-available/security.conf |
| 16 | Limitar métodos HTTP estándar | <LimitExcept GET POST HEAD OPTIONS> Require all denied |
apache2.conf o VirtualHost |
| 17 | Cabeceras de seguridad HTTP | HSTS, CSP, X-Frame-Options, X-Content-Type-Options, etc. | conf-available/security-headers.conf |
| 18 | Cookies de sesión seguras | Header edit Set-Cookie ... "Secure; HttpOnly; SameSite=Lax" |
conf-available/security-headers.conf |
| 19 | Cifrado TLS 1.3 y TLS 1.2 moderno | SSLProtocol -all +TLSv1.2 +TLSv1.3 |
mods-available/ssl.conf |
| 20 | Grapado OCSP y HTTP/2 | SSLUseStapling on y
Protocols h2 http/1.1 |
ssl-stapling.conf / VirtualHost |
| 21 | Mitigación DoS y Slowloris | mod_reqtimeout y reducción de
Timeout 30 |
apache2.conf /
reqtimeout.conf |
| 22 | Prevención de abusos y flood | mod_evasive con umbrales realistas |
mods-available/evasive.conf |
| 23 | Cortafuegos WAF en capa 7 | ModSecurity con OWASP Core Rule Set
(SecRuleEngine On) |
/etc/modsecurity/ |
| 24 | Prevención de intrusiones con IPS | Fail2Ban con jaulas para Apache y WAF | /etc/fail2ban/jail.local |
| 25 | Registros enriquecidos y rotación | Formato combined_secure, permisos
640, logrotate |
/var/log/apache2/ |
A continuación se plantean siete actividades prácticas guiadas para afianzar los conocimientos en una máquina virtual de prácticas con Ubuntu Server.
sudo apt install apache2).curl -I http://localhost y
anota la cabecera Server y las versiones visibles./etc/apache2/conf-available/security.conf para
establecer ServerTokens Prod y
ServerSignature Off.sudo a2enconf security), comprueba la sintaxis con
apache2ctl configtest y recarga el servicio.curl -I http://localhost y comprueba
que la versión exacta y la distribución han desaparecido./var/www/html/ un archivo oculto llamado
.env que contenga una clave simulada
(DB_PASSWORD=secreto123) y un volcado de base de datos
backup.sql./var/www/html/uploads/ y dentro de
él crea un script de prueba info.php que contenga
<?php phpinfo(); ?>.curl que inicialmente el
servidor permite descargar .env y ejecutar
info.php./etc/apache2/conf-available/security.conf las
reglas <FilesMatch> para bloquear archivos que
comiencen por punto y extensiones sensibles (.sql,
.bak, .env).<Directory "/var/www/html/uploads"> para denegar la
ejecución de scripts PHP y forzar
ForceType application/octet-stream.curl -I http://localhost/.env y
curl http://localhost/uploads/info.php que en ambos casos
se devuelve un código de estado 403 Forbidden./etc/apache2/sites-available/000-catchall.conf con una
configuración que deniegue todo el tráfico a peticiones dirigidas por IP
directa.sudo a2ensite 000-catchall.conf.misitio.conf) con
ServerName misitio.local.curl -H "Host: misitio.local" http://localhost se accede a
la web, mientras que al acceder mediante
curl http://localhost se recibe un error
403 Forbidden.sudo a2enmod headers)./etc/apache2/conf-available/security-headers.conf e incluye
las cabeceras recomendadas (X-Frame-Options,
X-Content-Type-Options, Referrer-Policy,
Permissions-Policy, Content-Security-Policy,
cookie security).sudo a2enconf security-headers), valida con
apache2ctl configtest y reinicia Apache.curl -I http://localhost y
verifica que todas las cabeceras aparecen correctamente en la respuesta
HTTP.mod_reqtimeout y
mod_evasivereqtimeout
(sudo a2enmod reqtimeout).apache2.conf un Timeout de 30
segundos y KeepAliveTimeout de 5 segundos.reqtimeout.conf los tiempos de lectura de
cabeceras y cuerpo con tasa mínima de 500 bytes/s.libapache2-mod-evasive con umbrales
adecuados (DOSPageCount 10,
DOSSiteCount 100).Instala los paquetes libapache2-mod-security2 y
modsecurity-crs.
Activa SecRuleEngine On en
/etc/modsecurity/modsecurity.conf.
Verifica la inclusión de las reglas OWASP en
/etc/apache2/mods-available/security2.conf y reinicia
Apache.
Simula un ataque de inyección SQL enviando la siguiente petición:
Verifica que Apache responde con un error
403 Forbidden y revisa la entrada generada en
/var/log/apache2/modsec_audit.log.
fail2ban
(sudo apt install fail2ban -y)./etc/fail2ban/jail.local habilitando las jaulas
apache-auth, apache-badbots,
apache-noscript y apache-overflows.sudo fail2ban-client status.curl http://localhost/admin.php,
curl http://localhost/setup.cgi, etc.) desde otra máquina o
usando herramientas de prueba.sudo fail2ban-client status apache-noscript que la IP
atacante ha sido detectada y bloqueada en el cortafuegos.
Esta
página forma parte del curso
Implantación de Aplicaciones
Web de José Juan Sánchez
Hernández y su contenido se distribuye bajo una
licencia
Creative Commons Reconocimiento-NoComercial-CompartirIgual 4.0
Internacional.