DEV Community

Cover image for Como Testar Sua API Contra Inputs Maliciosos (Evite Ataques de Hackers)
Lucas
Lucas

Posted on • Originally published at apidog.com

Como Testar Sua API Contra Inputs Maliciosos (Evite Ataques de Hackers)

Em resumo: A entrada da sua API é uma superfície de ataque. Teste-a como tal: envie campos superdimensionados, tipos incorretos, corpos malformados e strings de injeção. Verifique se o endpoint responde com 4xx, nunca com 5xx. Use validação de esquema com additionalProperties: false, enums e limites de tamanho. Execute esses testes no CI a cada mudança. Isso é ainda mais importante com agentes de IA, que podem gerar e encaminhar payloads na velocidade da máquina.

A maioria das suítes prova apenas que a API funciona quando o cliente se comporta bem: envia um corpo válido, recebe 200 e passa no teste. Isso não mostra o que acontece com entrada hostil. Considere como não confiável qualquer dado que seu endpoint não criou: corpos de requisição, query strings, cabeçalhos, uploads, webhooks e JSON gerado por agentes de IA.

Experimente o Apidog hoje

Em julho de 2026, o Hugging Face descreveu um incidente cujo vetor de entrada eram dados, não credenciais roubadas. Abordamos as lições dessa violação separadamente; aqui, o foco é implementar testes que simulem entradas hostis e executá-los automaticamente. Use o Top 10 de Segurança de API OWASP como referência. O Apidog pode ajudar a definir contratos e executar cenários, mas os exemplos se aplicam a qualquer stack.

A entrada é uma superfície de ataque

Validação não é apenas uma melhoria de UX. Cada campo aceito por uma API é uma suposição que o cliente pode quebrar.

Exemplos comuns:

  • Um limit esperado como inteiro pequeno chega como 999999999.
  • Um filename esperado como nome simples chega como ../../etc/passwd.
  • Um objeto config esperado como configuração chega com instruções ou campos inesperados.

Trate testes de segurança como testes negativos direcionados. Para cada campo, pergunte:

Qual é a pior entrada que ainda cabe neste formato?

Esse hábito cobre boa parte das práticas de segurança de API.

Como “carregar dados” pode virar “executar código”

No incidente do Hugging Face, conjuntos de dados maliciosos acionaram um carregador com execução remota de código, e uma injeção de template estava presente em uma configuração de dataset. Veja o relatório de incidente de segurança.

O padrão é importante:

  1. O endpoint aceita algo descrito como dado.
  2. O carregamento passa por um caminho que interpreta esse dado.
  3. O dado controlado pelo atacante vira instrução executável.

Esse risco existe em endpoints que aceitam:

  • nomes de loaders;
  • formatos de importação;
  • templates;
  • objetos serializados;
  • blocos de configuração;
  • caminhos de arquivo;
  • opções que acabam em comandos de shell.

Se você nunca enviou uma configuração hostil para esses endpoints, ainda não verificou se ela permanece inerte.

Use validação de esquema como controle de segurança

Um esquema rigoroso na borda impede que dados inválidos cheguem à lógica de negócio. Com JSON Schema, o contrato deixa de ser apenas documentação e passa a ser um filtro ativo.

