Accueil / Cybersécurité / Social engineering / Incident de sécurité chez Wikimedia via un ver JavaScript

Incident de sécurité chez Wikimedia via un ver JavaScript

Un code JavaScript auto-propagateur a perturbé plusieurs projets Wikimedia, en modifiant des scripts utilisateurs et en corrompant des pages Meta-Wiki, avant d’être neutralisé par les équipes techniques.

La Fondation Wikimedia a été confrontée à un incident de sécurité impliquant un ver JavaScript capable de se répliquer via les mécanismes de personnalisation de MediaWiki. Selon les éléments rendus publics, le code malveillant a touché des scripts utilisateurs, altéré des pages Meta-Wiki et conduit les ingénieurs à suspendre temporairement les modifications sur l’ensemble des projets par mesure de précaution. L’épisode, resté actif 23 minutes d’après la Fondation, n’aurait pas affecté Wikipédia elle-même ni provoqué de fuite de données. L’affaire met néanmoins en lumière un point sensible : l’exécution de code utilisateur dans l’environnement d’édition, au croisement de la sécurité applicative, des privilèges éditoriaux et de la gouvernance technique des plateformes collaboratives.

Un déclenchement via les scripts utilisateurs de MediaWiki

L’alerte est venue de contributeurs qui ont signalé sur Village Pump, l’espace d’assistance technique, des modifications automatisées massives. D’après leurs constats, des scripts cachés et du contenu indésirable avaient été ajoutés à des pages choisies de manière aléatoire. Face à cette activité anormale, la Fondation Wikimedia a restreint temporairement les modifications sur tous les projets, le temps de contenir l’incident.

Les premiers éléments techniques pointent vers un script malveillant hébergé sur Wikipédia en russe, à l’emplacement User:Ololoshka562/test.js. Le fichier, mis en ligne pour la première fois en mars 2024, avait déjà été lié à des outils employés lors d’autres attaques visant des projets Wikipédia. D’après les informations issues du suivi Phabricator, l’incident a commencé avec l’exécution de ce script. En examinant l’historique des modifications, les journalistes de Bleeping Computer ont ensuite relevé que ce code avait été lancé la semaine précédente par un employé de Wikimedia dans le cadre de tests portant sur les fonctionnalités liées aux scripts utilisateurs. À ce stade, il n’est pas établi si cette exécution relevait d’un geste intentionnel, d’une erreur de manipulation ou de l’usage d’un compte compromis.

Le fonctionnement du ver reposait sur une mécanique élémentaire, mais redoutablement efficace dans un environnement collaboratif. MediaWiki autorise en effet l’usage de fichiers JavaScript globaux et personnalisés, notamment MediaWiki:Common.js et User:<nom_utilisateur>/common.js. Ces scripts sont exécutés dans le navigateur des éditeurs afin d’adapter l’interface, d’automatiser certaines tâches ou d’ajouter des fonctions sur mesure. Une fois chargé dans le navigateur d’un utilisateur connecté, test.js tentait de modifier deux cibles en parallèle.

Au niveau du compte, il remplaçait common.js par un chargeur chargé d’appeler automatiquement test.js à chaque consultation du wiki. Autrement dit, l’infection persistait dès qu’un utilisateur touché revenait sur la plateforme. Au niveau du site, si la victime disposait de privilèges suffisants, le ver modifiait MediaWiki:Common.js, un fichier global exécuté pour tous les éditeurs concernés. Dans ce scénario, la propagation devenait immédiatement plus large, car le code malveillant s’insérait au cœur même de la couche de personnalisation commune.

News & alertes actualités cyber

Enquêtes, cyberveille, fuites, actu sécurité : recevez nos informations cyber là où vous êtes, chaque vendredi midi.

Meta-Wiki touché, restauration engagée et audit renforcé

Le ver ne se contentait pas d’assurer sa diffusion. Il appelait aussi une page aléatoire via Special:Random, puis y injectait une image ainsi qu’un chargeur JavaScript invisible. Ce dernier récupérait un script externe depuis le domaine basemetrika[.]ru. Cette dimension externe accroît l’intérêt de l’incident pour les observateurs de la cybersécurité : au-delà d’une simple dégradation interne, le mécanisme ouvrait une chaîne d’exécution capable d’introduire un contenu distant dans des pages du wiki, avec un effet potentiel sur l’intégrité éditoriale et la confiance accordée à l’environnement.

Selon les estimations citées par les journalistes, environ 3 996 pages ont été modifiées, tandis que les fichiers common.js d’environ 85 utilisateurs ont été remplacés. Le volume exact des suppressions de pages n’est pas connu. La Fondation Wikimedia a depuis annulé les modifications apportées à plusieurs fichiers common.js d’utilisateurs. Les pages altérées ont été masquées et n’apparaissent plus dans l’historique des modifications. Après la suppression du code injecté, l’édition a pu reprendre.

