Arkendia Classrooms
Leçon 6 sur 28 · 17 min

Automatiser une tâche de reconnaissance simple

Les listes de machines, de sous-domaines ou de ports ont toujours le même défaut : elles sont désordonnées. Doublons, majuscules, lignes vides, commentaires, fautes de frappe… Avant d'analyser quoi que ce soit, il faut nettoyer. Cette leçon vous fait écrire votre premier vrai script d'automatisation : il lit une liste de machines de votre laboratoire, la nettoie, la présente en tableau et l'exporte au format CSV. Aucune connexion réseau n'est nécessaire : le script travaille sur un fichier.

Reconnaissance : où s'arrête le périmètre ?

Dans la méthodologie d'un test d'intrusion, la reconnaissance se fait dans les limites fixées par l'autorisation écrite : cibles, plages d'adresses, horaires, techniques autorisées. Votre script ne doit manipuler que des machines de votre laboratoire ou de votre périmètre contractuel (articles 323-1 à 323-3-1 du Code pénal en France). Pour cette raison, nous écrivons ici un outil de mise en forme, pas un outil de balayage.

Le problème à résoudre

Après avoir monté votre laboratoire, vous tenez une liste de ses machines dans un fichier texte cibles-labo.txt, avec le format nom:port (le port est facultatif). Au fil des semaines, elle s'est salie :

# Machines de mon laboratoire (réseau interne isolé)
serveur-web.atelier.lab:8080
SERVEUR-WEB.atelier.lab:8080
dc01.atelier.lab:389

poste-compta.atelier.lab
base-test.atelier.lab:abc

On y trouve un commentaire, un doublon (en majuscules), une ligne vide, une machine sans port, et une entrée au port invalide (abc). Le script devra :

  1. ignorer commentaires et lignes vides ;
  2. mettre les noms en minuscules et supprimer les doublons ;
  3. attribuer le port 80 par défaut quand il est absent ;
  4. signaler les lignes invalides plutôt que de planter ;
  5. afficher un tableau et enregistrer un fichier CSV.

Étape 1 : nettoyer

La première fonction parcourt les lignes et ne garde que les lignes utiles. Elle utilise un ensemble (set), un type Python qui ne conserve qu'un exemplaire de chaque valeur : c'est la manière la plus simple de supprimer les doublons.

def nettoyer(lignes):
    """Retire les commentaires, les lignes vides et les doublons."""
    resultats = set()
    for ligne in lignes:
        ligne = ligne.strip().lower()
        if not ligne or ligne.startswith("#"):
            continue
        resultats.add(ligne)
    return sorted(resultats)

Le mot-clé continue passe directement à la ligne suivante. La fonction sorted renvoie une liste triée : un résultat stable est plus facile à comparer d'une exécution à l'autre.

Étape 2 : découper et valider

La seconde fonction sépare le nom du port. La méthode partition coupe un texte autour d'un séparateur et renvoie trois éléments : ce qui précède, le séparateur lui-même, ce qui suit. Si le séparateur est absent, le deuxième élément est vide, ce qui nous sert de test.

def decouper(entree):
    """Transforme 'nom:port' en (nom, port) ; le port par défaut est 80."""
    nom, separateur, port = entree.partition(":")
    if separateur and not port.isdigit():
        return None
    return nom, int(port) if port else 80

Remarquez le principe de validation des entrées : on ne fait jamais confiance à une donnée avant de l'avoir vérifiée. Ici, isdigit() contrôle que le port ne contient que des chiffres. Cette discipline est la même que celle qui protège les applications web contre les entrées malveillantes, vous la retrouverez dans le chapitre sur les API.

Étape 3 : assembler

La fonction principale lit le fichier, appelle les deux fonctions, affiche le tableau et écrit le CSV. Voici le script complet :

from pathlib import Path


def nettoyer(lignes):
    """Retire les commentaires, les lignes vides et les doublons."""
    resultats = set()
    for ligne in lignes:
        ligne = ligne.strip().lower()
        if not ligne or ligne.startswith("#"):
            continue
        resultats.add(ligne)
    return sorted(resultats)