Exemplo de esquema para configuração de dataset:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "additionalProperties": false,
  "required": ["loader", "name"],
  "properties": {
    "loader": {
      "enum": ["csv", "json", "parquet"]
    },
    "name": {
      "type": "string",
      "maxLength": 128,
      "pattern": "^[\\w .-]+$"
    },
    "rows": {
      "type": "integer",
      "minimum": 0,
      "maximum": 1000000
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Esse contrato aplica quatro controles distintos:

Controle O que bloqueia
additionalProperties: false Campos inesperados, como um template inserido no payload
enum em loader Valores não permitidos, como pickle://
maxLength Strings enormes que podem consumir memória
pattern Caracteres e formatos não suportados, como {{ ou '; DROP TABLE

A validação de esquema não elimina toda vulnerabilidade. Um valor pode ser válido no esquema e ainda ser perigoso em uma consulta SQL ou template. Mas ela bloqueia uma classe crítica de falhas: “não verificamos o que este endpoint aceita”.

Escreva testes negativos que comprovem a recusa

Um teste de caminho feliz prova que uma entrada válida gera uma saída válida.

Um teste negativo prova que uma entrada inválida recebe uma recusa controlada.

Para cada campo, crie casos que cubram:

  • tipo incorreto;
  • campo obrigatório ausente;
  • campo proibido presente;
  • valor fora do intervalo;
  • string longa demais;
  • JSON malformado;
  • strings de injeção compatíveis com o contexto.

Em cada resposta, valide pelo menos:

  1. O status é um 4xx, geralmente 400, 413 ou 422.
  2. O status não é 5xx.
  3. Quando aplicável, nenhum efeito colateral ocorreu.

Evite acoplar o teste ao texto exato da mensagem de erro. Prefira status code, estrutura de resposta e ausência de efeitos no banco, fila ou armazenamento.

A lista de verificação de testes de segurança de API pode servir como ponto de partida para essa matriz de casos.

Adicione testes para classes de injeção

Você não precisa testar todas as variações possíveis. Comece com uma sonda fixa por classe de injeção. Isso torna regressões visíveis no CI.

Injeção SQL

Envie uma string como:

1); DROP TABLE datasets;--
Enter fullscreen mode Exit fullscreen mode

Use-a em campos que possam chegar a consultas, filtros ou ordenação.

Resultado esperado:

  • 400 ou uma resposta vazia, dependendo do contrato;
  • nenhum erro interno de banco;
  • nunca um 500.

Mantenha consultas parametrizadas mesmo quando houver validação de esquema.

Injeção de template

Envie payloads como:

{{ 7*7 }}
{{ config.__class__ }}
Enter fullscreen mode Exit fullscreen mode

Teste campos de nome, rótulo, descrição e configuração.

Se a resposta contiver 49, sua entrada foi avaliada por um mecanismo de template. Isso deve ser tratado como uma falha crítica.

Desserialização insegura e loaders remotos

Teste valores inesperados em campos de formato ou loader:

{
  "loader": "pickle://s3/models/payload.pkl"
}
Enter fullscreen mode Exit fullscreen mode

A API deve usar uma allowlist explícita. Não tente “adivinhar” formatos ou carregar handlers dinamicamente com base no valor enviado pelo cliente.

Injeção de comando

Teste campos que possam virar argumento de shell, caminho ou opção de ferramenta:

; id
$(id)
Enter fullscreen mode Exit fullscreen mode

Campos de filename, conversão, exportação e importação merecem atenção especial.

Um 200 que retorna saída de comando não é apenas um bug: é uma descoberta crítica.

Para ampliar a cobertura depois, use ferramentas de detecção automatizada de vulnerabilidades de API. Ainda assim, os casos manuais são a forma mais rápida de capturar falhas óbvias.

Teste payloads superdimensionados e malformados

Nem toda entrada hostil é uma string de injeção. Muitas falhas aparecem antes da validação da aplicação, durante parsing ou alocação de memória.

Teste pelo menos:

  • um campo com 5 MB de caracteres;
  • um array JSON com milhões de itens;
  • JSON truncado;
  • JSON com vírgula final;
  • JSON profundamente aninhado;
  • corpos incompatíveis com o Content-Type.

Exemplos de comportamento esperado:

Entrada Resposta esperada
Corpo acima do limite 413 Payload Too Large
JSON inválido 400 Bad Request
Tipo incompatível 400 ou 415 Unsupported Media Type
JSON muito profundo Rejeição rápida, sem travar workers

Também teste confusão de tipo de conteúdo:

Content-Type: application/json
Enter fullscreen mode Exit fullscreen mode

Com corpo XML:

<dataset><name>test</name></dataset>
Enter fullscreen mode Exit fullscreen mode

E o inverso: XML declarado com JSON, ou JSON enviado como text/plain.

O servidor deve exigir coerência entre cabeçalho e corpo antes de analisar o payload.

Por que agentes de IA aumentam o risco

Agentes de IA não mudam a natureza do problema; eles mudam escala e velocidade.

Um agente pode:

  • gerar valores de entrada que nenhum desenvolvedor antecipou;
  • repetir chamadas automaticamente;
  • encadear milhares de requisições em segundos;
  • encaminhar dados recebidos de documentos, webhooks ou sistemas externos;
  • transportar instruções ocultas através de limites de confiança.

Um documento ou dataset contaminado pode acabar virando uma chamada real à sua API. Esse é o padrão em que “carregar dados” se torna “executar código”.

Leia mais sobre esse tipo de fluxo em injeção de prompt para equipes de API.

A defesa continua sendo a mesma:

  • valide na borda;
  • restrinja o contrato;
  • recuse entradas inesperadas;
  • registre falhas;
  • automatize os testes no CI.

Implemente uma suíte negativa com pytest

O exemplo abaixo envia configurações hostis para um endpoint de staging e verifica se a API recusa o payload de forma controlada.

import httpx
import pytest

BASE = "https://staging.internal/v1"

HOSTILE_CONFIGS = [
    {"loader": "pickle://s3/models/payload.pkl", "format": "auto"},
    {"loader": "csv", "name": "{{ 7*7 }}"},
    {"loader": "csv", "name": "{{ config.__class__ }}"},
    {"loader": "csv", "filter": "1); DROP TABLE datasets;--"},
    {"loader": "csv", "name": "A" * 5_000_000},
]

@pytest.mark.parametrize("config", HOSTILE_CONFIGS)
def test_dataset_config_is_refused(config):
    response = httpx.post(
        f"{BASE}/datasets",
        json={"config": config},
        timeout=10,
    )

    assert response.status_code in (400, 413, 422), response.text
    assert response.status_code < 500, (
        "5xx indica que o payload alcançou lógica que deveria ser inacessível"
    )
    assert "49" not in response.text, (
        "Template renderizado: possível injeção de template no servidor"
    )
Enter fullscreen mode Exit fullscreen mode

Ajuste os status aceitos ao contrato da sua API. Por exemplo, se o endpoint usa 415 para tipo de conteúdo inválido, inclua esse código nos testes específicos dessa condição.

Também vale incluir verificações de efeito colateral. Por exemplo, após enviar um payload inválido, confirme que nenhum dataset foi criado:

def test_invalid_config_creates_no_dataset():
    payload = {
        "config": {
            "loader": "pickle://s3/models/payload.pkl"
        }
    }

    response = httpx.post(f"{BASE}/datasets", json=payload, timeout=10)

    assert response.status_code in (400, 422)

    datasets = httpx.get(f"{BASE}/datasets", timeout=10).json()
    assert all(item["loader"] != "pickle://s3/models/payload.pkl" for item in datasets)
Enter fullscreen mode Exit fullscreen mode

Execute a suíte no CI

Faça esses testes rodarem em todo push e pull request. Um workflow mínimo de GitHub Actions:

name: api-abuse-tests

on: [push, pull_request]

jobs:
  negative-input:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4
      - run: pytest tests/negative_input.py -q
Enter fullscreen mode Exit fullscreen mode

Execute contra staging ou um ambiente isolado. Não rode payloads de estresse, injeção de comando ou requisições gigantes diretamente em produção.

No Apidog, você pode definir o endpoint a partir de um contrato OpenAPI e salvar cenários negativos ao lado dos cenários de caminho feliz:

  • campo superdimensionado;
  • tipo incorreto;
  • propriedade adicional;
  • loader fora da allowlist;
  • payload malformado;
  • strings de injeção.

Depois, execute os mesmos cenários no CI pela CLI do Apidog. Assim, uma alteração que enfraqueça a validação falha a build antes do deploy. Para começar, baixe o Apidog e adicione um cenário negativo a um endpoint existente.

O Apidog é uma ferramenta de design, teste, mock e documentação. Ele não substitui WAF, SIEM ou filtragem de tráfego em tempo real. O objetivo é deixar explícito o que o endpoint aceita e garantir, continuamente, que entradas fora desse contrato sejam recusadas.

Perguntas frequentes

Qual é a diferença entre teste negativo e fuzzing?

Teste negativo usa entradas ruins selecionadas intencionalmente, uma para cada falha relevante. Fuzzing envia grandes volumes de entradas aleatórias ou mutadas para descobrir casos não antecipados.

Comece com testes negativos: são rápidos, determinísticos e adequados para CI. Adicione fuzzing para ampliar a busca.

Esses testes devem rodar em produção?

Não. Execute-os em staging ou em um ambiente isolado.

Payloads superdimensionados, JSON profundamente aninhado e sondas de injeção podem estressar o sistema ou alterar dados se existir uma falha.

Um WAF não resolve esse problema?

Um WAF é uma camada útil de defesa em profundidade, mas não substitui a validação no aplicativo.

O WAF não conhece toda a sua lógica de negócio, regras podem ser contornadas e configurações podem falhar. O endpoint precisa recusar entradas inválidas por conta própria.

Quantos casos negativos são necessários por endpoint?

Tenha, no mínimo, um caso por campo para cada classe de falha aplicável:

  • tipo incorreto;
  • valor fora de intervalo;
  • valor muito longo;
  • campo obrigatório ausente;
  • campo proibido presente;
  • payload de injeção compatível com o contexto.

O objetivo é cobrir classes de risco, não maximizar a quantidade bruta de testes.

Validação de esquema impede injeção completamente?

Não. Ela reduz muito a superfície de ataque ao bloquear formatos inesperados, campos extras e tamanhos abusivos.

Ainda mantenha:

  • consultas parametrizadas;
  • desserialização segura;
  • allowlists para loaders e formatos;
  • codificação de saída;
  • isolamento de execução;
  • limites de tamanho e timeout.

O esquema é a primeira barreira: ele impede que entradas que sua API não suporta cheguem às camadas que precisam defendê-las.

Top comments (0)