Aller au contenu

Écrire des Scripts Shell Sécurisés

Mise à jour :

Beaucoup d’administrateurs systèmes sous-estiment les risques liés aux scripts Bash mal codés. Un script qui paraît inoffensif, comme la création de répertoires, peut en réalité exposer votre système à des attaques sérieuses s’il n’est pas écrit correctement. Les failles les plus courantes, comme l’injection de commandes, les permissions mal gérées ou les erreurs de gestion des variables, peuvent être exploitées par des utilisateurs malveillants pour accéder à des fichiers sensibles, modifier des configurations système ou causer des interruptions de service.

Présentation du script de base non sécurisé

Pour étayer ce guide, nous allons prendre un script Bash simple qui demande à l’utilisateur un fichier à supprimer, puis le supprime.

A priori, il est inoffensif, mais il présente plusieurs failles de sécurité importantes qui pourraient être exploitées par une personne malveillante pour endommager le système ou accéder à des données sensibles.

Problèmes identifiés :

  • Pas de validation de l’entrée utilisateur : Le script supprime n’importe quel fichier sans vérifier si le fichier existe ou si le nom de fichier est valide.
  • Variable non protégée : Le nom de fichier n’est pas entouré de guillemets, ce qui pourrait entraîner une mauvaise interprétation des noms de fichiers avec des espaces ou des caractères spéciaux.
  • Pas de gestion des erreurs : Si la commande rm échoue, il n’y a aucune vérification ou gestion de l’erreur.
  • Absence de chemins absolus : Le script utilise rm sans spécifier un chemin absolu, ce qui pourrait être détourné en modifiant la variable d’environnement PATH.
  • Permissions non contrôlées : Le script peut être exécuté par n’importe qui, sans restrictions.

Shebang explicite

L’une des premières bonne pratique pour éviter des erreurs lors du lancement d’un script Shell consiste à définir clairement l’interpréteur que le script doit utiliser. Cela est fait à travers la ligne qu’on appelle shebang, qui se trouve au début de script et spécifie quel shell doit être utilisé pour l’exécuter.

Pourquoi utiliser un shebang explicite ?

Sans shebang, un script pourrait être exécuté par un shell par défaut non prévu, ce qui pourrait mener à des comportements inattendus. Par exemple, si un administrateur écrit un script en pensant qu’il sera toujours exécuté avec bash mais qu’il est lancé avec un autre shell (comme sh ou dash), des erreurs peuvent survenir. En ajoutant un shebang explicite tel que #!/bin/bash, nous nous assurons que le bon interpréteur est utilisé, indépendamment des paramètres de l’utilisateur ou de son système.

Exemples de lignes shebang :

  • #!/bin/bash : Spécifie que le script doit être exécuté avec Bash.
  • #!/bin/sh : Utilise le shell POSIX par défaut, souvent utilisé pour des scripts plus simples et portables.

Utiliser des chemins absolus pour les commandes

Le chemin absolu est une autre bonne pratique de sécurité qui garantit que les commandes exécutées dans votre script proviennent du bon répertoire système et non d’une version potentiellement malveillante.

Pourquoi utiliser des chemins absolus ?

Si vous utilisez simplement des commandes comme rm, ls ou cp, le système peut rechercher ces commandes dans les répertoires définis par la variable d’environnement PATH. Un utilisateur malveillant pourrait modifier cette variable pour exécuter un programme différent avec le même nom que la commande. Pour éviter cela, il est préférable d’utiliser des chemins absolus comme /bin/rm ou /usr/bin/ls, assurant ainsi que la version officielle de la commande est utilisée.

Exemple d’amélioration :

#!/bin/bash
# Commande d'origine
# rm $filename
# Utilisation d'un chemin absolu pour rm
/bin/rm -- "$filename"

Validation et nettoyage de l’entrée utilisateur

Lorsque vous écrivez des scripts shell, il est essentiel de valider et nettoyer toutes les entrées utilisateur. En effet, ne jamais faire confiance aux données saisies par l’utilisateur est une règle de base en matière de sécurité.

Pourquoi valider l’entrée utilisateur ?

Lorsque vous demandez à un utilisateur de fournir des informations, comme le nom d’un fichier, vous ne pouvez pas être certain qu’il fournira des données valides. Un utilisateur malveillant pourrait par exemple entrer un chemin vers un fichier sensible ou pire, inclure des caractères spéciaux permettant une injection de commandes.

Prenons l’exemple d’un script qui demande le nom d’un fichier à supprimer. Si l’utilisateur entre une commande malveillante au lieu d’un nom de fichier, comme filename="; rm -rf /", sans validation, le script pourrait exécuter cette commande dangereuse.

