A maioria dos testes ofensivos ainda faz recon de sistemas de IA como se fossem aplicações web tradicionais: porta 443, uma tela de login, talvez um prefixo /api. Isso não é o suficiente.
Um alvo com IA não é uma aplicação só. É um ecossistema — camadas de orquestração, pipelines de RAG, vector stores, APIs de inferência e, frequentemente, vários agentes compartilhando o mesmo código-base em portas altas e não padronizadas. As pegadas são diferentes. O recon também deveria ser.
Este artigo percorre esse recon: sete técnicas passivas, um alvo de laboratório, uma ferramenta open-source (SpyAI) e um mapa completo de superfície de ataque produzido antes de qualquer prompt ser digitado.
"Dá para aprender mais sobre um sistema de IA pela infraestrutura do que pela janela de chat."
Swagger expõe endpoints de debug. JSON em /health revela nomes de agentes. Schemas OpenAPI listam paths de ingestão de RAG. JavaScript na UI confirma APIs POST-only que retornam 405 num GET simples. Tudo isso é observável sem credenciais, sem fuzzing e sem conversar com o modelo.
Essa é a diferença entre disparar prompt injections genéricos e mirar no agente, endpoint e trust boundary certos.
O SpyAI não envia credenciais, não faz fuzzing agressivo e não entrega payloads de exploit. Toda sondagem é somente-leitura: checagens de conexão TCP, requisições HEAD e chamadas GET contra uma wordlist curada.
Mas passivo aqui não significa invisível. O SpyAI gera tráfego de rede de verdade. Rode apenas em sistemas que você possui ou tem permissão explícita para testar — scan não autorizado pode violar lei ou política interna.
Todos os exemplos deste artigo são baseados em scans realizados no meu ambiente de laboratório pessoal da Offensive Security. Os endereços IP mencionados, como 192.168.217.21, são endereços privados (RFC1918) usados exclusivamente nesse ambiente isolado e não são acessíveis pela internet pública. Este artigo não divulga informações sensíveis ou proprietárias dos labs da Offensive Security.
# Roda o pipeline completo de recon passivo contra um alvo autorizado.
./spy-ai 192.168.217.21
~66 segundos depois, o SpyAI gera um relatório em Markdown e um log completo de execução:
| Porta | Serviço |
|---|---|
| 22 | ssh |
| 5432 | postgresql |
| 8001 | http |
| 8002 | http |
| 8003 | http |
| 8011 | http |
| 8012 | http |
Sete portas. Cinco na faixa "AI-likely". Um backend PostgreSQL. Zero mensagens de chat enviadas.
Esse padrão — várias portas altas mais um banco relacional — já sugere um deployment multi-agente com armazenamento persistente, provavelmente RAG ou estado de sessão. A arquitetura começa a tomar forma antes mesmo da primeira técnica rodar.
Scanners genéricos param em 22, 80, 443. Stacks de IA vivem em outro lugar: APIs de inferência em 8000–8080, Ollama na 11434, Elasticsearch na 9200, Kibana na 5601, MinIO na 9000/9001.
O SpyAI paraleliza sondagens TCP connect em 106 portas usando um scanner Python só com stdlib — sem depender de nmap:
// Retorna True se o handshake TCP para host:port tiver sucesso dentro do timeout.
def try_port(host: str, port: int, timeout: float) -> bool:
try:
with socket.create_connection((host, port), timeout=timeout):
return True
except (OSError, socket.timeout):
return False
// Sonda cada porta em paralelo e retorna a lista ordenada das abertas.
def scan(host: str, ports: Sequence[int], timeout: float, workers: int) -> List[int]:
open_ports: List[int] = []
with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as pool:
futures = {pool.submit(try_port, host, p, timeout): p for p in ports}
for fut in concurrent.futures.as_completed(futures):
port = futures[fut]
if fut.result():
open_ports.append(port)
return sorted(open_ports)
Portas entre 8000–8080 são rotuladas como "ai-likely". Serviços conhecidos ganham dicas explícitas: 11434 → Ollama, 9200 → Elasticsearch, 5601 → Kibana.
# Scan de 106 portas comuns + prováveis de IA; 7 voltaram abertas.
$ python3 lib/scan_ports.py 192.168.217.21 -p 22,25,53,80,...,11434,27017
scanning 106 port(s) on 192.168.217.21 ...
open: 7
22 5432 8001 8002 8003 8011 8012
Conclusão: cinco portas consecutivas prováveis de IA num único host não é um chatbot isolado. É uma frota.
Uma vez que portas HTTP-prováveis estão abertas, o SpyAI envia HEAD / — só cabeçalhos, sem corpo.
# Requisição HEAD só pelos headers; a resposta identifica o framework.
$ curl -sS -k -I http://192.168.217.21:8001/
HTTP/1.1 405 Method Not Allowed
server: uvicorn
allow: GET
content-type: application/json
405 + server: uvicorn + allow: GET é assinatura clássica de FastAPI/Starlette: a raiz serve uma UI de chat via GET, não HEAD. Framework confirmado antes de qualquer autenticação.
Todas as cinco portas de agente retornaram a mesma assinatura. Mesma stack, múltiplas instâncias.
O SpyAI também captura headers customizados quando presentes — X-AI-Backend, X-RAG-Provider, X-Model, X-Inference-Engine — que podem identificar o provedor de inferência ou o backend de RAG sem tocar na lógica da aplicação.
Conclusão: um 405 é informação, não um beco sem saída.
O port scan diz onde. A enumeração de paths diz o quê.
O SpyAI sonda 34 paths por porta HTTP aberta, a partir de uma wordlist agrupada por ecossistema:
| Categoria | Paths |
|---|---|
| FastAPI / Swagger | /openapi.json, /docs, /redoc, /health |
| Compatível com OpenAI | /v1/models, /v1/chat/completions |
| Object storage | /minio/health/live, /minio/health/ready |
| K8s / observabilidade | /-/health, /-/metrics, /metrics |
Resultados na porta 8001:
| Path | Status | Notas |
|---|---|---|
/ | 200 | title="IT Helpdesk Assistant" |
/docs | 200 | Swagger UI do FastAPI |
/redoc | 200 | ReDoc |
/openapi.json | 200 | 4.906 bytes |
/health | 200 | identidade do agente (abaixo) |
/chat | 405 | allow: POST |
# GET não autenticado em /health vaza o nome do agente e a porta associada.
$ curl http://192.168.217.21:8001/health
{"status":"healthy","agent":"IT Helpdesk Assistant","port":8001}
Uma requisição sem autenticação: nome do agente, status de saúde, porta associada.
Conclusão: /health costuma ser o inventário de agentes mais rápido do tabuleiro.
Endpoints documentados vivem no OpenAPI. Os não documentados se escondem no JavaScript do frontend.
O SpyAI analisa a página / em busca de chamadas fetch(), uso de axios, atributos data-endpoint e chaves de configuração comuns (apiUrl, endpoint, baseUrl).
// JS do frontend que revela o contrato real de /chat: POST + JSON, session_id na resposta.
const r = await fetch('/chat', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(body)
});
const d = await r.json();
sid = d.session_id;
Três descobertas, zero necessidade de OpenAPI:
/chat é POST-only com corpo JSON (message, session_id opcional).session_id e response.O GET simples em /chat retornou 405. Fácil de passar batido sem mineração de JS.
Conclusão: a UI de chat é fonte de recon, não só superfície de ataque.
Quando /openapi.json retorna 200, o SpyAI extrai todo path e método HTTP.
Porta 8001 — 16 paths:
# Todas as rotas e métodos, extraídos direto do /openapi.json exposto.
GET / Chat Ui
POST /chat Chat
POST /browse Browse
POST /upload Upload
POST /summarize Summarize Docs
POST /kb/add Kb Add
GET /kb/search Kb Search Endpoint
GET /kb/topics Kb Topics
GET /debug/db-schema Debug Schema
GET /debug/query Debug Query
GET /logs/last-tool-call Last Tool Call
GET /logs/latest Logs Latest
POST /session/new New Session
POST /reset Reset
POST /review Review
GET /health Health
O mesmo schema de 16 paths apareceu nas cinco portas de agente. Um código-base, cinco personas, uma única superfície de ataque compartilhada.
Conclusão: OpenAPI é a planta baixa. Trate um Swagger exposto como um painel de admin exposto.
O SpyAI sinaliza respostas JSON de /health que contêm um campo agent — um padrão comum em deployments de LLM baseados em uvicorn.
Inventário completo de agentes de um único scan:
| Porta | Agente |
|---|---|
| 8001 | IT Helpdesk Assistant |
| 8002 | Secure IT Assistant |
| 8003 | Knowledge Base Assistant |
| 8011 | Secure Engineering Portal |
| 8012 | Department Resources Assistant |
Cinco personas, um host. Os nomes sugerem trust boundaries e escopos de dados diferentes — um agente de helpdesk provavelmente vê um contexto diferente de um "Secure Engineering Portal". Esse mapeamento direciona o prompt injection e o posicionamento de documentos envenenados na fase seguinte.
Conclusão: nomes de agente são indicadores de escopo, não só rótulos.
Em portas HTTPS, o SpyAI roda openssl s_client para extrair subject, issuer, validade e SANs.
Este alvo de laboratório não tinha HTTPS na execução — todos os agentes serviram HTTP puro. Quando há TLS, os SANs costumam revelar hostnames internos, domínios de staging e infraestrutura relacionada, invisível só com DNS. O SpyAI roda essa etapa automaticamente nas portas 443, 8443 e outras prováveis de HTTPS.
Conclusão: certificados são OSINT passivo sentado em toda porta HTTPS.
Depois das sete técnicas, este é o mapa completo de superfície para a porta 8001:
| Path | Status | O que sinaliza |
|---|---|---|
/ | 200 | Chat UI — alvo de mineração de JS |
/docs | 200 | Swagger interativo — superfície de ataque por si só |
/redoc | 200 | Documentação da API |
/openapi.json | 200 | Schema completo — 16 paths |
/health | 200 | Identidade do agente |
/chat | 405 | API POST-only — confirmada via JS |
Antes de qualquer teste ativo, um pentester já sabe:
/kb/add, /kb/search, /upload, /summarize./debug/db-schema, /debug/query./logs/last-tool-call — possível vazamento de trace de MCP/RAG.Esse é o ROI do recon: um mapa preciso em menos de um minuto.
Recon passivo não explora achados — ele os mapeia. Esse mapa é o que torna a fase seguinte cirúrgica em vez de ruidosa.
MITRE ATLAS
| Técnica | O que o SpyAI revela |
|---|---|
| AML.T0006 — Active Scanning | TCP connect em faixas de porta prováveis de IA |
| AML.T0007 — Discover AI Artifacts | OpenAPI, Swagger, /health de agente |
| AML.T0024 — Exfiltration via AI Inference API | /kb/search, /debug/query, /logs/* |
OWASP Top 10 para LLM (2025)
| Risco | Recon |
|---|---|
| LLM02 — Sensitive Information Disclosure | /debug/db-schema, /debug/query, /logs/latest sem autenticação |
| LLM06 — Excessive Agency | /logs/last-tool-call; nomes de agente sugerem escopos de privilégio diferentes |
| LLM08 — Vector and Embedding Weaknesses | /kb/add, /kb/search, /upload mapeiam ingestão e recuperação de RAG |
O SpyAI é a primeira passada — deliberadamente estreito:
-sV). Rode o nmap separadamente para versões exatas.python3, curl, openssl (opcional). Só isso.Recon passivo responde: o que está deployado, onde, e o que expõe?
Recon ativo responde: como o sistema se comporta sob pressão?
O ponto de transição é esse mapa. Agora você sabe qual agente atacar, qual endpoint de debug sondar, qual path de RAG envenenar — antes do primeiro prompt pousar.
Se você está construindo capacidade de red team para IA, comece por aqui. Mapeie a stack. Depois, prompte.
Este artigo é o Capítulo 1 de uma série sobre recon de IA — mapeamento passivo antes de qualquer interação com o modelo. O Capítulo 2 cobre recon ativo (teste de contradição, corte de conhecimento, forçar citação). O objetivo é um playbook completo, construído a partir de trabalho de laboratório real.
# Clone, dê permissão de execução aos scripts e aponte o SpyAI para o seu próprio alvo.
git clone https://github.com/jmessiass/spy-ai.git
cd spy-ai
chmod +x spy-ai lib/scan_ports.py
./spy-ai <seu-alvo-autorizado>
Os relatórios ficam em output/<alvo>-<N>.md, com log completo de execução. As wordlists em data/ são extensíveis pela comunidade — abra uma issue ou PR se encontrar paths que valham a pena adicionar.
É isso, pessoal!
Repositório: github.com/jmessiass/spy-ai
Publicado originalmente em inglês no Medium: Spy Before You Prompt. Esta versão foi adaptada e traduzida para o português.
Copyright 2026. Designed By B2SEC - Cyber Protection