Uma API que retorna 200 com dados de outro usuário quando você troca um ID na URL tem um bug de autorização quebrada no nível do objeto. Você consegue detectar isso com um teste de API no Playwright durante o desenvolvimento regular, sem precisar de um pentester. O mesmo vale para SQL injection, XSS, brute-force em contas e exposição de dados sensíveis em respostas de API.

Por que QA deve fazer testes de segurança

Segurança é responsabilidade de todos. Engenheiros de QA têm vantagens reais:

  • Acesso ao código-fonte e aos ambientes de teste — pentesters frequentemente não têm
  • Entendimento da lógica da aplicação — você sabe o que deveria e não deveria ser possível
  • Infraestrutura de testes automatizados — verificações de segurança podem fazer parte do pipeline de CI
  • Cadência de testes frequente — regressões de segurança são detectadas cedo

O OWASP Top 10 (mais relevante para QA)

O Open Web Application Security Project publica as vulnerabilidades web mais comuns. Estas são as que você vai encontrar com mais frequência.

1. Controle de acesso quebrado

Usuários acessando recursos que não deveriam poder acessar.

Teste:

test('usuário comum não consegue acessar API de admin', async ({ request }) => {
  // Login como membro comum
  const loginResp = await request.post('/api/auth/login', {
    data: { email: 'member@test.com', password: 'MemberPass1' },
  });
  const { token } = await loginResp.json();
  
  // Tentar acessar endpoint de admin
  const response = await request.get('/api/admin/users', {
    headers: { Authorization: `Bearer ${token}` },
  });
  
  // Deve ser 403 Forbidden, não 200
  expect(response.status()).toBe(403);
});

test('usuário não consegue ver perfil de outro usuário trocando ID', async ({ request }) => {
  const user1Token = await getToken('user1@test.com', 'Pass1');
  
  // Usuário 1 tenta acessar perfil do Usuário 2 adivinhando o ID
  const response = await request.get('/api/users/999', {
    headers: { Authorization: `Bearer ${user1Token}` },
  });
  
  // Deve ser 403 ou 404, não 200 com os dados do usuário 2
  expect([403, 404]).toContain(response.status());
});

2. Problemas de autenticação

test('conta é bloqueada após tentativas falhas', async ({ request }) => {
  // Tentar senha errada 5 vezes
  for (let i = 0; i < 5; i++) {
    await request.post('/api/auth/login', {
      data: { email: 'user@test.com', password: 'SenhaErrada' },
    });
  }
  
  // 6ª tentativa deve ser bloqueada
  const response = await request.post('/api/auth/login', {
    data: { email: 'user@test.com', password: 'SenhaErrada' },
  });
  
  expect(response.status()).toBe(429);  // Too Many Requests
  const body = await response.json();
  expect(body.message).toMatch(/bloqueado|muitas tentativas/i);
});

test('token de reset de senha expira', async ({ request }) => {
  // Solicitar reset
  await request.post('/api/auth/forgot-password', {
    data: { email: 'user@test.com' },
  });
  
  // Em testes, use um token expirado do banco de dados ou mock
  const expiredToken = 'expired-reset-token-12345';
  
  const response = await request.post('/api/auth/reset-password', {
    data: { token: expiredToken, password: 'NovaSenha1!' },
  });
  
  expect(response.status()).toBe(400);
  const body = await response.json();
  expect(body.error).toMatch(/expirado|inválido/i);
});

3. Injeção: SQL injection

test('formulário de login não é vulnerável a SQL injection', async ({ page }) => {
  await page.goto('/login');
  
  // Payload clássico de SQL injection
  await page.fill('[data-testid="email"]', "' OR '1'='1");
  await page.fill('[data-testid="password"]', "' OR '1'='1");
  await page.click('[data-testid="submit"]');
  
  // NÃO deve estar logado
  await expect(page).not.toHaveURL('/dashboard');
  await expect(page.getByTestId('error-message')).toBeVisible();
});

test('busca não é vulnerável a SQL injection', async ({ request, authToken }) => {
  const injections = [
    "'; DROP TABLE users; --",
    "' UNION SELECT username, password FROM users--",
    "1=1",
    "' OR 1=1--",
  ];
  
  for (const payload of injections) {
    const response = await request.get(`/api/users?search=${encodeURIComponent(payload)}`, {
      headers: { Authorization: `Bearer ${authToken}` },
    });
    
    // Deve retornar 200 com resultados seguros/vazios, não erro ou dump de dados
    expect(response.status()).toBe(200);
    const body = await response.json();
    
    // Não deve retornar todos os usuários (indicaria que a injeção funcionou)
    const count = Array.isArray(body.data) ? body.data.length : body.length;
    expect(count).toBeLessThan(100);  // Verificação básica: não está despejando todos os usuários
  }
});

4. Cross-Site Scripting (XSS)