Exemple d’amélioration :

Pour éviter cela, nous devons vérifier que le fichier existe, et s’assurer que l’utilisateur a fourni une entrée correcte avant d’exécuter toute commande. Voici une méthode pour valider un nom de fichier et s’assurer qu’il existe :

Terminal window
# Demander à l'utilisateur de fournir un nom de fichier
echo "Entrez le nom du fichier à supprimer :"
read -r filename
# Validation de l'entrée : vérifier que le fichier existe
if [[ ! -e "$filename" ]]; then
echo "Erreur : le fichier '$filename' n'existe pas."
exit 1
fi

Dans cet exemple, la condition [[ ! -e "$filename" ]] vérifie si le fichier existe. Si ce n’est pas le cas, le script affiche un message d’erreur et s’arrête. Cela permet d’éviter que le script ne tente de supprimer un fichier inexistant ou pire, que l’utilisateur exploite cette faiblesse pour exécuter des commandes dangereuses.

Nettoyage de l’entrée utilisateur

Même avec une validation en place, il est également recommandé de nettoyer les variables contenant des données saisies par l’utilisateur. Cela consiste à supprimer les caractères spéciaux qui pourraient poser problème. Par exemple, on peut utiliser sed pour filtrer les caractères non autorisés :

Terminal window
# Nettoyage de l'entrée utilisateur pour enlever les caractères spéciaux
cleaned_filename=$(echo "$filename" | sed 's/[^A-Za-z0-9._-]//g')

Ce code supprime tous les caractères autres que des lettres, chiffres, points, tirets et tirets bas du nom de fichier. Cela réduit les risques que des caractères dangereux soient injectés dans la commande.

Activer les options de sécurité du shell

Une autre bonne pratique pour sécuriser vos scripts Bash consiste à activer les options de sécurité du shell. Ces options permettent d’améliorer le comportement du script en cas d’erreur et de renforcer sa robustesse. Il existe trois principales options que je vais vous présenter ici : set -e, set -u, et set -o pipefail. Ensemble, elles permettent d’éviter de nombreuses erreurs courantes pouvant conduire à des failles de sécurité.

set -e : Arrêter le script en cas d’erreur

L’option set -e permet d’arrêter l’exécution du script dès qu’une commande retourne un code d’erreur non nul. Sans cette option, le script continue d’exécuter les commandes même après une erreur, ce qui peut conduire à des comportements imprévus et potentiellement dangereux.

Exemple sans set -e :

#!/bin/bash
rm /nonexistent/file
echo "Fichier supprimé."

Même si le fichier n’existe pas, le script affichera le message “Fichier supprimé”, ce qui peut prêter à confusion. En activant set -e, le script s’arrête immédiatement après l’échec de la commande rm.

#!/bin/bash
set -e
rm /nonexistent/file
echo "Fichier supprimé."

set -u : Interdire l’utilisation de variables non définies

L’option set -u force le script à s’arrêter si une variable utilisée n’est pas définie. Cela évite des erreurs liées à l’utilisation de variables non initialisées qui pourraient contenir des valeurs inattendues.

Exemple sans set -u :

#!/bin/bash
echo "Le fichier est $filename"

Si filename n’est pas défini, le script affichera une ligne vide ou pourrait même causer des problèmes plus graves selon le contexte. En activant set -u, vous vous assurez que toute variable non définie provoquera un arrêt immédiat du script.

#!/bin/bash
set -u
echo "Le fichier est $filename"

set -o pipefail : Gérer correctement les erreurs dans les pipelines

Dans les scripts Bash, il est fréquent d’utiliser des pipelines pour enchaîner plusieurs commandes. Par défaut, le shell ne détecte qu’une erreur dans la dernière commande du pipeline, ignorant les éventuelles erreurs dans les commandes précédentes. L’option set -o pipefail garantit que le script s’arrête si n’importe quelle commande d’un pipeline échoue et pas seulement la dernière.

Exemple sans set -o pipefail :

#!/bin/bash
cat fichier_inexistant.txt | grep "mot"
echo "Pipeline terminé."

Même si la commande cat échoue parce que le fichier n’existe pas, le script continuera d’exécuter grep et affichera “Pipeline terminé”. En activant set -o pipefail, le script détectera cette erreur et s’arrêtera.

#!/bin/bash
set -o pipefail
cat fichier_inexistant.txt | grep "mot"
echo "Pipeline terminé."

Exemple d’utilisation combinée

Ces trois options peuvent être activées en une seule ligne pour assurer une exécution sécurisée et contrôlée de votre script :

#!/bin/bash
set -euo pipefail

