E-NO
Sécurité Bash 8 min de lecture

Durcissement de Bash avec des exemples pratiques : guide d’implémentation concret

calendar_today Publié : 2026-07-27
update Dernière mise à jour : 2026-07-27
analytics Efficacité SEO : 100%
Illustration du guide technique pour « Durcissement de Bash avec des exemples pratiques : guide d’implémentation concret ».

Introduction

Bash propulse des étapes de build, des déploiements, des traitements de données et des opérations du quotidien. Cette portée en fait aussi une source fréquente d’incidents, de fuites de données et de mésusages de privilèges. Ce guide se concentre sur des techniques pratiques et copiables pour les administrateurs Linux, SRE et développeurs qui maintiennent des scripts Bash opérationnels. Il fixe aussi des limites claires : Bash n’est pas une barrière de sécurité. Si vous avez besoin d’analyse complexe, de cryptographie à haut risque ou d’exécution multi‑locataire non fiable, privilégiez un langage plus sûr (Python, Go, Rust) ou un outil dédié.

Modèle de menaces en une page

Supposez que votre script puisse faire face aux risques suivants :

  • Entrées non fiables et injection de commandes
  • Surprises dues au découpage de mots et à l’expansion des jokers (glob)
  • Environnement hostile et manipulation de PATH
  • Conditions de course et fichiers temporaires non sûrs
  • Divulgation de secrets via argv, les journaux ou l’historique
  • Privilèges excessifs ou mésusage de setuid
  • Compromission de dépendances et téléchargements non vérifiés
  • Différences de locale qui modifient le comportement d’analyse

Faites correspondre les risques à des contrôles concrets et à des preuves observables que vous pouvez vérifier :

MenaceContrôle principalPreuve vérifiable
Injection de commandes à partir d’entrées non fiablesCiter toutes les expansions, utiliser des tableaux pour argv, listes d’autorisation (allowlists) pour les actions, ne jamais utiliser evalDes tests unitaires avec des entrées hostiles n’exécutent aucune commande inattendue
Découpage de mots et expansion des jokers (glob)Citer les variables, utiliser des tableaux, traiter les données utilisateur comme des données et non comme de la syntaxe shellDes tests avec espaces, retours à la ligne et caractères jokers se comportent de façon prévisible
Environnement hostile et manipulation de PATHDéfinir un PATH sûr, utiliser des chemins absolus quand approprié, désactiver les variables risquéesLe script affiche le PATH effectif en mode débogage, binaires résolus via command -v ou chemins absolus
Fichiers temporaires non sûrs et conditions de courseUtiliser mktemp, umask 077, vérifier la propriété, restreindre les répertoires, éviter les hypothèses TOCTOUls -ld des répertoires temporaires montre le mode 700 et l’appartenance à l’utilisateur; les tests de liens symboliques échouent de façon sûre
Divulgation de secretsNe pas passer de secrets dans argv, éviter set -x, caviarder les journaux, préférer des fichiers protégés ou des descripteurs de fichierLa sortie de ps ne montre aucun secret; les journaux affichent REDACTED le cas échéant
Privilèges excessifsExécuter sous un compte non privilégié, restreindre finement les règles sudo, séparer les étapes privilégiéesid affiche un uid non-root; l’entrée sudoers est minimale et auditée
Téléchargements non vérifiés et compromission de dépendancesVérification HTTPS, sources épinglées, contrôle d’empreinte (checksum) ou de signature, délais limitésValidation d’empreinte requise avant exécution; test négatif bloque en cas d’écart
Analyse sensible à la localeDéfinir LC_ALL=C pour un comportement déterministe d’outils comme sort, awk, grepLes tests réussissent sous différents réglages de LANG

Expansions plus sûres et gestion des arguments

Traitez toute donnée contrôlée par l’utilisateur strictement comme des données. Règles courantes :

  • Toujours citer les expansions de variables : "$var" et non $var.
  • Utiliser des tableaux pour construire argv et préserver les limites d’arguments : cmd "${args[@]}".
  • Utiliser -- pour terminer l’analyse des options quand vous transmettez des données utilisateur à des commandes qui l’acceptent.
  • Valider tôt les formats attendus (p. ex. chemins, noms d’hôte, plages numériques) et rejeter tout le reste.
  • Ne pas construire des commandes sous forme de chaînes.

Exemples :

# Préserver correctement les frontières d’arguments avec des tableaux
args=(--name "$NAME" --path "$P")
mytool "${args[@]}" --

# Itération sûre sur des noms de fichiers
while IFS= read -r -d '' file; do
  printf '%s\n' "$file"
done < <(find "$SOURCE" -type f -print0)

