Tutorial: Como testar a segurança de sua aplicação (OWASP)
Como usar o ZAP Spider de forma simples e descomplicada
Toda aplicação que eu coloco no ar passa por um ritual antes de ir para produção: subo um container do OWASP ZAP, aponto o spider para a aplicação e deixo ele mapear tudo que existe do lado de fora. Não é pentest. É higiene básica, o equivalente a rodar npm audit antes do deploy, só que na superfície HTTP.
Antes de mostrar os comandos, vale entender o que é OWASP, quais são as dez ameaças que ela cataloga e como um spider funciona de verdade. Sem isso, você acaba rodando uma ferramenta que cospe um relatório que você não sabe ler.
O que é OWASP
OWASP é a sigla de Open Worldwide Application Security Project, uma fundação sem fins lucrativos criada em 2001 que produz material de segurança de aplicações de forma aberta e gratuita. Não vende produto, não certifica ninguém, não tem dono comercial. Tudo que ela publica é comunitário e aberto.
Na prática, três coisas saem da OWASP e valem para qualquer desenvolvedor:
Documentos de referência, sendo o Top 10 o mais conhecido, além do ASVS (padrão de verificação de segurança) e dos Cheat Sheets, que são guias curtos e diretos por tema.
Ferramentas, principalmente o ZAP (o scanner que este artigo usa) e o Dependency-Track.
Aplicações de treino, como o Juice Shop, feitas para serem vulneráveis de propósito e servirem de campo de tiro.
A relevância do Top 10 não é técnica, é política. Ele virou linguagem comum: aparece em contrato, em questionário de fornecedor, em auditoria PCI-DSS, em requisito de compliance.
O Top 10, edição 2025
A lista é atualizada a cada três ou quatro anos. A oitava edição foi anunciada em novembro de 2025 e finalizada em janeiro de 2026. Se você conhece a lista de 2021 (e a maioria dos tutoriais na internet ainda ensina essa), ela mudou.
Duas categorias novas entraram, uma foi absorvida e várias mudaram de posição.
A01, Broken Access Control. Usuário consegue fazer ou ver o que não deveria. IDOR (trocar /pedido/123 por /pedido/124 e ver o pedido de outra pessoa), escalada de privilégio, endpoint administrativo sem checagem de papel. Continua em primeiro lugar pela quarta edição seguida, e agora absorveu o SSRF, que era categoria separada em 2021.
A02, Security Misconfiguration. Subiu de quinto para segundo. Header de segurança ausente, bucket público, painel de admin com senha padrão, mensagem de erro devolvendo stack trace, diretório listável. Subiu porque cada vez mais o comportamento da aplicação vive em arquivo de configuração, não em código.
A03, Software Supply Chain Failures. Categoria nova, expansão do antigo “componentes vulneráveis”. Não é só a dependência desatualizada: é o pacote npm sequestrado, o build system comprometido, a action de CI que exfiltra secret. A categoria com menor incidência nos dados e maior impacto médio quando acontece.
A04, Cryptographic Failures. Caiu de segundo para quarto. Senha guardada em MD5, dado sensível trafegando em texto claro, chave hardcoded no repositório, TLS mal configurado.
A05, Injection. Caiu de terceiro para quinto, o que não significa que sumiu. Entrada não confiável interpretada como comando: SQL injection, XSS, command injection, path traversal, SSTI. É a categoria com mais CVEs associados.
A06, Insecure Design. Falha de arquitetura, não de implementação. Fluxo de recuperação de senha que permite tomar conta alheia, ausência de rate limit em operação sensível, regra de negócio que não previu o abuso. Código perfeito para uma ideia errada.
A07, Authentication Failures. Mudou de nome (era “Identification and Authentication Failures”). Brute force sem bloqueio, sessão que não expira, token previsível, MFA que dá para pular.
A08, Software or Data Integrity Failures. Confiar em artefato sem verificar. Deserialização insegura, atualização automática sem assinatura, script de CDN sem integrity check.
A09, Security Logging & Alerting Failures. Também mudou de nome, de “Monitoring” para “Alerting”, e a mudança é o recado: log excelente sem alerta não serve para nada. Se você foi invadido há três meses e só descobre agora, é isso.
A10, Mishandling of Exceptional Conditions. Categoria nova. Tratamento errado de situação anormal: erro engolido, sistema que falha aberto em vez de fechado, condição de corrida, exceção que vaza detalhe interno. Reúne 24 CWEs que antes ficavam espalhados.
Repare em uma coisa: nem todas essas categorias são testáveis por ferramenta. A06 e A09, por natureza, não são. Guarde isso, porque no fim do artigo eu volto nesse ponto.
O que é o ZAP
O ZAP (Zed Attack Proxy) é o scanner de segurança web da OWASP. Ele funciona como um proxy interceptador: todo o tráfego entre um cliente e a aplicação passa por ele, que registra, analisa e, quando você manda, modifica e reenvia.
A partir desse proxy, ele oferece três coisas:
Spider, que descobre o que existe na aplicação.
Scanner passivo, que apenas observa o tráfego e aponta problemas sem tocar em nada.
Scanner ativo, que pega o que o spider descobriu e ataca de verdade, injetando payloads.
A ordem importa. O scanner ativo só consegue testar o que o spider encontrou. Um crawl ruim significa um scan ruim, por melhor que seja o resto da ferramenta.
O spider, em detalhe
Spider (ou crawler) é o componente que descobre a superfície de ataque. Ele começa em uma URL semente, busca a página, extrai tudo que parece um caminho para outro lugar e repete o processo em cada descoberta, até acabar ou até estourar o limite que você definiu.
Concretamente, ele procura por:
hrefde tags<a>e<link>;srcde<script>,<img>,<iframe>;actionde<form>, junto com todos os campos<input>e<select>de dentro dele;URLs dentro de arquivos JavaScript e CSS;
robots.txtesitemap.xml, que são presentes de mão beijada;redirecionamentos e headers como
Location.
O produto final não é uma lista de vulnerabilidades. É uma árvore de sites: cada URL encontrada, cada método HTTP aceito, cada parâmetro de query, cada campo de formulário. Isso é o insumo do scanner ativo. Se o spider descobriu GET /produto?id=5, o scanner ativo sabe que existe um parâmetro id para atacar. Se não descobriu, esse parâmetro simplesmente não existe para a ferramenta.
Os dois spiders
Aqui está o ponto que a maioria dos tutoriais pula. O ZAP tem dois spiders, e eles não são intercambiáveis.
Spider tradicional. Baixa o HTML cru, faz parse, extrai os links e segue. Rápido (centenas de URLs por minuto), barato, previsível. E completamente cego para qualquer coisa que dependa de JavaScript, porque ele nunca executa JavaScript. Ele lê o HTML que o servidor mandou, não o DOM que o navegador construiu.
AJAX Spider. Dirige um navegador headless de verdade (Firefox ou Chrome via Selenium). Carrega a página, espera o JavaScript rodar, clica em elementos clicáveis, observa o DOM mudar e registra as requisições XHR que aparecem. Lento, pesado em memória, e a única opção que funciona em SPA.
Se seu front é React, Vue ou Angular, o HTML inicial é praticamente uma <div id="root"> vazia. O spider tradicional vai descobrir a rota / e mais nada. Já vi gente concluir “o scan não achou nada, estamos seguros” a partir de um crawl que nunca saiu da tela de carregamento.
Tipo de appSpiderServer-side rendering (Rails, Django, PHP, Next SSR)Tradicional já cobre bemSPA / front desacopladoAJAX spider obrigatórioAPI REST/GraphQL puraNenhum dos dois, importe o OpenAPI
Na prática, rode os dois em sequência. O tradicional é barato e pega o esqueleto, o AJAX complementa com o que só existe depois do JavaScript.
O que o spider não faz
Não adivinha. Um endpoint sem nenhum link apontando para ele não vai ser encontrado. Para isso existe brute force de diretório (o add-on Forced Browse do ZAP, ou ferramentas como ffuf), que é técnica diferente.
Não entende regra de negócio. Ele preenche formulário com dados sintéticos. Um fluxo de checkout em três etapas com validação em cada uma provavelmente trava na primeira.
Não sabe o que é destrutivo. Se existe
GET /admin/delete/42, ele vai clicar. Por isso ambiente descartável.Não mantém sessão sozinho. Sem configuração de autenticação, ele testa só a área pública.
Por que rodar em Docker
O ZAP é uma aplicação Java com dezenas de add-ons. Instalar na máquina significa gerenciar JDK, plugins e um diretório de configuração que cresce sem parar. Em container:
ambiente descartável, cada scan começa limpo;
versão fixada, então o resultado de hoje é reproduzível amanhã;
a mesma linha de comando roda em qualquer máquina;
fácil isolar em uma rede Docker junto com o alvo, sem expor nada.
A imagem oficial mudou de casa. A antiga owasp/zap2docker-stable está deprecada. Use:
docker pull ghcr.io/zaproxy/zaproxy:stableUm alvo para praticar
Nunca escaneie o que não é seu. Sério: um spider agressivo dispara milhares de requisições, preenche formulários e pode gerar registros lixo no banco. Contra um terceiro, isso é ataque, com consequências jurídicas reais.
Para aprender, use o Juice Shop, a aplicação deliberadamente vulnerável mantida pela própria OWASP:
docker network create zapnet
docker run -d --name juice --network zapnet \
-p 3000:3000 bkimminich/juice-shopColocar alvo e scanner na mesma rede Docker significa que o ZAP acessa o alvo por
http://localhost:3000
sem nada precisar sair do host.
Passo 1: o baseline scan
O jeito mais rápido de começar. O baseline roda o spider por um tempo limitado e executa apenas o scanner passivo:
mkdir -p out
sudo chown -R 1000:1000 out
mkdir -p out && chmod 777 out
docker run --rm -t --network zapnet \
-v "$(pwd)/out:/zap/wrk:rw" \
ghcr.io/zaproxy/zaproxy:stable \
zap-baseline.py \
-t http://juice:3000 \
-r baseline.html \
-J baseline.json \
-d -a -i -j \
-l INFO \
-m 5 -T 10 \
-IPreparação do diretório
mkdir -p outcria a pasta local que vai receber os relatórios;chown -R 1000:1000 outtransfere a posse para o uid do usuáriozapdentro do container, sem o que a escrita em/zap/wrkfalha com “Permission denied”;chmod 777 outé a alternativa quando não hásudodisponível, mas use um ou outro, não os dois.
Flags do Docker
--rmdescarta o container ao terminar;-taloca um TTY, o que remove o buffer da saída e permite acompanhar o progresso em tempo real;--network zapnetcoloca o ZAP na mesma rede do alvo, o que faz o nomejuiceresolver por DNS interno;-v "$(pwd)/out:/zap/wrk:rw"monta o diretório de trabalho do ZAP, que é por onde os relatórios saem.
Alvo e relatórios
-t http://juice:3000define a URL a escanear, usando o nome do container (dentro do ZAP,localhostseria o próprio container, não a aplicação);-r baseline.htmlgera o relatório HTML em./out/;-J baseline.jsongera a versão JSON, adequada para processar comjqou consumir em pipeline de CI.
Escopo e profundidade
-ainclui as passive scan rules alpha, ampliando o conjunto de verificações ao custo do download de vários add-ons;-jativa o Ajax spider, necessário para aplicações SPA (Angular, React, Vue) cujas rotas o spider tradicional não descobre;-m 5limita o spider a 5 minutos;-T 10estabelece o teto total de 10 minutos para a execução.
Verbosidade e severidade
-dliga o modo debug, exibindo o log do ZAP e do script;-l INFObaixa o nível mínimo reportado, incluindo achados informativos além de WARN e FAIL;-iconverte em INFO as regras ausentes do arquivo de configuração, em vez de ignorá-las;-Iimpede que WARN altere o código de saída, útil quando o pipeline só deve quebrar em FAIL.
Observação sobre a combinação -i mais -I
Juntas, essas duas flags rebaixam quase tudo a INFO e neutralizam o exit code, o que produz relatórios com aparência limpa mesmo em aplicações reconhecidamente vulneráveis. Para uma leitura fiel dos achados, rode sem as duas.
Sobre o resultado: FAIL-NEW: 0 FAIL-INPROG: 0 WARN-NEW: 0 WARN-INPROG: 0 INFO: 11 IGNORE: 0 PASS: 58.
Zero WARN no Juice Shop é sinal de que a varredura ficou rasa, não de que a aplicação está segura. O -I que você passou faz WARN não afetar o exit code, e o -i converte regras fora do config em INFO. Ou seja, os 11 INFO são provavelmente os achados reais rebaixados. Abra o JSON para confirmar:
jq -r '.site[].alerts[] | "\(.riskdesc)\t\(.alert)"' out/baseline.json | sort -uOutro ponto: o spider parece ter percorrido pouca coisa (a saída mostra só / e /sitemap.xml). O Juice Shop é Angular SPA, então o spider tradicional não encontra quase nada. É aí que o -j (Ajax spider) importa, apesar do custo dos add-ons.
Leva alguns minutos e não escreve nada no alvo.
O passivo pega praticamente tudo que é A02, Security Misconfiguration: Content-Security-Policy ausente, X-Content-Type-Options faltando, cookie sem HttpOnly ou Secure, banner do servidor vazando versão, Cache-Control errado em página autenticada. Também pega parte de A04, Cryptographic Failures, como conteúdo misto e formulário de senha em HTTP.
Não é glamouroso. É exatamente o tipo de coisa que passa em code review e depois aparece no relatório do cliente.
Passo 2: rodando os dois spiders
Para ver a diferença na prática, rode o full scan, que encadeia spider tradicional, AJAX spider e scanner ativo. Note que sem os parâmetros de verbosidade o comando levará minutos em execução sem mostrar nada na tela. A execução do zap-full-scan.py é muito mais longa:
docker run --rm --network zapnet \
-v "$(pwd)/out:/zap/wrk:rw" \
ghcr.io/zaproxy/zaproxy:stable \
zap-full-scan.py -t http://juice:3000 -j -r full.htmlA flag -j é a que liga o AJAX spider. Sem ela, contra o Juice Shop (que é Angular), o crawl encontra pouca coisa.
O relatório de saída está em: out/full.html
Passo 3: testando APIs
Para API, o crawl não faz sentido. É necessário alimentar a especificação diretamente.
O Juice Shop não expõe /openapi.json. Verifique antes de rodar:
docker run --rm --network zapnet curlimages/curl -sI http://juice:3000/openapi.jsonSe retornar 404, você tem duas saídas:
Localizar a spec real. O Juice Shop tem partes documentadas em
/api-docs/swagger.json, mas a cobertura é parcial e nem todos os builds a incluem;Escrever sua própria spec mínima com os endpoints que interessam, salvar em
./out/juice.jsone apontar para o caminho local:
docker run --rm --network zapnet \
-v "$(pwd)/out:/zap/wrk:rw" \
ghcr.io/zaproxy/zaproxy:stable \
zap-api-scan.py -t http://juice:3000/openapi.json -f openapi -r api.htmlFormatos aceitos pelo -f
openapipara OpenAPI 2.0/3.x;soappara WSDL;graphqlpara endpoint GraphQL, que dispensa spec porque o schema vem por introspecção.
Duas advertências sobre o scanner ativo que não são opcionais:
É destrutivo. Formulários serão submetidos, endpoints de delete serão chamados, e-mails serão disparados. Nunca contra produção. Ambiente descartável com dados sintéticos.
É lento. Contra uma aplicação de tamanho médio, conte horas, não minutos.
Passo 4: autenticação, que é onde tudo trava
Um scan sem login testa a área pública e para por aí. E é justamente atrás do login que moram os bugs interessantes.
Para autenticação, a saída limpa é o Automation Framework: um YAML declarativo que descreve o plano inteiro. Ele substitui os scripts empacotados e vive no repositório junto com o código.
Crie o arquivo out/plan.yaml com o conteúdo abaixo para testar o juice, mas adapte o conteúdo do arquivo para a aplicação que estiver validando:
env:
contexts:
- name: app
urls: ["http://juice:3000"]
includePaths: ["http://juice:3000.*"]
excludePaths:
- "http://juice:3000/logout.*"
authentication:
method: json
parameters:
loginPageUrl: "http://juice:3000/#/login"
loginRequestUrl: "http://juice:3000/rest/user/login"
loginRequestBody: '{"email":"{%username%}","password":"{%password%}"}'
sessionManagement:
method: headers
parameters:
Authorization: "Bearer {%json:authentication.token%}"
users:
- name: tester
credentials:
username: "test@juice.sh"
password: "senha-de-teste"
jobs:
- type: spider
parameters:
context: app
user: tester
maxDuration: 5
- type: spiderAjax
parameters:
context: app
user: tester
maxDuration: 10
- type: passiveScan-wait
- type: activeScan
parameters:
context: app
user: tester
- type: report
parameters:
template: traditional-html
reportFile: authenticated-report.htmlRodando:
docker run --rm --network zapnet \
-v "$(pwd)/out:/zap/wrk:rw" \
ghcr.io/zaproxy/zaproxy:stable \
zap.sh -cmd -autorun /zap/wrk/plan.yamlRepare no excludePaths com o logout. Sem isso o spider clica em “sair” nos primeiros trinta segundos e o resto do scan roda deslogado, silenciosamente, sem erro nenhum. É o erro número um de quem está começando.
Sempre valide a sessão. Abra o relatório e confirme que URLs autenticadas aparecem na árvore de sites. Um scan que se acha logado e não está produz um relatório limpo e falso.
O que esse setup realmente cobre
Honestidade sobre escopo vale mais que uma lista de checkmarks:
A01, Broken Access Contro: (Fraca) Exige entender quem pode ver o quê. Rode dois scans com usuários de níveis diferentes e compare, mas a análise é manual.
A02, Security Misconfiguration: (Forte) Headers, cookies, diretórios expostos, páginas de erro verbosas. O ponto alto do scanner passivo.
A03, Software Supply Chain: (Nenhuma) Problema de outra ferramenta: SCA, SBOM, npm audit, Trivy, Dependabot.
A04, Cryptographic Failures: (Parcial) Detecta transporte fraco e conteúdo misto. Não vê como você guarda senha no banco.
A05, Injection: (Forte) O ponto alto do scanner ativo: SQLi, XSS, command injection, path traversal.
A06, Insecure Design: (Nenhuma) Falha de design é conversa de arquitetura, não de ferramenta.
A07, Authentication Failures: (Parcial) Vê sessão fraca e ausência de proteção contra brute force. Não avalia sua lógica de MFA.
A08, Software or Data Integrity: (Parcial) Aponta subresource integrity ausente.
A09, Logging & Alerting Failures: (Nenhuma) Por definição, é do lado de dentro.
A10, Mishandling of Exceptional Conditions: (Parcial) Erro verboso e comportamento estranho sob entrada malformada aparecem, mas interpretar exige olho humano.
Traduzindo: o ZAP resolve bem A02 e A05, ajuda em uns quatro outros e não toca em três. Isso já é muito valor por vinte minutos de setup, desde que ninguém confunda “scan verde” com “aplicação segura”.
O resumo
Entenda o Top 10 antes da ferramenta. O relatório é inútil se você não sabe o que está lendo.
Baseline scan é barato, passivo e seguro. Comece por ele.
Ligue o AJAX spider (
-j) se o front for SPA, senão você está escaneando uma página só.Full scan só em staging, nunca em produção.
Autentique o scan via Automation Framework e exclua o logout.
Trate o relatório como lista de hipóteses, não de fatos. Todo scanner tem falso positivo.
Vinte minutos de setup, um container descartável, e você elimina a classe inteira de bugs que aparece na primeira página de qualquer relatório de segurança. O resto (controle de acesso, falha de design, cadeia de suprimentos, logging) continua sendo trabalho humano. Ferramenta nenhuma vai fazer por você.
No que o ClickMVP ajuda nos testes?
O ClickMVP gera uma especificação OpenAPI 3.0.3 válida. Serve direto no zap-api-scan.py, sem precisar escrever spec manual.
Após a geração do preview, ao clicar no botão “Swagger”:
Você é direcionado para a página de documentação dos endpoints da API gerada. Logo abaixo do nome da aplicação você encontrará o caminho para o arquivo json com a spec.
Baixe e utilize nos testes
curl -o openapi.json https://magicbooks.clickmvp.com/docs/json