Avec cette ligne, vous vous assurez que :

  • Le script s’arrête en cas d’erreur (set -e).
  • Aucune variable non définie n’est utilisée (set -u).
  • Les erreurs dans les pipelines sont correctement gérées (set -o pipefail).

Limiter les permissions des fichiers

La gestion des permissions est une bonne pratique dans la sécurisation de vos scripts Bash. Les permissions définissent qui peut lire, modifier ou exécuter un fichier, et si elles ne sont pas configurées correctement, elles peuvent permettre à des utilisateurs non autorisés d’exécuter des actions nuisibles.

Pourquoi est-il important de limiter les permissions ?

Si un script Bash est exécuté avec des permissions trop larges, il peut être utilisé ou modifié par n’importe quel utilisateur sur le système. Par exemple, un script avec des permissions d’écriture pour tous les utilisateurs (chmod 777) pourrait être modifié par un utilisateur malveillant pour inclure du code nuisible. Même des scripts inoffensifs peuvent devenir dangereux s’ils sont modifiés sans précaution.

En limitant les permissions d’exécution aux seuls utilisateurs autorisés, vous réduisez considérablement les risques d’abus ou de modification non intentionnelle de vos scripts.

Comment configurer les permissions correctement ?

L’utilisation de la commande chmod permet de définir les permissions sur les fichiers. Voici les permissions de base que vous devriez appliquer à vos scripts :

  1. chmod 700 : Cette commande donne l’accès complet (lecture, écriture, exécution) uniquement au propriétaire du fichier. Aucun autre utilisateur ne peut lire, modifier ou exécuter le script.

    • Exemple :
    Terminal window
    chmod 700 mon_script.sh

    Avec ces permissions, seul l’utilisateur propriétaire du fichier pourra exécuter le script, ce qui garantit que des utilisateurs non autorisés ne pourront pas le modifier ou l’exécuter.

  2. Vérification des permissions avant l’exécution : En plus de configurer les permissions du script lui-même, il est judicieux de vérifier que le fichier sur lequel le script travaille a les bonnes permissions. Par exemple, si le script doit manipuler des fichiers, il peut être utile de vérifier si vous avez les droits d’écriture avant d’exécuter une commande rm ou mv.

    • Exemple de vérification des permissions de fichier :
    Terminal window
    if [[ ! -w "$filename" ]]; then
    echo "Erreur : vous n'avez pas les droits d'écriture sur '$filename'."
    exit 1
    fi

    Ce code vérifie si le fichier est modifiable par l’utilisateur exécutant le script. Si ce n’est pas le cas, le script s’arrête avec un message d’erreur.

Exemple d’amélioration :

Dans un script sécurisé, vous devez :

  • Utiliser chmod 700 pour restreindre les permissions aux seuls utilisateurs autorisés.
  • Vérifier les permissions des fichiers manipulés pour vous assurer que seules les personnes disposant des droits adéquats peuvent les modifier.

Voici un exemple de script qui applique ces principes :

#!/bin/bash
set -euo pipefail
# Restreindre les permissions du script lui-même
chmod 700 "$0"
# Demander le nom du fichier à supprimer
echo "Entrez le nom du fichier à supprimer :"
read -r filename
# Vérifier les droits d'écriture sur le fichier avant suppression
if [[ ! -w "$filename" ]]; then
echo "Erreur : vous n'avez pas les droits d'écriture sur '$filename'."
exit 1
fi
# Supprimer le fichier
/bin/rm -- "$filename"
echo "Le fichier '$filename' a été supprimé avec succès."

Dans ce script, nous avons restreint l’accès au fichier à son propriétaire avec chmod 700. Nous avons également ajouté une vérification des permissions de fichier pour s’assurer que l’utilisateur dispose bien des droits d’écriture sur le fichier avant de tenter de le supprimer.

Éviter le stockage d’informations sensibles

Lorsqu’il s’agit de sécurité dans les scripts Bash, une erreur fréquente est d’inclure des informations sensibles directement dans le script. Les mots de passe, les clés API, ou tout autre type d’information confidentielle ne doivent jamais être codés en dur dans le script. En effet, si le script est partagé, versionné ou mal protégé, ces informations peuvent facilement être compromises.

Pourquoi éviter le stockage des informations sensibles ?

Le fait d’inclure des informations sensibles directement dans un script pose un risque majeur pour la sécurité. Si un utilisateur non autorisé accède au script, il pourrait obtenir des informations comme des mots de passe, des identifiants d’accès à des services, ou des clés API qui pourraient être utilisés pour compromettre l’intégrité de votre système ou d’autres services associés.

Par exemple, un script qui inclut un mot de passe en clair pourrait être consulté par n’importe quel utilisateur ayant accès au fichier ou pire, être accidentellement envoyé dans un dépôt Git public.

