· Security musings

Catégories

Tags

🔍 Licence d'Utilisation 🔍

Sauf mention contraire, le contenu de ce blog est sous licence CC BY-NC-ND 4.0.

© 2025 à 2042 Sébastien Gioria. Tous droits réservés.

⏱️
Temps de lecture estimé
~8 minutes

Un endpoint de l’interface de gestion RabbitMQ répond à n’importe qui, sans authentification : GET /api/auth. Et il retourne le secret client OAuth configuré sur le broker. À partir de là, un attaquant obtient un jeton administrateur auprès du fournisseur d’identité, se présente à RabbitMQ avec les pleins pouvoirs, et contrôle messages, files, utilisateurs et configuration. Le bug était dans le code depuis début 2024.


Ce que Miggo a trouvé

Deux vulnérabilités, publiées sous une disclosure coordonnée avec les mainteneurs de RabbitMQ, référencées GHSA-pj24-8j6m-vq9q et GHSA-9q2j-2hq8-22r2.

CVE Sévérité Auth requise Composant
CVE-2026-57219 Critique (CVSS 8.7) Non GET /api/auth, plugin de gestion
CVE-2026-57221 Modérée (CVSS 5.3, CWE-862) Oui (compte sans permission) queue.declare / exchange.declare passifs

Les deux touchent les versions 3.13.0 et suivantes, patchées en 4.3.0, 4.2.6, 4.1.11, 4.0.20 et 3.13.15.

Ce qui rend intéressante cette découverte n’est pas tant la gravité individuelle des bugs, plutôt banale une fois expliquée, que la méthode qui les a fait remonter : VulnHunter n’a pas cherché un pattern connu, il a repéré une incohérence structurelle, un endpoint qui s’autorise différemment de ses voisins fonctionnels. C’est le genre de faille qu’un audit manuel pressé par le temps regarde en diagonale, précisément parce qu’elle ne ressemble à rien de connu.


Le mécanisme : CVE-2026-57219

Le code du endpoint est instructif. Dans rabbit_mgmt_wm_auth.erl, le callback is_authorized/2 est codé en dur pour retourner {true, ...} inconditionnellement. Aucune vérification de session, de jeton, ou de rôle. produce_auth_settings/2 construit ensuite une réponse JSON qui inclut, sans filtrage, le champ oauth_client_secret.

sequenceDiagram
    participant A as Attaquant
    participant RMQ as RabbitMQ :15672
    participant IdP as Fournisseur d'identité (Auth0/Azure AD/Keycloak/UAA)

    A->>RMQ: GET /api/auth (sans jeton)
    RMQ-->>A: 200 OK { oauth_client_secret: "..." }
    A->>IdP: Demande de jeton avec client_secret volé
    IdP-->>A: Jeton d'accès administrateur
    A->>RMQ: Connexion AMQP avec le jeton
    RMQ-->>A: Contrôle total : files, messages, utilisateurs, config

Cinq requêtes, aucune credential de départ. C’est ce que Miggo qualifie de “full broker takeover”, et l’expression n’est pas exagérée : une fois le jeton administrateur obtenu, l’attaquant a les mêmes droits qu’un opérateur légitime sur l’ensemble du broker.

Un endpoint qui retourne {true, ...} sans condition n’est pas un bug d’autorisation. C’est l’absence d’autorisation, déguisée en fonction qui en porte le nom.

La population exposée n’est pas anecdotique : tous les déploiements RabbitMQ utilisant OAuth 2 avec management.oauth_client_secret configuré, ce qui couvre une bonne partie des intégrations Auth0, Azure AD/Entra ID, Keycloak ou UAA. Le risque grimpe d’un cran dès que le port 15672 est joignable depuis un réseau qui n’est pas strictement de confiance, ce qui, sur pas mal d’architectures Kubernetes que j’ai pu observer, arrive plus souvent qu’on ne le pense pour un port “juste pour l’admin”.

Note du PandaRedacteur : “juste pour l’admin” est à peu près la phrase la plus dangereuse du vocabulaire réseau. Elle précède systématiquement une règle de firewall trop permissive et un ticket d’incident six mois plus tard.


Le mécanisme : CVE-2026-57221

Deuxième bug, moins spectaculaire mais révélateur de la même classe d’erreur. Le chemin “passive declare” de queue.declare et exchange.declare (mode où le client demande juste de vérifier qu’une ressource existe, sans la créer) ignore complètement le contexte d’autorisation. Les paramètres concernés sont préfixés d’un underscore, convention Erlang signalant “volontairement inutilisé” ; sauf qu’ici, ce qui est volontairement ignoré, c’est le contrôle de permission lui-même. Toutes les opérations comparables, queue.delete, basic.consume, basic.publish, effectuent correctement cette vérification.

Un compte AMQP valide mais sans aucune permission suffit. Il ouvre une connexion, envoie des passive queue.declare en boucle sur des noms de files devinés ou énumérés, et le serveur confirme leur existence avec les comptages de messages associés. Répété sur un vhost partagé entre plusieurs tenants, cela donne une cartographie complète de l’activité métier d’autres clients, sans qu’aucune ligne de log ne distingue ce trafic d’un usage légitime.