Constructions dangereuses à éviter (et quoi faire à la place)

  • eval et bash -c avec des entrées interpolées : tous deux interprètent du texte comme du code shell, ouvrant la voie à l’injection. À la place, assemblez argv avec des tableaux et exécutez directement.
  • Expansion indirecte comme ${!name} : facile à mal utiliser et peut traverser des frontières de variables. Préférez des mappages explicites ou des instructions case.
  • Téléchargement de scripts distants et exécution immédiate (curl | bash) : téléchargez sur disque, vérifiez l’authenticité et l’intégrité avec des empreintes ou des signatures obtenues par un canal indépendant, inspectez, puis exécutez.

Des alternatives plus sûres figurent dans le tableau comparatif ci‑dessous.

Mode strict et écueils du monde réel

set -euo pipefail est une valeur par défaut solide, mais ce n’est pas une solution complète.

  • -e (errexit) arrête le script quand une commande échoue, mais pas dans tous les contextes. Il est ignoré dans les listes conditionnelles comme cmd || recours et cmd && suivant, sauf si la commande en échec est la dernière. Dans les pipelines, seule la dernière commande est prise en compte à moins d’activer pipefail.
  • Les échecs dans les substitutions de commande peuvent être masqués selon le contexte. Lors de la capture de sortie, vérifiez explicitement le statut de sortie si la justesse est critique.
  • -u (nounset) traite les variables non définies comme des erreurs. Utilisez ${var:-default} pour des valeurs par défaut intentionnelles.
  • pipefail fait échouer un pipeline si l’une des commandes échoue.
  • Utilisez set -E pour que les pièges ERR se déclenchent dans les fonctions et les sous‑shells.

Exemples de motifs :

set -Eeuo pipefail
trap 'printf "ERREUR à la ligne %s\n" "$LINENO" >&2' ERR

# Si vous souhaitez ignorer un échec, faites-le explicitement et documentez-le
may_fail || printf 'non fatal : may_fail a échoué\n' >&2

# Toujours vérifier les substitutions critiques
data=$(curl --fail --silent --show-error --max-time 10 "$url") || {
  printf 'échec du téléchargement\n' >&2; exit 1; }

Ne prétendez pas que le mode strict rend un script sûr. Il rend simplement les échecs plus visibles.

Validation des entrées et noms de fichiers sûrs

  • Utiliser des listes d’autorisation et des instructions case explicites pour les actions.
  • Valider les chemins de fichiers avec -e ou -d et rejeter les chemins vides ou proches de la racine avant les opérations destructrices.
  • Traiter les données utilisateur comme des octets opaques; ne pas s’appuyer sur le découpage de mots. Utiliser read -r et find -print0 pour des noms de fichiers arbitraires.
  • Terminer les options par -- avant les données positionnelles quand c’est pris en charge.
action=${1:-}
case "$action" in
  backup|verify) ;;
  *) printf 'Action invalide : %s\n' "$action" >&2; exit 2 ;;
esac

Exécution prévisible : PATH, environnement, locale

  • Définir un PATH minimal et réputé sûr puis l’exporter.
  • Utiliser des chemins absolus pour les outils critiques ou les résoudre une fois via command -v.
  • Désactiver les variables risquées dont vous n’avez pas besoin : IFS, CDPATH, GLOBIGNORE.
  • Définir LC_ALL=C pour un traitement de texte déterministe.
PATH="/usr/sbin:/usr/bin:/sbin:/bin"; export PATH
unset CDPATH GLOBIGNORE
export LC_ALL=C LANG=C
for bin in tar sha256sum find; do command -v "$bin" >/dev/null || exit 127; done

Ressources temporaires et sécurité face aux conditions de course

  • Créer des fichiers et répertoires uniques avec mktemp et un umask restrictif (077).
  • Vérifier la propriété et les permissions si vous opérez dans des répertoires partagés.
  • Utiliser des pièges (trap) pour nettoyer à la sortie, et éviter les hypothèses TOCTOU sur des fichiers que vous n’avez pas créés à l’instant.
umask 077
tmpdir=$(mktemp -d)
cleanup() { rm -rf -- "$tmpdir"; }
trap cleanup EXIT

Gestion sûre des secrets

  • Ne jamais passer de secrets dans argv ni les consigner. De nombreux systèmes exposent argv via les listes de processus.
  • Préférer la lecture depuis stdin, des fichiers protégés (chmod 600) ou un gestionnaire de secrets.
  • Ne pas activer set -x autour des secrets; si vous devez tracer, caviardez avant d’afficher.
read -r -s -p 'Entrez le jeton API : ' API_TOKEN; printf '\n' >&2
hdr=$(mktemp "$tmpdir/h.XXXXXX"); chmod 600 "$hdr"
printf 'Authorization: Bearer %s\n' "$API_TOKEN" > "$hdr"
# Utiliser @fichier pour garder les secrets hors d'argv
curl --fail --silent --show-error --header @"$hdr" "$endpoint" > "$tmpdir/resp.json"
shred -u "$hdr" 2>/dev/null || rm -f -- "$hdr"

