Buzeli
buzeliSoluções Digitais
Segurança

ModSecurity bloqueou o próprio CDN: quando o WAF não sabe que está atrás da Akamai e bane os edge nodes

Publicado em 2 de maio de 2026

O problema: ERR_READ_ERROR no CDN com WAF saudável

A Akamai reportava ERR_READ_ERROR|no_resp_hdrs — conexão TCP quebrada antes de receber os headers da origin. Do ponto de vista do usuário final, o site carregava intermitentemente: às vezes 200, às vezes timeout sem resposta. O WAF estava funcionando, o nginx backend estava respondendo para outros domínios. A falha era específica para este domínio.

A arquitetura do servidor concentra múltiplas camadas em um único host OCI:

Copiar
# Topologia de rede
Internet → Akamai (CDN puro, sem WAF/segurança)
         → WAF container (network_mode: host, porta 443)
           ModSecurity 3.0.14 + OWASP CRS 4.14.0-dev + CrowdSec bouncer
         → proxy_pass https://172.28.5.61
         → nginx backend (bridge cdn-bridge0, IP 172.28.5.61)
         → fastcgi → PHP-FPM (network_mode: host)

O detalhe crítico: o WAF está em network_mode host. Isso significa que ele enxerga diretamente os IPs dos edge nodes da Akamai — sem NAT, sem camada intermediária. O $remote_addr para todas as requisições do site é um IP da Akamai, não o IP do visitante real.

A evidência: 928.219 bloqueios em um único log

O primeiro sinal concreto foi o tamanho do error.log: 839 MB. Sem rotação desde 9 de fevereiro — mais de cinco semanas acumulando. O modsec_audit.log estava em 6,9 GB. O access.log em 1,3 GB.

Ao filtrar os bloqueios no error.log, o padrão ficou imediato:

Copiar
# Contar bloqueios ModSecurity no error.log
grep -c 'ModSecurity' /var/log/nginx/error.log
# 928219

# Identificar os IPs mais bloqueados
grep 'ModSecurity.*blocked' /var/log/nginx/error.log   | grep -oP 'client: K[0-9.]+'   | sort | uniq -c | sort -rn | head -10

Os IPs bloqueados apareciam sempre os mesmos: 192.0.2.12, 192.0.2.13, 198.51.100.14, 198.51.100.15. Uma verificação rápida contra os ranges publicados pela Akamai confirmou a origem:

Copiar
# Verificar se IPs pertencem aos ranges Akamai (publicados em https://techdocs.akamai.com/...)
# Range 192.0.2.0/24 cobre 192.0.2.0 até 192.0.2.255
# Range 198.51.100.0/24 cobre 198.51.100.0 até 198.51.100.255

python3 -c "
import ipaddress
akamai_ranges = ['192.0.2.0/24', '198.51.100.0/24']
test_ips = ['192.0.2.12', '192.0.2.13', '198.51.100.14', '198.51.100.15']
for ip in test_ips:
    for cidr in akamai_ranges:
        if ipaddress.ip_address(ip) in ipaddress.ip_network(cidr):
            print(f'{ip} pertence a {cidr}')
"
# 192.0.2.12 pertence a 198.51.100.0/24
# 192.0.2.13 pertence a 198.51.100.0/24
# 198.51.100.14 pertence a 192.0.2.0/24
# 198.51.100.15 pertence a 192.0.2.0/24
O WAF estava bloqueando os edge nodes da própria Akamai. Toda requisição que passava pela CDN tinha anomaly score calculado contra o IP do edge node, não contra o IP do visitante real.

Por que o OWASP CRS bloqueava a Akamai

O OWASP CRS calcula um anomaly score acumulado por requisição. Quando o score passa de um threshold (padrão: 5 para bloquear), a requisição é negada. Nas requisições vindas da Akamai, o score estava chegando a 40-60.

O caso mais revelador foi o bloqueio do wp-cron.php. A Akamai envia probes periódicas para validar que a origin está respondendo. Essas probes incluem um referrer com um pattern XSS que é parte do comportamento padrão do edge node:

Copiar
# Trecho do modsec_audit.log — probe da Akamai ao wp-cron.php
--abc123--A--
[13/Mar/2026:14:23:01 +0000] "GET /wp-cron.php?doing_wp_cron HTTP/1.1"
--abc123--B--
Referer: https://blog.cliente-exemplo.com.br/<script>alert(81)</script>
User-Agent: Mozilla/5.0 (compatible; AkamaiGHost/...)

--abc123--H--
ModSecurity: Warning. XSS Attack Detected via libinjection.
[file "REQUEST-941-APPLICATION-ATTACK-XSS.conf"]
[id "941100"] [rev ""] [msg "XSS Attack Detected via libinjection"]
[data "Matched Data: <script> found within ARGS:doing_wp_cron"] ...
[severity "CRITICAL"] [ver "OWASP_CRS/4.14.0-dev"]
[tag "application-multi"]
[anomaly-score 15]

ModSecurity: Access denied with code 403 (phase 2).
[score: 40] [threshold: 5]

O referrer com pattern XSS disparava a regra 941100 (XSS Detection via libinjection) com score 15. Somado a outras regras que pontuavam pelo User-Agent incomum e pelo header pattern, o score total chegava a 40. Com threshold de 5, o bloqueio era inevitável.

POSTs legítimos de usuários também eram afetados — qualquer POST que incluísse campos com caracteres especiais acumulava score suficiente para bloquear, porque o $remote_addr era da Akamai e a reputação do IP não ajudava. A ausência de real_ip correto também afetava as regras de rate limiting do WAF, que agrupavam todo o tráfego de um edge node em um único bucket.

