Aller au contenu

Sécuriser une application PHP avec un fichier .htaccess

Objectif

Ce document explique comment utiliser un fichier .htaccess Apache pour réduire la surface d’attaque d’une application PHP MVC.

Important : un fichier .htaccess ne corrige pas toutes les failles de sécurité. Il ne remplace pas :

Il sert plutôt à renforcer la configuration Apache et à éviter certaines expositions dangereuses.


Table des matières

  1. Rôle réel d’un fichier .htaccess
  2. Où placer le fichier .htaccess
  3. Exemple complet de fichier .htaccess
  4. Explication des principales directives
  5. Version minimale recommandée pour un TP
  6. Ce que .htaccess ne remplace pas
  7. Résumé

1. Rôle réel d’un fichier .htaccess

Un fichier .htaccess est un fichier de configuration Apache placé dans un dossier de l’application.

Il permet notamment de :

En revanche, il ne connaît pas la logique métier de l’application.
Il ne peut donc pas savoir si un utilisateur a vraiment le droit d’accéder à une facture, un profil ou un document.


2. Où placer le fichier .htaccess

Dans une application PHP MVC, la bonne structure est généralement (comme pour Symfony) :

mon-projet/
├── app/
├── config/
├── database/
├── vendor/
└── public/
    ├── index.php
    ├── .htaccess
    └── assets/

Le serveur Apache doit pointer vers le dossier : public/

Le fichier .htaccess doit donc être placé ici :

public/.htaccess

Cela évite d’exposer directement :

app/
config/
database/
vendor/
.env
composer.json
docker-compose.yml

3. Exemple complet de fichier .htaccess

# ============================================================
# .htaccess de sécurité pour application PHP MVC
# Apache 2.4+
# À placer dans le dossier public/
# ============================================================


# ------------------------------------------------------------
# 1. Activer le moteur de réécriture d’URL
# ------------------------------------------------------------
<IfModule mod_rewrite.c>
    RewriteEngine On
</IfModule>


# ------------------------------------------------------------
# 2. Forcer HTTPS
# À activer uniquement en production avec un certificat valide.
# En local Wamp/Docker sans HTTPS, laisser commenté.
# ------------------------------------------------------------
# <IfModule mod_rewrite.c>
#     RewriteCond %{HTTPS} !=on
#     RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# </IfModule>


# ------------------------------------------------------------
# 3. Rediriger les requêtes MVC vers index.php dans notre cas
# ------------------------------------------------------------
<IfModule mod_rewrite.c>

    RewriteCond %{REQUEST_FILENAME} -f [OR]
    RewriteCond %{REQUEST_FILENAME} -d
    RewriteRule ^ - [L]

    RewriteRule ^ index.php [L]

</IfModule>


# ------------------------------------------------------------
# 4. Désactiver le listing des dossiers
# ------------------------------------------------------------
Options -Indexes


# ------------------------------------------------------------
# 5. Bloquer les fichiers sensibles
# ------------------------------------------------------------
<FilesMatch "^(\.env|\.git|\.htaccess|composer\.json|composer\.lock|package\.json|package-lock\.json|yarn\.lock|docker-compose\.yml|Dockerfile|README\.md)$">
    Require all denied
</FilesMatch>


# ------------------------------------------------------------
# 6. Bloquer les fichiers techniques ou de sauvegarde
# ------------------------------------------------------------
<FilesMatch "\.(ini|log|conf|sql|bak|backup|old|orig|dist|sh|bat|cmd|ps1|yaml|yml)$">
    Require all denied
</FilesMatch>


# ------------------------------------------------------------
# 7. Bloquer les dossiers techniques s’ils sont exposés
# ------------------------------------------------------------
<IfModule mod_rewrite.c>
    RewriteRule ^(app|src|vendor|database|config|tests|storage|var|logs)/ - [F,L]
</IfModule>


# ------------------------------------------------------------
# 8. Limiter les méthodes HTTP autorisées
# Attention : à adapter pour une API REST.
# ------------------------------------------------------------
<LimitExcept GET POST HEAD>
    Require all denied
</LimitExcept>


