Skip to content

Défense contre l'injection SQL

Les méthodes de base de données de NENE2 (execute, insert, fetchOne, fetchAll) utilisent en interne des requêtes préparées PDO. Toute valeur passée dans le tableau $parameters est liée comme un paramètre PDO — jamais interpolée dans la chaîne SQL.

Sûr par défaut : paramètres de valeur

php
// Toutes les valeurs passent par la liaison PDO — immunisé aux injections quelle que soit leur contenu
$product = $this->db->fetchOne(
    'SELECT * FROM products WHERE id = ?',
    [$userId],
);

// Recherche LIKE — wildcard dans le littéral SQL, valeur liée séparément
$rows = $this->db->fetchAll(
    "SELECT * FROM products WHERE name LIKE '%' || ? || '%'",
    [$searchQuery],
);

Les payloads classiques (' OR '1'='1, '; DROP TABLE products; --, UNION SELECT ...) deviennent des chaînes de recherche littérales car PDO ne les interpole jamais dans le SQL.

Le piège ORDER BY — liste blanche requise

PDO ne peut pas paramétrer les noms de colonnes ni les éléments structuraux SQL. ORDER BY ? ne fonctionne pas — il lie une valeur de chaîne littérale, pas une référence de colonne.

Si un développeur met directement l'entrée utilisateur dans ORDER BY, cela devient un vecteur d'injection :

php
// NON SÛR — ne jamais faire ceci
$sort = QueryStringParser::string($request, 'sort') ?? 'id';
$rows = $this->db->fetchAll("SELECT * FROM products ORDER BY {$sort} ASC");
// ?sort=id;+DROP+TABLE+products;+-- exécute le DROP

Toujours valider contre une liste blanche explicite avant d'interpoler les noms de colonnes :

php
private const array ALLOWED_SORT_FIELDS = ['id', 'name', 'price', 'created_at'];

public function list(string $sortField, string $sortDir): array
{
    if (!in_array($sortField, self::ALLOWED_SORT_FIELDS, true)) {
        throw new InvalidSortFieldException("Invalid sort field: {$sortField}");
    }

    // Uniquement ASC ou DESC — normaliser, jamais interpoler l'entrée utilisateur brute
    $dir  = strtolower($sortDir) === 'desc' ? 'DESC' : 'ASC';
    $rows = $this->db->fetchAll(
        "SELECT * FROM products ORDER BY {$sortField} {$dir}",
    );

    return $rows;
}

Le même principe s'applique à tout élément structurel SQL : noms de tables, noms de colonnes dans GROUP BY, HAVING, INSERT INTO ... (col1, col2) — aucun de ces éléments ne peut être lié comme paramètre PDO. Valider avec une liste blanche avant d'interpoler.

Clause IN avec longueur variable

PDO ne supporte pas la liaison d'une liste de longueur variable directement. Construire la liste de placeholders explicitement :

php
$ids          = [1, 2, 3];
$placeholders = implode(', ', array_fill(0, count($ids), '?'));
$rows         = $this->db->fetchAll(
    "SELECT * FROM products WHERE id IN ({$placeholders})",
    $ids,
);

Résumé

Type d'entréeMéthode sûre
Valeur filtre (WHERE col = ?)Placeholder ? dans $parameters
Valeur LIKE'%' || ? || '%' — valeur dans $parameters
Colonne ORDER BYListe blanche in_array + interpoler seulement après validation
Direction ORDERNormaliser en littéral 'ASC' ou 'DESC'
Liste INConstruire les placeholders ? depuis count(), répandre le tableau comme params
Nom de table/colonneListe blanche uniquement — jamais accepter depuis l'entrée utilisateur

Publié sous licence MIT.