Exemple d’un script non sécurisé :

#!/bin/bash
DB_PASSWORD="mon_mot_de_passe"
mysql -u root -p"$DB_PASSWORD"

Ici, le mot de passe de la base de données est visible en clair. Si ce script est partagé ou s’il est exécuté par un autre utilisateur, le mot de passe est immédiatement compromis.

Alternatives pour sécuriser les informations sensibles

Il existe plusieurs méthodes pour éviter de stocker des informations sensibles directement dans un script. Voici quelques-unes des meilleures pratiques :

Utiliser des variables d’environnement

Au lieu de coder en dur les informations sensibles dans le script, vous pouvez utiliser des variables d’environnement. Cela permet de conserver les données sensibles en dehors du script et d’y accéder de manière sécurisée.

Exemple avec variables d’environnement :

#!/bin/bash
mysql -u root -p"$DB_PASSWORD"

Dans cet exemple, DB_PASSWORD n’est pas défini dans le script lui-même, mais provient d’une variable d’environnement qui peut être définie de manière sécurisée avant l’exécution du script. Vous pouvez définir cette variable directement dans le terminal :

Terminal window
export DB_PASSWORD="mon_mot_de_passe"

Ensuite, lorsque vous exécutez le script, il utilisera la valeur de la variable d’environnement. Cela garantit que le mot de passe ne figure pas dans le script et n’est visible que temporairement dans l’environnement où il est défini.

Utiliser un fichier de configuration sécurisé

Une autre méthode consiste à stocker les informations sensibles dans un fichier de configuration sécurisé qui n’est pas accessible à tous les utilisateurs. Ce fichier peut être lu par le script, mais il doit avoir des permissions strictes (comme chmod 600 pour empêcher toute lecture par d’autres utilisateurs).

Exemple d’utilisation d’un fichier de configuration :

#!/bin/bash
source /chemin/vers/fichier_config.conf
mysql -u root -p"$DB_PASSWORD"

Le fichier fichier_config.conf contiendra les informations sensibles et aura des permissions restreintes pour empêcher tout accès non autorisé :

Terminal window
DB_PASSWORD="mon_mot_de_passe"

Utiliser un gestionnaire de secrets

Pour les environnements plus complexes, il est recommandé d’utiliser un gestionnaire de secrets (comme HashiCorp Vault, Sops, ou Infisical) pour stocker et gérer les informations sensibles. Ces outils offrent des fonctionnalités de gestion sécurisée des secrets, notamment le chiffrement et la rotation automatique des clés et mots de passe.

Le script peut alors récupérer les informations sensibles en les demandant dynamiquement au gestionnaire de secrets, plutôt que de les stocker localement ou dans le script lui-même.

Exemple avec un gestionnaire de secrets (pseudocode) :

#!/bin/bash
DB_PASSWORD=$(vault kv get -field=password secret/data/db)
mysql -u root -p"$DB_PASSWORD"

Protéger les fichiers contenant des informations sensibles

Si vous choisissez d’utiliser un fichier de configuration ou un autre fichier externe pour stocker des informations sensibles, il est indispensable de protéger ce fichier avec des permissions strictes. Par exemple, vous pouvez utiliser la commande suivante pour restreindre l’accès :

Terminal window
chmod 600 /chemin/vers/fichier_config.conf

Cela garantit que seul le propriétaire du fichier peut le lire ou le modifier.

Citer correctement les variables

Lorsque vous travaillez avec des scripts Bash, citer les variables correctement est une étape clé pour éviter les erreurs et les comportements imprévus. Si vous ne citez pas vos variables de manière adéquate, le shell peut mal interpréter les valeurs de ces variables, particulièrement lorsque celles-ci contiennent des espaces, des caractères spéciaux ou des chaînes vides.

Dans le contexte des scripts Bash, l’expression “citer les variables” fait référence à l’utilisation de guillemets autour des variables.

Dans un script shell, si une variable n’est pas entourée de guillemets, elle est susceptible de subir des opérations involontaires de séparation de mots (word-splitting) ou de globbing (expansion des caractères génériques comme * et ?). Cela peut entraîner des erreurs ou des actions inattendues.

  1. Séparation de mots (word-splitting) : Lorsque le shell rencontre une variable non citée, il la sépare en plusieurs mots si elle contient des espaces, ce qui peut provoquer des erreurs ou des résultats inattendus.

  2. Globbing : Si une variable non citée contient des caractères spéciaux comme * ou ?, le shell peut les interpréter comme des caractères génériques, entraînant l’expansion de la variable pour correspondre à des fichiers ou répertoires du système de fichiers. Cela peut être dangereux si vous supprimez accidentellement des fichiers en utilisant rm *, par exemple.