# ------------------------------------------------------------
# 9. Ajouter des en-têtes HTTP de sécurité
# ------------------------------------------------------------
<IfModule mod_headers.c>
    Header always set X-Content-Type-Options "nosniff"
    Header always set X-Frame-Options "SAMEORIGIN"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    Header always set Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(), usb=()"
</IfModule>


# ------------------------------------------------------------
# 10. Content Security Policy basique
# À adapter si vous utilisez Tailwind, Flowbite ou un CDN.
# ------------------------------------------------------------
<IfModule mod_headers.c>
    Header always set Content-Security-Policy "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:;"
</IfModule>


# ------------------------------------------------------------
# 11. HSTS
# À activer uniquement si HTTPS fonctionne parfaitement.
# ------------------------------------------------------------
# <IfModule mod_headers.c>
#     Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# </IfModule>


# ------------------------------------------------------------
# 12. Masquer certains en-têtes techniques
# ------------------------------------------------------------
<IfModule mod_headers.c>
    Header unset X-Powered-By
    Header unset Server
</IfModule>


# ------------------------------------------------------------
# 13. Bloquer quelques requêtes grossièrement suspectes
# Attention : cela ne remplace pas une correction dans le code.
# ------------------------------------------------------------
<IfModule mod_rewrite.c>

    RewriteCond %{REQUEST_URI} "\.\." [NC,OR]
    RewriteCond %{QUERY_STRING} "(php://|data://|expect://|input://)" [NC,OR]
    RewriteCond %{QUERY_STRING} "(union.*select|select.*from|insert.*into|drop.*table|sleep\(|benchmark\()" [NC,OR]
    RewriteCond %{QUERY_STRING} "(<script|%3Cscript|javascript:|onerror=|onload=)" [NC]

    RewriteRule ^ - [F,L]

</IfModule>


# ------------------------------------------------------------
# 14. Bloquer l’exécution PHP dans le dossier uploads/
# ------------------------------------------------------------
<IfModule mod_rewrite.c>
    RewriteRule ^uploads/.*\.php$ - [F,L]
    RewriteRule ^uploads/.*\.phtml$ - [F,L]
    RewriteRule ^uploads/.*\.phar$ - [F,L]
</IfModule>


# ------------------------------------------------------------
# 15. Limiter la taille et le temps des requêtes PHP
# Fonctionne surtout avec mod_php.
# Avec PHP-FPM/FCGI, configurer php.ini ou le pool PHP.
# ------------------------------------------------------------
<IfModule mod_php.c>
    php_value upload_max_filesize 5M
    php_value post_max_size 6M
    php_value max_execution_time 30
    php_value max_input_time 30
</IfModule>


# ------------------------------------------------------------
# 16. Désactiver l’affichage des erreurs PHP en production
# ------------------------------------------------------------
<IfModule mod_php.c>
    php_flag display_errors Off
    php_flag log_errors On
</IfModule>


# ------------------------------------------------------------
# 17. Encodage par défaut
# ------------------------------------------------------------
AddDefaultCharset UTF-8

4. Explication des principales directives

4.1 RewriteEngine On

RewriteEngine On

Active le moteur de réécriture d’URL Apache. Il permet de rediriger toutes les URL applicatives vers index.php, qui joue le rôle de contrôleur frontal en MVC.

Exemple :

/failles/A05

peut être traité par :

public/index.php

4.2 Forcer HTTPS

RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

Cette règle redirige automatiquement HTTP vers HTTPS.

À utiliser uniquement lorsque :

En local, il faut généralement laisser cette règle commentée.


4.3 Redirection vers index.php

RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]

RewriteRule ^ index.php [L]

Signification :

C’est le fonctionnement classique d’une application MVC.


4.4 Désactiver le listing des dossiers

Options -Indexes

Sans cette directive, Apache peut afficher le contenu d’un dossier si aucun fichier index.php ou index.html n’est présent.

Exemples dangereux :

/uploads/
/logs/
/backup/

Avec Options -Indexes, Apache refuse d’afficher la liste des fichiers.


4.5 Protéger les fichiers sensibles

<FilesMatch "^(\.env|\.git|\.htaccess|composer\.json|composer\.lock|docker-compose\.yml|Dockerfile)$">
    Require all denied
</FilesMatch>

