Créé par Guido van Rossum (première version en 1991), son nom est un clin d'œil aux Monty Python.
Python 2 ou Python 3 ?
Python 2 n'est plus maintenu depuis le 1er janvier 2020. Pourtant, beaucoup de vieux scripts sont encore écrits en Python 2. Il faut savoir les reconnaître et les adapter.
print "pwned" # Python 2
print("pwned") # Python 3
Installez Python 3.12 au minimum, idéalement la dernière version stable.
py --versionpython ouvre le Microsoft Store : Paramètres → Applications → Alias d'exécution d'applications → désactivez python.exebrew install pythonpython3 --versionpython3 fourni avec les outils Xcode est trop ancien (3.9). Vérifiez toujours la versionsudo apt install python3 python3-venv python3-pippython3 --versionvenv est un paquet séparéMode interactif : pour tester une ligne
$ python3 (py sous Windows)
>>> 2 ** 10
1024
>>> exit()
Script : un fichier .py, exécuté de haut en bas
$ python3 scan.py # macOS / Linux
> py scan.py # Windows
Ctrl+Maj+P → Python: Select Interpreter → choisissez celui du dossier .venv. Exécutez avec le bouton ▶ ou dans le terminal intégré.
.venv propre au projet, avec son Python et ses paquetsCôté cyber
Chaque outil de sécurité s'installe dans son propre venv. Il n'y a pas de conflit entre outils et vous savez exactement ce qui est installé sur votre machine.
Attention au typosquatting
Des paquets malveillants imitent des noms connus à une lettre près (reqeusts au lieu de requests). Vérifiez toujours le nom exact sur PyPI avant d'installer.
cd mon-projet
py -m venv .venv
.venv\Scripts\Activate.ps1
cd mon-projet
python3 -m venv .venv
source .venv/bin/activate
python -m venv .venv crée le venv, une seule fois par projetactivate active le venv dans le terminal ouvert. Les commandes python et pip utilisent désormais le Python et les paquets du venv plutôt que ceux de l'ordinateur(.venv). C'est le signe que le venv est actifSet-ExecutionPolicy -Scope CurrentUser RemoteSigned
pip install requests # installer un paquet
pip list # paquets installés
pip freeze > requirements.txt
pip install -r requirements.txt
deactivate # désactiver le venv
.venv/
__pycache__/
pip install, sinon le paquet s'installe sur tout l'ordinateur. L'activation vaut pour un terminal. À chaque nouveau terminal, on réactive. deactivate revient au Python de l'ordinateurrequirements.txt liste les paquets et leurs versions. On le versionne dans Git.venv se recrée en une commande et __pycache__ contient le code déjà compilé que Python garde pour démarrer plus vite. On ne versionne ni l'un ni l'autreUne variable est une boîte étiquetée. Le nom est l'étiquette, la valeur est le contenu. On la crée avec l'opérateur d'affectation =.
username = "Chris"
failed_attempts = 0
MAX_ATTEMPTS = 3 # constante : majuscules
failed_attempts = failed_attempts + 1
print(username, failed_attempts) # Chris 1
Règles de nommage (PEP 8)
failed_attemptsfailed_attempts plutôt que fauser et User sont deux variables différentesMAX_ATTEMPTSLa PEP 8 (Python Enhancement Proposal n° 8) est le guide officiel de style du code Python : nommage, indentation, longueur des lignes…
port = 443 # int
cvss_score = 9.8 # float
hostname = "srv-web-01" # str
is_admin = False # bool
last_login = None # aucune valeur
print(type(port)) # <class 'int'>
print(type(cvss_score)) # <class 'float'>
print(type(last_login)) # <class 'NoneType'>
| Type | Contenu |
|---|---|
int | entier |
float | nombre à virgule (écrit avec un point) |
str | chaîne de caractères, entre guillemets |
bool | True ou False |
None | absence de valeur |
Typage dynamique : le type est porté par la valeur et déterminé à l'exécution, on ne le déclare pas ; type() permet de le vérifier.
Le score CVSS note la gravité d'une vulnérabilité de 0 à 10.
port = input("Port : ") # on tape 8080
print(type(port)) # <class 'str'>
port = int(port) # conversion en int
print(port + 1) # 8081
score = float("7.5") # 7.5
label = str(443) # "443"
print("Port " + label) # Port 443
input() renvoie toujours une chaîne de caractères, même si l'utilisateur tape un nombre. Pour calculer, il faut convertir avec int() ou float().
print() affiche une ou plusieurs valeurs, séparées par des espaces+ colle deux chaînes de caractères bout à bout. Les deux côtés doivent être des chaînes. D'où le str(443)| 1 | 9 | 2 | . | 1 | 6 | 8 | . | 1 | . | 4 | 2 |
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| -12 | -11 | -10 | -9 | -8 | -7 | -6 | -5 | -4 | -3 | -2 | -1 |
ip = "192.168.1.42"
print(ip[0]) # 1
print(ip[-1]) # 2
print(ip[0:7]) # 192.168
print(ip[8:]) # 1.42
print(len(ip)) # 12
s[début:fin] : la fin est exclue ; début omis = depuis le début, fin omise = jusqu'au boutip[0] = "2" provoque une erreur
line = " Failed password for root from 10.0.0.5 "
line = line.strip() # retire les espaces
words = line.split(" ") # liste de mots
user, ip = words[3], words[-1]
print(line.startswith("Failed")) # True
print(user.upper()) # ROOT
print(f"Échec pour {user} depuis {ip}")
print(f"CVSS : {9.8123:.1f}") # CVSS : 9.8
| Méthode | Rôle |
|---|---|
strip() | retire les espaces autour |
split(sep) | découpe en liste |
lower() | met en minuscules |
startswith(x) | commence par x ? |
replace(a,b) | remplace a par b |
x in s | s contient x ? |
La f-string (f"…{variable}…") est une concaténation moderne. Au lieu de coller des morceaux avec +,
on insère les valeurs directement dans le texte. "Échec pour " + user devient f"Échec pour {user}", sans conversion avec str().
user = "Admin"
ip = "10.0.0.5"
attempts = "3"
print(user.lower() + "@" + ip[-1])
print(attempts * 2)
print(int(attempts) * 2)
admin@5 : + concatène les chaînes, ip[-1] est le dernier caractère33 : attempts est une chaîne, * 2 la répète6 : après conversion en entier, * 2 multiplie
print(17 + 5, 17 - 5, 17 * 5) # 22 12 85
print(17 / 5) # 3.4 division
print(17 // 5) # 3 division entière
print(17 % 5) # 2 reste (modulo)
print(2 ** 10) # 1024 puissance
port = 8080
print(port == 443, port != 443, port > 1024)
# False True True
| Comparaison | Signification |
|---|---|
== / != | égal / différent |
< / <= | inférieur / ou égal |
> / >= | supérieur / ou égal |
% (modulo) donne le reste d'une division. Il permet de « faire le tour » d'un cycle (les heures d'une horloge, les lettres d'un alphabet). Il est très utilisé en cryptographie.
Ne pas confondre : = affecte une valeur, == compare deux valeurs. L'opérateur === de JavaScript ou de PHP n'existe pas en Python.
if, elif, else
role = "analyst"
failed_attempts = 1
if failed_attempts >= 3:
print("Compte verrouillé")
elif role == "admin":
print("Accès total")
elif role == "analyst":
print("Accès en lecture") # ← exécuté
else:
print("Accès refusé")
elif et else sont facultatifs: ouvrent un bloc ; l'indentation (4 espaces) délimite ce blocand, or, not, in
password_ok = True
mfa_ok = False
blocked_ips = ["10.0.0.5", "203.0.113.7"]
ip = "10.0.0.5"
if password_ok and mfa_ok:
print("Connexion autorisée")
if not mfa_ok:
print("Code MFA manquant") # ← exécuté
if ip in blocked_ips or ip.startswith("192.0.2."):
print(f"{ip} est bloquée") # ← exécuté
| Opérateur | Vrai si… |
|---|---|
a and b | les deux sont vrais |
a or b | au moins un est vrai |
not a | a est faux |
x in s | x appartient à s (liste, chaîne…) |
match / case
command = "scan"
match command:
case "scan":
print("Lancement du scan…") # ← exécuté
case "report" | "export":
print("Génération du rapport")
case "quit":
print("Au revoir")
case _:
print("Commande inconnue")
| signifie « ou »_ est le cas par défautswitch / case de nombreux langages (C, Java, JavaScript…)
open_ports = [443, 22, 80]
print(open_ports[0]) # 443
open_ports.append(3306) # ajoute à la fin
open_ports.remove(80) # retire la valeur 80
open_ports.sort() # trie sur place
print(open_ports) # [22, 443, 3306]
print(len(open_ports)) # 3
print(22 in open_ports) # True
print(open_ports[-2:]) # [443, 3306]
[]liste[-2:] part du 2e élément en partant de la fin et va jusqu'au bout. logs[-10:] donne ainsi les 10 dernières lignes d'un journalremove() retire la première occurrence trouvée
# Tuple : non modifiable
target = ("10.0.0.5", 22)
ip, port = target # déballage
print(f"{ip}:{port}") # 10.0.0.5:22
# Ensemble : valeurs uniques, sans ordre
ips = ["10.0.0.5", "10.0.0.9", "10.0.0.5"]
unique_ips = set(ips)
print(len(unique_ips)) # 2
unique_ips.add("10.0.0.12")
print("10.0.0.9" in unique_ips) # True
( )set
user = {"username": "Chris", "role": "analyst",
"mfa": True}
print(user["role"]) # analyst
user["role"] = "admin" # modification
user["last_ip"] = "10.0.0.5" # ajout d'une clé
print(user.get("email")) # None
print(user.get("email", "?")) # ?
print("mfa" in user) # True
print(list(user.keys()))
# ['username', 'role', 'mfa', 'last_ip']
{}user["email"] provoque une erreur KeyError si la clé n'existe pas ; user.get("email") renvoie None
accounts = [
{"username": "Chris", "role": "admin"},
{"username": "Manon", "role": "analyst"},
]
new_account = {"username": "Cosmo", "role": "guest"}
accounts.append(new_account)
print(len(accounts)) # 3
print(accounts[1]["username"]) # Manon
| Ordre | Modifiable | Doublons | |
|---|---|---|---|
list | oui | oui | oui |
tuple | oui | non | oui |
set | non | oui | non |
dict | oui | oui | clés : non |
for
open_ports = [22, 80, 443]
for port in open_ports:
print(f"Port {port} ouvert")
for i in range(1, 4): # 1, 2, 3
print(f"Tentative {i}")
for i, port in enumerate(open_ports, 1):
print(i, port) # 1 22 / 2 80 / 3 443
user = {"name": "Chris", "role": "admin"}
for key, value in user.items():
print(f"{key} = {value}")
for parcourt chaque élément d'une séquence : liste, chaîne, tuple, ensemble, dictionnairerange(début, fin) produit une suite d'entiers, fin exclue ; enumerate() numérote les éléments ; .items() donne clé et valeurwhile, break, continue
SECRET = "S3cur3!"
attempts = 0
while True:
password = input("Mot de passe : ")
attempts += 1 # attempts = attempts + 1
if password == SECRET:
print("Accès autorisé")
break # sortie immédiate
if attempts == 3:
print("Compte verrouillé")
break
while cond: répète tant que la condition est vraiewhile True: sans fin, sauf breakcontinue passe au tour suivant
for port in [21, 22, 80]:
if port == 21:
continue # saute FTP
print(port) # 22 puis 80
log = [
"Failed password for root from 10.0.0.5",
"Accepted password for Chris from 10.0.0.9",
"Failed password for admin from 10.0.0.5",
]
failures = {}
for line in log:
if line.startswith("Failed"):
ip = line.split(" ")[-1]
failures[ip] = failures.get(ip, 0) + 1
print(failures) # {'10.0.0.5': 2}
failures.get(ip, 0) renvoie le compteur actuel ou 0 si l'IP est nouvelle
ports = [21, 22, 80, 443, 8080]
count = 0
for port in ports:
if port < 80:
continue
if port == 443:
break
count += 1
print(count)
total = 0
for i in range(3):
total += i
print(total)
1 : 21 et 22 sont sautés par continue, 80 est compté, puis la boucle s'arrête sur 443. Le port 8080 n'est jamais atteint3 : range(3) produit 0, 1 et 2, donc 0 + 1 + 2 = 3
def is_valid_port(port):
"""Vrai si port est un port TCP valide."""
return 0 <= port <= 65535
def format_alert(ip, level="INFO"):
return f"[{level}] Activité suspecte : {ip}"
print(is_valid_port(8080)) # True
print(is_valid_port(70000)) # False
print(format_alert("10.0.0.5")) # [INFO] …
print(format_alert("10.0.0.5", level="HIGH"))
# [HIGH] Activité suspecte : 10.0.0.5
def nom(paramètres): définit un bloc réutilisablereturn renvoie un résultat et termine la fonction. Il n'est pas obligatoire. Sans return, la fonction renvoie None (on parle alors parfois de procédure)"""…""") documente la fonctionlevel="INFO" : valeur par défaut, utilisée si l'argument est omisis_valid_port(port) sert pour n'importe quel port.
check_password, count_failures), comme pour les variables et les fichiers.
En anglais, en snake_case et explicite : on doit comprendre ce que fait la fonction sans lire son code.
def parse_login(line):
words = line.split(" ") # locale
return words[3], words[-1] # un tuple
log = "Failed password for root from 10.0.0.5"
user, ip = parse_login(log)
print(user, ip) # root 10.0.0.5
print(words) # erreur !
NameError: name 'words' is not defined
if ou une boucle ne crée pas de portée. Une variable créée dans un bloc reste accessible après lui. Seules les fonctions et les classes en créent une
"""Utilitaires de sécurité."""
def is_valid_port(port):
return 0 <= port <= 65535
if __name__ == "__main__":
# si on exécute ce fichier directement
print(is_valid_port(443))
import security
from security import is_valid_port
print(security.is_valid_port(22)) # True
print(is_valid_port(-1)) # False
.py est un module importable depuis un fichier du même dossierif __name__ == "__main__": contient le code exécuté seulement quand on lance le fichier directement, pas quand on l'importe| Module | Usage en cybersécurité |
|---|---|
os, pathlib | parcourir des dossiers, manipuler des chemins |
sys, argparse | lire les arguments d'un outil en ligne de commande |
re | rechercher des motifs dans des journaux (expressions régulières) |
datetime | horodater, comparer des dates d'événements |
secrets | générer mots de passe et jetons |
hashlib, json | calculer des empreintes (hash), sauvegarder des données |
socket | communiquer sur le réseau |
Référence complète : la bibliothèque standard de Python.
random ou secrets ?Deux modules de la bibliothèque standard produisent du hasard… mais un seul convient à la sécurité.
✗ random : prévisible
import random
random.seed(2026) # même graine…
a = random.randint(0, 999999)
random.seed(2026)
b = random.randint(0, 999999)
print(a, b) # 124906 124906
print(a == b) # True
✓ secrets : conçu pour la sécurité
import secrets
import string
token = secrets.token_hex(16) # jeton
print(token) # 32 caractères au hasard
c = secrets.choice(string.ascii_letters)
print(c) # une lettre au hasard
random est pseudo-aléatoire. Qui connaît son état prédit la suite. Parfait pour un jeu, jamais pour un mot de passe, un jeton ou une clé. Utilisez secrets.
Traceback (most recent call last):
File "scan.py", line 1, in <module>
port = int(input("Port : "))
ValueError: invalid literal for int() with base 10: 'https'
| Erreur | Cause fréquente |
|---|---|
SyntaxError | faute de syntaxe, : oublié |
NameError | variable inconnue |
TypeError | "3" + 1 |
ValueError | int("https") |
IndexError | indice inexistant |
KeyError | clé absente |
try, except, else, raise
def ask_port():
while True:
try:
port = int(input("Port : "))
except ValueError:
print("Entrez un nombre entier.")
else:
if 0 <= port <= 65535:
return port
raise ValueError("port hors limites")
print(f"Scan du port {ask_port()}")
try : le code qui peut échouerexcept ValueError : ce qu'on fait si cette erreur précise survient ; le programme ne plante plus. C'est l'équivalent du catch d'autres langages (Java, JavaScript, PHP)else : exécuté seulement si aucune erreur n'a eu lieuraise : lever soi-même une erreur quand une valeur est inacceptable. C'est l'équivalent du throw d'autres langages✗ À éviter
try:
port = int(user_input)
except:
pass # erreur effacée sans trace
✓ À préférer
try:
port = int(user_input)
except ValueError:
print("Port invalide.") # sobre
except: pass masque les anomalies, y compris une tentative d'attaque. On attrape toujours une erreur précise| Horaire | Programme |
|---|---|
| 14h00 – 15h15 | Défis 1 à 3 : politique de mots de passe · détection de brute force dans un journal · générateur de mots de passe avec secrets |
| 15h15 – 16h45 | TP1 – Fondation : gestion fonctionnelle de l'équipage |
| 16h45 – 17h00 | Kahoot : 10 questions sur la journée |