def decouper(entree):
    """Transforme 'nom:port' en (nom, port) ; le port par défaut est 80."""
    nom, separateur, port = entree.partition(":")
    if separateur and not port.isdigit():
        return None
    return nom, int(port) if port else 80


def main():
    lignes = Path("cibles-labo.txt").read_text(encoding="utf-8").splitlines()
    ignorees = []
    cibles = []
    for entree in nettoyer(lignes):
        resultat = decouper(entree)
        if resultat is None:
            ignorees.append(entree)
        else:
            cibles.append(resultat)

    print(f"{'Machine':<30} Port")
    for nom, port in cibles:
        print(f"{nom:<30} {port}")

    with open("cibles-labo.csv", "w", encoding="utf-8") as sortie:
        sortie.write("machine,port\n")
        for nom, port in cibles:
            sortie.write(f"{nom},{port}\n")

    print(f"\n{len(cibles)} cible(s) retenue(s), {len(ignorees)} ligne(s) ignorée(s).")
    for entree in ignorees:
        print(f"  à vérifier : {entree}")


if __name__ == "__main__":
    main()

La dernière ligne, if __name__ == "__main__":, signifie « n'exécute main() que si le fichier est lancé directement ». Ainsi, vous pourrez plus tard importer les fonctions dans un autre script sans déclencher l'exécution. Enregistrez ce code dans nettoyer_cibles.py, placez-le à côté de votre fichier de liste, puis lancez-le.

Le résultat attendu

Machine                        Port
dc01.atelier.lab               389
poste-compta.atelier.lab       80
serveur-web.atelier.lab        8080

3 cible(s) retenue(s), 1 ligne(s) ignorée(s).
  à vérifier : base-test.atelier.lab:abc

Le doublon a disparu, le port par défaut a été appliqué, la ligne invalide est signalée sans bloquer le traitement. Le fichier cibles-labo.csv est prêt à être ouvert dans un tableur ou lu par un autre script.

Pourquoi cela fait gagner du temps

Pour 6 lignes, le gain est nul. Pour 6 000 lignes issues de plusieurs sources (inventaire, export d'outil, notes de réunion), il est énorme, et surtout reproductible : chaque exécution produit le même résultat, ce qui permet de comparer deux inventaires à deux dates différentes. Cette reproductibilité est l'un des principaux arguments de l'automatisation en audit : un résultat que l'on peut rejouer est un résultat que l'on peut défendre.

À vous de jouer

Améliorez le script pour qu'il vérifie aussi que le port est dans la plage valide (1 à 65535) et qu'il compte combien de machines utilisent le port par défaut. Testez avec une ligne test.atelier.lab:70000.

Voir une solution

Dans decouper, ajoutez après la conversion une vérification de plage :

def decouper(entree):
    nom, separateur, port = entree.partition(":")
    if separateur and not port.isdigit():
        return None
    numero = int(port) if port else 80
    if not 1 <= numero <= 65535:
        return None
    return nom, numero

Pour compter les ports par défaut dans main : par_defaut = sum(1 for _, port in cibles if port == 80), puis affichez le résultat. Une valeur comme 70000 est maintenant rejetée et listée dans « à vérifier ». Attention : une machine qui demande explicitement le port 80 est comptée comme « par défaut » ; si la distinction compte, conservez l'information dans la structure de données.

Versionnez vos scripts

Commencez à ranger vos scripts dans un dépôt de code (même local, avec Git). Vous pourrez ensuite le publier dans votre portfolio, après avoir vérifié qu'il ne contient ni secret, ni donnée réelle, ni nom de machine appartenant à un tiers.

À retenir

  • Automatiser, c'est écrire une fois une tâche répétitive pour pouvoir la rejouer à l'identique.
  • Un ensemble (set) supprime les doublons ; partition découpe proprement une chaîne.
  • Validez toujours une entrée avant de l'utiliser et signalez les lignes invalides plutôt que de planter.
  • Ce script ne touche pas au réseau : il met en forme les données de votre laboratoire.
  • La reconnaissance réelle n'a lieu que dans le périmètre autorisé par écrit.
Publicité
Connectez-vous pour enregistrer votre progression. Se connecter