Aller au contenu

5. Questions de CyberSécurité⚓︎

Laravel est un framework très utilisé et donc sensible à une attaque Cyber. Il faut toujours rester en veille sur le framework et apporter les correctifs nécessaires dès que possible.

Ressources Documentaires :

1. Validation des entrées⚓︎

Toujours s’assurer que toutes les données saisies par l’utilisateur sont validées et assainies constitue la première ligne de défense contre de nombreuses attaques courantes, comme l’injection SQL ou le cross-site scripting (XSS). Laravel fournit de puissantes règles de validation que tu peux utiliser pour garantir l’intégrité des données.

🐘 PHP
$request->validate([
    'username' => 'required|string|max:255',
    'email' => 'required|email|unique:users,email',
    'password' => 'required|min:8|confirmed',
]);
  • Required → champ obligatoire
  • string → doit être une chaîne de caractères
  • max:255 → longueur maximale 255 caractères
  • email → doit être une adresse e-mail valide
  • unique:users,email → l’e-mail ne doit pas déjà exister dans la table users
  • min:8 → minimum 8 caractères
  • confirmed → doit correspondre au champ password_confirmation

Exemple déjà mis en place dans NewPasswordController.php :

🐘 PHP
public function store(Request $request): RedirectResponse
    {
        $request->validate([
            'token' => ['required'],
            'email' => ['required', 'email'],
            'password' => ['required', 'confirmed', Rules\Password::defaults()],
        ]);

A faire

Ajouter une règle pour vérifier que le todo est une chaine obligatoire de caractères de moins de 256 car.

🐘 PHP
public function saveTodo(Request $request)
    {
        // Vérification des entrées du formulaire
        $request->validate([
            'texte' => 'required|string|max:256',
        ]);
        ....
    }

2. Prevention contre SQL Injection⚓︎

L'utilisation de l'ORM Eloquent aide à prévenir les attaques par injection SQL. Utilisez toujours des requêtes paramétrées et évitez de concaténer l'entrée de l'utilisateur dans les instructions SQL.

🐘 PHP
// Safe query using Eloquent
$user = User::where('email', $email)->first();

3. Prévention des attaques XSS⚓︎

Pour éviter les attaques XSS, toujours échapper à l'entrée de l'utilisateur avant de la rendre dans la vue. Le moteur de modèle Blade de Laravel échappe automatiquement à la sortie des données en utilisant le {{ }} syntaxe.

🐘 PHP
<!-- Rendu sûr dans Blade -->
{{ $user->name }}

4. CSRF Protection⚓︎

Laravel inclut une protection intégrée de la CSRF. Assurez-vous que vos formulaires incluent le jeton CSRF en utilisant le @csrf Directive.

dans template.blade.php : <meta name="csrf-token" content="{{ csrf_token() }}"> et dans home.blade.php :

HTML
<form action="{{ route('todo.save') }}" method="POST" class="add">
    @csrf 

5. Limitation de débit et régulation des requêtes⚓︎

Pour protéger l'application contre les attaques par force brute, on peut mettre en place une limitation du nombre de requêtes grâce au middleware ThrottleRequests de Laravel.

dans routes/auth.php, repérer les routes /login et modifier les ainsi.

🐘 PHP
Route::get('login', [AuthenticatedSessionController::class, 'create'])
        ->middleware('throttle:10,1')   // 10 requêtes / min sur l’affichage du formulaire
        ->name('login');

Route::post('login', [AuthenticatedSessionController::class, 'store'])
    ->middleware('throttle:5,1');   // 5 tentatives de connexion / min

  • throttle:10,1 signifie : → maximum 10 requêtes par minute (le second chiffre indique la durée en minutes).
  • Si la limite est dépassée, Laravel renvoie automatiquement une réponse 429 (Too Many Requests).

le throttling est utile pour protéger :

  • l’accès aux endpoints sensibles (ex : login)
  • les API exposées publiques
  • les formulaires soumis à répétition

👀 Il faut tester !!

🐍 Python
import requests
import time

URL = "http://localhost:8000/login"  # adapte si besoin (port, domaine, etc.)

for i in range(1, 16):  # 15 requêtes
    response = requests.get(URL)
    print(f"Requête {i}: status = {response.status_code}")

    # Affiche quelques headers utiles si présents
    print("  X-RateLimit-Limit     :", response.headers.get("X-RateLimit-Limit"))
    print("  X-RateLimit-Remaining :", response.headers.get("X-RateLimit-Remaining"))
    print("  Retry-After           :", response.headers.get("Retry-After"))
    print("-" * 40)

    # On attend très peu pour rester dans la même minute
    time.sleep(1)  # 2 secondes entre chaque requête
on obtient bien la trace :
📋 Texte
----------------------------------------
Requête 4: status = 200
  X-RateLimit-Limit     : 10
  X-RateLimit-Remaining : 1
  Retry-After           : None
----------------------------------------
Requête 5: status = 200
  X-RateLimit-Limit     : 10
  X-RateLimit-Remaining : 0
  Retry-After           : None
----------------------------------------
Requête 6: status = 429
  X-RateLimit-Limit     : 10
  X-RateLimit-Remaining : 0
  Retry-After           : 8
----------------------------------------

💥 Ca sera bien de "rendre visible" le throttling dans les logs en interceptant l’exception 429 générée par le middleware throttle.

6. Journalisation et supervision : ThrottleRequestsException⚓︎

Lorsqu’un utilisateur dépasse la limite, Laravel lève une exception ThrottleRequestsException. Nous allons la capturer pour enregistrer un événement de sécurité.

🔧 Étape 1 : Modifier la gestion des exceptions dans bootstrap/app.php

Ouvre bootstrap/app.php. Tu dois avoir quelque chose de ce type (structure Laravel 11/12) :

🐘 PHP
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        // ...
    )
    ->withMiddleware(function (Middleware $middleware) {
        // ...
    })
    ->withExceptions(function (Exceptions $exceptions) {
        // ...
    })
    ->create();