Privilèges

  • Faire tourner les scripts par défaut sous un compte non privilégié.
  • Éviter les scripts shell setuid; ils sont fondamentalement non sûrs.
  • Utiliser des règles sudo à portée très limitée et séparer les opérations privilégiées dans un petit utilitaire passé en revue.
  • Valider la propriété et les permissions des fichiers avant d’opérer dessus.

Téléchargements et appels réseau

  • Exiger HTTPS avec TLS moderne, épingler les sources et vérifier l’intégrité avec des empreintes ou des signatures issues d’un canal indépendant.
  • Utiliser des délais bornés et des reprises, et échouer de façon sûre en cas d’erreur de vérification.
curl --proto '=https' --tlsv1.2 --location --fail --silent --show-error \
  --output "$tmpdir/tool.sh" "$url"
actual=$(sha256sum "$tmpdir/tool.sh" | awk '{print $1}')
[ "$actual" = "$expected" ] || { printf 'empreinte (checksum) non concordante\n' >&2; exit 1; }

Modèles Bash risqués versus plus sûrs

Modèle risquéModèle plus sûrMise en garde
Construire une commande en chaîne avec des variables non citéesConstruire argv avec des tableaux puis exécuter : cmd "${args[@]}"Les tableaux préservent les limites d’arguments
Variable non citée dans une commandeCiter : rm -- "$target"Empêche le découpage et le globbing
eval sur des chaînes construitesNe pas utiliser eval; appeler la commande directement avec des tableauxÉlimine l’interprétation des données comme du code
bash -c avec des entrées interpoléesExécuter directement le binaire cible avec des arguments validésÉvite l’analyse en sous‑shell de données utilisateur
Expansion indirecte ${!name}Utiliser une instruction case ou un tableau associatifGarde le contrôle sur les variables lues
for x in $(cat file)while IFS= read -r line; do ...; done < filePréserve les espaces et caractères spéciaux
Fichier temporaire nommé à la main dans /tmptmp=$(mktemp); chmod 600 "$tmp"Évite collisions et courses
Téléchargement directement pipé dans un shellTélécharger sur disque, vérifier empreinte ou signature, puis exécuterNécessite un canal de confiance indépendant

Validation statique et dynamique

  • Vérification de syntaxe : bash -n yourscript.sh
  • Linting : ShellCheck pour détecter les problèmes de citation, de découpage et de tableaux.
  • Tests unitaires et d’intégration : inclure des tests négatifs avec des entrées hostiles et des noms de fichiers atypiques.
  • Garde‑fous CI : exécuter syntaxe, lint et tests à chaque changement; exiger une revue de code.
  • Journalisation : rester observable sans fuite; caviarder les secrets et éviter le traçage autour des identifiants.

Exemple pratique : sauvegarde sécurisée et testable

Scénario illustratif : créer une archive tar d’un répertoire source vers un répertoire de destination, avec sortie d’empreinte. Tout le travail se fait dans un espace temporaire isolé.

#!/usr/bin/env bash
set -Eeuo pipefail
set -o noclobber
umask 077
export LC_ALL=C LANG=C
PATH="/usr/sbin:/usr/bin:/sbin:/bin"; export PATH
trap 'printf "ERREUR à la ligne %s\n" "$LINENO" >&2' ERR

usage() {
  printf 'Utilisation : %s --action backup --source DIR --dest DIR\n' "$0"
}

# Analyse des drapeaux
ACTION=""; SOURCE=""; DEST=""
while [ $# -gt 0 ]; do
  case "$1" in
    --action) ACTION=${2:-}; shift 2 ;;
    --source) SOURCE=${2:-}; shift 2 ;;
    --dest)   DEST=${2:-}; shift 2 ;;
    -h|--help) usage; exit 0 ;;
    *) printf 'Option inconnue : %s\n' "$1" >&2; usage; exit 2 ;;
  esac
done

case "$ACTION" in
  backup) ;;
  *) usage; exit 2 ;;
esac

# Validation des chemins
[ -d "$SOURCE" ] || { printf 'La source doit être un répertoire\n' >&2; exit 2; }
[ -n "$DEST" ] || { printf 'La destination doit être fournie\n' >&2; exit 2; }
[ -d "$DEST" ] || { printf 'La destination doit être un répertoire existant\n' >&2; exit 2; }

# Résolution des outils requis
for bin in tar sha256sum find; do command -v "$bin" >/dev/null || { printf 'Binaire manquant : %s\n' "$bin" >&2; exit 127; }; done

# Espace de travail isolé
workdir=$(mktemp -d)
cleanup() { rm -rf -- "$workdir"; }
trap cleanup EXIT

