O ModSecurity com o conjunto de regras OWASP CRS liga-se em dez minutos e desliga-se três dias depois, quando os artigos deixam de gravar na administração, o carregamento de ficheiros se parte e um cliente não consegue fazer uma encomenda porque a morada tinha um apóstrofo. A conclusão «o WAF atrapalha o trabalho» surge sozinha, e é falsa: quase todos esses bloqueios se resolvem com três ou quatro exceções precisas, e tudo está em encontrá-las corretamente.
Não ligue já o bloqueio
A primeira semana é só de observação. Em /etc/modsecurity/modsecurity.conf:
SecRuleEngine DetectionOnly
Neste modo o WAF regista tudo o que teria bloqueado e não bloqueia nada. Uma semana de tráfego real — incluindo o seu próprio trabalho na administração, o carregamento de imagens e uma encomenda — dá a lista dos falsos positivos verdadeiros em vez de hipotéticos. Passar a On só faz sentido quando essa lista estiver tratada.
Como funciona o CRS: não uma regra, mas uma soma
É a chave de tudo o que se segue. O CRS quase nunca bloqueia um pedido com uma única regra. Cada regra que dispara acrescenta pontos de anomalia, e o bloqueio ocorre quando a soma ultrapassa um limiar. Por isso no registo verá não uma linha mas várias, e a última será a regra com o identificador 949110, a que faz o total.
Consequência prática: a exceção tem de ser para a regra que marcou os pontos, não para a 949110. Ao desligar a regra que soma, desliga o conjunto inteiro, e do WAF fica apenas uma linha num ficheiro de configuração.
A segunda consequência é o nível de paranoia. Está em um por omissão, e essa é a escolha certa. Os níveis 2 e 3 acrescentam regras que, por construção, produzem falsos positivos em sítios vulgares; ativá-los só faz sentido quando o nível um estiver totalmente afinado.
Encontrar a regra culpada
Tudo o que é preciso está no registo de auditoria (/var/log/modsec_audit.log) e no registo de erros do servidor web. Procure pela hora do bloqueio:
sudo grep -o 'id "[0-9]*"' /var/log/modsec_audit.log | sort | uniq -c | sort -rn | head
É uma lista de frequência das regras disparadas. Depois, para um identificador concreto, veja com que exatamente disparou:
sudo grep -A5 'id "942100"' /var/log/modsec_audit.log | head -40
São precisas três coisas: o identificador da regra, o nome do parâmetro (ARGS:content, ARGS:comment) e o caminho do pedido. É com isso que se constrói a exceção.
Os suspeitos do costume
A lista repete-se de sítio para sítio:
- 942100: injeção de SQL. Dispara em campos de texto com conteúdo longo: corpo de artigo, descrição de produto, comentário. Aspas, parênteses e palavras como
selectparecem suspeitos ao detetor mesmo em prosa normal; - 941100 e a família 941xxx: XSS. Chegam com o editor visual: as etiquetas HTML num campo são precisamente o sentido do seu trabalho;
- 920420: Content-Type não permitido. Parte APIs e carregamentos de ficheiros: o conjunto de tipos permitidos por omissão é estreito, e em versões antigas
application/jsonnão estava lá; - 913100: verificador pelo User-Agent. Juntamente com os verificadores apanha ferramentas legítimas: a monitorização de disponibilidade, o curl nos seus próprios scripts;
- 200002, 200004: erros na análise do corpo do pedido. Costumam significar não um ataque mas um limite de tamanho excedido, ou seja, o carregamento de um ficheiro grande.
Três formas de criar uma exceção
Por ordem crescente de rudeza. Todas vão para um ficheiro próprio (por exemplo /etc/modsecurity/crs/REQUEST-900-EXCLUSION-RULES.conf) e não para os ficheiros do CRS: o conjunto de regras é atualizado e as suas alterações desapareceriam com ele.
Retirar um parâmetro de uma regra. A variante mais precisa, e a que se deve procurar:
SecRuleUpdateTargetById 942100 "!ARGS:content"
Desligar uma regra apenas num caminho. Serve quando é uma página concreta a fazer barulho: um editor, uma importação, um formulário de avaliação:
SecRule REQUEST_URI "@beginsWith /admin/post" \
"id:1001,phase:1,pass,nolog,ctl:ruleRemoveById=942100"
Desligar a regra por completo. Último recurso e quase sempre sinal de que a causa não foi encontrada:
SecRuleRemoveById 942100
A diferença entre a primeira e a terceira é considerável. No primeiro caso um campo — o texto do artigo — deixa de ser verificado contra injeção de SQL por uma regra; no segundo e no terceiro deixa de ser verificado o sítio inteiro. A diferença de esforço é de cerca de cinco minutos.
Depois das alterações, verifique a configuração e recarregue com suavidade:
sudo apachectl configtest && sudo systemctl reload apache2
sudo nginx -t && sudo systemctl reload nginx
Uma ordem que poupa uma semana
- Uma semana em
DetectionOnlycom trabalho normal no sítio, administração e carregamentos incluídos. - Lista de frequência das regras a partir do registo de auditoria. Percorre-se de cima para baixo: as três ou quatro primeiras somam noventa por cento do ruído.
- Para cada uma: apurar que parâmetro e em que página. A exceção faz-se por parâmetro, não por regra.
- Só agora
SecRuleEngine On. - Uma vez por mês, espreitar os bloqueios: o sítio mudou, logo apareceram novos falsos positivos.
É neste último ponto que tudo costuma partir-se: o registo de auditoria são gigabytes de texto que ninguém lerá à mão. O sentido de um painel é ter à vista a lista de regras disparadas, os pedidos bloqueados e o conjunto ativo, em vez de os extrair com grep. Como isso fica mostra-o a página de demonstração abaixo.