Cette règle bloque l’accès à des fichiers qui ne doivent jamais être lus depuis un navigateur.

Exemple de fichier dangereux :

.env

Il peut contenir :

DB_PASSWORD=secret
APP_SECRET=abc123

4.6 Bloquer les fichiers techniques

<FilesMatch "\.(ini|log|conf|sql|bak|backup|old|yaml|yml)$">
    Require all denied
</FilesMatch>

Cette règle bloque les extensions sensibles.

Extension Risque
.sql dump de base de données
.log erreurs, chemins, tokens
.bak sauvegarde oubliée
.yml configuration
.ini configuration serveur ou application

4.7 Bloquer les dossiers techniques

RewriteRule ^(app|src|vendor|database|config|tests|storage|var|logs)/ - [F,L]

Cette règle bloque l’accès direct à des dossiers internes. Dans une application bien structurée, ces dossiers ne devraient pas être accessibles depuis le web.


4.8 Limiter les méthodes HTTP

<LimitExcept GET POST HEAD>
    Require all denied
</LimitExcept>

Cette directive autorise uniquement :

GET
POST
HEAD

Elle bloque :

PUT
PATCH
DELETE
TRACE
OPTIONS

Pour une application MVC classique, c’est souvent acceptable. Pour une API REST, il faut adapter cette règle, car les méthodes PUT, PATCH, DELETE et OPTIONS peuvent être nécessaires.


4.9 X-Content-Type-Options

Header always set X-Content-Type-Options "nosniff"

Empêche le navigateur de deviner le type d’un fichier. Cela limite certains comportements dangereux où un fichier pourrait être interprété comme un script.


4.10 X-Frame-Options

Header always set X-Frame-Options "SAMEORIGIN"

Protège contre le clickjacking (je ne sais pas letraduire en français ;) ). Le clickjacking consiste à intégrer une page dans une iframe invisible pour piéger l’utilisateur.

Valeurs possibles :


4.11 Referrer-Policy

Header always set Referrer-Policy "strict-origin-when-cross-origin"

Réduit les informations transmises dans l’en-tête Referer. Cela évite d’envoyer des URLs complètes à des sites externes.


4.12 Permissions-Policy

Header always set Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(), usb=()"

Désactive certaines API navigateur. Si une application n’a pas besoin de caméra, micro, géolocalisation ou paiement, il vaut mieux les interdire.


4.13 Content Security Policy

Header always set Content-Security-Policy "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:;"

La CSP limite les ressources que le navigateur peut charger.

Quelques directives importantes :

Directive Rôle
default-src 'self' Charger par défaut depuis le même domaine
script-src 'self' Limiter les scripts JavaScript
style-src 'self' Limiter les feuilles de style
object-src 'none' Interdire les objets anciens
frame-ancestors 'self' Limiter l’intégration dans une iframe
form-action 'self' Limiter la destination des formulaires

Attention : unsafe-inline est pratique, mais moins sécurisé. Il faut l’éviter en production si possible.


4.14 CSP avec Tailwind ou Flowbite CDN

Si vous utilisez Tailwind ou Flowbite depuis un CDN, la CSP stricte peut bloquer les ressources.

Exemple plus permissif pour un TP local :

<IfModule mod_headers.c>
    Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.tailwindcss.com https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com; img-src 'self' data:; font-src 'self' data: https://cdnjs.cloudflare.com; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self';"
</IfModule>

En production, il vaut mieux :


4.15 HSTS

Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"

HSTS indique au navigateur :

Utilise toujours HTTPS pour ce domaine.

Très utile en production, mais à activer seulement si HTTPS est parfaitement fonctionnel.


4.16 Masquer certains en-têtes techniques

Header unset X-Powered-By
Header unset Server

Cela tente de masquer certaines informations techniques.

Attention : l’en-tête Server ne peut pas toujours être supprimé depuis .htaccess.

Il faut parfois configurer Apache globalement :

ServerTokens Prod
ServerSignature Off

4.17 Bloquer quelques requêtes suspectes

RewriteCond %{QUERY_STRING} "(union.*select|drop.*table|<script|javascript:)" [NC]
RewriteRule ^ - [F,L]

Cela bloque quelques attaques très visibles. Mais ce n’est pas une vraie protection contre les injections.

