A Lista de Permissões que Não Permite: Teste de Substring em uma Ferramenta de Agente LLM

Segurança de IA Nível 3/4 ~5 min 18 de setembro de 2026

O desafio

Atlas é o assistente que a Halcyon mantém para o próprio time. Ele tem exatamente uma ferramenta que toca a rede: fetch_url, para ler uma página que um colega envia antes de responder sobre ela. O time sabia que dar um cliente HTTP a um modelo é o caminho curto para server-side request forgery, então escreveu uma proteção antes de publicar. Apenas três domínios de documentação são alcançáveis. Apenas http e https. Uma lista de bloqueio cobre o endereço de metadados da nuvem e o loopback. Passou por revisão duas vezes. Mesmo assim não segura. Uma das três verificações aceita hosts que ninguém na Halcyon possui, e quem conseguir colocar um link na frente do Atlas pode apontar a ferramenta para uma infraestrutura que controla. Leia os três arquivos e diga o nome da função cuja verificação deixa passar hosts controlados pelo atacante.

O que você vai aprender

  • Diferenciar um teste de pertinência de um teste de substring em Python
  • Explicar por que a comparação por substring é segura em uma lista de bloqueio e insegura em uma lista de permissões
  • Construir um hostname que engana uma lista de permissões baseada em substring
  • Descrever como uma ferramenta de agente transforma um SSRF em saída legível para o atacante
  • Escrever uma comparação de host que não pode ser ampliada por um sufixo controlado pelo atacante

Habilidades testadas

Revisão de código-fonteRaciocínio sobre SSRFSegurança de ferramentas de agentes de IA

Pré-requisitos

  • Leitura de funções e comprehensions em Python
  • O que são um nome de host e um subdomínio

Como funciona

Dar um cliente HTTP a um modelo é dar a ele um forjador de requisições. O modelo decide a URL, o servidor faz a requisição, e o servidor fica dentro da sua rede. Então toda a propriedade de segurança de uma ferramenta fetch_url se resume a uma pergunta: esse host é um dos que escolhemos permitir?

O Atlas responde a essa pergunta em host_allowed, e por acidente responde a uma pergunta diferente. any(domain in host for domain in ALLOWED_DOMAINS) parece um teste de pertinência, e em scheme_allowed a mesma palavra-chave realmente é um, porque urlparse(url).scheme in ("http", "https") compara contra os elementos de uma tupla. Entre duas strings, in significa substring. host_allowed está perguntando se o texto docs.halcyon.example aparece em algum lugar dentro do host.

Um atacante que possui qualquer domínio possui um número infinito de hosts que contêm esse texto. docs.halcyon.example.attacker.example é um subdomínio comum de attacker.example. Não custa nada, resolve para onde quer que ele aponte, e satisfaz a verificação. Esconder o nome permitido no meio funciona igualmente bem.

A lista de bloqueio algumas linhas acima também é um teste de substring, e esse não é um bug. Ampliar uma lista de bloqueio faz com que ela capture mais coisas, então any(bad in url.lower() for bad in BLOCKED) recusa mais URLs do que o pretendido, em vez de menos. A direção importa: uma comparação frouxa é perigosa quando concede acesso e apenas tosca quando nega.

O que torna isso pior do que um SSRF cego é que o agente relata de volta. O corpo da resposta é anexado à conversa como saída de ferramenta e depois resumido para quem estiver falando com o Atlas, então o atacante não precisa de um canal lateral para ler o que o servidor buscou.

Erros comuns

  • Responder com scheme_allowed. Usa a mesma palavra-chave, mas compara contra uma tupla, então é um teste de pertinência de verdade e está correto.
  • Responder com target_allowed. Sua ordem e cobertura estão corretas; ela falha apenas porque o auxiliar em que confia está errado.
  • Chamar a lista de bloqueio de bug. A comparação por substring em uma lista de bloqueio bloqueia demais, que é a direção segura.
  • Culpar allow_redirects. Está explicitamente definido como False, que é a única coisa que os autores acertaram pelo motivo certo.
  • Achar que o endereço de metadados é necessário. Qualquer host do atacante já é suficiente, porque o corpo buscado volta para o modelo.

Como se proteger

Compare hosts da mesma forma que o DNS: como rótulos inteiros, por igualdade.

  • Faça o parse uma vez com urlparse, use hostname em vez de netloc para que credenciais e portas já venham removidas, converta para minúsculas e remova um ponto final.
  • Teste contra um set com == ou in nesse conjunto. Se subdomínios precisarem ser permitidos, verifique host == d or host.endswith("." + d), nunca uma substring simples.
  • Resolva o nome de host e rejeite a requisição se o endereço for privado, loopback, link-local ou multicast, depois conecte-se ao endereço que você validou, para que o DNS não possa mudar debaixo de você.
  • Mantenha os redirecionamentos desativados, ou execute a verificação completa novamente a cada salto.
  • Filtre a saída (egress) do container em que o agente roda. A lista de permissões é o primeiro controle, não o único.

Solução completa

Membros Pro e Max desbloqueiam o passo a passo completo.

Assinar Pro

Estatísticas da comunidade

125 resoluções
75% taxa de sucesso
M2F14M3 Primeiro sangue

Hacks de hoje relacionados

Colaboradores

Colaboradores creditados

Estes hackers relataram problemas, melhoraram o conteúdo e ajudaram a fortalecer esta página.

27.000+ Hackers 100+ Labs & Cursos Grátis
Comece Grátis