Exemple sans guillemets :

#!/bin/bash
filename="mon fichier avec des espaces.txt"
rm $filename

Le script va interpréter cette commande comme deux fichiers distincts : “mon” et “fichier”, et tentera de supprimer deux fichiers au lieu d’un seul. Cela pourrait causer des erreurs ou entraîner la suppression de fichiers non voulus.

Exemple avec guillemets :

#!/bin/bash
filename="mon fichier avec des espaces.txt"
rm "$filename"

En ajoutant des guillemets autour de la variable "$filename", vous vous assurez que le nom de fichier complet, y compris les espaces, est interprété comme un seul argument. Cela permet au script de fonctionner correctement, même si des espaces ou d’autres caractères spéciaux sont présents dans les noms de fichiers.

Gérer les erreurs efficacement

Un aspect souvent négligé dans les scripts Bash est la gestion des erreurs. La plupart du temps, lorsque nous écrivons un script, nous nous attendons à ce que tout se passe comme prévu. Cependant, il est indispensable de prendre en compte les situations où les choses pourraient mal tourner.

Pourquoi est-il important de gérer les erreurs ?

La gestion des erreurs permet d’éviter que le script ne continue d’exécuter des commandes dans des conditions imprévues. Par exemple, si une opération de suppression de fichier échoue, mais que le script continue de tourner sans se soucier de cette erreur, cela peut laisser des fichiers indésirables sur le système, voire conduire à des comportements encore plus graves dans des scénarios plus complexes.

Sans une bonne gestion des erreurs, il est difficile de savoir ce qui a échoué dans le script, et cela peut rendre le débogage extrêmement compliqué. Cela peut aussi entraîner des actions non désirées qui affectent la stabilité du système.

Ajouter une gestion d’erreurs basique

L’ajout d’une gestion d’erreurs dans un script Bash est assez simple et se fait souvent en vérifiant le code de retour d’une commande, généralement avec une commande conditionnelle comme if ou &&.

Exemple sans gestion d’erreurs :

#!/bin/bash
cp fichier_source.txt fichier_destination.txt
echo "Fichier copié avec succès."

Dans cet exemple, si la copie échoue pour une raison quelconque (par exemple, le fichier source n’existe pas), le script affichera toujours le message “Fichier copié avec succès.”, ce qui pourrait prêter à confusion.

Exemple avec gestion d’erreurs :

#!/bin/bash
if cp fichier_source.txt fichier_destination.txt; then
echo "Fichier copié avec succès."
else
echo "Erreur : Impossible de copier le fichier."
exit 1
fi

Ici, nous avons ajouté un bloc conditionnel pour vérifier si la commande cp a réussi. Si elle échoue, le script affiche un message d’erreur et quitte avec un code d’erreur (généralement 1 pour indiquer une erreur générale).

Utilisation de trap pour intercepter les erreurs

En plus des blocs conditionnels, Bash propose un mécanisme appelé trap, qui permet d’intercepter les signaux ou les erreurs dans un script. Cela permet de nettoyer correctement les ressources (comme les fichiers temporaires) ou d’afficher des messages utiles avant la fin du script, même en cas d’erreurs non anticipées.

Exemple avec trap :

#!/bin/bash
trap 'echo "Une erreur est survenue. Le script s’arrête."; exit 1' ERR
cp fichier_source.txt fichier_destination.txt
echo "Fichier copié avec succès."

Ici, nous utilisons trap pour intercepter l’erreur et afficher un message avant de quitter le script. Le mot-clé ERR intercepte toutes les erreurs dans le script, et la commande associée est exécutée automatiquement si une erreur survient.

Utiliser set -euo pipefail pour une gestion automatique des erreurs

Comme mentionné dans une section précédente, activer l’option set -euo pipefail est une bonne pratique pour arrêter immédiatement l’exécution du script dès qu’une erreur survient, éviter l’utilisation de variables non définies et s’assurer que les erreurs dans les pipelines sont correctement gérées. Cela simplifie la gestion des erreurs en automatisant l’arrêt du script dès que quelque chose ne se passe pas comme prévu.

Exemple combiné :

#!/bin/bash
set -euo pipefail
trap 'echo "Une erreur critique est survenue."; exit 1' ERR
cp fichier_source.txt fichier_destination.txt
echo "Fichier copié avec succès."

Ce script est maintenant robuste et arrêtera son exécution dès qu’une erreur survient, tout en affichant un message d’erreur clair.

Importance des codes de retour

Chaque commande dans un script shell retourne un code de sortie. Un code de sortie de 0 indique un succès, tandis qu’un code différent de 0 indique un échec. Il est indispensable de prendre en compte ces codes de retour dans vos scripts pour vous assurer que les commandes échouées sont gérées correctement.

