Pourquoi Securiser une application PHP ?
Le développement d'application web est un processus qui exige une attention particulière à la sécurité. Dans le monde numérique actuel, il est essentiel que nous protégeions les données et l'intégrité de nos utilisateurs. En tant qu'évolutif du langage PHP, il est crucial pour vous comme développeur d'être conscient des risques potentiels et de mettre en œuvre des pratiques de sécurité robustes dès le début.
Un cas concret de la nécessité de sécuriser une application PHP se trouve dans l'industrie financière. Les informations personnelles et financières des utilisateurs sont sensibles, et leur protection est une priorité absolue. Une application non sécurisée pourrait être vulnérable à des attaques telles que les injections SQL, la perte de données ou même l'accès non autorisé aux comptes utilisateurs.
Prerequis
Avant de commencer à coder, il est important d'être équipé de certaines connaissances et outils. Voici ce dont vous aurez besoin :
- PHP : La version recommandée est la 7.4 ou ultérieure.
- Un environnement de développement local : XAMPP, WAMP ou MAMP pour Windows/Mac, ou Docker pour un environnement plus portable.
- Gestionnaire de paquets Composer : Pour gérer les dépendances PHP.
- IDE/Editeur de code : Visual Studio Code est une option populaire.
Concepts fondamentaux
1. Injection SQL
L'injection SQL est l'un des vulnérabilités les plus courantes dans les applications web. Elle se produit lorsque la saisie utilisateur n'est pas correctement traitée et que le code PHP génère une requête SQL dynamique.
## ❌ Mauvais : Injection SQL possible
$username = $_POST['username'];
$password = $_POST['password'];
$sql = "SELECT * FROM users WHERE username='$username' AND password='$password'";
$result = mysqli_query($conn, $sql);
Pour éviter cette vulnérabilité, utilisez des requêtes préparées.
## ✅ Correct : Utilisation de requêtes préparées
$stmt = $pdo->prepare('SELECT * FROM users WHERE username=? AND password=?');
$stmt->execute([$username, $password]);
$result = $stmt->fetchAll();
2. Cross-Site Scripting (XSS)
L'XSS est une vulnérabilité qui permet aux attaquants d'injecter du code JavaScript dans les pages web visibles par les autres utilisateurs.
## ❌ Mauvais : Injection XSS possible
$user_input = $_GET['input'];
echo "<p>User Input: $user_input</p>";
Pour prévenir l'XSS, utilisez la fonction htmlspecialchars() pour échapper le contenu utilisateur.
## ✅ Correct : Échappement des entrées utilisateurs
$user_input = htmlspecialchars($_GET['input'], ENT_QUOTES, 'UTF-8');
echo "<p>User Input: $user_input</p>";
3. Authentification et Autorisation
La gestion de l'authentification et de l'autorisation est essentielle pour protéger les ressources sensibles de votre application.
## ❌ Mauvais : Authentification simple sans vérification de rôle
if ($_POST['username'] == 'admin' && $_POST['password'] == 'secret') {
session_start();
$_SESSION['user'] = 'admin';
}
Pour une authentification sécurisée, utilisez des fonctions cryptées et des rôles d'utilisateur.
## ✅ Correct : Authentification sécurisée avec rôles utilisateurs
if (password_verify($_POST['password'], $hashed_password)) {
session_start();
$_SESSION['user'] = 'admin';
$_SESSION['role'] = 'admin';
}
4. Protection contre les attaques CSRF
Les attaques CSRF, ou Cross-Site Request Forgery, permettent aux attaquants d'effectuer des actions non autorisées sur le compte d'un utilisateur en tromplant l'utilisateur.
## ❌ Mauvais : Formulaires sans token CSRF
<form action="submit.php" method="post">
<input type="text" name="data">
<button type="submit">Submit</button>
</form>
Pour prévenir les attaques CSRF, utilisez un jeton CSRF généré et vérifié.
## ✅ Correct : Formulaires avec token CSRF
<form action="submit.php" method="post">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
<input type="text" name="data">
<button type="submit">Submit</button>
</form>
## Vérification du token CSRF
if ($_POST['csrf_token'] == $_SESSION['csrf_token']) {
// Traitement de la requête
}
Mise en pratique : Projet fil rouge
Dans cette section, nous allons construire un mini-projet complet et réaliste : un gestionnaire de tâches simple. Le projet comprendra les fonctionnalités suivantes :
- Inscription et connexion des utilisateurs.
- Ajout, modification et suppression de tâches.
Étape 1 : Initialisation du projet
Créez un nouveau dossier pour votre projet et initialisez un fichier composer.json.
mkdir task-manager
cd task-manager
echo '{}' > composer.json
Ajoutez les dépendances nécessaires.
{
"require": {
"php": "^7.4",
"pdo-mysql": "*"
}
}
Installez les dépendances avec Composer.
composer install
Étape 2 : Configuration de la base de données
Créez un fichier .env pour stocker vos informations de connexion à la base de données.
DB_HOST=localhost
DB_NAME=task_manager
DB_USER=root
DB_PASS=
Ajoutez un fichier config.php pour charger ces variables d'environnement.
<?php
require 'vendor/autoload.php';
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();
return [
'db' => [
'host' => $_ENV['DB_HOST'],
'name' => $_ENV['DB_NAME'],
'user' => $_ENV['DB_USER'],
'pass' => $_ENV['DB_PASS']
]
];
Étape 3 : Connexion à la base de données
Créez un fichier database.php pour gérer la connexion à la base de données.
<?php
$config = require __DIR__ . '/config.php';
try {
$pdo = new PDO(
"mysql:host={$config['db']['host']};dbname={$config['db']['name']}",
$config['db']['user'],
$config['db']['pass']
);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
die('Database connection failed: ' . $e->getMessage());
}
?>
Étape 4 : Modèle pour les tâches
Créez un fichier Task.php pour définir le modèle des tâches.
<?php
class Task {
private $pdo;
public function __construct($pdo) {
$this->pdo = $pdo;
}
public function create($title, $description) {
$stmt = $this->pdo->prepare('INSERT INTO tasks (title, description) VALUES (?, ?)');
return $stmt->execute([$title, $description]);
}
public function getTasks() {
$stmt = $this->pdo->query('SELECT * FROM tasks');
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
public function delete($id) {
$stmt = $this->pdo->prepare('DELETE FROM tasks WHERE id=?');
return $stmt->execute([$id]);
}
}
?>
Étape 5 : Vues
Créez des fichiers index.php, login.php et dashboard.php pour afficher les tâches, la connexion et le tableau de bord.
index.php
<?php
require 'config.php';
require 'database.php';
$pdo = new PDO(
"mysql:host={$config['db']['host']};dbname={$config['db']['name']}",
$config['db']['user'],
$config['db']['pass']
);
session_start();
if (isset($_SESSION['user'])) {
header('Location: dashboard.php');
exit;
}
require 'login.php';
?>
login.php
<?php
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$username = $_POST['username'];
$password = $_POST['password'];
// TODO: Authentification utilisateur
}
?>
<form method="post">
<input type="text" name="username" placeholder="Username">
<input type="password" name="password" placeholder="Password">
<button type="submit">Login</button>
</form>
dashboard.php
<?php
require 'config.php';
require 'database.php';
$pdo = new PDO(
"mysql:host={$config['db']['host']};dbname={$config['db']['name']}",
$config['db']['user'],
$config['db']['pass']
);
session_start();
if (!isset($_SESSION['user'])) {
header('Location: index.php');
exit;
}
$taskModel = new Task($pdo);
$tasks = $taskModel->getTasks();
// TODO: Afficher les tâches et le formulaire d'ajout
?>
Étape 6 : Traitement des formulaires
Ajoutez le traitement des formulaires dans dashboard.php.
<?php
require 'config.php';
require 'database.php';
$pdo = new PDO(
"mysql:host={$config['db']['host']};dbname={$config['db']['name']}",
$config['db']['user'],
$config['db']['pass']
);
session_start();
if (!isset($_SESSION['user'])) {
header('Location: index.php');
exit;
}
$taskModel = new Task($pdo);
$tasks = $taskModel->getTasks();
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
if (isset($_POST['add_task'])) {
$title = $_POST['title'];
$description = $_POST['description'];
$taskModel->create($title, $description);
header('Location: dashboard.php');
exit;
}
if (isset($_POST['delete_task'])) {
$id = $_POST['delete_task'];
$taskModel->delete($id);
header('Location: dashboard.php');
exit;
}
}
?>
<form method="post">
<input type="text" name="title" placeholder="Title">
<textarea name="description" placeholder="Description"></textarea>
<button type="submit" name="add_task">Add Task</button>
</form>
<table>
<tr>
<th>Title</th>
<th>Description</th>
<th>Action</th>
</tr>
<?php foreach ($tasks as $task): ?>
<tr>
<td><?php echo htmlspecialchars($task['title']); ?></td>
<td><?php echo htmlspecialchars($task['description']); ?></td>
<td>
<form method="post" style="display: inline;">
<input type="hidden" name="delete_task" value="<?php echo $task['id']; ?>">
<button type="submit">Delete</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</table>
Erreurs frequentes et debugging
1. Injection SQL
Erreur :
## Mauvais : Injection SQL possible
$username = $_POST['username'];
$password = $_POST['password'];
$sql = "SELECT * FROM users WHERE username='$username' AND password='$password'";
$result = mysqli_query($conn, $sql);
Correction :
## Correct : Utilisation de requêtes préparées
$stmt = $pdo->prepare('SELECT * FROM users WHERE username=? AND password=?');
$stmt->execute([$username, $password]);
$result = $stmt->fetchAll();
2. Cross-Site Scripting (XSS)
Erreur :
## Mauvais : Injection XSS possible
$user_input = $_GET['input'];
echo "<p>User Input: $user_input</p>";
Correction :
## Correct : Échappement des entrées utilisateurs
$user_input = htmlspecialchars($_GET['input'], ENT_QUOTES, 'UTF-8');
echo "<p>User Input: $user_input</p>";
3. Authentification simple
Erreur :
## Mauvais : Authentification simple sans vérification de rôle
if ($_POST['username'] == 'admin' && $_POST['password'] == 'secret') {
session_start();
$_SESSION['user'] = 'admin';
}
Correction :
## Correct : Authentification sécurisée avec rôles utilisateurs
if (password_verify($_POST['password'], $hashed_password)) {
session_start();
$_SESSION['user'] = 'admin';
$_SESSION['role'] = 'admin';
}
Pour aller plus loin
1. Sécurité des cookies
Utilisez les fonctionnalités de sécurité avancées pour les cookies, comme secure et httponly.
## Configuration de cookies sécurisés
session_set_cookie_params([
'lifetime' => 0,
'path' => '/',
'domain' => $_SERVER['HTTP_HOST'],
'secure' => true,
'httponly' => true,
]);
session_start();
2. Utilisation de middleware
Créez un système de middleware pour gérer les actions avant et après chaque requête.
class Middleware {
public function handle($request, $next) {
// Actions avant la requête
return $next($request);
}
}
$middleware = new Middleware();
$response = $middleware->handle($request, function($request) {
// Traitement de la requête
});
3. Sécurité des fichiers
Utilisez des fonctions sécurisées pour gérer les fichiers uploadés.
## Gestion des fichiers uploadés
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_FILES['file'])) {
$file = $_FILES['file'];
$target_dir = "uploads/";
$target_file = $target_dir . basename($file["name"]);
move_uploaded_file($file["tmp_name"], $target_file);
}
Défi pratique
Développez une fonction qui vérifie si une adresse e-mail est valide et sécurisée avant de l'utiliser dans votre application.
function isValidEmail($email) {
// Vérification basique
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return false;
}
// Extraction du domaine
$domain = explode('@', $email)[1];
// Vérification de la validité du domaine
if (checkdnsrr($domain, 'MX')) {
return true;
}
return false;
}
Ce tutoriel couvre les concepts fondamentaux de la sécurité en PHP et guide vous à travers le processus de création d'un projet complet. En suivant ces étapes et en appliquant ces pratiques de sécurité, vous serez bien équipé pour développer des applications web sécurisées et robustes.