Nous allons compléter le bloc withExceptions.

Ajoute les use nécessaires en haut du fichier :

🐘 PHP
use Illuminate\Http\Exceptions\ThrottleRequestsException;
use Illuminate\Support\Facades\Log;
use Illuminate\Http\Request;

Puis remplace / complète le bloc withExceptions ainsi :

🐘 PHP
->withExceptions(function (Exceptions $exceptions): void {

    // Logging sécurité pour les dépassements de limite (HTTP 429)
    $exceptions->render(function (ThrottleRequestsException $e, Request $request) {
        Log::channel('security')->warning('Brute-force / rate limit déclenché', [
            'ip'         => $request->ip(),
            'url'        => $request->fullUrl(),
            'method'     => $request->method(),
            'user_agent' => $request->userAgent(),
        ]);

        // Ne rien retourner ici => Laravel utilisera la réponse 429 par défaut
        // (page "Too Many Attempts" ou JSON selon le contexte).
    });

    // Tu peux laisser d’autres config ici si Laravel en a généré
})->create();

Render VS report

Dans Laravel 11/12, les exceptions peuvent être interceptées à deux niveaux distincts :

report() : pour décider si une exception doit être enregistrée dans les logs
report() sert à signaler une exception (log, Sentry, Bugsnag, etc.). Laravel ne reporte pas toutes les exceptions. Certaines exceptions sont explicitement ignorées (don’tReport).

Et certaines, comme le ThrottleRequestsException, ne sont pas considérées comme « erreurs applicatives » mais comme comportements normaux du middleware RateLimiter.

👉 Résultat : ThrottleRequestsException peut ne JAMAIS passer dans report() → donc aucun log, même si tu le demandes.

render() : pour intercepter toutes les exceptions juste avant la réponse HTTP
render() est appelé au moment où Laravel prépare la réponse HTTP :Quand une exception doit devenir un 429, 403, 404, 500, etc. Laravel appelle systématiquement render(), même si l’exception n’est pas "reportable".

Donc avec render(): On est certain que la logique se déclenche à chaque dépassement Throttle.