test('input do usuário não é executado como script', async ({ page }) => {
  await page.goto('/login');
  
  // Fazer login primeiro
  await page.fill('[data-testid="email"]', 'user@test.com');
  await page.fill('[data-testid="password"]', 'SenhaValida1');
  await page.click('[data-testid="submit"]');
  
  // Tentar injetar script em um campo controlado pelo usuário
  await page.goto('/profile');
  
  const xssPayload = '<script>alert("XSS")</script>';
  await page.fill('[data-testid="display-name"]', xssPayload);
  await page.click('[data-testid="save"]');
  
  // Navegar para outra página e voltar para ver se o script executa
  await page.goto('/dashboard');
  await page.goto('/profile');
  
  // Verificar se um dialog aparece (apareceria se o XSS funcionasse)
  let dialogAppeared = false;
  page.on('dialog', dialog => {
    dialogAppeared = true;
    dialog.dismiss();
  });
  
  await page.waitForTimeout(1000);  // Dar tempo para o script executar se estiver presente
  
  expect(dialogAppeared).toBe(false);
  
  // O nome deve aparecer como texto, não ser executado
  const nameField = page.getByTestId('display-name');
  const value = await nameField.inputValue();
  expect(value).toBe(xssPayload);  // Armazenado como texto, não executado
});

5. Exposição de dados sensíveis

test('senha não é retornada na resposta da API', async ({ request, authToken }) => {
  const response = await request.get('/api/users/1', {
    headers: { Authorization: `Bearer ${authToken}` },
  });
  
  const body = await response.json();
  
  expect(body.password).toBeUndefined();
  expect(body.passwordHash).toBeUndefined();
  expect(body.passwordSalt).toBeUndefined();
});

test('token de auth não aparece nos headers de resposta', async ({ request }) => {
  const response = await request.post('/api/auth/login', {
    data: { email: 'user@test.com', password: 'SenhaValida1' },
  });
  
  // Token deve estar no body, não exposto em headers para todos
  const headers = response.headers();
  expect(headers['x-auth-token']).toBeUndefined();
  expect(headers['authorization']).toBeUndefined();
  
  // Mas deve estar no body
  const body = await response.json();
  expect(body.token).toBeDefined();
});

test('API usa HTTPS', async ({ request }) => {
  const response = await request.get('/api/users');
  
  // Verificar header HSTS
  const headers = response.headers();
  expect(headers['strict-transport-security']).toBeDefined();
});

OWASP ZAP: scanning de segurança automatizado

O OWASP ZAP é uma ferramenta gratuita de scanning de segurança. Você pode integrá-la com o Playwright:

// Usando proxy do ZAP com Playwright
test.use({ proxy: { server: 'http://localhost:8080' } });  // proxy do ZAP

test('scan de segurança via ZAP', async ({ page }) => {
  await page.goto('/');
  await page.goto('/login');
  await page.goto('/products');
  // O ZAP faz scan passivo de todas as requisições feitas pelo browser
});

Ou rode o ZAP programaticamente:

# Scan do ZAP via Docker
docker run -t owasp/zap2docker-stable zap-baseline.py \
  -t https://staging.myapp.com \
  -r zap-report.html

Verificação de security headers

test('security headers presentes', async ({ request }) => {
  const response = await request.get('/');
  const headers = response.headers();
  
  // Content Security Policy
  expect(headers['content-security-policy']).toBeDefined();
  
  // Prevenir clickjacking
  expect(headers['x-frame-options']).toBeDefined();
  
  // Prevenir sniffing de tipo MIME
  expect(headers['x-content-type-options']).toBe('nosniff');
  
  // Proteção XSS (legado, mas recomendado)
  expect(headers['x-xss-protection']).toBeDefined();
  
  // Apenas HTTPS
  expect(headers['strict-transport-security']).toBeDefined();
  
  // Não enviar informações de referência para sites externos
  expect(headers['referrer-policy']).toBeDefined();
});

Testando casos extremos de autorização

test.describe('IDOR (Referência Direta Insegura a Objetos)', () => {
  test('usuário não consegue deletar post de outro usuário', async ({ request }) => {
    // Obter tokens de dois usuários diferentes
    const user1Token = await getToken('user1@test.com', 'Pass1');
    const user2Token = await getToken('user2@test.com', 'Pass2');
    
    // Usuário 2 cria um post
    const createResp = await request.post('/api/posts', {
      data: { title: 'Post do Usuário 2', content: 'Conteúdo' },
      headers: { Authorization: `Bearer ${user2Token}` },
    });
    const post = await createResp.json();
    
    // Usuário 1 tenta deletar o post do Usuário 2
    const deleteResp = await request.delete(`/api/posts/${post.id}`, {
      headers: { Authorization: `Bearer ${user1Token}` },
    });
    
    // Deve ser 403 Forbidden
    expect(deleteResp.status()).toBe(403);
    
    // Post ainda deve existir
    const getResp = await request.get(`/api/posts/${post.id}`);
    expect(getResp.status()).toBe(200);
  });
});

Resumo

Testes de segurança que o QA deve sempre incluir:

| Teste | O que verifica |

|-------|----------------|

| Controle de acesso | Usuários não conseguem acessar recursos acima do seu papel |

| IDOR | Usuários não conseguem acessar recursos de outros usuários pelo ID |

| Bloqueio de conta | Proteção contra brute force |

| SQL injection | Input do usuário não é interpretado como SQL |

| XSS | Input do usuário não é executado como script |

| Exposição de dados | Campos sensíveis não são retornados na API |

| Security headers | Headers de HTTPS, CSP e proteção XSS presentes |

Não é necessário encontrar exploits de dia zero. Detectar e prevenir as vulnerabilidades do OWASP Top 10 torna a aplicação dramaticamente mais segura e impede os ataques mais comuns que realmente acontecem no mundo real.

→ Veja também: Fundamentos de Testes de Segurança que Todo Engenheiro QA Deve Conhecer | Autenticação em Testes de API: Chaves API, Tokens Bearer, OAuth2, JWT | O que é uma API REST? Um Guia Prático para Engenheiros QA