Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

Chapitre 1515 minDébutant

Sécurité des scripts : stratégie d'exécution, secrets et signature

Par Josselyn C., prof de BTS SIO. Mis à jour le .

À la fin de ce chapitre, tu sauras

  • expliquer la stratégie d'exécution de PowerShell et ses limites ;
  • signer un script et vérifier sa signature ;
  • ranger un secret sans l'écrire dans le script ;
  • journaliser PowerShell et protéger les scripts Linux et leurs règles sudo.

4 exercices corrigés

Sécuriser un script, c'est empêcher qu'il devienne une porte d'entrée : qu'un attaquant le modifie pour exécuter ses propres commandes, y lise un mot de passe, ou détourne les droits avec lesquels il tourne. Ce chapitre explique la stratégie d'exécution de PowerShell (et ce qu'elle ne protège pas), la signature des scripts, le rangement des secrets, la journalisation de PowerShell, les droits des scripts Linux et l'analyse automatique du code.

La stratégie d'exécution de PowerShell

La stratégie d'exécution (execution policy) dit quels scripts PowerShell accepte de lancer sous Windows.

ValeurCe qu'elle autoriseUsage
RestrictedAucun script, seulement les commandes tapéesDéfaut de Windows PowerShell 5.1 sur les postes Windows 10 et 11
AllSignedSeulement les scripts signés par un éditeur approuvéServeurs sensibles, environnement maîtrisé
RemoteSignedLes scripts locaux ; ceux venus d'Internet doivent être signésDéfaut de Windows Server, bon choix général
UnrestrictedTout, avec un avertissement pour les scripts venus d'InternetDéfaut, non modifiable, de PowerShell sous Linux et macOS
BypassTout, sans aucun avertissementOutils qui lancent PowerShell en interne
UndefinedAucune valeur à cette portée

PowerShell 7 installé sous Windows démarre en RemoteSigned : son fichier powershell.config.json, dans le dossier d'installation, fixe cette valeur.

Elle se règle à plusieurs portées, de la plus forte à la plus faible : MachinePolicy et UserPolicy (fixées par GPO), Process (la session en cours), CurrentUser, LocalMachine. La première portée définie l'emporte.

powershell
Get-ExecutionPolicy -List
text
        Scope ExecutionPolicy
        ----- ---------------
MachinePolicy       Undefined
   UserPolicy       Undefined
      Process       Undefined
  CurrentUser    RemoteSigned
 LocalMachine       Undefined
powershell
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned   # pour ton compte, sans droits d'administrateur

En entreprise, la stratégie se fixe par GPO : Configuration ordinateur > Stratégies > Modèles d'administration > Composants Windows > Windows PowerShell > Activer l'exécution des scripts. Une valeur fixée par GPO ne peut pas être changée par Set-ExecutionPolicy.

« Venu d'Internet » se reconnaît à la marque du Web : quand un navigateur ou un client de messagerie enregistre un fichier, Windows lui ajoute un flux de données Zone.Identifier qui indique sa provenance. Après avoir lu et vérifié un script téléchargé, Unblock-File .\script.ps1 retire cette marque.

Signer un script

Une signature Authenticode garantit qu'un script vient bien de son auteur et qu'il n'a pas été modifié depuis : le moindre caractère changé invalide la signature. Avec la stratégie AllSigned, un script modifié par un attaquant ne s'exécute plus.