La Fondation affirme que le code malveillant n’est resté actif que 23 minutes. Durant cette période, il aurait modifié et supprimé du contenu uniquement sur Meta-Wiki, avant restauration des données. L’organisation précise qu’aucun signe d’attaque visant Wikipédia elle-même n’a été observé et qu’aucune fuite de données n’a été détectée. Dans sa déclaration, elle explique que son personnel menait un audit de sécurité du code utilisateur sur Wikipédia, et que du code jusque-là inactif a été exécuté puis rapidement identifié comme malveillant. Par précaution, la modification sur Wikipédia et sur d’autres projets Wikimedia a été suspendue le temps d’éliminer ce code et de sécuriser l’environnement.

Cet épisode illustre une zone de risque connue des plateformes extensibles : les fonctions de personnalisation, utiles à l’exploitation quotidienne, peuvent aussi devenir des vecteurs de propagation lorsqu’un script hostile bénéficie d’une exécution légitime dans le navigateur d’un utilisateur connecté. Le fait qu’un simple chargement puisse conduire à la réécriture de scripts utilisateurs, puis éventuellement d’un fichier global, montre combien la frontière entre assistance à l’édition et surface d’attaque peut se réduire. Wikimedia indique désormais déployer des mesures de sécurité supplémentaires pour limiter le risque de récidive.

Au-delà du rétablissement du service, l’incident rappelle que la sécurité des plateformes collaboratives dépend aussi du contrôle du code client, des privilèges d’édition et de la capacité à détecter rapidement une propagation interne avant qu’elle ne devienne un problème de gouvernance numérique.

Étiquetté :

Répondre

Nos partenaires




Actualités du mois

mars 2026
L M M J V S D
 1
2345678
9101112131415
16171819202122
23242526272829
3031  

Articles en UNE

Actus zataz



Réseaux sociaux


Liste des sujets


<!-- Cyber'Émission ZATAZ — badge volant (déplaçable) + réduire/fermer -->
<div class="zataz-yt-float" id="zatazYtFloat" role="region" aria-label="Cyber'Émission ZATAZ">
  <div class="zataz-yt-float__bar" id="zatazYtBar">
    <span class="zataz-yt-float__title">Cyber'Émission ZATAZ</span>

    <div class="zataz-yt-float__actions">
      <button type="button" class="zataz-yt-float__btn" id="zatazYtMin" aria-label="Réduire">—</button>
      <button type="button" class="zataz-yt-float__btn zataz-yt-float__btn--close" id="zatazYtClose" aria-label="Fermer">×</button>
    </div>
  </div>

  <a class="zataz-yt-badge" href="https://www.youtube.com/@ZATAZCOM" target="_blank" rel="noopener noreferrer"
     aria-label="Regarder Cyber'Émission ZATAZ sur YouTube (nouvel onglet)">
    <span class="zataz-yt-badge__thumb" aria-hidden="true">
      <span class="zataz-yt-badge__play" aria-hidden="true"></span>
    </span>
  </a>
</div>

