Maîtrisez toutes les vulnérabilités des formulaires web et apprenez à créer des applications PHP ultra-sécurisées contre les attaques les plus courantes.
L'une des vulnérabilités les plus courantes : injection de code JavaScript malveillant dans les pages web.
Payload XSS renvoyé immédiatement dans la réponse, souvent via URL.
Payload stocké en base, exécuté à chaque affichage. Plus dangereux.
Manipulation DOM côté client via JavaScript vulnérable.
Attaque qui force un utilisateur authentifié à exécuter des actions non désirées.
L'une des attaques les plus dévastatrices : manipulation des requêtes de base de données.
L'upload de fichiers peut permettre l'exécution de code malveillant. Voici comment se protéger.
<?php
/**
* Upload de Fichiers Ultra-Sécurisé
* Protection contre : shells, virus, bombes, injections, etc.
*/
class UploadSecurise {
private $extensions_autorisees = [
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'png' => 'image/png',
'gif' => 'image/gif',
'pdf' => 'application/pdf',
'txt' => 'text/plain'
];
private $taille_max = 5 * 1024 * 1024; // 5MB
private $dossier_upload = 'uploads/';
private $dossier_quarantaine = 'quarantine/';
public function __construct() {
// Créer dossiers si nécessaire
if (!is_dir($this->dossier_upload)) {
mkdir($this->dossier_upload, 0755, true);
file_put_contents($this->dossier_upload . '.htaccess', 'php_flag engine off');
}
if (!is_dir($this->dossier_quarantaine)) {
mkdir($this->dossier_quarantaine, 0700, true);
}
}
public function uploadFichier($fichier) {
$erreurs = [];
// 1. VÉRIFICATIONS DE BASE
if (!isset($fichier) || $fichier['error'] !== UPLOAD_ERR_OK) {
$erreurs[] = "Erreur d'upload : " . $this->getErrorMessage($fichier['error']);
return ['success' => false, 'erreurs' => $erreurs];
}
$nom_original = $fichier['name'];
$nom_temp = $fichier['tmp_name'];
$taille = $fichier['size'];
$type_mime = $fichier['type'];
// 2. VALIDATION TAILLE
if ($taille > $this->taille_max) {
$erreurs[] = "Fichier trop volumineux. Maximum : " . ($this->taille_max / 1024 / 1024) . " MB";
}
if ($taille < 10) {
$erreurs[] = "Fichier trop petit (possiblement corrompu)";
}
// 3. VALIDATION EXTENSION
$extension = strtolower(pathinfo($nom_original, PATHINFO_EXTENSION));
if (!array_key_exists($extension, $this->extensions_autorisees)) {
$erreurs[] = "Extension non autorisée. Autorisées : " . implode(', ', array_keys($this->extensions_autorisees));
}
// 4. VALIDATION MIME TYPE (Double vérification)
$mime_reel = mime_content_type($nom_temp);
$mime_attendu = $this->extensions_autorisees[$extension];
if ($mime_reel !== $mime_attendu) {
$erreurs[] = "Type MIME invalide. Attendu : $mime_attendu, reçu : $mime_reel";
}
// 5. VALIDATION NOM DE FICHIER
if (!$this->validerNomFichier($nom_original)) {
$erreurs[] = "Nom de fichier invalide (caractères dangereux détectés)";
}
// 6. SCAN ANTIVIRUS BASIQUE (Signature de fichiers malveillants)
if ($this->detecterFichierMalveillant($nom_temp)) {
$erreurs[] = "Fichier potentiellement malveillant détecté";
// Déplacer en quarantaine
$nom_quarantaine = uniqid('malware_') . '.' . $extension;
move_uploaded_file($nom_temp, $this->dossier_quarantaine . $nom_quarantaine);
// Logger l'incident
error_log("FICHIER MALVEILLANT DÉTECTÉ: " . $_SERVER['REMOTE_ADDR'] . " - " . $nom_original);
}
// 7. VALIDATION IMAGE (si c'est une image)
if (in_array($extension, ['jpg', 'jpeg', 'png', 'gif'])) {
if (!$this->validerImage($nom_temp)) {
$erreurs[] = "Image corrompue ou contient du code malveillant";
}
}
// 8. PROTECTION CONTRE LES BOMBES DE DÉCOMPRESSION
if (in_array($extension, ['zip', 'rar', 'tar', 'gz'])) {
if ($this->detecterBombeDecompression($nom_temp)) {
$erreurs[] = "Archive potentiellement dangereuse (bombe de décompression)";
}
}
// Si erreurs, arrêter ici
if (!empty($erreurs)) {
return ['success' => false, 'erreurs' => $erreurs];
}
// 9. GÉNÉRATION NOM SÉCURISÉ
$nouveau_nom = $this->genererNomSecurise($extension);
$chemin_destination = $this->dossier_upload . $nouveau_nom;
// 10. DÉPLACEMENT SÉCURISÉ
if (move_uploaded_file($nom_temp, $chemin_destination)) {
// 11. PERMISSIONS SÉCURISÉES
chmod($chemin_destination, 0644);
// 12. MÉTADONNÉES SÉCURISÉES
$metadonnees = [
'nom_original' => $this->nettoyer($nom_original),
'nom_securise' => $nouveau_nom,
'taille' => $taille,
'extension' => $extension,
'mime_type' => $mime_reel,
'date_upload' => date('Y-m-d H:i:s'),
'ip_utilisateur' => $_SERVER['REMOTE_ADDR'] ?? 'inconnue',
'user_agent' => substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 255)
];
// 13. JOURNALISATION
$this->journaliserUpload($metadonnees);
return [
'success' => true,
'fichier' => $nouveau_nom,
'metadonnees' => $metadonnees
];
} else {
return ['success' => false, 'erreurs' => ['Impossible de déplacer le fichier']];
}
}
private function validerNomFichier($nom) {
// Caractères dangereux dans les noms de fichiers
$caracteres_interdits = ['..', '/', '\\', ':', '*', '?', '"', '<', '>', '|', "\0"];
foreach ($caracteres_interdits as $char) {
if (strpos($nom, $char) !== false) {
return false;
}
}
// Vérifier noms de fichiers système Windows/Linux
$noms_interdits = ['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'LPT1', '.htaccess', '.htpasswd'];
$nom_sans_ext = pathinfo($nom, PATHINFO_FILENAME);
if (in_array(strtoupper($nom_sans_ext), $noms_interdits)) {
return false;
}
return true;
}
private function detecterFichierMalveillant($chemin) {
$contenu = file_get_contents($chemin, false, null, 0, 8192); // Lire début du fichier
// Signatures de code malveillant
$signatures_malveillantes = [
'<?php',
'<?=',
'<script',
'eval(',
'exec(',
'system(',
'shell_exec(',
'passthru(',
'base64_decode(',
'file_get_contents(',
'fopen(',
'curl_exec(',
'\\x',
'%3C%3F', // <? encodé
'FromBase64String',
'CreateObject'
];
foreach ($signatures_malveillantes as $signature) {
if (stripos($contenu, $signature) !== false) {
return true;
}
}
return false;
}
private function validerImage($chemin) {
// Vérifier que c'est vraiment une image
$info_image = getimagesize($chemin);
if ($info_image === false) {
return false;
}
// Vérifier dimensions raisonnables
if ($info_image[0] > 10000 || $info_image[1] > 10000) {
return false; // Image trop grande (possible attaque DoS)
}
// Lire début de l'image pour détecter du code
$contenu_debut = file_get_contents($chemin, false, null, 0, 1024);
// Chercher signatures de code dans l'image
if (strpos($contenu_debut, '<?php') !== false ||
strpos($contenu_debut, '<script') !== false) {
return false;
}
return true;
}
private function detecterBombeDecompression($chemin) {
// Cette fonction est simplifiée - en production, utilisez une vraie librairie
$taille_fichier = filesize($chemin);
// Ratio suspect : fichier très petit mais probablement très volumineux décompressé
if ($taille_fichier < 1024 && $taille_fichier > 0) {
// Heuristique simple : fichier zip de moins de 1KB suspect
return true;
}
return false;
}
private function genererNomSecurise($extension) {
return uniqid('upload_', true) . '_' . time() . '.' . $extension;
}
private function nettoyer($texte) {
return htmlspecialchars(trim($texte), ENT_QUOTES, 'UTF-8');
}
private function getErrorMessage($code) {
$messages = [
UPLOAD_ERR_OK => 'Upload réussi',
UPLOAD_ERR_INI_SIZE => 'Fichier trop volumineux (php.ini)',
UPLOAD_ERR_FORM_SIZE => 'Fichier trop volumineux (formulaire)',
UPLOAD_ERR_PARTIAL => 'Upload partiel',
UPLOAD_ERR_NO_FILE => 'Aucun fichier',
UPLOAD_ERR_NO_TMP_DIR => 'Dossier temporaire manquant',
UPLOAD_ERR_CANT_WRITE => 'Impossible d\'écrire',
UPLOAD_ERR_EXTENSION => 'Extension PHP bloque l\'upload'
];
return $messages[$code] ?? 'Erreur inconnue';
}
private function journaliserUpload($metadonnees) {
$log = date('Y-m-d H:i:s') . " - UPLOAD - " . json_encode($metadonnees) . "\n";
file_put_contents('upload_logs.txt', $log, FILE_APPEND | LOCK_EX);
}
}
// UTILISATION
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['fichier'])) {
$uploader = new UploadSecurise();
$resultat = $uploader->uploadFichier($_FILES['fichier']);
if ($resultat['success']) {
echo "✅ Upload réussi : " . $resultat['fichier'];
} else {
echo "❌ Erreurs : " . implode(', ', $resultat['erreurs']);
}
}
?>
<!-- FORMULAIRE HTML SÉCURISÉ -->
<form method="POST" enctype="multipart/form-data">
<input type="hidden" name="MAX_FILE_SIZE" value="5242880"> <!-- 5MB -->
<input type="file" name="fichier" accept=".jpg,.jpeg,.png,.gif,.pdf,.txt" required>
<button type="submit">Upload Sécurisé</button>
</form>
Une validation rigoureuse est la première ligne de défense contre toutes les attaques.
<?php
/**
* Classe de Validation Ultra-Sécurisée
* Protection contre injections, XSS, et données malformées
*/
class ValidateurSecurise {
private $erreurs = [];
private $donnees_nettoyees = [];
// NETTOYAGE GÉNÉRAL
public function nettoyer($valeur) {
if (is_array($valeur)) {
return array_map([$this, 'nettoyer'], $valeur);
}
// Supprimer espaces, slashes, null bytes
$valeur = trim($valeur);
$valeur = stripslashes($valeur);
$valeur = str_replace("\0", '', $valeur);
return $valeur;
}
// PROTECTION XSS
public function protegerXSS($valeur) {
return htmlspecialchars($valeur, ENT_QUOTES | ENT_HTML5, 'UTF-8');
}
// VALIDATION EMAIL ULTRA-STRICTE
public function validerEmail($email, $obligatoire = true) {
$email = $this->nettoyer($email);
if (empty($email)) {
if ($obligatoire) {
$this->erreurs[] = "Email obligatoire";
}
return false;
}
// Longueur maximale
if (strlen($email) > 254) {
$this->erreurs[] = "Email trop long (max 254 caractères)";
return false;
}
// Validation PHP native
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$this->erreurs[] = "Format email invalide";
return false;
}
// Validation regex stricte
$pattern = '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/';
if (!preg_match($pattern, $email)) {
$this->erreurs[] = "Email contient des caractères non autorisés";
return false;
}
// Vérifier domaine
$domaine = substr(strrchr($email, "@"), 1);
if (!$this->validerDomaine($domaine)) {
$this->erreurs[] = "Domaine email invalide";
return false;
}
// Blacklist domaines temporaires
$domaines_temporaires = ['10minutemail.com', 'temp-mail.org', 'guerrillamail.com'];
if (in_array($domaine, $domaines_temporaires)) {
$this->erreurs[] = "Domaines temporaires non autorisés";
return false;
}
$this->donnees_nettoyees['email'] = strtolower($email);
return true;
}
// VALIDATION MOT DE PASSE SÉCURISÉ
public function validerMotDePasse($password, $confirmation = null) {
if (empty($password)) {
$this->erreurs[] = "Mot de passe obligatoire";
return false;
}
// Longueur minimale
if (strlen($password) < 12) {
$this->erreurs[] = "Mot de passe trop court (minimum 12 caractères)";
}
// Longueur maximale (protection DoS)
if (strlen($password) > 128) {
$this->erreurs[] = "Mot de passe trop long (maximum 128 caractères)";
}
// Complexité
$criteres = 0;
if (preg_match('/[a-z]/', $password)) $criteres++; // Minuscule
if (preg_match('/[A-Z]/', $password)) $criteres++; // Majuscule
if (preg_match('/[0-9]/', $password)) $criteres++; // Chiffre
if (preg_match('/[^a-zA-Z0-9]/', $password)) $criteres++; // Spécial
if ($criteres < 3) {
$this->erreurs[] = "Mot de passe pas assez complexe (3 types de caractères requis)";
}
// Mots de passe communs
$mots_interdits = ['password', '123456', 'azerty', 'admin', 'root', 'user'];
if (in_array(strtolower($password), $mots_interdits)) {
$this->erreurs[] = "Mot de passe trop commun";
}
// Confirmation
if ($confirmation !== null && $password !== $confirmation) {
$this->erreurs[] = "Confirmation mot de passe différente";
}
return empty($this->erreurs);
}
// VALIDATION NOM/PRÉNOM
public function validerNom($nom, $champ = 'nom') {
$nom = $this->nettoyer($nom);
if (empty($nom)) {
$this->erreurs[] = ucfirst($champ) . " obligatoire";
return false;
}
// Longueur
if (strlen($nom) < 2 || strlen($nom) > 50) {
$this->erreurs[] = ucfirst($champ) . " doit contenir 2-50 caractères";
}
// Caractères autorisés (lettres, espaces, apostrophes, traits d'union)
if (!preg_match('/^[a-zA-ZÀ-ÿ\s\'-]+$/u', $nom)) {
$this->erreurs[] = ucfirst($champ) . " contient des caractères non autorisés";
}
// Pas que des espaces
if (trim($nom) === '') {
$this->erreurs[] = ucfirst($champ) . " ne peut pas être vide";
}
$this->donnees_nettoyees[$champ] = $this->protegerXSS($nom);
return empty($this->erreurs);
}
// VALIDATION TÉLÉPHONE INTERNATIONAL
public function validerTelephone($telephone, $obligatoire = false) {
$telephone = $this->nettoyer($telephone);
if (empty($telephone)) {
if ($obligatoire) {
$this->erreurs[] = "Téléphone obligatoire";
}
return !$obligatoire;
}
// Supprimer tous les caractères non numériques sauf +
$tel_clean = preg_replace('/[^\d+]/', '', $telephone);
// Validation format international
$patterns = [
'/^\+33[1-9]\d{8}$/', // France
'/^\+1\d{10}$/', // US/Canada
'/^\+44\d{10,11}$/', // UK
'/^\+49\d{10,11}$/', // Allemagne
'/^\+[1-9]\d{7,14}$/' // International générique
];
$valide = false;
foreach ($patterns as $pattern) {
if (preg_match($pattern, $tel_clean)) {
$valide = true;
break;
}
}
if (!$valide) {
$this->erreurs[] = "Format téléphone invalide (format international requis)";
}
$this->donnees_nettoyees['telephone'] = $tel_clean;
return $valide;
}
// VALIDATION MESSAGES/COMMENTAIRES
public function validerMessage($message, $min = 10, $max = 1000) {
$message = $this->nettoyer($message);
if (empty($message)) {
$this->erreurs[] = "Message obligatoire";
return false;
}
$longueur = strlen($message);
if ($longueur < $min || $longueur > $max) {
$this->erreurs[] = "Message doit contenir $min-$max caractères";
}
// Détection spam basique
if ($this->detecterSpam($message)) {
$this->erreurs[] = "Message détecté comme spam";
}
$this->donnees_nettoyees['message'] = $this->protegerXSS($message);
return empty($this->erreurs);
}
// VALIDATION DATE
public function validerDate($date, $format = 'Y-m-d') {
if (empty($date)) {
$this->erreurs[] = "Date obligatoire";
return false;
}
$date_obj = DateTime::createFromFormat($format, $date);
if (!$date_obj || $date_obj->format($format) !== $date) {
$this->erreurs[] = "Format de date invalide";
return false;
}
// Vérifier plage raisonnable
$maintenant = new DateTime();
$min = new DateTime('1900-01-01');
$max = new DateTime('+10 years');
if ($date_obj < $min || $date_obj > $max) {
$this->erreurs[] = "Date hors de la plage autorisée";
}
$this->donnees_nettoyees['date'] = $date;
return empty($this->erreurs);
}
// VALIDATION URL
public function validerURL($url, $obligatoire = false) {
$url = $this->nettoyer($url);
if (empty($url)) {
if ($obligatoire) {
$this->erreurs[] = "URL obligatoire";
}
return !$obligatoire;
}
if (!filter_var($url, FILTER_VALIDATE_URL)) {
$this->erreurs[] = "Format URL invalide";
return false;
}
// Vérifier protocoles autorisés
$protocoles_autorises = ['http', 'https'];
$protocole = parse_url($url, PHP_URL_SCHEME);
if (!in_array($protocole, $protocoles_autorises)) {
$this->erreurs[] = "Protocole URL non autorisé";
}
$this->donnees_nettoyees['url'] = $url;
return empty($this->erreurs);
}
// DÉTECTION SPAM BASIQUE
private function detecterSpam($texte) {
$mots_spam = ['viagra', 'casino', 'lottery', 'winner', 'click here', 'free money'];
$texte_lower = strtolower($texte);
foreach ($mots_spam as $mot) {
if (strpos($texte_lower, $mot) !== false) {
return true;
}
}
// Trop de liens
if (substr_count($texte_lower, 'http') > 2) {
return true;
}
// Trop de majuscules
if (strlen(preg_replace('/[^A-Z]/', '', $texte)) > strlen($texte) * 0.5) {
return true;
}
return false;
}
// VALIDATION DOMAINE
private function validerDomaine($domaine) {
if (empty($domaine)) return false;
// Validation format
if (!preg_match('/^[a-zA-Z0-9.-]+$/', $domaine)) {
return false;
}
// Vérifier existence DNS (optionnel)
return checkdnsrr($domaine, 'MX') || checkdnsrr($domaine, 'A');
}
// RÉCUPÉRER ERREURS
public function getErreurs() {
return $this->erreurs;
}
// RÉCUPÉRER DONNÉES NETTOYÉES
public function getDonneesNettoyees() {
return $this->donnees_nettoyees;
}
// RÉINITIALISER
public function reset() {
$this->erreurs = [];
$this->donnees_nettoyees = [];
}
}
// EXEMPLE D'UTILISATION
$validateur = new ValidateurSecurise();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Validation complète du formulaire
$validateur->validerNom($_POST['nom'] ?? '', 'nom');
$validateur->validerNom($_POST['prenom'] ?? '', 'prenom');
$validateur->validerEmail($_POST['email'] ?? '');
$validateur->validerTelephone($_POST['telephone'] ?? '');
$validateur->validerMessage($_POST['message'] ?? '');
if (!empty($_POST['password'])) {
$validateur->validerMotDePasse($_POST['password'], $_POST['password_confirm'] ?? null);
}
$erreurs = $validateur->getErreurs();
if (empty($erreurs)) {
$donnees = $validateur->getDonneesNettoyees();
// Traitement sécurisé des données validées
echo "✅ Formulaire validé avec succès !";
} else {
echo "❌ Erreurs : " . implode(', ', $erreurs);
}
}
?>
Protégez les sessions contre le vol, la fixation et le détournement.
Un exemple de formulaire intégrant toutes les protections de sécurité vues précédemment.
<?php
/**
* FORMULAIRE ULTRA-SÉCURISÉ
* Intègre TOUTES les protections de sécurité
*/
session_start();
// Configuration sécurisée
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1);
ini_set('session.cookie_samesite', 'Strict');
// Classes de sécurité (inclure les classes précédentes)
require_once 'ValidateurSecurise.php';
require_once 'UploadSecurise.php';
class FormulaireSuperSecurise {
private $validateur;
private $uploader;
private $max_tentatives = 5;
private $delai_tentatives = 300; // 5 minutes
public function __construct() {
$this->validateur = new ValidateurSecurise();
$this->uploader = new UploadSecurise();
// CSP Header
header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'");
// Autres headers de sécurité
header("X-Frame-Options: DENY");
header("X-Content-Type-Options: nosniff");
header("X-XSS-Protection: 1; mode=block");
header("Referrer-Policy: strict-origin-when-cross-origin");
}
public function traiterFormulaire() {
// 1. VÉRIFICATION MÉTHODE
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
return $this->erreur("Méthode non autorisée");
}
// 2. RATE LIMITING
if (!$this->verifierRateLimit()) {
return $this->erreur("Trop de tentatives. Réessayez plus tard.");
}
// 3. HONEYPOT (piège à bots)
if (!empty($_POST['website'])) {
$this->journaliser("BOT_DETECTE", $_SERVER['REMOTE_ADDR']);
return $this->erreur("Formulaire invalide");
}
// 4. VÉRIFICATION CSRF
if (!$this->verifierCSRF($_POST['csrf_token'] ?? '')) {
return $this->erreur("Token CSRF invalide");
}
// 5. VALIDATION SESSION
if (!$this->validerSession()) {
return $this->erreur("Session invalide");
}
// 6. VALIDATION CAPTCHA (si implémenté)
if (!$this->verifierCaptcha($_POST['captcha'] ?? '')) {
return $this->erreur("Captcha invalide");
}
// 7. VALIDATION COMPLÈTE DES DONNÉES
$this->validateur->reset();
$this->validateur->validerNom($_POST['nom'] ?? '', 'nom');
$this->validateur->validerNom($_POST['prenom'] ?? '', 'prenom');
$this->validateur->validerEmail($_POST['email'] ?? '');
$this->validateur->validerTelephone($_POST['telephone'] ?? '');
$this->validateur->validerMessage($_POST['message'] ?? '');
$erreurs = $this->validateur->getErreurs();
if (!empty($erreurs)) {
return $this->erreur("Données invalides: " . implode(', ', $erreurs));
}
// 8. UPLOAD SÉCURISÉ (si fichier)
$fichier_uploade = null;
if (isset($_FILES['piece_jointe']) && $_FILES['piece_jointe']['error'] === UPLOAD_ERR_OK) {
$resultat_upload = $this->uploader->uploadFichier($_FILES['piece_jointe']);
if (!$resultat_upload['success']) {
return $this->erreur("Upload échoué: " . implode(', ', $resultat_upload['erreurs']));
}
$fichier_uploade = $resultat_upload['fichier'];
}
// 9. SAUVEGARDE SÉCURISÉE
$donnees = $this->validateur->getDonneesNettoyees();
$donnees['fichier'] = $fichier_uploade;
$donnees['ip'] = $_SERVER['REMOTE_ADDR'];
$donnees['user_agent'] = substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 255);
$donnees['timestamp'] = time();
if ($this->sauvegarderContact($donnees)) {
// 10. NETTOYAGE ET SÉCURITÉ POST-TRAITEMENT
$this->resetRateLimit(); // Reset compteur en cas de succès
$this->regenererCSRF();
// 11. JOURNALISATION
$this->journaliser("CONTACT_ENVOYE", $donnees['email']);
return $this->succes("Message envoyé avec succès !");
} else {
return $this->erreur("Erreur lors de la sauvegarde");
}
}
private function verifierRateLimit() {
$ip = $_SERVER['REMOTE_ADDR'];
$cle = "rate_limit_$ip";
if (!isset($_SESSION[$cle])) {
$_SESSION[$cle] = ['count' => 0, 'time' => time()];
}
$data = $_SESSION[$cle];
// Reset si délai écoulé
if (time() - $data['time'] > $this->delai_tentatives) {
$_SESSION[$cle] = ['count' => 1, 'time' => time()];
return true;
}
// Vérifier limite
if ($data['count'] >= $this->max_tentatives) {
return false;
}
$_SESSION[$cle]['count']++;
return true;
}
private function resetRateLimit() {
$ip = $_SERVER['REMOTE_ADDR'];
unset($_SESSION["rate_limit_$ip"]);
}
private function verifierCSRF($token) {
return isset($_SESSION['csrf_token']) &&
hash_equals($_SESSION['csrf_token'], $token);
}
private function regenererCSRF() {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
private function genererCSRF() {
if (!isset($_SESSION['csrf_token'])) {
$this->regenererCSRF();
}
return $_SESSION['csrf_token'];
}
private function validerSession() {
// Vérifier IP
if (isset($_SESSION['ip'])) {
if ($_SESSION['ip'] !== $_SERVER['REMOTE_ADDR']) {
session_destroy();
return false;
}
} else {
$_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
}
// Régénération périodique
if (!isset($_SESSION['created'])) {
$_SESSION['created'] = time();
} elseif (time() - $_SESSION['created'] > 1800) {
session_regenerate_id(true);
$_SESSION['created'] = time();
}
return true;
}
private function verifierCaptcha($reponse) {
// Implémentation simple - en production, utilisez reCAPTCHA
return isset($_SESSION['captcha']) &&
strtolower($reponse) === strtolower($_SESSION['captcha']);
}
private function genererCaptcha() {
$operations = [
['question' => 'Combien font 5 + 3 ?', 'reponse' => '8'],
['question' => 'Combien font 10 - 4 ?', 'reponse' => '6'],
['question' => 'Combien font 2 x 4 ?', 'reponse' => '8'],
['question' => 'Quelle est la couleur du ciel ?', 'reponse' => 'bleu']
];
$operation = $operations[array_rand($operations)];
$_SESSION['captcha'] = $operation['reponse'];
return $operation['question'];
}
private function sauvegarderContact($donnees) {
// Utiliser PDO avec requêtes préparées
try {
$pdo = new PDO('sqlite:contacts_securises.db');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Créer table si nécessaire
$pdo->exec("CREATE TABLE IF NOT EXISTS contacts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nom TEXT NOT NULL,
prenom TEXT NOT NULL,
email TEXT NOT NULL,
telephone TEXT,
message TEXT NOT NULL,
fichier TEXT,
ip TEXT NOT NULL,
user_agent TEXT,
timestamp INTEGER NOT NULL
)");
$sql = "INSERT INTO contacts (nom, prenom, email, telephone, message, fichier, ip, user_agent, timestamp)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
$stmt = $pdo->prepare($sql);
return $stmt->execute([
$donnees['nom'],
$donnees['prenom'],
$donnees['email'],
$donnees['telephone'],
$donnees['message'],
$donnees['fichier'],
$donnees['ip'],
$donnees['user_agent'],
$donnees['timestamp']
]);
} catch (PDOException $e) {
error_log("Erreur BDD: " . $e->getMessage());
return false;
}
}
private function journaliser($action, $details) {
$log = date('Y-m-d H:i:s') . " - $action - " .
$_SERVER['REMOTE_ADDR'] . " - $details\n";
file_put_contents('security_logs.txt', $log, FILE_APPEND | LOCK_EX);
}
private function erreur($message) {
return ['success' => false, 'message' => htmlspecialchars($message)];
}
private function succes($message) {
return ['success' => true, 'message' => htmlspecialchars($message)];
}
public function afficherFormulaire() {
$captcha_question = $this->genererCaptcha();
$csrf_token = $this->genererCSRF();
return "
<form method='POST' enctype='multipart/form-data' id='contact-securise'>
<!-- Protection CSRF -->
<input type='hidden' name='csrf_token' value='$csrf_token'>
<!-- Honeypot anti-bot -->
<input type='text' name='website' style='display:none'>
<div>
<label for='nom'>Nom *</label>
<input type='text' id='nom' name='nom' required maxlength='50'>
</div>
<div>
<label for='prenom'>Prénom *</label>
<input type='text' id='prenom' name='prenom' required maxlength='50'>
</div>
<div>
<label for='email'>Email *</label>
<input type='email' id='email' name='email' required maxlength='254'>
</div>
<div>
<label for='telephone'>Téléphone</label>
<input type='tel' id='telephone' name='telephone'>
</div>
<div>
<label for='message'>Message *</label>
<textarea id='message' name='message' required minlength='10' maxlength='1000'></textarea>
</div>
<div>
<label for='piece_jointe'>Pièce jointe</label>
<input type='file' id='piece_jointe' name='piece_jointe'
accept='.jpg,.jpeg,.png,.gif,.pdf,.txt'>
</div>
<div>
<label for='captcha'>$captcha_question</label>
<input type='text' id='captcha' name='captcha' required>
</div>
<button type='submit'>Envoyer (Sécurisé)</button>
</form>";
}
}
// UTILISATION
$formulaire = new FormulaireSuperSecurise();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$resultat = $formulaire->traiterFormulaire();
echo $resultat['success'] ?
"<div class='success'>✅ " . $resultat['message'] . "</div>" :
"<div class='error'>❌ " . $resultat['message'] . "</div>";
}
echo $formulaire->afficherFormulaire();
?>
Vous connaissez maintenant toutes les vulnérabilités des formulaires web et comment créer des applications PHP ultra-sécurisées. Passez aux sujets avancés !
Protection XSS • Anti-CSRF • SQL Injection • Upload Sécurisé • Rate Limiting • Sessions Blindées • Détection Malware • Validation Stricte