Il faut un certificat de signature de code, délivré par l'autorité de certification de l'entreprise (modèle de certificat « Signature de code » d'AD CS) ; le fonctionnement des certificats et de la signature numérique est expliqué dans le cours Cryptographie.

powershell
$cert = Get-ChildItem -Path Cert:\CurrentUser\My -CodeSigningCert | Select-Object -First 1
Set-AuthenticodeSignature -FilePath .\New-ComptesArrivees.ps1 -Certificate $cert `
    -TimestampServer 'http://timestamp.digicert.com'
Get-AuthenticodeSignature -FilePath .\New-ComptesArrivees.ps1 | Select-Object Status, StatusMessage

La signature s'ajoute en bas du fichier, dans un bloc de commentaires # SIG # Begin signature block. L'horodatage prouve que le script a été signé pendant la validité du certificat : la signature reste valable après l'expiration de celui-ci. Get-AuthenticodeSignature renvoie un Status :

StatusSens
ValidSignature correcte, certificat approuvé
HashMismatchLe script a été modifié après sa signature
NotSignedPas de signature
NotTrusted ou UnknownErrorLe certificat ou son autorité ne sont pas approuvés sur cette machine

Pour que les postes et serveurs fassent confiance aux scripts signés, le certificat de l'autorité racine doit être dans le magasin des autorités racines de confiance, et celui du signataire dans les éditeurs approuvés : une GPO les déploie.

Ranger les secrets

Un mot de passe ou un jeton écrit dans un script est lu par tous ceux qui lisent le script : les collègues, la sauvegarde, le dépôt Git, l'attaquant qui a pris pied sur le serveur. Les solutions, de la meilleure à la moins bonne :

  1. Pas de secret du tout. Une tâche planifiée qui tourne sous un gMSA s'authentifie sur le réseau sans que personne ne connaisse de mot de passe ; un script lancé par un administrateur utilise ses propres droits (Kerberos).
  2. Un coffre de secrets. Le module Microsoft.PowerShell.SecretManagement donne des commandes uniques (Get-Secret, Set-Secret) au-dessus de différents coffres : le coffre local SecretStore, ou un coffre d'entreprise (Azure Key Vault, HashiCorp Vault, KeePass, par des extensions).
  3. Un fichier chiffré pour un seul compte. Export-Clixml enregistre un objet PSCredential dont le mot de passe est chiffré par la DPAPI de Windows : seul le même compte, sur la même machine, peut le relire.
  4. Une variable d'environnement ou un fichier protégé, lisible par le seul compte qui lance le script : la solution courante sous Linux.
powershell
# Le coffre SecretStore, une fois pour toutes (PowerShell 7.4+ inclut Install-PSResource)
Install-PSResource -Name Microsoft.PowerShell.SecretManagement, Microsoft.PowerShell.SecretStore
Register-SecretVault -Name Coffre -ModuleName Microsoft.PowerShell.SecretStore -DefaultVault
Set-Secret -Name ZabbixLecture -Secret (Read-Host -AsSecureString 'Jeton Zabbix')

# Dans le script
$jeton = Get-Secret -Name ZabbixLecture -AsPlainText
powershell
# Un identifiant chiffré pour le compte qui lance la tâche : à créer EN TANT QUE ce compte, sur ce serveur
Get-Credential | Export-Clixml -Path C:\Scripts\Secrets\copie-compta.xml
$cred = Import-Clixml -Path C:\Scripts\Secrets\copie-compta.xml

Le coffre SecretStore demande par défaut un mot de passe pour s'ouvrir, ce qui ne convient pas à une tâche sans surveillance ; Set-SecretStoreConfiguration permet de régler ce comportement, au prix d'une protection qui repose alors sur le compte Windows. Dans Windows PowerShell 5.1, les modules s'installent avec Install-Module au lieu d'Install-PSResource.

Sous Linux, un secret se range dans un fichier qui n'appartient qu'au compte du script, et se lit au lancement :

bash
sudo install -o rapports -g rapports -m 0600 /dev/null /etc/sorbier/zabbix.token   # fichier vide, droits 600
sudoedit /etc/sorbier/zabbix.token                                                  # y coller le jeton
bash
ZABBIX_TOKEN=$(< /etc/sorbier/zabbix.token)

Pour un script lancé par systemd, la directive LoadCredential=zabbix-token:/etc/sorbier/zabbix.token dans la section [Service] met le secret à disposition du seul service, dans le fichier $CREDENTIALS_DIRECTORY/zabbix-token, sans qu'il soit lisible par les autres processus.

Enfin, un secret qui a été commité dans Git, même supprimé ensuite, reste dans l'historique : on le considère comme compromis et on le change. Le cours Git et GitHub explique comment l'éviter avec .gitignore et que faire si c'est arrivé.

Journaliser ce que fait PowerShell

PowerShell est un outil d'administration puissant, présent sur toutes les machines Windows : c'est aussi l'un des outils préférés des attaquants, qui l'utilisent pour agir sans déposer de programme (on parle de living off the land). La défense consiste à tout journaliser pour pouvoir détecter et enquêter :

  • la journalisation des blocs de script (Script Block Logging), activée par GPO (Modèles d'administration > Composants Windows > Windows PowerShell > Activer la journalisation de blocs de scripts PowerShell), enregistre le code réellement exécuté dans l'événement 4104 du journal Microsoft-Windows-PowerShell/Operational, même s'il avait été masqué ou encodé ;
  • la transcription garde un fichier texte de chaque session, dans un dossier protégé ;
  • les événements sont envoyés au serveur de journaux et analysés par le SIEM, qui alerte sur les signes classiques : un powershell.exe lancé avec -EncodedCommand ou -WindowStyle Hidden par un traitement de texte, un téléchargement suivi d'une exécution, une tâche planifiée créée hors des heures habituelles (événement 4698 du journal de sécurité).

La mise en place de ces journaux et de leur transfert est décrite dans le cours Windows Server.

Les droits des scripts Linux et sudo

Sur un serveur Linux, la sécurité d'un script tient à trois règles :

  • un script lancé par root n'est modifiable que par root, de même que le dossier qui le contient : install -o root -g root -m 0750 (chapitre 13) ;
  • un compte de service n'a que les droits utiles. Si le compte rapports doit lancer un seul script avec les droits de root, une règle sudo l'autorise pour ce script et rien d'autre, écrite avec sudo visudo -f /etc/sudoers.d/rapports :
text
rapports ALL=(root) NOPASSWD: /usr/local/sbin/sauvegarde-config.sh
  • une valeur venue de l'extérieur n'est jamais exécutée. Pas d'eval en Bash ni d'Invoke-Expression en PowerShell sur une donnée lue dans un fichier, une API ou un argument : un nom de fichier comme x; rm -rf / deviendrait une commande. Les guillemets autour des variables, -- avant les noms de fichiers et les filtres avec variables de l'annuaire jouent le même rôle que les requêtes préparées contre l'injection SQL.

Analyser le code automatiquement

Deux outils relisent tes scripts et signalent les erreurs classiques avant qu'elles ne se produisent :

powershell
Install-PSResource -Name PSScriptAnalyzer
Invoke-ScriptAnalyzer -Path C:\Scripts -Recurse        # alias inutilisés, variables jamais lues, mots de passe en clair…
bash
sudo apt install shellcheck
shellcheck /usr/local/sbin/sauvegarde-config.sh       # guillemets oubliés, tests mal écrits, pièges de set -e…

PSScriptAnalyzer signale par exemple un paramètre nommé Password de type [string] (règle PSAvoidUsingPlainTextForPassword) ou un Invoke-Expression. ShellCheck signale chaque variable non protégée (SC2086), chaque cd non vérifié (SC2164). Les deux s'intègrent à l'éditeur et à un pipeline d'intégration continue, qui refuse un script défectueux avant son déploiement (cours Intégration continue).

Exercices

Exercice 1 sur 4 Lire les stratégies d'exécution Réussi À revoir

Sur un poste du domaine, Get-ExecutionPolicy -List affiche :

text
        Scope ExecutionPolicy
        ----- ---------------
MachinePolicy        AllSigned
   UserPolicy        Undefined
      Process        Undefined
  CurrentUser     Unrestricted
 LocalMachine     RemoteSigned
  1. Quelle stratégie s'applique ?
  2. Un utilisateur tape Set-ExecutionPolicy -Scope CurrentUser Bypass. Qu'est-ce que ça change ?
  3. Le script Inventaire.ps1, écrit par un technicien et non signé, peut-il s'exécuter avec .\Inventaire.ps1 ? Avec powershell.exe -ExecutionPolicy Bypass -File .\Inventaire.ps1 ?
  4. Qu'en conclus-tu sur la protection apportée ?
Voir la réponseMasquer la réponse de l'exercice 1
  1. AllSigned : MachinePolicy, fixée par GPO, est la portée la plus forte et elle est définie.
  2. Rien en pratique : la valeur de CurrentUser est bien enregistrée, mais PowerShell affiche une erreur qui prévient qu'elle est remplacée par une stratégie définie à une portée plus forte. La stratégie effective reste AllSigned.
  3. Avec .\Inventaire.ps1 : non, le script n'est pas signé. Avec -ExecutionPolicy Bypass : non plus dans ce cas, car l'option règle la portée Process, plus faible que MachinePolicy. Mais l'utilisateur peut toujours ouvrir le fichier et coller son contenu dans la console : les commandes s'exécutent une par une, sans être un « script ».
  4. La stratégie d'exécution, même par GPO, évite les exécutions accidentelles et oblige à signer les scripts officiels. Elle n'empêche pas un utilisateur décidé d'exécuter du code. Le vrai contrôle passe par App Control for Business ou AppLocker (et le mode de langage restreint), la journalisation des blocs de script et le moindre privilège.

Tu avais trouvé ?

Exercice 2 sur 4 La signature cassée Réussi À revoir

Le script signé Disable-ComptesDeparts.ps1 tournait tous les matins sur un serveur en AllSigned. Ce matin, il refuse de démarrer. Get-AuthenticodeSignature renvoie HashMismatch. Que s'est-il passé, et que fais-tu ?

Voir la réponseMasquer la réponse de l'exercice 2

HashMismatch signifie que le contenu du fichier ne correspond plus à celui qui a été signé : quelqu'un a modifié le script après sa signature. Ce peut être une correction légitime d'un collègue qui a oublié de le re-signer, ou une modification malveillante. Dans l'ordre :

  1. ne pas re-signer le fichier tel quel, ni passer la stratégie en RemoteSigned « pour dépanner » ;
  2. comparer le fichier avec la version de référence, dans le dépôt Git (git diff) ou la dernière sauvegarde, pour voir ce qui a changé ;
  3. chercher qui l'a modifié et quand : date de modification du fichier, journaux d'accès au partage, journaux de sécurité ;
  4. si la modification n'est pas expliquée, la traiter comme un incident de sécurité (un attaquant a pu écrire dans le dossier des scripts, donc il a des droits élevés) ; sinon, valider la correction, la commiter et re-signer le script.

C'est exactement le rôle de la signature : sans elle, la version modifiée aurait tourné sans que personne ne le voie.

Tu avais trouvé ?

Exercice 3 sur 4 Ranger les secrets Réussi À revoir

Pour chaque script, propose la façon de gérer le secret dont il a besoin, et justifie.

  1. La tâche planifiée nocturne de srv-fichiers qui copie des fichiers sur un partage de srv-gestion.
  2. Le script interactif de Chloé qui interroge l'API de Zabbix depuis son poste d'administration.
  3. Le timer systemd de srv-journaux qui envoie chaque matin un rapport par l'API d'un outil de messagerie, avec un jeton.
  4. Un script Bash que Moussa lance à la main, qui demande un mot de passe d'administration d'un équipement.
Voir la réponseMasquer la réponse de l'exercice 3
  1. Un gMSA comme compte de la tâche, avec le droit d'écrire sur ce seul partage : aucun secret à stocker, le mot de passe est géré par l'annuaire et change seul.
  2. Un coffre SecretManagement (SecretStore sur son poste, ou le coffre de l'entreprise) : Get-Secret -Name ZabbixLecture -AsPlainText dans le script. Le coffre est déverrouillé par Chloé quand elle travaille.
  3. Un fichier 0600 appartenant au compte du service, transmis par LoadCredential= dans l'unité systemd : seul ce service le lit, et il n'apparaît ni dans le script ni dans les arguments de commande.
  4. Une saisie masquée : read -rsp "Mot de passe : " mdp; echo. Le mot de passe ne s'affiche pas, ne va pas dans l'historique et disparaît à la fin du script ; il ne doit pas être passé en argument à une autre commande, mais par l'entrée standard ou une variable d'environnement.

Tu avais trouvé ?

Exercice 4 sur 4 Une règle sudo dangereuse Réussi À revoir

Pour que le compte rapports lance la sauvegarde, un technicien a écrit dans /etc/sudoers.d/rapports la ligne rapports ALL=(root) NOPASSWD: /bin/bash /home/rapports/sauvegarde.sh. Explique pourquoi cette règle donne en pratique tous les droits de root au compte rapports, et corrige.

Voir la réponseMasquer la réponse de l'exercice 4

Le script est dans /home/rapports, le dossier du compte rapports : ce compte peut le modifier à volonté, y ajouter n'importe quelle commande, puis le lancer en root sans mot de passe. La règle revient à « rapports peut exécuter n'importe quoi en root ». De plus, autoriser /bin/bash avec un argument est fragile : tout ce que fait le script se fait en root.

Correction : déplacer le script dans /usr/local/sbin, propriété de root et non modifiable par les autres (install -o root -g root -m 0750 sauvegarde.sh /usr/local/sbin/sauvegarde-config.sh, après l'avoir relu), le rendre exécutable par son shebang, et n'autoriser que lui :

text
rapports ALL=(root) NOPASSWD: /usr/local/sbin/sauvegarde-config.sh

Toujours éditer avec sudo visudo -f /etc/sudoers.d/rapports, qui vérifie la syntaxe : une erreur dans un fichier sudoers peut bloquer sudo pour tout le monde.

Tu avais trouvé ?

Chapitre terminé !

Fais les 4 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Exercices corrigés PowerShell et Bash type E6 et E7 SISRRevoir le sommaire du cours