<style>
  .zataz-yt-float{
    position:fixed;
    right:18px;
    bottom:18px;
    z-index:99999;
    width:320px;
    max-width:calc(100vw - 36px);
    border-radius:14px;
    overflow:hidden;
    background:linear-gradient(135deg,#111827,#0b1220 55%,#111827);
    border:1px solid rgba(255,255,255,.12);
    box-shadow:0 14px 40px rgba(0,0,0,.35);
    transform:translateZ(0);
    user-select:none;
    touch-action:none; /* drag mobile */
  }

  /* Barre de drag + boutons */
  .zataz-yt-float__bar{
    display:flex;
    align-items:center;
    justify-content:space-between;
    gap:10px;
    padding:10px 10px 10px 12px;
    font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;
    color:#fff;
    background:rgba(0,0,0,.18);
    border-bottom:1px solid rgba(255,255,255,.10);
    cursor:grab;
  }
  .zataz-yt-float__bar:active{ cursor:grabbing; }
  .zataz-yt-float__title{
    font-weight:800;
    letter-spacing:.2px;
    font-size:15px;
    line-height:1;
    white-space:nowrap;
    overflow:hidden;
    text-overflow:ellipsis;
  }
  .zataz-yt-float__actions{ display:flex; gap:8px; }
  .zataz-yt-float__btn{
    appearance:none;
    border:1px solid rgba(255,255,255,.18);
    background:rgba(0,0,0,.28);
    color:#fff;
    width:32px;
    height:28px;
    border-radius:10px;
    font-weight:900;
    line-height:1;
    cursor:pointer;
    display:grid;
    place-items:center;
  }
  .zataz-yt-float__btn:hover{ background:rgba(255,255,255,.08); border-color:rgba(255,255,255,.28); }
  .zataz-yt-float__btn--close:hover{ background:rgba(239,68,68,.22); border-color:rgba(239,68,68,.45); }

  /* Contenu (votre vignette) */
  .zataz-yt-badge{
    display:block;
    text-decoration:none;
    color:#fff;
  }
  .zataz-yt-badge__thumb{
    display:block;
    height:180px;
    background:#0f172a url("https://i.ytimg.com/vi/HUo8dnD6Swk/hqdefault.jpg") center/cover no-repeat;
    position:relative;
  }
  .zataz-yt-badge__play{
    position:absolute;
    left:50%;
    top:50%;
    width:54px;
    height:54px;
    margin:-27px 0 0 -27px;
    border-radius:999px;
    background:rgba(0,0,0,.55);
    border:1px solid rgba(255,255,255,.25);
    box-shadow:0 10px 22px rgba(0,0,0,.35);
  }
  .zataz-yt-badge__play:before{
    content:"";
    position:absolute;
    left:22px;
    top:16px;
    width:0;height:0;
    border-top:11px solid transparent;
    border-bottom:11px solid transparent;
    border-left:16px solid #fff;
  }

  .zataz-yt-float:hover{
    box-shadow:0 18px 55px rgba(0,0,0,.45);
    border-color:rgba(255,255,255,.18);
  }
  .zataz-yt-badge:active{ transform:scale(.99); }

  /* Etat réduit */
  .zataz-yt-float.is-min .zataz-yt-badge{ display:none; }
  .zataz-yt-float.is-min{ width:260px; }

  /* Mobile : plus compact */
  @media (max-width:480px){
    .zataz-yt-float{ width:280px; right:12px; bottom:12px; }
    .zataz-yt-badge__thumb{ height:158px; }
    .zataz-yt-float.is-min{ width:220px; }
  }
</style>

<script>
(() => {
  const box = document.getElementById('zatazYtFloat');
  const bar = document.getElementById('zatazYtBar');
  const btnMin = document.getElementById('zatazYtMin');
  const btnClose = document.getElementById('zatazYtClose');

  if (!box || !bar || !btnMin || !btnClose) return;

  // Réduire / restaurer
  btnMin.addEventListener('click', (e) => {
    e.stopPropagation();
    box.classList.toggle('is-min');
    btnMin.textContent = box.classList.contains('is-min') ? '▢' : '—';
    btnMin.setAttribute('aria-label', box.classList.contains('is-min') ? 'Restaurer' : 'Réduire');
  });

  // Fermer
  btnClose.addEventListener('click', (e) => {
    e.stopPropagation();
    box.remove();
  });

  // Drag (souris + tactile) via Pointer Events
  let dragging = false;
  let startX = 0, startY = 0;
  let startLeft = 0, startTop = 0;

  // Position initiale: on convertit right/bottom en left/top pour le drag
  const init = () => {
    const r = box.getBoundingClientRect();
    box.style.left = r.left + 'px';
    box.style.top  = r.top  + 'px';
    box.style.right = 'auto';
    box.style.bottom = 'auto';
  };
  init();

  const clamp = (v, min, max) => Math.min(Math.max(v, min), max);

  bar.addEventListener('pointerdown', (e) => {
    // pas de drag quand on clique sur les boutons
    if (e.target === btnMin || e.target === btnClose) return;

    dragging = true;
    bar.setPointerCapture(e.pointerId);

    const r = box.getBoundingClientRect();
    startX = e.clientX;
    startY = e.clientY;
    startLeft = r.left;
    startTop = r.top;

    e.preventDefault();
  });

  bar.addEventListener('pointermove', (e) => {
    if (!dragging) return;

    const dx = e.clientX - startX;
    const dy = e.clientY - startY;

    const r = box.getBoundingClientRect();
    const w = r.width;
    const h = r.height;

    const maxLeft = window.innerWidth - w - 8;
    const maxTop  = window.innerHeight - h - 8;

    box.style.left = clamp(startLeft + dx, 8, maxLeft) + 'px';
    box.style.top  = clamp(startTop + dy, 8, maxTop) + 'px';
  });

  const endDrag = () => { dragging = false; };
  bar.addEventListener('pointerup', endDrag);
  bar.addEventListener('pointercancel', endDrag);

  // Re-clamp au resize
  window.addEventListener('resize', () => {
    const r = box.getBoundingClientRect();
    const maxLeft = window.innerWidth - r.width - 8;
    const maxTop  = window.innerHeight - r.height - 8;
    box.style.left = clamp(r.left, 8, maxLeft) + 'px';
    box.style.top  = clamp(r.top, 8, maxTop) + 'px';
  });
})();
</script>