Implementación de Web Application Firewall (WAF) en un servidor Apache utilizando:
- ModSecurity.
- OWASP Core Rule Set (CRS).
Cuando exponemos un servidor web a Internet, automáticamente se convierte en objetivo de:
- SQL Injection.
- XSS (Cross-Site Scripting).
- Escaneos masivos.
- Bots automatizados.
- Fuerza bruta.
- Remote Code Execution.
Aunque nuestra aplicación esté bien desarrollada, Apache por sí solo no analiza la intención del tráfico HTTP.
Ahí es donde entra un WAF. Haremos uso de ModSecurity, uno de los WAF open-source más utilizados.
¿Qué es un WAF y cómo funciona?
Un Web Application Firewall (WAF) es una solución de seguridad que inspecciona y filtra el tráfico HTTP/HTTPS entre el cliente y la aplicación web.
ModSecurity no es un firewall tradicional de red (como iptables o un firewall perimetral). Es un módulo de Apache que intercepta el tráfico HTTP en distintas fases del procesamiento de una request.
Apache procesa una solicitud en varias etapas:
- Request headers.
- Request body.
- Fase de autorización.
- Respuesta.
- Logging.
ModSecurity puede inspeccionar cada una de esas fases.
Si una regla coincide con un patrón malicioso:
- Puede bloquear la request (403).
- Puede registrar el evento.
- Puede modificar la respuesta.
Es básicamente un motor de inspección profunda de HTTP.
Instalar Apache
sudo apt install apache2 -yConfigurar un Login en Apache
Nos dirigimos al siguiente directorio:
cd /var/www/htmlLuego borramos el index.html que nos aparece en el directorio y creamos uno:
rm -rf index.html && touch index.htmlAbrimos el editor para colocar el código:
sudo nano index.html<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Login Seguro (Simulado)</title>
<style>
* {
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
margin: 0;
height: 100vh;
background: linear-gradient(-45deg, #1e3c72, #2a5298, #0f2027, #203a43);
background-size: 400% 400%;
animation: gradientBG 15s ease infinite;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
}
@keyframes gradientBG {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
.container {
background: rgba(255, 255, 255, 0.05);
padding: 40px;
border-radius: 15px;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);
backdrop-filter: blur(12px);
width: 100%;
max-width: 400px;
}
h1 {
text-align: center;
margin-bottom: 30px;
font-size: 28px;
color: #f1c40f;
}
label {
display: block;
margin-top: 10px;
margin-bottom: 5px;
font-weight: 600;
}
input[type="text"],
input[type="password"] {
width: 100%;
padding: 12px;
border: none;
border-radius: 8px;
background-color: #ffffff15;
color: #fff;
font-size: 16px;
margin-bottom: 15px;
transition: background-color 0.3s ease;
}
input::placeholder {
color: #ccc;
}
input:focus {
outline: none;
background-color: #ffffff25;
}
button {
width: 100%;
padding: 12px;
background-color: #f1c40f;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: bold;
color: #000;
cursor: pointer;
transition: background-color 0.3s ease;
}
button:hover {
background-color: #e0b90a;
}
.message {
margin-top: 20px;
padding: 15px;
border-radius: 10px;
text-align: center;
font-weight: bold;
font-size: 16px;
}
.success {
background-color: #27ae60cc;
color: #ecf0f1;
}
.error {
background-color: #e74c3ccc;
color: #fff;
}
.note {
text-align: center;
margin-top: 30px;
font-size: 0.85em;
color: #ccc;
}
code {
background-color: rgba(255,255,255,0.1);
padding: 2px 6px;
border-radius: 5px;
color: #f1c40f;
}
</style>
</head>
<body>
<div class="container">
<h1>Acceso al Sistema</h1>
<form method="GET" action="">
<label for="user">Usuario:</label>
<input type="text" id="user" name="user" placeholder="admin" required />
<label for="pass">Contraseña:</label>
<input type="password" id="pass" name="pass" placeholder="123123" required />
<button type="submit">Iniciar sesión</button>
</form>
<div id="mensaje"></div>
<div class="note">
<p><strong>⚠️ Este ejemplo es educativo.</strong></p>
<p>Los datos se reflejan en la URL. No es seguro para producción.</p>
</div>
</div>
<script>
const params = new URLSearchParams(window.location.search);
const user = params.get('user');
const pass = params.get('pass');
const mensaje = document.getElementById('mensaje');
if (user && pass) {
if (user === "admin" && pass === "123123") {
mensaje.innerHTML = `<div class="message success">✅ Bienvenido, <strong>${user}</strong>. Has iniciado sesión correctamente.</div>`;
} else {
mensaje.innerHTML = `<div class="message error">❌ Usuario o contraseña incorrectos.</div>`;
}
}
</script>
</body>
</html>Si abrimos ahora nuestra IP del servidor en el navegador veremos el Login:

Instalar y configurar ModSecurity
Probamos un payload clásico de SQL Injection en el login y el sistema respondió con (usuario o contraseña incorrectos)
Esto indica que la consulta no fue alterada y que el código no parece vulnerable a una inyección básica.
Aun así, implementar un WAF sigue siendo una buena práctica, ya que proporciona una capa adicional de defensa y permite detectar y registrar intentos de explotación.

Instalamos ModSecurity:
sudo apt install libapache2-mod-security2 -y¿Qué ocurre internamente?
- Se descarga el paquete desde repositorios Debian/Ubuntu.
- Se instala el módulo dinámico
security2.load. - Se crean archivos de configuración en:
/etc/modsecurity/
/etc/apache2/mods-available/El módulo queda disponible, pero todavía no necesariamente activo.
Luego activamos el modulo en Apache:
sudo a2enmod security2¿Qué hace realmente a2enmod?
Crea un enlace simbólico desde:
/etc/apache2/mods-available/security2.loadHacia:
/etc/apache2/mods-enabled/Apache solo carga módulos que estén en mods-enabled.
Es decir: ahora Apache cargará ModSecurity al iniciar.
Reiniciar Apache
sudo systemctl restart apache2Configuración base de ModSecurity
Al instalarlo, ModSecurity viene en modo seguro (no bloquea nada).
Editamos:
sudo nano /etc/modsecurity/modsecurity.conf-recommendedPor defecto lo tendriamos asi:

Aquí solo está detectando pero no bloqueando, para que detecte y bloquee cambiamos el (SecRuleEngine DetectionOnly) por:
SecRuleEngine OnAhora:
- Analiza.
- Evalúa reglas.
- Bloquea tráfico malicioso.
Instalar OWASP Core Rule Set
Hasta ahora tenemos un motor…
Pero sin reglas es inútil.
Ahí entra el OWASP CRS.
Es un conjunto de reglas mantenidas por expertos en seguridad que detectan:
- SQL Injection.
- XSS.
- RFI.
- Command Injection.
- Bots.
- Escaneos automatizados.
Clonamos el repositorio oficial:
cd /etc/modsecurity
sudo git clone https://github.com/coreruleset/coreruleset.gitEsto descarga:
crs-setup.conf.example.- Carpeta
rules/con cientos de reglas.
Activamos la configuración principal:
cd coreruleset
sudo cp crs-setup.conf.example crs-setup.confEse archivo define:
- Configuración general.
- Ajustes globales.
Integramos CRS en Apache, para eso editamos:
sudo nano /etc/apache2/mods-enabled/security2.confY agregamos:
IncludeOptional /etc/modsecurity/coreruleset/crs-setup.conf
IncludeOptional /etc/modsecurity/coreruleset/rules/*.conf
¿Qué pasa ahora?
Cuando Apache inicia:
- Carga ModSecurity.
- Carga su configuración.
- Carga el CRS.
- Compila todas las reglas en memoria.
Activamos explícitamente ModSecurity en el VirtualHost.
Editamos:
sudo nano /etc/apache2/sites-available/000-default.confDentro del bloque <VirtualHost *:80> agregamos:
<IfModule security2_module>
SecRuleEngine On
</IfModule><IfModule security2_module>
Verifica que el módulo ModSecurity esté cargado antes de aplicar la directiva.
Evita errores si el módulo no está disponible.
SecRuleEngine On
Fuerza la activación del motor de reglas dentro de este VirtualHost específico.
¿Qué pasa cuando llega una request?
Supongamos que alguien intenta un SQL Injection.
Apache recibe la request.
Antes de pasarla a PHP:
- ModSecurity intercepta la request.
- Evalúa reglas de SQL Injection.
- Detecta patrón
' OR '1'='1. - Bloquea la petición.
- Devuelve 403.
Todo ocurre antes de que el código de la aplicación se ejecute.

Personalizar (Error 403)
Podemos personalizar a nuestro gusto el redireccionamiento del Error 403, para eso volvemos a:
cd /var/www/htmlY creamos el archivo:
sudo nano 403.htmlDentro del archivo colocamos este código de ejemplo:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Error 403 - Acceso Prohibido</title>
<style>
body {
margin: 0;
padding: 0;
background: linear-gradient(145deg, #1f1f1f, #121212);
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
color: #ffffff;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
overflow: hidden;
text-align: center;
}
.error-container {
animation: fadeIn 1.2s ease-in-out;
}
h1 {
font-size: 10rem;
margin: 0;
color: #ff4c4c;
text-shadow: 2px 2px 20px rgba(255, 76, 76, 0.4);
}
h2 {
font-size: 2rem;
margin: 0.5em 0;
color: #f0db4f;
}
p {
font-size: 1.2rem;
max-width: 500px;
margin: 1em auto;
color: #cccccc;
}
.btn {
display: inline-block;
margin-top: 20px;
padding: 12px 24px;
background: #f0db4f;
color: #000;
text-decoration: none;
font-weight: bold;
border-radius: 8px;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(240, 219, 79, 0.4);
}
.btn:hover {
background: #ffe84f;
transform: translateY(-3px);
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.icon-lock {
font-size: 5rem;
margin-bottom: 20px;
color: #ff4c4c;
}
</style>
</head>
<body>
<div class="error-container">
<div class="icon-lock">🔒</div>
<h1>403</h1>
<h2>Acceso Prohibido</h2>
<p>No tienes permiso para acceder a este recurso. Si crees que esto es un error, contacta con el administrador del sitio.</p>
<a href="/" class="btn">Volver al Inicio</a>
</div>
</body>
</html>Luego lo enlazamos en:
sudo nano /etc/apache2/sites-available/000-default.confColocamos:
ErrorDocument 403 /403.html
Visualización final
Aquí podremos ver como queda finalmente el WAF configurado con sus reglas y con sus estilos agregados al redireccionamiento del Error 403:

Conclusión
Implementar ModSecurity + OWASP CRS transforma Apache en algo más que un simple servidor web.
Lo convierte en:
- Un filtro inteligente.
- Un sistema de detección de ataques.
- Una primera línea real de defensa.
No reemplaza buenas prácticas.
Pero reduce drásticamente la superficie de ataque.
GabrielGR








