← Accueil

PCTAMALOU

Semaine 25 : L'Automatisation des Ombres – Scripting Python Avancé pour l'Énumération Web

Par Platon-Y – pctamalou.fr & e-of-h.fr

🎯 Objectif de la semaine

Maîtriser le scripting Python avancé pour créer des outils personnalisés d'énumération web. Transformer vos scripts en armes autonomes et furtives capables de découvrir des surfaces d'attaque cachées, sans dépendre d'outils tiers détectables.

Pourquoi c'est essentiel : Les outils standards (gobuster, dirb) sont bien connus des systèmes de défense. Un script personnalisé est plus discret, plus adapté à votre cible, et démontre une compréhension technique supérieure.

flowchart TD S[📜 Script Python] --> R[📦 requests] S --> B[🍲 BeautifulSoup] S --> T[🧵 Threading] S --> A[⚡ asyncio] R -->|HTTP Requests| W[🌐 Cible Web] B -->|Parse HTML| H[📄 Contenu extrait] T -->|Parallélisation| F[🚀 Performance ×50] subgraph "🎯 Applications" SUB[Subdomain Enumeration] DIR[Directory Bruteforce] PAR[Parameter Discovery] API[API Endpoint Scanning] end S --> SUB S --> DIR S --> PAR S --> API SUB --> O1[📊 Sous-domaines trouvés] DIR --> O2[📁 Directories découvertes] PAR --> O3[🔍 Paramètres exposés] API --> O4[⚡ Endpoints API] style S fill:#0a0e17,stroke:#00d4ff,stroke-width:3px style R fill:#11151f,stroke:#9d4edd,stroke-width:2px style T fill:#11151f,stroke:#00f5a0,stroke-width:2px

📚 Sujet – Théorie (1 heure)

Le Scripting Offensif : L'Art de Forger Ses Propres Armes

Un pentester qui ne sait pas scripter est un soldat qui ne sait pas affûter son épée. Le scripting Python avancé vous permet de :

🚀

Automatiser la Répétition

Transformer des tâches manuelles fastidieuses en processus automatisés et reproductibles.

🎭

Contourner les Détections

Éviter les signatures d'outils connus (WAF, IDS) avec des patterns uniques.

🔧

Adapter à la Cible

Créer des outils spécifiques pour l'architecture, la stack tech, ou les défenses particulières.

Les Bibliothèques Essentielles

📚 Écosystème Python Offensif

📡 Réseau & Web
  • requests : HTTP client avancé
  • aiohttp : Asynchrone pour performance
  • scapy : Manipulation paquets bas-niveau
  • dnspython : Résolution DNS programmatique
🔍 Parsing & Analyse
  • BeautifulSoup4 : Parsing HTML/XML
  • lxml : Alternative plus rapide
  • regex : Expressions régulières avancées
  • json : Manipulation JSON native
⚡ Performance
  • concurrent.futures : Threading/Processing
  • asyncio : Programmation asynchrone
  • multiprocessing : Vrai parallélisme CPU
  • queue : Gestion files d'attente

Philosophie du Scripting Offensif

💡 Les 5 Principes du Gardien-Scripteur

  1. Silence : Logs minimaux, erreurs gérées, timeouts configurables
  2. Robustesse : Gestion complète des exceptions, retry logic
  3. Modularité : Code réutilisable, configuration externe
  4. Performance : Parallélisation intelligente, pas de DoS accidentel
  5. Documentation : Usage clair, résultats structurés (JSON/CSV)

Threading vs Asyncio : Comprendre la Différence

⚠️ Attention aux Pièges Courants

  • GIL (Global Interpreter Lock) : Python ne peut exécuter qu'un thread Python à la fois
  • I/O Bound vs CPU Bound : Threading pour I/O, Multiprocessing pour CPU
  • Rate Limiting : Toujours respecter les limites du serveur cible
  • Fingerprinting : User-Agents, délais entre requêtes, patterns évidents

🔬 Mission pratique – Laboratoire (2 heures)

Architecture du Laboratoire Scripting

flowchart LR K[Kali Linux
Scripts Python] -->|Requêtes HTTP| T1[DVWA
192.168.56.102] K -->|Tests| T2[bWAPP
192.168.56.103] K -->|Wordlists| W[📁 SecLists] subgraph "🎯 Cibles de Test" T1 T2 end subgraph "🛠️ Outils Développés" S1[subdomain_enum.py] S2[directory_buster.py] S3[api_discover.py] end K --> S1 K --> S2 K --> S3 style K fill:#11151f,stroke:#00d4ff,stroke-width:3px style S1 fill:#0a0e17,stroke:#9d4edd,stroke-width:2px

