🎯 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.
📚 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
- Silence : Logs minimaux, erreurs gérées, timeouts configurables
- Robustesse : Gestion complète des exceptions, retry logic
- Modularité : Code réutilisable, configuration externe
- Performance : Parallélisation intelligente, pas de DoS accidentel
- 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
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 :
- Support HTTP et HTTPS automatique
- Détection des redirections (301, 302)
- Extraction des liens depuis les pages trouvées (crawling récursif)
- Filtrage par taille de réponse et type de contenu
- 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 :
- Teste les endpoints API communs (
/api/v1/,/graphql,/rest/) - Identifie les méthodes HTTP supportées (GET, POST, PUT, DELETE)
- Détecte les versions d'API via headers ou réponse
- 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