Vous pouvez utiliser la commande $? pour vérifier le code de retour de la dernière commande exécutée. Si vous souhaitez capturer et gérer manuellement le code de retour, vous pouvez le faire ainsi :

#!/bin/bash
cp fichier_source.txt fichier_destination.txt
status=$?
if [ $status -ne 0 ]; then
echo "Erreur : Impossible de copier le fichier. Code de retour : $status"
exit $status
fi

Protéger les fichiers temporaires

Les fichiers temporaires jouent souvent un rôle essentiel dans les scripts Bash. Ils permettent de stocker des données provisoires pendant l’exécution d’un script, mais s’ils ne sont pas correctement gérés, ils peuvent exposer votre système à des failles de sécurité. Une mauvaise gestion des fichiers temporaires peut être exploitée par des utilisateurs malveillants pour accéder à des données sensibles, ou pour écraser des fichiers critiques du système via une attaque par lien symbolique (symlink attack).

Pourquoi les fichiers temporaires sont une cible ?

Les fichiers temporaires sont généralement créés dans des répertoires partagés comme /tmp ou /var/tmp, qui sont accessibles par tous les utilisateurs du système. Si plusieurs utilisateurs peuvent accéder à ces répertoires, il est possible pour un attaquant de créer ou modifier un fichier temporaire avec le même nom que celui que votre script prévoit d’utiliser. En l’absence de mécanismes de protection, cela peut conduire à :

  • L’écrasement de fichiers : Si votre script crée un fichier temporaire sans vérifier s’il existe déjà, un attaquant peut créer un fichier à cet emplacement et forcer le script à écraser un fichier important du système.
  • Les attaques par lien symbolique (symlink attack) : Un utilisateur malveillant peut créer un lien symbolique pointant vers un fichier sensible. Si le script accède à ce lien pensant qu’il s’agit d’un fichier temporaire, il pourrait lire ou écrire dans un fichier critique du système.

Comment sécuriser les fichiers temporaires ?

Pour éviter ces risques, voici quelques meilleures pratiques à suivre lors de la gestion des fichiers temporaires dans vos scripts Bash.

Utiliser mktemp pour créer des fichiers et répertoires temporaires

L’une des méthodes les plus sûres pour créer des fichiers temporaires est d’utiliser la commande mktemp. Cette commande crée des fichiers ou des répertoires temporaires avec des noms uniques, empêchant ainsi toute collision avec des fichiers déjà existants.

Exemple :

#!/bin/bash
temp_file=$(mktemp) # Crée un fichier temporaire sécurisé
echo "Fichier temporaire sécurisé créé : $temp_file"
# Utilisez le fichier temporaire ici
# ...
# Supprimer le fichier temporaire après utilisation
rm -f "$temp_file"

Dans cet exemple, mktemp garantit que le fichier temporaire créé est unique et sécurisé, ce qui évite les collisions avec d’autres fichiers. Une fois le script terminé, le fichier temporaire est supprimé avec rm -f, ce qui libère de l’espace et supprime les données sensibles.

Utiliser un répertoire temporaire privé avec mktemp -d

Si votre script nécessite un répertoire temporaire, vous pouvez utiliser l’option -d de mktemp, qui crée un répertoire temporaire sécurisé. Cela est particulièrement utile si votre script génère plusieurs fichiers temporaires.

Exemple :

#!/bin/bash
temp_dir=$(mktemp -d) # Crée un répertoire temporaire sécurisé
echo "Répertoire temporaire sécurisé créé : $temp_dir"
# Utilisez le répertoire temporaire ici
# ...
# Supprimer le répertoire temporaire après utilisation
rm -rf "$temp_dir"

Ici, le répertoire temporaire est sécurisé, et une fois que le script a terminé son travail, il est supprimé avec la commande rm -rf.

Vérifier les permissions des répertoires temporaires

Les répertoires /tmp et /var/tmp sont souvent partagés entre plusieurs utilisateurs. Bien qu’ils soient configurés pour empêcher les utilisateurs d’accéder aux fichiers des autres, il est recommandé de vérifier les permissions des fichiers temporaires que vous créez.

Par exemple, vous pouvez restreindre les permissions pour qu’aucun autre utilisateur ne puisse accéder au fichier temporaire que vous créez :

#!/bin/bash
temp_file=$(mktemp)
chmod 600 "$temp_file" # Restreindre les permissions
echo "Fichier temporaire sécurisé avec des permissions restreintes."

Cela garantit que le fichier temporaire ne peut être lu ou modifié que par l’utilisateur qui l’a créé.

Supprimer les fichiers temporaires après utilisation

