Un confrère m’envoie un lien RIPE Atlas : « Tiens, regarde, t’as un truc bizarre sur ton réseau. »
Le lien est tout ce qu’il y a de plus normal : https://atlas.ripe.net/measurements/<id>.
Un domaine ripe.net de confiance, un outil que j’utilise quasiment toutes les semaines.
Je clique. Évidemment que je clique… Vous auriez cliqué aussi.
Et c’est tout.
À cet instant, les ROA de tous les LIR dont j’ai la gestion viennent d’être remplacées par des ROA AS0, sans le moindre signal à l’écran.
Quelques minutes plus tard, Algathia commence à disparaître d’Internet, et je n’en ai pas encore la moindre idée.
Ce scénario n’est pas arrivé. Il aurait pu.
C’est la chaîne d’exploitation que Sasha Romijn a découverte et documentée. Tout le crédit lui revient, l’article original est à lire en entier ici : https://mxsasha.eu/posts/ripe-ncc-rpki-exploit-chain/.
Je n’ai vu personne en parler en français, et comme beaucoup, je me serais fait avoir.
Alors voici une lecture côté opérateur : comment fonctionnait cette chaîne de failles, quels mécanismes étaient en jeu et ce qu’elle change concrètement quand on possède son propre numéro d’AS.
RPKI en bref
Internet, c’est des dizaines de milliers de réseaux qui s’annoncent en permanence les uns aux autres : « pour joindre telle plage d’adresses IP, passez par moi ». Par défaut, rien ne vérifie que celui qui l’annonce en a le droit, et ce n’est pas un problème du passé : les détournements de trafic, par erreur de configuration comme par malveillance, sont toujours d’actualité¹.
RPKI ajoute une signature cryptographique à ce mécanisme. Le détenteur légitime d’une plage d’adresses publie une ROA, une déclaration signée numériquement qui dit « voici le ou les réseaux autorisés à annoncer ces adresses ». Les autres réseaux vérifient cette signature et rejettent ce qui ne correspond pas. Cas particulier, déjà évoqué en introduction : une ROA pointant vers AS0, une valeur réservée qui signifie « cette plage ne doit être annoncée par personne ». Côté RIPE NCC, chaque membre dispose d’une autorité de certification (CA) qui héberge et signe ses ROA. Aujourd’hui, environ 60 % des annonces sur Internet sont signées par une ROA².
La confiance qu’inspire RPKI est aussi son talon d’Achille : si vos ROA ne correspondent plus à vos annonces réelles, le reste d’Internet vous considère comme illégitime et cesse de vous joindre.
Autrement dit : falsifier vos ROA, c’est vous effacer de la carte.
La chaîne d’attaque
Trois étapes : entrer dans la page, traverser la session, publier des ROA falsifiées
1. Injecter du JavaScript dans *.ripe.net
Sasha Romijn a découvert trois points d’entrée XSS distincts, dans deux outils RIPE NCC : l’ancien RIPEstat (un outil d’information réseau) et RIPE Atlas (un réseau de sondes de mesure) :
- L’ancienne version de stat.ripe.net affichait
version.bind, une requête DNS de debug qui retourne la version du serveur interrogé, sans échappement. Sasha y a glissé un payload, du code JavaScript qui s’exécute dans le navigateur de la cible. - RIPE Atlas, lors d’une mesure DNS, affichait le NSID — un champ permettant d’identifier le serveur qui a répondu — également sans échappement. Là encore, on pouvait y glisser du code.
- RIPE Atlas, lors d’une mesure TLS, affichait les
SubjectAlternativeName, la liste des noms d’hôte pour lesquels le certificat est valide… Bon, je vous refais pas le topo.
Les trois vecteurs n’ont ni la même surface d’attaque ni la même ergonomie. Les deux premiers exigent une interaction supplémentaire de la cible : ouvrir un widget précis ou naviguer dans une vue particulière. Le troisième s’exécute à la simple ouverture de la page de résultats. C’est lui qui rend l’attaque triviale à propager : un lien Atlas, partagé par un confrère.
2. Traverser la session SSO
Une XSS dans atlas.ripe.net, c’est une compromission de RIPE Atlas. Pas de RPKI. Sauf que tous les services *.ripe.net partagent une seule session, matérialisée par un cookie crowd.token_key. Se connecter à RIPE Labs (un blog), à un module de formation ou à Atlas suffit à ouvrir une session valide sur le dashboard RPKI et la RIPE Database.
Le cookie est HttpOnly, JavaScript ne peut pas le lire. C’est la bonne pratique, et ça empêche un script de l’exfiltrer vers un serveur tiers. Mais ce n’est pas ce qu’on cherche à faire : on ne veut pas voler la session pour s’en resservir ailleurs, on veut juste s’en servir depuis le navigateur de la cible, là où il se trouve déjà. Et pour ça, lire le cookie est inutile. Le navigateur, lui, l’envoie automatiquement avec chaque requête vers *.ripe.net.
Le cookie est aussi SameSite=Lax. En théorie, ça doit bloquer les requêtes cross-site. En pratique, tous les sous-domaines *.ripe.net sont same-site entre eux : du point de vue du navigateur, atlas.ripe.net et dashboard.rpki.ripe.net ne sont pas deux sites différents. Une requête de l’un vers l’autre passe sans déclencher la protection.
Restent les protections cross-origin du navigateur, plus strictes. Elles refusent par défaut qu’un script lise la réponse d’un domaine différent. Mais en mode no-cors, le navigateur autorise quand même la requête, il interdit juste de lire la réponse. Pour écrire des ROA, ce n’est pas un obstacle : on n’a pas besoin de savoir si ça a marché, on le verra quelques minutes plus tard dans le routage de la cible…
Trois mécanismes qui font chacun leur travail correctement. Le SSO mutualise les sessions, comme n’importe quel SSO. SameSite=Lax autorise les requêtes same-site. Le mode no-cors permet des écritures aveugles, c’est précisément sa raison d’être.
Aucun n’est cassé. Aucun n’a été pensé en supposant la présence des deux autres.
3. Publier des ROA falsifiées
Le dashboard RPKI, derrière son interface graphique, est une API GraphQL. Toutes les actions sensibles (créer une ROA, en supprimer une, recréer la CA) sont de simples mutations GraphQL envoyées à un seul endpoint : dashboard.rpki.ripe.net/graphql.
Et cet endpoint n’avait, jusqu’à la disclosure, aucune protection CSRF.
La défense standard contre la CSRF, c’est un token unique attaché à chaque session, qu’un attaquant ne peut pas deviner depuis un autre domaine. Une variante plus légère consiste à vérifier l’en-tête Origin : si la requête vient d’ailleurs que https://dashboard.rpki.ripe.net lui-même, on la refuse.
Rien de tout ça n’était en place. Le cookie de session faisait toute l’authentification, et le cookie, on l’a vu, s’envoie tout seul.
Reste une dernière subtilité : le navigateur, en mode no-cors, n’autorise qu’un nombre limité de types MIME. Pas de application/json, qui est pourtant le format natif de GraphQL. Mais beaucoup d’implémentations GraphQL, par souci de compatibilité, acceptent aussi application/x-www-form-urlencoded, précisément l’un des types autorisés en no-cors.
Toutes les pièces sont là.
Le payload complet tient en quelques lignes :
const caId = new URLSearchParams(
document.cookie.replaceAll("; ", "&")
).get("activeMembershipId");
const params = new URLSearchParams();
params.set("query", `mutation {
publishRoas(
caId: "${caId}",
roasToDelete: [
{ asn: "AS213279", maximalLength: 24, prefix: "2.57.252.0/24" },
{ asn: "AS213279", maximalLength: 32, prefix: "2a14:b400::/32" }
],
roasToAdd: [
{ asn: "AS0", maximalLength: 24, prefix: "2.57.252.0/24" },
{ asn: "AS0", maximalLength: 48, prefix: "2a14:b400::/29" }
]
) {
id
}
}`);
fetch("https://dashboard.rpki.ripe.net/graphql", {
method: "POST",
mode: "no-cors",
credentials: "include",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: params.toString()
});
Et c’est là que ça devient particulièrement vicieux : le payload n’a pas besoin de savoir qui clique. Deux cookies, activeRegId et activeMembershipId, ne sont pas HttpOnly : JavaScript peut les lire. Ils indiquent quelle organisation et quelle CA sont actives dans la session courante. Le script s’en sert pour ajuster la mutation à la volée : il supprime les ROA de l’organisation qui vient d’ouvrir le lien, et publie des AS0 à leur place.
Un seul lien peut donc viser plusieurs réseaux différents. L’attaquant n’a pas besoin de connaître ses cibles à l’avance. Il poste son lien sur une mailing-list opérateur, et chaque membre RIPE NCC qui clique se prend une attaque calibrée pour son propre AS.
Une page de mesure Atlas.
Un certificat TLS bricolé.
Une mutation GraphQL de huit lignes.
Un lien envoyé par un confrère.
C’est tout ce qui aurait été nécessaire pour faire tomber un opérateur.
Du clic au blackout
Le RIPE NCC propose des notifications par e-mail pour signaler tout changement de ROA. C’est exactement le genre de filet qu’on aimerait avoir si quelqu’un publie des AS0 dans notre dos. Sauf qu’elles sont désactivées par défaut, et que la chaîne d’attaque les contourne trivialement : détruire puis recréer la CA, c’est une mutation GraphQL au même endpoint, dans la même session. L’opération efface au passage les abonnements aux notifications, et les ROA. L’attaquant publie ensuite ses AS0 sur une CA vierge, sans aucune alerte à déclencher.
Le dashboard RPKI n’est pas la seule cible accessible avec la même session. La RIPE Database, la base qui décrit qui possède quoi, qui maintient quoi, qui peut modifier quoi, est elle aussi exposée. L’endpoint syncupdates accepte des modifications authentifiées par le même cookie, sans protection CSRF non plus.
L’effet sur le routage est plus lent que pour le RPKI, mais l’effet sur la propriété, lui, est immédiat. En remplaçant le mnt-by d’un objet par un mainteneur sous son contrôle, l’attaquant verrouille la cible hors de ses propres ressources. Pas de bouton “récupérer mon compte”, pas de procédure self-service : la restauration passe obligatoirement par un échange manuel avec le support du RIPE NCC.
Quel impact, concrètement ? Sasha l’a mesuré sur son propre AS, en publiant volontairement une ROA AS0 sur ses préfixes.
Cinq minutes pour perdre la joignabilité de ripe.net.
Onze minutes pour perdre bgp.tools.
Vingt-six minutes pour perdre Cloudflare et tous les sites qu’il héberge.
Une heure pour perdre 80 % d’Internet.
Un précédent existe, à plus petite échelle. En janvier 2024, le compte RIPE NCC d’Orange España a été compromis. L’attaquant a modifié les ROA et causé une coupure de huit heures avant restauration. Et c’était un cas plutôt favorable : un seul opérateur, des modifications limitées, un attaquant qui a publiquement revendiqué sur Twitter, accélérant la détection.
La chaîne de Sasha aurait permis l’inverse : plusieurs opérateurs simultanément, pas de revendication, et une dégradation progressive impossible à attribuer dans l’immédiat.
Une dernière idée fausse à dissiper.
Faire tourner sa propre CA pouvait sembler une protection : on ne dépend pas du dashboard RPKI, donc on ne dépend pas de ses failles. C’est faux. La mutation recreate CA détruit la délégation existante et crée une CA hébergée à la place. L’attaquant reprend ensuite la main par la voie habituelle.
Et nous, opérateurs ?
Toutes ces failles sont aujourd’hui corrigées. Mais s’arrêter là, ce serait passer à côté de la vraie question : qu’est-ce qui, dans ma façon d’utiliser les portails RIPE NCC, m’aurait protégé pendant les quatorze mois où la chaîne était exploitable sans que personne ne le sache publiquement ?
Une seule chose : ne pas avoir de session RIPE NCC active au moment du clic.
C’est le seul levier entre vos mains. Trois façons concrètes d’y arriver :
- Un navigateur ou un profil dédié aux portails critiques. Firefox containers, profils Chrome, ou carrément un autre navigateur que celui du quotidien. Vous n’ouvrez le portail RIPE NCC que dans ce profil-là, et vous ne l’utilisez à rien d’autre : pas de Twitter, pas de Slack, pas de lien envoyé par un confrère.
- Une session courte, fermée explicitement. Vous vous loguez pour faire ce que vous avez à faire, vous vous déloguez à la fin. Pas de “je laisse l’onglet ouvert au cas où”. Le cookie ne survit pas à la déconnexion.
- Activer les notifications ROA, même si elles sont contournables. Sur la chaîne complète de Sasha (recreate CA), elles ne déclenchent pas. Sur 90 % des scénarios plus simples, y compris un compte compromis comme Orange España, elles sauvent vos quatre heures de coupure.
Et au-delà de RIPE NCC :
- Surveiller ses annonces depuis l’extérieur. Une alerte BGP.tools, un monitoring RIS, un cron qui interroge votre propre Routinator depuis un point hors-réseau. L’attaque ne donne aucun signal côté portail ; le seul endroit où elle est visible, c’est dans le routage global. Si vous attendez de le découvrir parce qu’un client appelle, vous avez déjà perdu plusieurs heures.
- Avoir un runbook avant d’en avoir besoin. Si vos ROA viennent d’être réécrites, deux cas. Vous avez encore accès à votre compte RIPE NCC : republier les ROA légitimes restaure la situation en quelques minutes côté propagation. Si vous êtes verrouillé hors du compte (compromission, mot de passe changé, MFA réinitialisé) : contacter directement le support du RIPE NCC, par tous les canaux disponibles (email, chat, téléphone).
Pour finir
RPKI reste indispensable. La crypto est solide, les standards sont bien faits, l’opération du RIPE NCC sur la partie cryptographique est sérieuse. Cette chaîne d’exploitation n’invalide rien de tout ça.
Ce qu’elle révèle, c’est autre chose. Un système critique ne se résume jamais à sa partie critique. Autour des HSM et des cérémonies de clés, il y a un portail web. Autour du portail web, il y a un SSO. Autour du SSO, il y a des outils de diagnostic dont la vocation même est d’afficher des données venues d’Internet. Et c’est précisément cette périphérie, que personne ne regarde comme “security-critical”, qui a fourni le point d’entrée.
Le motif n’est pas propre au RIPE NCC. Tous les portails web qui mélangent des actions banales et des actions critiques dans le même domaine de session sont concernés par le même genre de question. Votre registrar DNS, vos fournisseurs de transit, votre interface de PNI: aucun n’est exempt par construction.
Bonne lecture du post original de Sasha Romijn, qui détaille la mécanique avec le niveau de rigueur que mérite le sujet. Et un mot pour son travail : quatorze mois de disclosure, trois correctifs successifs avant que la chaîne soit fermée, tout ça documenté publiquement et sans sensationnalisme. C’est exactement le genre de recherche dont notre écosystème a besoin.
Et un dernier merci à Amandine, qui m’a partagé l’article, sans payload, cette fois.