⚠️ Environnement de Développement

🐍 Configuration Python :

# Vérifier Python3
python3 --version  # Doit être 3.8+

# Créer environnement virtuel (bonne pratique)
python3 -m venv ~/mon_lab/python_avance/venv
source ~/mon_lab/python_avance/venv/bin/activate

# Installation bibliothèques
pip install requests beautifulsoup4 aiohttp colorama

Étapes Détaillées de Développement

Étape 1 : Subdomain Enumeration Avancée

#!/usr/bin/env python3
"""
subdomain_enum.py - Énumération avancée de sous-domaines
Usage: python3 subdomain_enum.py -d example.com -w wordlist.txt -t 20
"""

import argparse
import requests
import sys
from concurrent.futures import ThreadPoolExecutor, as_completed
from urllib.parse import urlparse
import time
import json
from colorama import init, Fore, Style

init(autoreset=True)  # Couleurs terminal

class SubdomainEnumerator:
    def __init__(self, domain, wordlist, threads=10, timeout=5, 
                 user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64)"):
        self.domain = domain
        self.wordlist = wordlist
        self.threads = threads
        self.timeout = timeout
        self.headers = {'User-Agent': user_agent}
        self.results = []
        self.session = requests.Session()
        
    def check_subdomain(self, subdomain):
        """Vérifie un sous-domaine avec plusieurs techniques"""
        url_http = f"http://{subdomain}.{self.domain}"
        url_https = f"https://{subdomain}.{self.domain}"
        
        techniques = [
            (url_https, "HTTPS"),
            (url_http, "HTTP")
        ]
        
        for url, protocol in techniques:
            try:
                response = self.session.head(
                    url, 
                    timeout=self.timeout, 
                    headers=self.headers,
                    allow_redirects=True
                )
                
                if response.status_code < 400:
                    # Récupère le titre si c'est une page HTML
                    if 'text/html' in response.headers.get('Content-Type', ''):
                        try:
                            full_resp = self.session.get(
                                url, 
                                timeout=self.timeout,
                                headers=self.headers
                            )
                            from bs4 import BeautifulSoup
                            soup = BeautifulSoup(full_resp.text, 'html.parser')
                            title = soup.title.string.strip() if soup.title else "No title"
                        except:
                            title = "Unknown"
                    else:
                        title = response.headers.get('Content-Type', 'Unknown')
                    
                    result = {
                        'subdomain': f"{subdomain}.{self.domain}",
                        'url': url,
                        'protocol': protocol,
                        'status_code': response.status_code,
                        'title': title,
                        'server': response.headers.get('Server', 'Unknown'),
                        'content_length': response.headers.get('Content-Length', 'Unknown')
                    }
                    
                    self.results.append(result)
                    
                    # Affichage coloré
                    color = Fore.GREEN if response.status_code == 200 else Fore.YELLOW
                    print(f"{color}[+] {result['subdomain']} ({protocol}) - {response.status_code} - {result['title']}")
                    
                    return result
                    
            except requests.exceptions.SSLError:
                print(f"{Fore.YELLOW}[!] SSL Error for {subdomain}.{self.domain}")
            except requests.exceptions.ConnectionError:
                pass  # Silence pour connexion refusée
            except requests.exceptions.Timeout:
                print(f"{Fore.RED}[!] Timeout for {subdomain}.{self.domain}")
            except Exception as e:
                print(f"{Fore.RED}[!] Error for {subdomain}.{self.domain}: {e}")
        
        return None
    
    def load_wordlist(self):
        """Charge la wordlist avec gestion d'erreurs"""
        try:
            with open(self.wordlist, 'r', encoding='utf-8', errors='ignore') as f:
                return [line.strip() for line in f if line.strip()]
        except FileNotFoundError:
            print(f"{Fore.RED}[!] Wordlist {self.wordlist} non trouvée")
            sys.exit(1)
    
    def run(self):
        """Exécute l'énumération"""
        print(f"{Fore.CYAN}[*] Démarrage énumération pour {self.domain}")
        print(f"{Fore.CYAN}[*] Threads: {self.threads}, Timeout: {self.timeout}s")
        
        subdomains = self.load_wordlist()
        print(f"{Fore.CYAN}[*] {len(subdomains)} sous-domaines à tester")
        
        start_time = time.time()
        
        with ThreadPoolExecutor(max_workers=self.threads) as executor:
            futures = {
                executor.submit(self.check_subdomain, sub): sub 
                for sub in subdomains[:100]  # Limite pour démonstration
            }
            
            for future in as_completed(futures):
                subdomain = futures[future]
                try:
                    future.result(timeout=self.timeout + 2)
                except Exception as e:
                    print(f"{Fore.RED}[!] Exception pour {subdomain}: {e}")
        
        elapsed = time.time() - start_time
        print(f"{Fore.CYAN}[*] Scan terminé en {elapsed:.2f} secondes")
        print(f"{Fore.CYAN}[*] {len(self.results)} sous-domaines trouvés")
        
        return self.results
    
    def save_results(self, filename="subdomains.json"):
        """Sauvegarde les résultats en JSON"""
        with open(filename, 'w') as f:
            json.dump(self.results, f, indent=2)
        print(f"{Fore.GREEN}[+] Résultats sauvegardés dans {filename}")