O arquivo common/akamai.conf já existia — só não estava incluído

Ao inspecionar a estrutura de configuração do servidor, o arquivo de real_ip para a Akamai estava presente:

Copiar
# Arquivo /home/developer/webserver/common/akamai.conf
set_real_ip_from 192.0.2.0/24;
set_real_ip_from 198.51.100.0/24;
# ... outros ranges Akamai
real_ip_header X-Forwarded-For;
real_ip_recursive on;

O problema era que o vhost do WAF (waf-enabled/blog.cliente-exemplo.com.br) incluía o arquivo errado: common/gocache.conf em vez de common/akamai.conf. O servidor havia sido configurado originalmente com GoCache como CDN, e quando a Akamai substituiu o GoCache, apenas o DNS foi alterado — os vhosts ficaram com os includes antigos.

Copiar
# O que estava no vhost WAF (incorreto)
server {
    listen 443 ssl;
    server_name blog.cliente-exemplo.com.br;
    include common/gocache.conf;   # <-- ERRADO: ranges GoCache, não Akamai
    ...
}

# O que o vhost nginx backend também tinha (incorreto)
server {
    listen 443;
    server_name blog.cliente-exemplo.com.br;
    include common/gocache.conf;   # <-- ERRADO: mesma linha no backend
    ...
}

A correção: dois vhosts, dois reloads

A correção precisou ser aplicada em ambas as camadas — WAF e nginx backend — porque cada uma tem sua própria configuração de real_ip:

Copiar
# Backup dos vhosts antes de alterar
cp /home/developer/webserver/waf-enabled/blog.cliente-exemplo.com.br    /home/developer/webserver/blog.cliente-exemplo.com.br.waf-bak.20260319

cp /home/developer/webserver/sites-enabled/blog.cliente-exemplo.com.br    /home/developer/webserver/blog.cliente-exemplo.com.br.bak.20260319

# Corrigir vhost do WAF: trocar gocache.conf por akamai.conf
sed 's|include common/gocache.conf;|include common/akamai.conf;|g'    /home/developer/webserver/waf-enabled/blog.cliente-exemplo.com.br > /tmp/waf-new
cp /tmp/waf-new /home/developer/webserver/waf-enabled/blog.cliente-exemplo.com.br

# Corrigir vhost do nginx backend: mesmo ajuste
sed 's|include common/gocache.conf;|include common/akamai.conf;|g'    /home/developer/webserver/sites-enabled/blog.cliente-exemplo.com.br > /tmp/nginx-new
cp /tmp/nginx-new /home/developer/webserver/sites-enabled/blog.cliente-exemplo.com.br

# Testar e recarregar o WAF primeiro
docker exec waf nginx -t && docker exec waf nginx -s reload

# Testar e recarregar o nginx backend
docker exec nginx nginx -t && docker exec nginx nginx -s reload

Os resultados

Após o reload do WAF, os bloqueios zeraram imediatamente. Os edge nodes da Akamai passaram a ser reconhecidos como proxies confiáveis — o $remote_addr do ModSecurity passou a ser o IP real do visitante, calculado a partir do X-Forwarded-For.

O modsec_audit.log parou de crescer. Para os 10,5 GB de logs acumulados, foi feita rotação e compressão manual:

Copiar
# Rotação manual dos logs acumulados
cd /var/log/nginx
mv error.log error.log.20260319         # 839 MB
mv modsec_audit.log modsec_audit.log.20260319   # 6.9 GB
mv access.log access.log.20260319       # 1.3 GB

# Recarregar para criar novos arquivos de log
docker exec nginx nginx -s reload

# Comprimir os logs antigos (~10.5 GB → ~296 MB)
gzip error.log.20260319
gzip modsec_audit.log.20260319
gzip access.log.20260319

A lição: WAF atrás de CDN exige real_ip antes de qualquer regra

Esse é um erro de configuração clássico em arquiteturas WAF + CDN. O ModSecurity — como qualquer sistema de detecção baseado em IP — precisa saber quais endereços são proxies confiáveis. Sem isso, qualquer requisição que passe pelo CDN tem o IP do edge node como endereço de origem.

A ordem importa: o set_real_ip_from deve ser processado antes das regras do ModSecurity. No nginx, isso significa que o include do arquivo de real_ip deve vir antes das diretivas do ModSecurity no server block.

Outros sistemas de WAF lidam com isso de forma diferente. Soluções WAF gerenciadas por cloud (como o WAF da OCI integrado ao Load Balancer, descrito em outro post) recebem o tráfego após o CDN já ter feito o unwrap do IP real — o problema não existe porque a camada de CDN é parte da mesma plataforma. Quando o WAF é self-managed e fica na origin, a configuração de real_ip é responsabilidade do operador.

928.219 bloqueios, 839 MB de error.log, disco em 70% — tudo causado por uma única linha incorreta em dois arquivos de vhost. include common/gocache.conf onde deveria estar include common/akamai.conf.

Checklist para WAF em network_mode host atrás de CDN

1. Identificar o CDN em uso: Akamai, Cloudflare, CloudFront, GoCache — cada um tem ranges de IPs distintos.

2. Verificar real_ip_header: Akamai envia X-Forwarded-For e True-Client-IP. Cloudflare usa CF-Connecting-IP. Confirmar qual header o CDN popula.

3. Aplicar em AMBAS as camadas: WAF e nginx backend precisam do set_real_ip_from. Corrigir só o WAF deixa rate limiting e logs do backend com IP errado.

4. Configurar logrotate: error.log e modsec_audit.log crescem rapidamente em caso de falsos positivos. Sem rotação, o disco enche silenciosamente.