ModSecurity con el conjunto de reglas OWASP CRS se activa en diez minutos y se desactiva tres días después, cuando los artículos dejan de guardarse en la administración, la subida de archivos se rompe y un cliente no puede hacer un pedido porque su dirección contenía un apóstrofo. La conclusión «el WAF estorba» se impone sola, y es falsa: casi todos esos bloqueos se resuelven con tres o cuatro excepciones precisas, y todo consiste en encontrarlas correctamente.
No active el bloqueo de inmediato
La primera semana es solo de observación. En /etc/modsecurity/modsecurity.conf:
SecRuleEngine DetectionOnly
En ese modo el WAF registra todo lo que habría bloqueado y no bloquea nada. Una semana de tráfico real —incluido su propio trabajo en la administración, la subida de imágenes y un pedido— da la lista de falsos positivos auténticos en lugar de hipotéticos. Pasar a On tiene sentido solo cuando esa lista está resuelta.
Cómo funciona el CRS: no una regla, sino una suma
Esta es la clave de todo lo que sigue. El CRS casi nunca bloquea una petición con una sola regla. Cada regla que se dispara añade puntos de anomalía, y el bloqueo ocurre cuando la suma supera un umbral. Por eso en el registro no verá una línea sino varias, y la última será la regla con el identificador 949110, la que hace el total.
Consecuencia práctica: la excepción debe aplicarse a la regla que sumó los puntos, no a 949110. Si desactiva la regla que suma, desactiva el conjunto entero y del WAF solo queda una línea en un archivo de configuración.
La segunda consecuencia es el nivel de paranoia. Está en uno por omisión, y esa es la elección correcta. Los niveles 2 y 3 añaden reglas que por diseño producen falsos positivos en sitios corrientes; activarlos tiene sentido solo cuando el nivel uno está completamente ajustado.
Encontrar la regla culpable
Todo lo necesario está en el registro de auditoría (/var/log/modsec_audit.log) y en el registro de errores del servidor web. Busque por la hora del bloqueo:
sudo grep -o 'id "[0-9]*"' /var/log/modsec_audit.log | sort | uniq -c | sort -rn | head
Es una lista de frecuencia de las reglas disparadas. Después, para un identificador concreto, mire con qué se disparó exactamente:
sudo grep -A5 'id "942100"' /var/log/modsec_audit.log | head -40
Hacen falta tres cosas: el identificador de la regla, el nombre del parámetro (ARGS:content, ARGS:comment) y la ruta de la petición. Con eso se construye la excepción.
Los sospechosos habituales
La lista se repite de un sitio a otro:
- 942100: inyección SQL. Se dispara en campos de texto con contenido largo: cuerpo de artículo, descripción de producto, comentario. Las comillas, los paréntesis y palabras como
selectle parecen sospechosas al detector incluso en prosa normal; - 941100 y la familia 941xxx: XSS. Llegan con el editor visual: las etiquetas HTML en un campo son precisamente el sentido de su trabajo;
- 920420: Content-Type no permitido. Rompe API y subidas de archivos: el conjunto de tipos permitidos por omisión es estrecho, y en versiones antiguas
application/jsonno estaba; - 913100: escáner por User-Agent. Junto con los escáneres captura herramientas legítimas: la supervisión de disponibilidad, curl en sus propios scripts;
- 200002, 200004: errores al analizar el cuerpo de la petición. Suelen significar no un ataque sino un límite de tamaño superado, es decir, la subida de un archivo grande.
Tres formas de crear una excepción
Por orden de brusquedad creciente. Todas van a un archivo propio (por ejemplo /etc/modsecurity/crs/REQUEST-900-EXCLUSION-RULES.conf) y no a los archivos del CRS: el conjunto de reglas se actualiza y sus cambios desaparecerían con él.
Sacar un parámetro de una regla. La variante más precisa, y la que hay que perseguir:
SecRuleUpdateTargetById 942100 "!ARGS:content"
Desactivar una regla solo en una ruta. Sirve cuando una página concreta hace ruido: un editor, una importación, un formulario de opiniones:
SecRule REQUEST_URI "@beginsWith /admin/post" \
"id:1001,phase:1,pass,nolog,ctl:ruleRemoveById=942100"
Desactivar la regla por completo. Último recurso y casi siempre señal de que no se encontró la causa:
SecRuleRemoveById 942100
La diferencia entre la primera y la tercera es considerable. En el primer caso un campo —el texto del artículo— deja de comprobarse contra inyección SQL por una regla; en el segundo y el tercero deja de comprobarse el sitio entero. La diferencia de esfuerzo es de unos cinco minutos.
Tras los cambios, compruebe la configuración y recargue con suavidad:
sudo apachectl configtest && sudo systemctl reload apache2
sudo nginx -t && sudo systemctl reload nginx
Un orden que ahorra una semana
- Una semana en
DetectionOnlycon trabajo normal en el sitio, administración y subidas incluidas. - Lista de frecuencia de reglas desde el registro de auditoría. Se recorre de arriba abajo: las tres o cuatro primeras suman el noventa por ciento del ruido.
- Para cada una: averiguar qué parámetro y en qué página. La excepción se hace por parámetro, no por regla.
- Solo ahora,
SecRuleEngine On. - Una vez al mes, asomarse a los bloqueos: el sitio ha cambiado, así que han aparecido falsos positivos nuevos.
En ese último punto es donde todo suele romperse: el registro de auditoría son gigabytes de texto que nadie leerá a mano. El sentido de un panel es tener a la vista la lista de reglas disparadas, las peticiones bloqueadas y el conjunto activo, en lugar de extraerlos con grep. Cómo se ve eso lo muestra la página de demostración de abajo.