def main():
    parser = argparse.ArgumentParser(description="Énumération avancée de sous-domaines")
    parser.add_argument("-d", "--domain", required=True, help="Domaine cible")
    parser.add_argument("-w", "--wordlist", required=True, help="Fichier wordlist")
    parser.add_argument("-t", "--threads", type=int, default=10, help="Nombre de threads")
    parser.add_argument("-o", "--output", help="Fichier de sortie JSON")
    parser.add_argument("--timeout", type=int, default=5, help="Timeout des requêtes")
    
    args = parser.parse_args()
    
    enumerator = SubdomainEnumerator(
        domain=args.domain,
        wordlist=args.wordlist,
        threads=args.threads,
        timeout=args.timeout
    )
    
    results = enumerator.run()
    
    if args.output:
        enumerator.save_results(args.output)
    else:
        enumerator.save_results()

if __name__ == "__main__":
    main()

📝 Exécution et Test :

# Téléchargement wordlist
wget https://raw.githubusercontent.com/danielmiessler/SecLists/master/Discovery/DNS/subdomains-top1million-5000.txt

# Exécution avec options
python3 subdomain_enum.py -d example.com -w subdomains-top1million-5000.txt -t 20 -o results.json

# Test sur cible locale (DVWA)
python3 subdomain_enum.py -d 192.168.56.102 -w subdomains-top1million-5000.txt -t 5

Étape 2 : Directory Buster Intelligent

🎯 Défi de Programmation

Créez directory_buster.py avec ces fonctionnalités :

  1. Support HTTP et HTTPS automatique
  2. Détection des redirections (301, 302)
  3. Extraction des liens depuis les pages trouvées (crawling récursif)
  4. Filtrage par taille de réponse et type de contenu
  5. Sortie en format CSV et JSON

Indice : Utilisez requests.Session() pour la persistance et BeautifulSoup pour l'extraction de liens.

#!/usr/bin/env python3
"""
directory_buster.py - Bruteforce intelligent de répertoires
"""

import requests
from concurrent.futures import ThreadPoolExecutor
import argparse
from urllib.parse import urljoin
from bs4 import BeautifulSoup

class DirectoryBuster:
    def __init__(self, base_url, wordlist, extensions=[".php", ".html", ".txt", ".bak"]):
        self.base_url = base_url.rstrip('/')
        self.wordlist = wordlist
        self.extensions = extensions
        self.found = []
        self.session = requests.Session()
        
    def check_directory(self, path):
        """Vérifie un répertoire/path"""
        # Test sans extension
        url = f"{self.base_url}/{path}"
        response = self.session.get(url, timeout=5)
        
        if response.status_code == 200:
            self.found.append({
                'url': url,
                'status': 200,
                'title': self.extract_title(response.text),
                'size': len(response.content)
            })
            print(f"[+] 200: {url}")
            
            # Extraction liens pour crawling récursif
            self.extract_links(response.text, url)
            
        # Test avec extensions
        for ext in self.extensions:
            url_ext = f"{url}{ext}"
            try:
                resp_ext = self.session.get(url_ext, timeout=3)
                if resp_ext.status_code == 200:
                    self.found.append({
                        'url': url_ext,
                        'status': 200,
                        'title': self.extract_title(resp_ext.text),
                        'size': len(resp_ext.content)
                    })
                    print(f"[+] 200: {url_ext}")
            except:
                pass
    
    def extract_title(self, html):
        """Extrait le titre de la page HTML"""
        soup = BeautifulSoup(html, 'html.parser')
        title = soup.title
        return title.string if title else "No title"
    
    def extract_links(self, html, base_url):
        """Extrait les liens pour crawling récursif"""
        soup = BeautifulSoup(html, 'html.parser')
        for link in soup.find_all('a', href=True):
            href = link['href']
            if href.startswith('/'):
                full_url = urljoin(base_url, href)
                # Ajouter à la wordlist pour vérification
                # (implémentation avancée)
    
    def run(self):
        """Exécute le scan"""
        with open(self.wordlist, 'r') as f:
            paths = [line.strip() for line in f if line.strip()]
        
        with ThreadPoolExecutor(max_workers=10) as executor:
            for path in paths:
                executor.submit(self.check_directory, path)
        
        print(f"\n[*] Scan terminé. {len(self.found)} répertoires trouvés.")
        return self.found

