flawopen.com/SQL Injection/ID numériques
Pourquoi le formatage de chaîne est-il dangereux, même pour les ID numériques ?
Le risque ne dépend pas du sens réel de la valeur : la requête est construite en collant du texte. « C’est toujours un nombre » est une hypothèse sur les données, pas une garantie appliquée par le code.
Quand l’hypothèse échoue
Un paramètre d’URL ou de formulaire est transmis comme texte, quel que soit le type attendu par la colonne en base. Sans validation explicite, le framework n’empêche pas l’envoi de id=1 OR 1=1 à la place d’un nombre avant que le code le reçoive.
Un code sûr aujourd’hui parce que l’ID vient toujours d’une source interne fiable peut devenir vulnérable si un nouvel appelant transmet une valeur utilisateur à la même fonction. La faille était latente, en attente de ce changement.
Dans les langages à typage dynamique notamment, une valeur qui semble numérique dans les journaux peut rester une chaîne contenant des caractères supplémentaires. Sans validation explicite, le type attendu ne garantit pas le type reçu.
Paramétrez la requête, même après validation du type
Validez l’ID selon le format attendu par l’application, puis transmettez-le via l’API de paramètres du pilote de base de données. La validation protège le contrat d’entrée ; la paramétrisation empêche l’interprétation de l’entrée comme syntaxe SQL. Le comportement du pilote sur le protocole peut varier : ne supposez pas que tous transmettent les valeurs de la même manière. Les paramètres PostgreSQL ne représentent pas les identifiants de table ou de colonne ; choisissez la structure dynamique de la requête dans une liste fixe autorisée par l’application.
Lier la valeur numérique et valider sa signification
Validez l’ID comme entier positif sûr si c’est le type attendu par l’application, mais liez-le tout de même comme valeur de requête. La validation protège le contrat d’entrée de l’application ; la paramétrisation empêche l’entrée de devenir de la syntaxe SQL. Les paramètres PostgreSQL ne remplacent pas les identifiants de table ou de colonne : choisissez toute structure de requête dynamique dans une liste fixe autorisée par l’application.
const rawId = new URL(request.url).searchParams.get("id");
const result = await pool.query(
`SELECT id FROM users WHERE id = ${rawId}`
);
const rawId = new URL(request.url).searchParams.get("id");
if (rawId === null || !/^[0-9]+$/.test(rawId)) {
return new Response("Invalid id", { status: 400 });
}
const id = Number(rawId);
if (!Number.isSafeInteger(id) || id < 1) {
return new Response("Invalid id", { status: 400 });
}
const result = await pool.query(
"SELECT id FROM users WHERE id = $1",
Array.of(id)
);
Références
- flawopen.com — SQL Injection (toutes les langues)
- Qu'est-ce qu'une requête paramétrée ?
- node-postgres — Parameterized query API