B3.4 - Sécurité des Applications Web
Protection des données et sécurisation des services - BTS SIO
🎯 Objectifs du cours
À l'issue de ce cours, vous serez capable de : - Comprendre les protocoles de sécurisation web (HTTPS, TLS) - Implémenter des mécanismes d'authentification et d'autorisation - Protéger les données personnelles selon le RGPD - Sécuriser les communications et les sessions utilisateur
🔒 Protocoles de sécurisation
HTTPS (HyperText Transfer Protocol Secure)
Version sécurisée du protocole HTTP utilisant TLS/SSL pour chiffrer les communications entre le client et le serveur, garantissant confidentialité, intégrité et authentification.
Comparaison HTTP vs HTTPS
HTTP
- Port 80
- Données en clair
- Vulnérable aux écoutes
- Pas d'authentification du serveur
- Risque de modification des données
HTTPS
- Port 443
- Données chiffrées
- Protection contre l'écoute
- Certificat serveur
- Intégrité des données garantie
Processus de connexion TLS
1. Client Hello
Le client envoie sa version TLS, les algorithmes supportés et un nombre aléatoire.
2. Server Hello
Le serveur répond avec sa version TLS, l'algorithme choisi, son certificat et un nombre aléatoire.
3. Vérification du certificat
Le client vérifie la validité du certificat serveur auprès d'une autorité de certification.
4. Échange de clés
Génération d'une clé de session symétrique pour chiffrer les communications.
5. Communication sécurisée
Toutes les données sont chiffrées avec la clé de session.
Configuration HTTPS avec Python Flask
Serveur Flask avec HTTPS
from flask import Flask, request, session, redirect, url_for
import ssl
app = Flask(__name__)
app.secret_key = 'votre_cle_secrete_tres_longue_et_aleatoire'
@app.route('/')
def index():
return '''
<h1>Serveur HTTPS sécurisé</h1>
<p>Cette connexion est chiffrée !</p>
<a href="/login">Se connecter</a>
'''
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
# Vérification sécurisée (à implémenter)
if verify_credentials(username, password):
session['user'] = username
return redirect(url_for('dashboard'))
else:
return 'Identifiants incorrects', 401
return '''
<form method="post">
<input type="text" name="username" placeholder="Nom d'utilisateur" required>
<input type="password" name="password" placeholder="Mot de passe" required>
<button type="submit">Se connecter</button>
</form>
'''
@app.route('/dashboard')
def dashboard():
if 'user' not in session:
return redirect(url_for('login'))
return f'Bienvenue {session["user"]} !'
def verify_credentials(username, password):
# Implémentation sécurisée avec hachage
# Ne jamais stocker les mots de passe en clair !
return True # Exemple simplifié
if __name__ == '__main__':
# Configuration SSL/TLS
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
context.load_cert_chain('certificat.pem', 'cle_privee.pem')
app.run(host='0.0.0.0', port=443, ssl_context=context, debug=False)
🔐 Authentification et autorisation
Authentification vs Autorisation
Authentification : Vérifier l'identité de l'utilisateur ("Qui êtes-vous ?")
Autorisation : Vérifier les permissions de l'utilisateur ("Que pouvez-vous faire ?")
Autorisation : Vérifier les permissions de l'utilisateur ("Que pouvez-vous faire ?")
Méthodes d'authentification
🔑 Authentification basique
Nom d'utilisateur et mot de passe. Simple mais nécessite HTTPS.
🎫 Authentification par token
JWT (JSON Web Token) pour les API et applications modernes.
📱 Authentification multi-facteurs (MFA)
Combinaison de plusieurs facteurs : mot de passe + SMS/app.
🔗 OAuth 2.0
Délégation d'authentification via des services tiers (Google, Facebook).
Implémentation sécurisée des mots de passe
Gestion sécurisée des mots de passe
import hashlib
import secrets
import bcrypt
from werkzeug.security import generate_password_hash, check_password_hash
class UserManager:
def __init__(self):
self.users = {} # En production : base de données
def hash_password_bcrypt(self, password):
"""Hachage sécurisé avec bcrypt (recommandé)"""
salt = bcrypt.gensalt()
hashed = bcrypt.hashpw(password.encode('utf-8'), salt)
return hashed
def verify_password_bcrypt(self, password, hashed):
"""Vérification avec bcrypt"""
return bcrypt.checkpw(password.encode('utf-8'), hashed)
def hash_password_werkzeug(self, password):
"""Alternative avec Werkzeug"""
return generate_password_hash(password, method='pbkdf2:sha256', salt_length=16)
def verify_password_werkzeug(self, password, hashed):
"""Vérification avec Werkzeug"""
return check_password_hash(hashed, password)
def create_user(self, username, password, email):
"""Créer un nouvel utilisateur"""
if username in self.users:
raise ValueError("Utilisateur déjà existant")
# Validation du mot de passe
if not self.is_strong_password(password):
raise ValueError("Mot de passe trop faible")
# Hachage sécurisé
hashed_password = self.hash_password_bcrypt(password)
self.users[username] = {
'password_hash': hashed_password,
'email': email,
'created_at': datetime.now(),
'last_login': None,
'failed_attempts': 0,
'locked_until': None
}
return True
def authenticate(self, username, password):
"""Authentifier un utilisateur"""
user = self.users.get(username)
if not user:
return False
# Vérifier si le compte est verrouillé
if user['locked_until'] and datetime.now() < user['locked_until']:
raise Exception("Compte temporairement verrouillé")
# Vérifier le mot de passe
if self.verify_password_bcrypt(password, user['password_hash']):
# Réinitialiser les tentatives échouées
user['failed_attempts'] = 0
user['locked_until'] = None
user['last_login'] = datetime.now()
return True
else:
# Incrémenter les tentatives échouées
user['failed_attempts'] += 1
# Verrouiller après 5 tentatives
if user['failed_attempts'] >= 5:
user['locked_until'] = datetime.now() + timedelta(minutes=30)
return False
def is_strong_password(self, password):
"""Vérifier la force du mot de passe"""
if len(password) < 8:
return False
has_upper = any(c.isupper() for c in password)
has_lower = any(c.islower() for c in password)
has_digit = any(c.isdigit() for c in password)
has_special = any(c in "!@#$%^&*()_+-=[]{}|;:,.<>?" for c in password)
return has_upper and has_lower and has_digit and has_special
# Exemple d'utilisation
user_manager = UserManager()
try:
user_manager.create_user("alice", "MonMotDePasse123!", "alice@example.com")
print("Utilisateur créé avec succès")
if user_manager.authenticate("alice", "MonMotDePasse123!"):
print("Authentification réussie")
else:
print("Échec de l'authentification")
except ValueError as e:
print(f"Erreur : {e}")
Gestion des sessions sécurisées
Sessions sécurisées avec Flask
from flask import Flask, session, request, jsonify
import secrets
import time
app = Flask(__name__)
app.secret_key = secrets.token_hex(32) # Clé aléatoire sécurisée
# Configuration sécurisée des sessions
app.config.update(
SESSION_COOKIE_SECURE=True, # HTTPS uniquement
SESSION_COOKIE_HTTPONLY=True, # Pas d'accès JavaScript
SESSION_COOKIE_SAMESITE='Lax', # Protection CSRF
PERMANENT_SESSION_LIFETIME=1800 # 30 minutes
)
class SessionManager:
def __init__(self):
self.active_sessions = {}
def create_session(self, user_id):
"""Créer une nouvelle session"""
session_id = secrets.token_urlsafe(32)
session_data = {
'user_id': user_id,
'created_at': time.time(),
'last_activity': time.time(),
'ip_address': request.remote_addr,
'user_agent': request.headers.get('User-Agent', '')
}
self.active_sessions[session_id] = session_data
session['session_id'] = session_id
session.permanent = True
return session_id
def validate_session(self, session_id):
"""Valider une session existante"""
if session_id not in self.active_sessions:
return False
session_data = self.active_sessions[session_id]
current_time = time.time()
# Vérifier l'expiration (30 minutes d'inactivité)
if current_time - session_data['last_activity'] > 1800:
self.destroy_session(session_id)
return False
# Vérifier l'IP (optionnel, peut poser problème avec les proxies)
if session_data['ip_address'] != request.remote_addr:
# Log de sécurité
print(f"Changement d'IP détecté pour la session {session_id}")
# Mettre à jour l'activité
session_data['last_activity'] = current_time
return True
def destroy_session(self, session_id):
"""Détruire une session"""
if session_id in self.active_sessions:
del self.active_sessions[session_id]
session.clear()
def get_user_id(self, session_id):
"""Récupérer l'ID utilisateur d'une session"""
if session_id in self.active_sessions:
return self.active_sessions[session_id]['user_id']
return None
session_manager = SessionManager()
@app.before_request
def check_session():
"""Vérifier la session avant chaque requête"""
if request.endpoint in ['login', 'static']:
return # Pas de vérification pour ces endpoints
session_id = session.get('session_id')
if not session_id or not session_manager.validate_session(session_id):
return jsonify({'error': 'Session invalide'}), 401
@app.route('/logout')
def logout():
"""Déconnexion sécurisée"""
session_id = session.get('session_id')
if session_id:
session_manager.destroy_session(session_id)
return jsonify({'message': 'Déconnexion réussie'})
🛡️ Protection contre les vulnérabilités web
Top 10 OWASP des vulnérabilités
Rang | Vulnérabilité | Description | Protection |
---|---|---|---|
1 | Injection | SQL, NoSQL, LDAP injection | Requêtes préparées, validation |
2 | Authentification cassée | Sessions, mots de passe faibles | MFA, sessions sécurisées |
3 | Exposition de données | Données sensibles non protégées | Chiffrement, HTTPS |
4 | XXE | XML External Entity | Désactiver les entités externes |
5 | Contrôle d'accès cassé | Autorisations mal configurées | Principe du moindre privilège |
6 | Configuration de sécurité | Paramètres par défaut non sécurisés | Durcissement, mise à jour |
7 | XSS | Cross-Site Scripting | Échappement, CSP |
8 | Désérialisation | Objets non fiables | Validation, signature |
9 | Composants vulnérables | Bibliothèques obsolètes | Mise à jour régulière |
10 | Logging insuffisant | Détection d'incidents limitée | Logs détaillés, monitoring |
Protection contre les injections SQL
Prévention des injections SQL
import sqlite3
from flask import Flask, request, jsonify
app = Flask(__name__)
class DatabaseManager:
def __init__(self, db_path):
self.db_path = db_path
self.init_database()
def init_database(self):
"""Initialiser la base de données"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
username TEXT UNIQUE NOT NULL,
email TEXT NOT NULL,
password_hash TEXT NOT NULL,
role TEXT DEFAULT 'user'
)
''')
conn.commit()
conn.close()
def get_user_vulnerable(self, username):
"""VULNÉRABLE - Ne jamais faire cela !"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# DANGER : Injection SQL possible
query = f"SELECT * FROM users WHERE username = '{username}'"
cursor.execute(query)
result = cursor.fetchone()
conn.close()
return result
def get_user_secure(self, username):
"""SÉCURISÉ - Utilisation de requêtes préparées"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# Sécurisé : paramètres liés
cursor.execute("SELECT * FROM users WHERE username = ?", (username,))
result = cursor.fetchone()
conn.close()
return result
def search_users_secure(self, search_term, role=None):
"""Recherche sécurisée avec paramètres multiples"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
query = "SELECT id, username, email, role FROM users WHERE username LIKE ?"
params = [f"%{search_term}%"]
if role:
query += " AND role = ?"
params.append(role)
cursor.execute(query, params)
results = cursor.fetchall()
conn.close()
return results
db = DatabaseManager('app.db')
@app.route('/user/<username>')
def get_user(username):
"""Endpoint sécurisé pour récupérer un utilisateur"""
# Validation d'entrée
if not username or len(username) > 50:
return jsonify({'error': 'Nom d'utilisateur invalide'}), 400
# Utilisation de la méthode sécurisée
user = db.get_user_secure(username)
if user:
return jsonify({
'id': user[0],
'username': user[1],
'email': user[2],
'role': user[4]
})
else:
return jsonify({'error': 'Utilisateur non trouvé'}), 404
@app.route('/search')
def search_users():
"""Recherche d'utilisateurs avec validation"""
search_term = request.args.get('q', '').strip()
role = request.args.get('role', '').strip()
# Validation des paramètres
if not search_term or len(search_term) < 2:
return jsonify({'error': 'Terme de recherche trop court'}), 400
if role and role not in ['user', 'admin', 'moderator']:
return jsonify({'error': 'Rôle invalide'}), 400
results = db.search_users_secure(search_term, role)
return jsonify({
'users': [
{
'id': user[0],
'username': user[1],
'email': user[2],
'role': user[3]
}
for user in results
]
})
Protection XSS et CSP
Protection contre XSS
from flask import Flask, render_template_string, request, escape
from markupsafe import Markup
import html
import re
app = Flask(__name__)
class XSSProtection:
@staticmethod
def sanitize_input(user_input):
"""Nettoyer les entrées utilisateur"""
if not user_input:
return ""
# Échapper les caractères HTML
sanitized = html.escape(user_input)
# Supprimer les scripts potentiels
sanitized = re.sub(r'<script[^>]*>.*?</script>', '', sanitized, flags=re.IGNORECASE | re.DOTALL)
return sanitized
@staticmethod
def validate_url(url):
"""Valider une URL pour éviter les redirections malveillantes"""
if not url:
return False
# Autoriser seulement les URLs relatives ou HTTPS
if url.startswith('/') or url.startswith('https://'):
return True
return False
@app.route('/comment', methods=['POST'])
def add_comment():
"""Ajouter un commentaire avec protection XSS"""
comment = request.form.get('comment', '')
# Nettoyer l'entrée
clean_comment = XSSProtection.sanitize_input(comment)
# Validation supplémentaire
if len(clean_comment) > 1000:
return "Commentaire trop long", 400
# Sauvegarder en base (exemple)
# db.save_comment(clean_comment)
return f"Commentaire ajouté : {clean_comment}"
@app.after_request
def add_security_headers(response):
"""Ajouter des en-têtes de sécurité"""
# Content Security Policy
response.headers['Content-Security-Policy'] = (
"default-src 'self'; "
"script-src 'self' 'unsafe-inline'; "
"style-src 'self' 'unsafe-inline'; "
"img-src 'self' data: https:; "
"font-src 'self'; "
"connect-src 'self'; "
"frame-ancestors 'none';"
)
# Autres en-têtes de sécurité
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'DENY'
response.headers['X-XSS-Protection'] = '1; mode=block'
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
return response
🔐 Protection des données personnelles
RGPD (Règlement Général sur la Protection des Données)
Règlement européen entré en vigueur en 2018, qui encadre le traitement des données personnelles et renforce les droits des individus sur leurs données.
Principes fondamentaux du RGPD
🎯 Finalité
Les données doivent être collectées pour des finalités déterminées, explicites et légitimes.
📏 Minimisation
Collecter uniquement les données nécessaires à la finalité poursuivie.
⏰ Conservation limitée
Les données ne doivent pas être conservées plus longtemps que nécessaire.
🔒 Sécurité
Mettre en place des mesures techniques et organisationnelles appropriées.
Droits des personnes concernées
Droit à l'information
Être informé de la collecte et du traitement de ses données personnelles.
Droit d'accès
Obtenir une copie de ses données personnelles et des informations sur leur traitement.
Droit de rectification
Demander la correction de données inexactes ou incomplètes.
Droit à l'effacement
Demander la suppression de ses données dans certaines conditions.
Droit à la portabilité
Récupérer ses données dans un format structuré et les transférer.
Droit d'opposition
S'opposer au traitement de ses données pour des raisons légitimes.
Implémentation RGPD en Python
Gestion RGPD avec Python
from datetime import datetime, timedelta
import json
import hashlib
class GDPRManager:
def __init__(self):
self.data_retention_policies = {
'user_logs': timedelta(days=365),
'marketing_data': timedelta(days=1095), # 3 ans
'financial_data': timedelta(days=2555), # 7 ans
'session_data': timedelta(hours=24)
}
self.consent_records = {}
self.data_processing_log = []
def record_consent(self, user_id, purpose, consent_given, ip_address):
"""Enregistrer le consentement utilisateur"""
consent_record = {
'user_id': user_id,
'purpose': purpose,
'consent_given': consent_given,
'timestamp': datetime.now().isoformat(),
'ip_address': self.hash_ip(ip_address),
'version': '1.0' # Version de la politique de confidentialité
}
if user_id not in self.consent_records:
self.consent_records[user_id] = []
self.consent_records[user_id].append(consent_record)
# Log de l'activité
self.log_data_processing(user_id, 'consent_recorded', purpose)
def hash_ip(self, ip_address):
"""Hasher l'adresse IP pour la pseudonymisation"""
return hashlib.sha256(ip_address.encode()).hexdigest()[:16]
def log_data_processing(self, user_id, action, purpose, data_type=None):
"""Logger les activités de traitement de données"""
log_entry = {
'user_id': user_id,
'action': action,
'purpose': purpose,
'data_type': data_type,
'timestamp': datetime.now().isoformat(),
'processor': 'system'
}
self.data_processing_log.append(log_entry)
def export_user_data(self, user_id):
"""Exporter toutes les données d'un utilisateur (portabilité)"""
user_data = {
'user_id': user_id,
'export_date': datetime.now().isoformat(),
'consent_records': self.consent_records.get(user_id, []),
'processing_log': [
log for log in self.data_processing_log
if log['user_id'] == user_id
]
}
# Ajouter d'autres données utilisateur depuis la base
# user_data.update(self.get_user_profile(user_id))
# user_data.update(self.get_user_orders(user_id))
self.log_data_processing(user_id, 'data_exported', 'user_request')
return json.dumps(user_data, indent=2, ensure_ascii=False)
def delete_user_data(self, user_id, reason='user_request'):
"""Supprimer les données d'un utilisateur (droit à l'effacement)"""
# Vérifier si la suppression est autorisée
if not self.can_delete_user_data(user_id, reason):
raise Exception("Suppression non autorisée pour des raisons légales")
# Supprimer les données personnelles
# self.delete_user_profile(user_id)
# self.anonymize_user_orders(user_id)
# Conserver les logs de consentement (obligation légale)
self.log_data_processing(user_id, 'data_deleted', reason)
return True
def can_delete_user_data(self, user_id, reason):
"""Vérifier si les données peuvent être supprimées"""
# Vérifier les obligations légales de conservation
# Par exemple, données financières à conserver 7 ans
# Vérifier s'il y a des litiges en cours
# if self.has_pending_legal_case(user_id):
# return False
return True
def anonymize_expired_data(self):
"""Anonymiser les données expirées selon les politiques de rétention"""
current_time = datetime.now()
for data_type, retention_period in self.data_retention_policies.items():
expiry_date = current_time - retention_period
# Identifier et anonymiser les données expirées
# self.anonymize_data_by_type(data_type, expiry_date)
print(f"Données {data_type} antérieures à {expiry_date} anonymisées")
def generate_privacy_report(self, user_id):
"""Générer un rapport de confidentialité pour un utilisateur"""
consent_history = self.consent_records.get(user_id, [])
processing_activities = [
log for log in self.data_processing_log
if log['user_id'] == user_id
]
report = {
'user_id': user_id,
'report_date': datetime.now().isoformat(),
'active_consents': [
consent for consent in consent_history
if consent['consent_given']
],
'data_processing_summary': {
'total_activities': len(processing_activities),
'last_activity': max(
(log['timestamp'] for log in processing_activities),
default='Aucune activité'
)
},
'retention_status': self.get_retention_status(user_id)
}
return report
def get_retention_status(self, user_id):
"""Obtenir le statut de rétention des données"""
# Calculer quand les différents types de données expireront
current_time = datetime.now()
status = {}
for data_type, retention_period in self.data_retention_policies.items():
# En pratique, récupérer la date de création des données
# creation_date = self.get_data_creation_date(user_id, data_type)
# expiry_date = creation_date + retention_period
status[data_type] = {
'retention_period_days': retention_period.days,
'status': 'active' # ou 'expired', 'anonymized'
}
return status
# Exemple d'utilisation
gdpr_manager = GDPRManager()
# Enregistrer un consentement
gdpr_manager.record_consent(
user_id='user123',
purpose='marketing',
consent_given=True,
ip_address='192.168.1.1'
)
# Exporter les données d'un utilisateur
user_data_export = gdpr_manager.export_user_data('user123')
print("Données exportées :", user_data_export)
# Générer un rapport de confidentialité
privacy_report = gdpr_manager.generate_privacy_report('user123')
print("Rapport de confidentialité :", privacy_report)
🔍 Monitoring et détection d'incidents
### Mise en place de logs de sécuritéSystème de logging sécurisé
import logging
import json
from datetime import datetime
from flask import request, g
import hashlib
class SecurityLogger:
def __init__(self, log_file='security.log'):
self.logger = logging.getLogger('security')
self.logger.setLevel(logging.INFO)
# Handler pour fichier
file_handler = logging.FileHandler(log_file)
file_handler.setLevel(logging.INFO)
# Format JSON pour faciliter l'analyse
formatter = logging.Formatter('%(message)s')
file_handler.setFormatter(formatter)
self.logger.addHandler(file_handler)
def log_security_event(self, event_type, user_id=None, details=None, severity='INFO'):
"""Logger un événement de sécurité"""
event = {
'timestamp': datetime.now().isoformat(),
'event_type': event_type,
'severity': severity,
'user_id': user_id,
'ip_address': self.get_client_ip(),
'user_agent': request.headers.get('User-Agent', ''),
'endpoint': request.endpoint,
'method': request.method,
'details': details or {}
}
self.logger.info(json.dumps(event))
def get_client_ip(self):
"""Obtenir l'IP du client en tenant compte des proxies"""
if request.headers.get('X-Forwarded-For'):
return request.headers.get('X-Forwarded-For').split(',')[0].strip()
elif request.headers.get('X-Real-IP'):
return request.headers.get('X-Real-IP')
else:
return request.remote_addr
def log_login_attempt(self, username, success, reason=None):
"""Logger une tentative de connexion"""
self.log_security_event(
event_type='login_attempt',
user_id=username,
details={
'success': success,
'reason': reason
},
severity='WARNING' if not success else 'INFO'
)
def log_permission_denied(self, user_id, resource, action):
"""Logger un accès refusé"""
self.log_security_event(
event_type='permission_denied',
user_id=user_id,
details={
'resource': resource,
'action': action
},
severity='WARNING'
)
def log_suspicious_activity(self, user_id, activity_type, details):
"""Logger une activité suspecte"""
self.log_security_event(
event_type='suspicious_activity',
user_id=user_id,
details={
'activity_type': activity_type,
'details': details
},
severity='CRITICAL'
)
# Intégration avec Flask
security_logger = SecurityLogger()
@app.before_request
def log_request():
"""Logger les requêtes sensibles"""
sensitive_endpoints = ['login', 'admin', 'api']
if any(endpoint in request.endpoint for endpoint in sensitive_endpoints):
security_logger.log_security_event(
event_type='sensitive_endpoint_access',
details={'endpoint': request.endpoint}
)
@app.after_request
def log_response(response):
"""Logger les réponses d'erreur"""
if response.status_code >= 400:
security_logger.log_security_event(
event_type='error_response',
details={
'status_code': response.status_code,
'endpoint': request.endpoint
},
severity='WARNING' if response.status_code < 500 else 'ERROR'
)
return response
📚 Pour aller plus loin
🔗 Ressources complémentaires :
- OWASP Top 10 : Guide des vulnérabilités web les plus critiques
- Let's Encrypt : Certificats SSL/TLS gratuits
- CNIL : Guide RGPD et protection des données
- Mozilla Observatory : Test de sécurité des sites web
- Burp Suite : Outil de test de sécurité des applications web
✅ Points clés à retenir
- HTTPS : Obligatoire pour protéger les communications
- Authentification forte : MFA, sessions sécurisées, mots de passe robustes
- Protection XSS/CSRF : Validation, échappement, CSP
- RGPD : Consentement, droits des utilisateurs, minimisation des données
- Monitoring : Logs de sécurité, détection d'incidents
🎓 Prochaine étape : Dans le cours B3.5, nous découvrirons les outils et méthodologies de sécurité (OWASP, Burp Suite, tests de pénétration).