# Créer l'archive dans l'espace de travail, puis la déplacer en place
archive_name="backup-$(date +%Y%m%d-%H%M%S).tar"
archive_path="$workdir/$archive_name"
(
  cd "$SOURCE"
  # Utiliser tar avec un chemin déterministe
  tar -cf "$archive_path" .
)

# Calculer l'empreinte
sum=$(sha256sum "$archive_path" | awk '{print $1}')

# Déplacer les résultats de façon atomique dans la destination
final_archive="$DEST/$archive_name"
final_checksum="$DEST/$archive_name.sha256"

# S'assurer de ne pas écraser un fichier existant involontairement
[ -e "$final_archive" ] && { printf 'Refus d\'écraser une archive existante\n' >&2; exit 1; }
[ -e "$final_checksum" ] && { printf 'Refus d\'écraser une empreinte existante\n' >&2; exit 1; }

mv -- "$archive_path" "$final_archive"
printf '%s  %s\n' "$sum" "$archive_name" > "$final_checksum"
chmod 600 "$final_checksum"

printf 'Créés %s et %s\n' "$final_archive" "$final_checksum"

Validations et tests rapides exécutables en toute sécurité :

# Vérifications statiques
bash -n safe_backup.sh
shellcheck -s bash safe_backup.sh

# Préparer un jeu d\'essai isolé avec des noms de fichiers piégeux
src=$(mktemp -d); dest=$(mktemp -d)
mkdir -p "$src/sub dir"; touch "$src/sub dir/file [1].txt" "$src/asterisk*.txt"

# Lancer la sauvegarde
./safe_backup.sh --action backup --source "$src" --dest "$dest"

# Vérifier que l'archive existe et que l'empreinte concorde
sha256sum -c "$dest"/*.sha256 --ignore-missing --strict

# Test négatif : tentative d\'écrasement
./safe_backup.sh --action backup --source "$src" --dest "$dest" || true

Liste de contrôle de durcissement et de tests avant déploiement

ÉlémentPrioritéResponsablePreuve
Shebang et options strictes set -Eeuo pipefail avec piège ERRÉlevéeEn-tête présent et le piège affiche la ligne en cas d’échec
Assainir PATH et l’environnement, définir LC_ALL=CÉlevéePATH affiché en débogage, locale exportée
Citer les expansions et utiliser des tableaux pour argvÉlevéeShellCheck propre; tests avec espaces et jokers réussissent
Pas de eval, backticks, ni bash -c avec entrée interpoléeÉlevéeÉlément de liste de revue; vérifications grep en CI
Listes d’autorisation des entrées et validation expliciteÉlevéeInstruction case pour les actions; entrées invalides rejetées
Répertoires temporaires via mktemp et nettoyage par trapMoyennemktemp utilisé dans le code; répertoires temporaires supprimés à la sortie
Modèle de privilèges : non privilégié par défaut, sudo restreintÉlevéeid non-root; entrée sudoers passée en revue
Secrets : pas de fuite argv, journaux caviardés, pas de set -x près des secretsÉlevéeps sans secrets; journaux contiennent REDACTED
Réseau : HTTPS uniquement, sources épinglées, checksum ou signature, délaisÉlevéeVérification d’empreinte requise avant exécution
Analyse statique : bash -n et ShellCheck sans erreurMoyenneArtéfacts CI au vert
Tests : unitaires, intégration et entrées négativesÉlevéeSuite de tests réussie en CI
Revue de code effectuée avant la mise en productionÉlevéeLien PR avec approbations

Quand remplacer Bash

Choisissez Python, Go, Rust ou un outil d’automatisation déclaratif quand :

  • Vous devez traiter en sécurité des entrées non fiables ou multi‑locataires.
  • La tâche exige une analyse complexe, des données structurées ou une concurrence robuste.
  • La maintenabilité et la testabilité à long terme priment sur la rapidité d’un one‑liner.
  • Vous avez besoin de cryptographie solide, d’analyse vérifiée ou d’un riche écosystème de bibliothèques.

Utilisez Bash pour de petits assemblages (glue) où les arguments, l’environnement et les effets de bord sont strictement contrôlés.

Conclusion

Durcir Bash, c’est adopter des valeurs par défaut disciplinées et une intention explicite : citer les expansions, valider les entrées avec des listes d’autorisation, éviter eval et bash -c, gérer PATH et l’environnement de manière prévisible, utiliser mktemp et des traps pour les ressources temporaires, garder les secrets hors d’argv et des journaux, minimiser les privilèges et vérifier les téléchargements avant usage. Associez ces pratiques à des contrôles statiques et à des tests négatifs. Quand le problème dépasse Bash, passez à un langage ou un outil plus sûr avant que le risque ne vous y oblige.

Score de qualité de l’article

Utilité pour le lecteur 100%
  • check_circle Guide prêt à lire
  • check_circle Exemples pratiques inclus
  • check_circle URL d’article optimisée pour le SEO