# Utilisation
if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("-u", "--url", required=True)
    parser.add_argument("-w", "--wordlist", required=True)
    args = parser.parse_args()
    
    buster = DirectoryBuster(args.url, args.wordlist)
    results = buster.run()

Étape 3 : API Discovery Tool

🎯 Défi Expert

Créez api_discover.py qui :

  1. Teste les endpoints API communs (/api/v1/, /graphql, /rest/)
  2. Identifie les méthodes HTTP supportées (GET, POST, PUT, DELETE)
  3. Détecte les versions d'API via headers ou réponse
  4. Teste les paramètres communs avec fuzzing basique

Exercice Avancé : Intégration avec le Projet

🔗 Création d'un Framework Modulaire

Structurez vos scripts en framework réutilisable :

mon_scanner/
├── __init__.py
├── scanner.py          # Classe mère
├── modules/
│   ├── subdomain.py
│   ├── directory.py
│   ├── api.py
│   └── portscan.py
├── utils/
│   ├── helpers.py
│   ├── loggers.py
│   └── exporters.py
└── config/
    └── settings.yaml

Créez un point d'entrée unifié : python3 mon_scanner.py --target example.com --module all

Bonnes Pratiques de Développement

⚠️ Sécurité et Éthique

  • Rate limiting : time.sleep(0.1) entre requêtes
  • User-Agent rotation : Liste d'agents réalistes
  • Scope stricte : Ne pas scanner hors de la cible autorisée
  • Logs discrets : Pas d'informations sensibles en clair

📋 Objectif de la semaine – Checklist de validation

✅ Compétences techniques acquises

  • Script subdomain enumeration avec threading
  • Directory buster avec BeautifulSoup
  • Gestion avancée des exceptions et timeouts
  • Sauvegarde résultats JSON/CSV
  • Arguments CLI avec argparse

🧠 Changement mental accompli

  • Vision des outils comme extensions de sa volonté
  • Compréhension des compromis performance/discrétion
  • Habitude de structurer le code pour réutilisation
  • Conscience des limites éthiques du scanning automatique

💪 Pour aller plus loin :

Intégrez votre subdomain scanner avec des sources externes (VirusTotal, Censys, Shodan) via leurs APIs. Créez un dashboard Flask pour visualiser les résultats.

📚 Ressources pour Approfondir

Livres et Documentation :

  • Black Hat Python : Justin Seitz (Python pour pentesters)
  • Violent Python : TJ O'Connor
  • Real Python : Tutoriels avancés gratuits
  • Python Security : OWASP Python Security Project

⚠️ Aspects Légaux du Scanning Automatisé

  • robots.txt : Respecter (mais pas obligatoire légalement)
  • Terms of Service : Vérifier avant scanning
  • Rate limiting : Éviter le déni de service
  • Bug Bounty Rules : Suivre les guidelines des programmes

🔮 Frère d'armes,

Tu viens d'apprendre à forger tes propres armes.

Le scripting Python avancé n'est pas une compétence technique parmi d'autres – c'est l'art de matérialiser ta volonté en code. Un besoin naît dans ton esprit, et tes doigts le transforment en script. Une faiblesse suspectée, et tu crées l'outil pour la révéler.

Cette semaine, tu as vu la différence entre utiliser des outils et les comprendre. Entre exécuter gobuster et savoir comment il fonctionne. Entre dépendre des autres et être autonome.

Mais voici la leçon la plus importante : un script personnalisé est plus qu'un outil efficace – c'est une extension de ta pensée. Il reflète ta compréhension de la cible, ta stratégie d'attaque, ta discrétion opérationnelle.

Tu n'es plus un simple utilisateur d'outils open-source. Tu es devenu un forgeron numérique. Tu sais prendre une idée – "trouver tous les sous-domaines" – et la transformer en machine efficace, parallélisée, robuste.

Mais pour l'instant, forge. Améliore tes scripts. Ajoute des fonctionnalités, optimise les performances, perfectionne la discrétion. Un bon script est comme une bonne lame : elle s'affûte avec l'usage.

Un Gardien qui maîtrise le scripting ne dépend jamais des outils des autres pour faire son travail.

— Platon-Y