On peut distinguer ici deux logiques d’attaque très différentes sous une même famille de bug : la première (CVE-2026-57219) est une porte grande ouverte, la seconde (CVE-2026-57221) est une fissure d’observation. Mais dans un environnement multi-tenant où le modèle de permissions RabbitMQ constitue l’unique frontière entre clients, cette fissure suffit à casser l’hypothèse d’isolation sur laquelle repose toute l’architecture, un sujet que j’ai déjà creusé sous un autre angle dans AST06: Isolation faible.


Analyse STRIDE

Catégorie STRIDEApplicableExplication
Spoofing (Usurpation d'identité)OuiLe secret OAuth volé permet à l'attaquant d'obtenir un jeton légitime auprès de l'IdP et de se faire passer pour un administrateur du broker.
Tampering (Falsification)OuiUn administrateur usurpé peut modifier files, exchanges, bindings, politiques et utilisateurs.
Repudiation (Répudiation)ModéréL'appel `GET /api/auth` non authentifié n'est pas nécessairement rattachable à un identifiant, ce qui complique l'attribution forensique.
Information Disclosure (Divulgation)OuiLe secret OAuth lui-même (CVE-2026-57219) et les métadonnées de queues/exchanges d'autres tenants (CVE-2026-57221) sont directement exposés.
Denial of Service (Déni de service)FaibleAucune des deux CVE ne documente d'impact direct sur la disponibilité ; un attaquant avec accès admin pourrait néanmoins en provoquer un, mais ce n'est pas le vecteur décrit.
Elevation of Privilege (Élévation de privilèges)OuiC'est le cœur de CVE-2026-57219 : un attaquant anonyme finit avec des privilèges administrateur complets sur le broker.

Impact potentiel

Impact Niveau Description
Confidentialité Critique Secret OAuth exposé sans authentification ; contenu des messages et métadonnées inter-tenants accessibles après exploitation.
Intégrité Critique Un attaquant avec jeton admin peut modifier ou supprimer files, exchanges, utilisateurs, politiques.
Disponibilité Élevé Non documenté directement dans l'advisory, mais un accès admin complet permet trivialement la purge ou la suppression de files critiques.
Réputation Sévère Un broker de messages compromis touche potentiellement tous les services en aval qui en dépendent ; l'incident se propage bien au-delà du composant initial.

Mitigations

  1. Patcher immédiatement. Montez vers 4.3.0, 4.2.6, 4.1.11, 4.0.20 ou 3.13.15 selon votre branche. Pas de contournement de configuration qui neutralise complètement CVE-2026-57219 sans le correctif.
  2. Faites une rotation du secret OAuth. Si le port 15672 a jamais été joignable depuis un réseau non fiable, supposez le secret compromis et régénérez-le côté IdP, sans attendre une preuve d’exploitation.
  3. Isolez le port 15672. L’interface de gestion n’a rien à faire exposée au-delà d’un réseau d’administration strictement filtré, VPN ou bastion compris.
  4. Séparez vos tenants par vhost distinct, pas par permissions dans un vhost partagé. C’est la seule mitigation réelle contre CVE-2026-57221 : le modèle de permissions RabbitMQ n’était pas conçu comme frontière de sécurité forte entre locataires non fiables.
  5. Vérifiez vos images figées. Conteneurs pinnés, charts Helm, produits tiers embarquant RabbitMQ : la vulnérabilité traîne depuis début 2024, donc une image “stable” datant d’avant le patch reste vulnérable même si le dépôt source, lui, est à jour. C’est exactement le type de dérive que je décrivais dans AST02: Supply Chain Compromise et dans AST07: Update Drift.
# Vérification rapide de la version exposée par l'interface de gestion
import requests

def check_rabbitmq_version(base_url: str) -> str:
    resp = requests.get(f"{base_url}/api/overview", timeout=5)
    resp.raise_for_status()
    return resp.json().get("rabbitmq_version", "inconnue")

# Comparer contre les versions patchées : 4.3.0, 4.2.6, 4.1.11, 4.0.20, 3.13.15

Pour prioriser ce correctif face au reste de votre backlog de vulnérabilités, la matrice CVE/CVSS/EPSS/KEV reste mon outil de référence : un CVSS de 8.7 sur un composant exposé publiquement, sans authentification requise, se traite en urgence, pas au prochain sprint de maintenance.

Note du PandaRedacteur : je note aussi que si vous n’avez toujours pas de vhost séparé par tenant sur votre RabbitMQ partagé, cet article est un bon prétexte pour l’ajouter à votre backlog avant que quelqu’un d’autre ne vous le rappelle via un incident.


À retenir 📌

  • CVE-2026-57219 (CVSS 8.7) : `GET /api/auth` sans authentification retourne le secret client OAuth du broker RabbitMQ, menant à une prise de contrôle admin complète.
  • CVE-2026-57221 (CVSS 5.3) : les `queue.declare`/`exchange.declare` passifs sautent le contrôle de permissions, permettant l'énumération de files et exchanges d'autres tenants.
  • Les deux bugs dataient de début 2024, découverts par le système autonome VulnHunter de Miggo, pas par un audit manuel classique.
  • Patcher vers 4.3.0/4.2.6/4.1.11/4.0.20/3.13.15 et faire une rotation du secret OAuth si le port 15672 a été exposé.
  • Séparez vos tenants par vhost, pas seulement par permissions : le modèle RabbitMQ n'a jamais été pensé comme frontière de sécurité forte à lui seul.