Il est essentiel de s’assurer que les fichiers temporaires sont supprimés après leur utilisation. Cela empêche tout utilisateur malveillant de retrouver des fichiers qui pourraient contenir des données sensibles.

Utilisez toujours rm -f pour supprimer les fichiers temporaires à la fin de votre script :

Terminal window
rm -f "$temp_file"

Cela garantit que même si le script échoue ou se termine prématurément, les fichiers temporaires seront nettoyés.

Ajouter des messages de log pour le débogage

L’ajout de messages de log dans vos scripts Bash est une pratique souvent sous-estimée, mais elle peut grandement faciliter le débogage et améliorer la transparence de l’exécution du script.

Pourquoi ajouter des messages de log ?

Sans messages de log, il est difficile de suivre l’exécution d’un script, surtout dans des environnements de production ou lors de l’automatisation de tâches critiques. Les logs permettent de :

  • Diagnostiquer les erreurs : Vous pouvez savoir où et pourquoi une erreur est survenue, ce qui facilite le débogage.
  • Suivre l’exécution : Les logs montrent quelle partie du script est en cours d’exécution et si tout se déroule comme prévu.
  • Audit : En cas de problème de sécurité ou de dysfonctionnement, les logs peuvent fournir des informations précieuses sur ce qui s’est passé.

Comment ajouter des messages de log dans un script Shell ?

Il existe plusieurs façons de créer des messages de log dans un script Bash. Voici quelques pratiques essentielles :

Utilisation de la commande echo pour des logs simples

L’utilisation la plus basique consiste à afficher des messages avec echo. C’est une manière simple d’afficher ce qui se passe dans le script au fur et à mesure de son exécution.

Exemple :

#!/bin/bash
echo "Début du script."
echo "Tentative de suppression du fichier : $filename"
rm "$filename"
if [[ $? -eq 0 ]]; then
echo "Le fichier $filename a été supprimé avec succès."
else
echo "Erreur : impossible de supprimer le fichier $filename."
fi
echo "Fin du script."

Dans cet exemple, les messages affichés par echo permettent de suivre l’exécution du script et de voir si les différentes étapes sont réussies ou échouent.

Rediriger les logs vers un fichier

Au lieu d’afficher les messages de log directement dans la console, vous pouvez rediriger les sorties vers un fichier de log pour les conserver et les analyser plus tard. Cela est particulièrement utile dans un environnement de production où vous voulez garder une trace des opérations effectuées.

Exemple avec redirection :

#!/bin/bash
logfile="/var/log/mon_script.log"
echo "Début du script." >> "$logfile"
echo "Tentative de suppression du fichier : $filename" >> "$logfile"
rm "$filename" 2>> "$logfile"
if [[ $? -eq 0 ]]; then
echo "Le fichier $filename a été supprimé avec succès." >> "$logfile"
else
echo "Erreur : impossible de supprimer le fichier $filename." >> "$logfile"
fi
echo "Fin du script." >> "$logfile"

Dans cet exemple, toutes les sorties sont redirigées vers un fichier de log situé dans /var/log/mon_script.log. Cela permet de conserver une trace des événements même après la fin du script.

Utilisation de logger pour des journaux système

Pour une gestion encore plus robuste des logs, vous pouvez utiliser la commande logger, qui permet d’envoyer des messages au système de journalisation (syslog) du système d’exploitation. Ces logs peuvent ensuite être consultés dans des fichiers système tels que /var/log/syslog ou /var/log/messages, selon la configuration de votre système.

Exemple avec logger :

#!/bin/bash
logger "Début du script."
logger "Tentative de suppression du fichier : $filename"
rm "$filename"
if [[ $? -eq 0 ]]; then
logger "Le fichier $filename a été supprimé avec succès."
else
logger "Erreur : impossible de supprimer le fichier $filename."
fi
logger "Fin du script."

Avec logger, les messages sont automatiquement envoyés au système de journalisation, ce qui permet une gestion centralisée des logs, particulièrement utile dans des environnements multi-utilisateurs ou des serveurs.

Ajouter des options de verbosité**

Pour rendre le script plus flexible, vous pouvez ajouter une option de verbosité (mode verbose) qui permettra de contrôler si les messages de log doivent être affichés ou non. Cela peut être fait en utilisant une variable verbose qui est activée ou désactivée via les arguments du script.

Exemple avec une option de verbosité :

#!/bin/bash
verbose=false
while getopts "v" option; do
case $option in
v) verbose=true ;;
esac
done
log() {
if [ "$verbose" = true ]; then
echo "$1"
fi
}
log "Début du script."
log "Tentative de suppression du fichier : $filename"
rm "$filename"
if [[ $? -eq 0 ]]; then
log "Le fichier $filename a été supprimé avec succès."
else
log "Erreur : impossible de supprimer le fichier $filename."
fi
log "Fin du script."