Une injection SQL se corrige avec une requête préparée :

$stmt = $pdo->prepare('SELECT * FROM users WHERE email = ?');
$stmt->execute([$email]);

Une XSS se corrige avec un échappement :

echo htmlspecialchars($comment, ENT_QUOTES, 'UTF-8');

4.18 Bloquer l’exécution PHP dans uploads/

RewriteRule ^uploads/.*\.php$ - [F,L]
RewriteRule ^uploads/.*\.phtml$ - [F,L]
RewriteRule ^uploads/.*\.phar$ - [F,L]

Très important si l’application accepte l’envoi de fichiers.

Un attaquant peut essayer d’envoyer :

shell.php
avatar.phtml
image.phar

Puis tenter d’appeler :

/uploads/shell.php

Cette règle bloque l’exécution de scripts PHP dans uploads/.


4.19 Limiter les uploads

php_value upload_max_filesize 5M
php_value post_max_size 6M

Ces directives limitent la taille des fichiers envoyés.

Attention : elles fonctionnent surtout avec mod_php.

Avec PHP-FPM ou FCGI, il faut configurer php.ini ou le pool PHP.


4.20 Désactiver l’affichage des erreurs

php_flag display_errors Off
php_flag log_errors On

En production, les erreurs ne doivent pas être affichées au navigateur.

Mauvais exemple :

Fatal error: Uncaught PDOException in /var/www/app/Database.php line 12

Cela révèle trop d’informations techniques.


5. Version minimale recommandée pour un TP

Pour un TP local, cette version est plus simple et limite les risques de casser Tailwind, Flowbite ou Swagger.

<IfModule mod_rewrite.c>
    RewriteEngine On

    RewriteCond %{REQUEST_FILENAME} -f [OR]
    RewriteCond %{REQUEST_FILENAME} -d
    RewriteRule ^ - [L]

    RewriteRule ^ index.php [L]
</IfModule>

Options -Indexes

<FilesMatch "^(\.env|\.git|\.htaccess|composer\.json|composer\.lock|docker-compose\.yml|Dockerfile)$">
    Require all denied
</FilesMatch>

<FilesMatch "\.(ini|log|conf|sql|bak|backup|old|yml|yaml)$">
    Require all denied
</FilesMatch>

<IfModule mod_rewrite.c>
    RewriteRule ^(app|src|vendor|database|config|tests|logs)/ - [F,L]
    RewriteRule ^uploads/.*\.(php|phtml|phar)$ - [F,L]
</IfModule>

<LimitExcept GET POST HEAD>
    Require all denied
</LimitExcept>

<IfModule mod_headers.c>
    Header always set X-Content-Type-Options "nosniff"
    Header always set X-Frame-Options "SAMEORIGIN"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    Header always set Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(), usb=()"
</IfModule>

AddDefaultCharset UTF-8

6. Ce que .htaccess ne remplace pas

6.1 Injection SQL

À corriger dans le code :

$stmt = $pdo->prepare('SELECT * FROM users WHERE email = ?');
$stmt->execute([$email]);

6.2 XSS

À corriger à l’affichage :

echo htmlspecialchars($comment, ENT_QUOTES, 'UTF-8');

6.3 CSRF

À corriger avec un token :

if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
    http_response_code(403);
    exit('CSRF invalide');
}

6.4 Mots de passe

À corriger avec :

$hash = password_hash($password, PASSWORD_DEFAULT);

if (password_verify($passwordSaisi, $hash)) {
    echo "Connexion acceptée";
}

6.5 Contrôle d’accès

À corriger côté serveur :

$stmt = $pdo->prepare(
    'SELECT * FROM documents WHERE id = ? AND owner_id = ?'
);

$stmt->execute([$documentId, $userId]);

Un utilisateur ne doit pas pouvoir accéder à une ressource simplement parce qu’il connaît son identifiant.


7. Résumé

Un .htaccess sécurisé sert à :

Mais la vraie sécurité applicative repose surtout sur :

Conclusion

Le .htaccess, c’est le vigile à l’entrée. Mais si l’application laisse les clés du coffre sur le comptoir, le vigile ne pourra pas faire de miracle.