🔧 Étape 2 : Modifier config/logging.php

Création d’un canal de log dédié à la sécurité 📁🛡️. Pour isoler les traces d’attaque dans un fichier spécifique : storage/logs/security.log

Dans "channels", ajouter :

🐘 PHP
'security' => [
    'driver' => 'single',
    'path'   => storage_path('logs/security.log'),
    'level'  => 'warning',
],
💥💥 Relancer le script Python qui simule l'attaque par force brute

Analyse des logs de sécurité 📜🕵️‍♂️⚓︎

Ouvrir les logs storage/logs/security.log

Vous devez voir des entrées de ce type :

📋 Texte
[2025-11-25 20:01:45] local.WARNING: Brute-force / rate limit déclenché {"ip":"127.0.0.1","url":"http://localhost:8000/login","method":"GET","user_agent":"python-requests/2.31.0"} 
Ce que montrent ces logs :

  • IP de l’attaquant
  • URL cible
  • Méthode HTTP
  • User-Agent du script automatisé
  • Heure exacte de l’incident

Conclusion : Si tu veux tracer une attaque (brute force, flood HTTP, etc.), il faut intercepter l’exception au niveau où elle génère la réponse. 👉 C’est render(), pas report().

3.3.7 Journalisation des logins⚓︎

Pour tracer les connexions réussies et échouées, le plus simple est de le faire directement dans le contrôleur d’authentification (AuthenticatedSessionController) en réutilisant ton canal security.

Objectif :
Login OK → entrée “info” dans security.log
Login KO (mauvais mot de passe) → entrée “warning” dans security.log

Préparation : canal de log⚓︎

On réutilise le même canal que pour le throttle (déjà en place) dans config/logging.php :

'channels' => [ // ...

📋 Texte
'security' => [
    'driver' => 'single',
    'path'   => storage_path('logs/security.log'),
    // Gestion du niveaux de logs dans le .env
    'level'  => env('LOG_LEVEL', 'debug'),
],

],

Journaliser dans AuthenticatedSessionController⚓︎

Ouvre app/Http/Controllers/Auth/AuthenticatedSessionController.php.

En haut du fichier, ajoute les use suivants :

🐘 PHP
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException;

Puis adapte la méthode store comme suit :

🐘 PHP
use Illuminate\Support\Facades\Log;
use App\Providers\RouteServiceProvider;
use Illuminate\Validation\ValidationException;  

public function store(LoginRequest $request): RedirectResponse
{
    try {
        // Tentative d'authentification (throw ValidationException si KO)
        $request->authenticate();

        // Regénération de la session en cas de succès
        $request->session()->regenerate();

        // LOG: connexion réussie
        Log::channel('security')->info('Login OK', [
            'ip'         => $request->ip(),
            'email'      => $request->input('email'),
            'user_id'    => optional($request->user())->id,
            'user_agent' => $request->userAgent(),
        ]);

        return redirect()->intended('/');
    } catch (ValidationException $e) {
        // LOG: tentative de connexion échouée
        Log::channel('security')->warning('Login KO', [
            'ip'         => $request->ip(),
            'email'      => $request->input('email'),
            'user_agent' => $request->userAgent(),
            'errors'     => $e->errors(),   // optionnel, pour tracer le type d’erreur
        ]);

        // On relance l’exception pour laisser Laravel gérer le retour formulaire + messages
        throw $e;
    }
}

**note : **

  • LoginRequest::authenticate() lève une ValidationException quand les identifiants sont faux.
  • Le try/catch permet d’instrumenter cette exception sans changer le comportement utilisateur.
  • On trace IP, email, user_agent, éventuellement user_id si l’authentification a réussi.

👀 Il faut tester !!

Niveau de logs

Vous pouvez consigner des informations dans les journaux à l'aide de la façade Log. Comme mentionné précédemment, le système de journalisation propose huit niveaux de journalisation définis.

📋 Texte
Log::emergency($message);
Log::alert($message);
Log::critical($message);
Log::error($message);
Log::warning($message);
Log::notice($message);
Log::info($message);
Log::debug($message);