Risques de sécurité

Les bases de données contiennent souvent des données sensibles et permettent des opérations dangereuses. Nette Database offre un certain nombre de fonctions de sécurité. Toutefois, il est essentiel de comprendre la différence entre les API sûres et non sûres.

Injection SQL

L'injection SQL est le risque de sécurité le plus sérieux lorsque l'on travaille avec des bases de données. Elle se produit lorsque des données utilisateur non vérifiées sont intégrées à une requête SQL. Un pirate peut alors injecter ses propres commandes SQL, ce qui lui permet d'obtenir ou de modifier des données dans la base de données.

// ❌ UNSAFE CODE - vulnérable à l'injection SQL
$database->query("SELECT * FROM users WHERE name = '$_GET[name]'");

// L'attaquant peut saisir quelque chose comme: ' OR '1'='1
// La requête résultante sera:
// SELECT * FROM users WHERE name = '' OR '1'='1'
// Cela renvoie tous les utilisateurs !

Il en va de même pour Database Explorer :

// ❌ UNSAFE CODE
$table->where('name = ' . $_GET['name']);
$table->where("name = '$_GET[name]'");

Requêtes paramétrées sûres

La manière la plus sûre d'insérer des valeurs dans les requêtes SQL est d'utiliser des requêtes paramétrées. Nette Database propose plusieurs façons de les utiliser.

Points d'interrogation

La méthode la plus simple est d'utiliser des points d'interrogation à la place du texte :

// ✅ Requêtes paramétrées sûres
$database->query('SELECT * FROM users WHERE name = ?', $_GET['name']);

// ✅ Condition de sécurité dans l'explorateur
$table->where('name = ?', $_GET['name']);

Il en va de même pour toutes les autres méthodes de l'Explorateur de bases de données qui permettent d'insérer des expressions avec des points d'interrogation et des paramètres.

Les valeurs doivent être de type scalaire (string, int, float, bool) ou null. Si, par exemple, $_GET['name'] est un tableau, Nette Database inclura tous ses éléments dans la requête SQL, ce qui peut être indésirable.

Tableaux de valeurs

Pour les clauses INSERT, UPDATE, ou WHERE, nous pouvons utiliser des tableaux de valeurs :

// ✅ INSERT sûr
$database->query('INSERT INTO users', [
	'name' => $_GET['name'],
	'email' => $_GET['email'],
]);

// ✅ UPDATE sûr
$database->query('UPDATE users SET', [
	'name' => $_GET['name'],
	'email' => $_GET['email'],
], 'WHERE id = ?', $_GET['id']);

Nette Database échappe automatiquement toutes les valeurs passées dans les requêtes paramétrées. Cependant, nous devons nous assurer que le type de données des paramètres est correct.

Les clés de tableau ne sont pas une API sûre

Si les valeurs des tableaux sont sûres, il n'en va pas de même pour les clés :

// ❌ UNSAFE CODE - les clés peuvent contenir une injection SQL
$database->query('INSERT INTO users', $_GET);
$database->query('SELECT * FROM users WHERE', $_GET);
$table->where($_GET);

Pour les commandes INSERT et UPDATE, il s'agit d'une faille de sécurité critique : un pirate pourrait insérer ou modifier n'importe quelle colonne de la base de données. Par exemple, il pourrait définir is_admin = 1 ou insérer des données arbitraires dans des colonnes sensibles.

Dans les conditions de WHERE, c'est encore plus dangereux car cela permet une énumération SQL – une technique pour récupérer progressivement des informations sur la base de données. Un attaquant pourrait tenter d'explorer les salaires des employés en injectant dans $_GET ce qui suit :

$_GET = ['salary >', 100000];   // commence à déterminer les fourchettes de salaires

Le principal problème, cependant, est que les conditions WHERE supportent les expressions SQL dans les clés :

// Utilisation légitime des opérateurs dans les clés
$table->where([
    'age > ?' => 18,
    'ROUND(score, ?) > ?' => [2, 75.5],
]);

// ❌ INCONNU: l'attaquant peut injecter son propre code SQL
$_GET = ['1) UNION SELECT name, salary FROM users WHERE (is_admin = ?' => 1];
$table->where($_GET); // permet à l'attaquant d'obtenir les salaires des administrateurs

Il s'agit encore une fois d'une injection SQL.

Liste blanche de colonnes

Si vous souhaitez permettre aux utilisateurs de choisir des colonnes, utilisez toujours une liste blanche :

// ✅ Traitement sécurisé - seules les colonnes autorisées
$allowedColumns = ['name', 'email', 'active'];
$values = array_intersect_key($_GET, array_flip($allowedColumns));

$database->query('INSERT INTO users', $values);

Identificateurs dynamiques

Pour les noms de tables et de colonnes dynamiques, utilisez l'espace réservé ?name:

// ✅ Utilisation sûre d'identifiants de confiance
$table = 'users';
$column = 'name';
$database->query('SELECT ?name FROM ?name', $column, $table);

// ❌ UNSAFE - ne jamais utiliser les données de l'utilisateur
$database->query('SELECT ?name FROM users', $_GET['column']);

Le symbole ?name ne doit être utilisé que pour les valeurs de confiance définies dans le code de l'application. Pour les valeurs fournies par l'utilisateur, utilisez à nouveau une liste blanche.

version: 4.0