Avec cette approche, l’utilisateur peut activer le mode verbose en exécutant le script avec l’option -v, ce qui affichera les messages de log. Sinon, le script fonctionne de manière silencieuse sans afficher les logs.

Utilisation de exit

Dans le cadre de la sécurisation des scripts Bash, l’utilisation correcte de la commande exit peut être utile pour contrôler la façon dont un script se termine. En définissant un code de sortie explicite, vous garantissez que les autres processus ou scripts qui appellent votre script sont correctement informés de son succès ou de son échec, ce qui permet de prévenir des comportements imprévus ou dangereux.

Exemple :

#!/bin/bash
echo "Entrez le nom du fichier à supprimer :"
read -r filename
if [[ -f "$filename" ]]; then
rm "$filename"
echo "Le fichier $filename a été supprimé."
exit 0 # Indique un succès
else
echo "Erreur : Le fichier n'existe pas."
exit 1 # Indique une erreur
fi

Dans ce script :

  • exit 0 signale que la suppression s’est bien déroulée.
  • exit 1 signale un échec en raison de l’absence du fichier.

Le code complet sécurisé

#!/bin/bash
set -euo pipefail # Assure la robustesse du script
# Utilisation du chemin absolu pour rm
RM_CMD="/bin/rm"
# Fonction pour afficher un message d'erreur et quitter
function error_exit {
echo "$1" >&2
exit 1
}
# Gestion des erreurs inattendues avec trap
trap 'error_exit "Erreur inattendue rencontrée. Sortie du script."' ERR
# Validation de l'entrée utilisateur
echo "Entrez le nom du fichier à supprimer :"
read -r filename
# Vérification que le fichier existe
if [[ ! -e "$filename" ]]; then
error_exit "Erreur : le fichier '$filename' n'existe pas."
fi
# Vérification des permissions sur le fichier
if [[ ! -w "$filename" ]]; then
error_exit "Erreur : vous n'avez pas les droits d'écriture sur '$filename'."
fi
# Vérification des permissions d'exécution si nécessaire
if [[ -x "$filename" ]]; then
echo "Attention : le fichier '$filename' est exécutable."
fi
# Suppression sécurisée du fichier avec gestion des erreurs
if ! $RM_CMD -- "$filename"; then
error_exit "Erreur : impossible de supprimer '$filename'."
fi
# Confirmation de la suppression
echo "Le fichier '$filename' a été supprimé avec succès."
# Message de fin propre
exit 0 # Sortie avec succès

Utilisation de ShellCheck pour analyser et améliorer vos scripts

ShellCheck est un outil d’analyse statique conçu pour examiner les scripts shell, comme ceux écrits en Bash, et détecter les erreurs, les mauvaises pratiques, ainsi que des vulnérabilités potentielles. C’est un outil précieux pour les administrateurs systèmes et les développeurs qui veulent s’assurer que leurs scripts sont corrects, sécurisés et faciles à maintenir.

ShellCheck peut être utilisé en ligne, installé localement sur votre machine, ou intégré dans des éditeurs de texte comme VS Code. Voici quelques façons de l’utiliser :

Si vous ne souhaitez pas installer l’outil, vous pouvez simplement copier et coller votre script sur le site web de ShellCheck (ShellCheck Online) pour obtenir un rapport d’analyse.

Vous pouvez installer ShellCheck sur votre machine via un gestionnaire de paquets. Voici un exemple d’installation sur Ubuntu :

Terminal window
sudo apt-get install shellcheck

Une fois installé, vous pouvez exécuter l’outil en ligne de commande sur vos scripts :

Terminal window
shellcheck mon_script.sh

Conclusion

Les scripts Bash, même simples, peuvent devenir des cibles d’attaques si on ne respecte pas certaines règles de base. Mais en adoptant progressivement ces bonnes pratiques, et avec l’aide d’outils comme ShellCheck, vous développez des réflexes de sécurité et d’optimisation qui garantiront la stabilité et la fiabilité de vos scripts.

Cela peut sembler fastidieux au début. Entre l’ajout de validations d’entrées, la gestion des permissions, l’utilisation de commandes sécurisées et la protection contre les erreurs, la liste des pratiques à suivre peut paraître longue et complexe. Cependant, plus vous pratiquez, plus ces techniques deviennent automatiques et naturelles.

Ainsi, prenez le temps de mettre en pratique ces recommandations et vous verrez que l’effort initial sera largement récompensé par la tranquillité d’esprit et la qualité de vos scripts Bash.

Plus loin