Procédure publique · ORM_CSHX2 · Utilitaires SQL

ORM_Quote

Prépare une valeur (chaîne, Date, DateHeure, Heure) en vue de son insertion dans une requête SQL : encadrement par les quotes, échappement des caractères spéciaux, gestion des valeurs NULL, formatage adapté au provider (MySQL · MariaDB · PostgreSQL). Protection contre l'injection SQL intégrée.

PUBLIQUE SURCHARGÉE SÉCURITÉ MySQL · MariaDB · PostgreSQL
01

📋 Description

ORM_Quote est une procédure surchargée qui prépare une valeur quel que soit son type pour insertion sûre dans une requête SQL. Elle existe en quatre variantes selon le type d'entrée — chaîne, Date, DateHeure, Heure — toutes retournant une chaîne directement injectable dans le texte d'une requête.

L'appel à ORM_Quote est la pratique recommandée chaque fois qu'une valeur est concaténée dans une requête SQL en dehors des méthodes ORM standard (qui appliquent le quoting automatiquement). C'est notamment indispensable pour les valeurs venant de saisies utilisateur, de fichiers externes, ou d'API tierces.

🎯 Quand utiliser cette procédure

1. Construire une clause WHERE manuellement avec des valeurs venues de l'utilisateur ou d'un calcul. Le quoting protège contre l'injection SQL et garantit le formatage correct.

2. Insérer une date / datetime / heure dans une requête. Le formatage est adapté au provider cible (MySQL · MariaDB · PostgreSQL) sans avoir à connaître les spécificités de chaque moteur.

3. Gérer correctement les valeurs NULL. Si la valeur est vide et que la colonne accepte NULL, la procédure retourne le mot-clé SQL NULL (sans quotes) au lieu d'une chaîne vide quotée.

ℹ️ Compatibilité multi-provider

Le formatage produit par ORM_Quote est compatible avec MySQL, MariaDB et PostgreSQL. Le provider courant est détecté automatiquement par le framework. Le code applicatif reste identique quel que soit le moteur cible.

02

🔑 Signatures

La procédure existe en quatre variantes selon le type de la valeur à quoter. La résolution est automatique via la surcharge WLangage : il suffit de passer la valeur, le framework choisit la signature qui correspond.

Variante 1 — Chaîne

Déclaration
PROCÉDURE ORM_Quote(
  LOCAL sChaine est une chaîne,
  LOCAL nTaille est un entier = 0,
  LOCAL bNullable est un booléen = Faux,
  LOCAL bModeStrict est un booléen = Vrai
) : chaîne
ParamètreTypeDéfautRôle
sChainechaîneValeur à quoter pour insertion SQL.
nTailleentier0Taille maximale de la chaîne après quoting. 0 = pas de limite. Utile pour respecter les contraintes de longueur d'une colonne SQL.
bNullablebooléenFauxVoir §04.
bModeStrictbooléenVraiActive la détection de patterns d'injection SQL sur la chaîne — voir §03.

Variante 2 — Date

Déclaration
PROCÉDURE ORM_Quote(
  LOCAL dDate est une Date,
  LOCAL bNullable est un booléen = Vrai,
  LOCAL bModeStrict est un booléen = Vrai
) : chaîne
ParamètreTypeDéfautRôle
dDateDateDate à quoter pour insertion SQL.
bNullablebooléenVraiVoir §04.
bModeStrictbooléenVraiActive la détection de dates aberrantes (sentinelles, plage déraisonnable) — voir §04.

Variante 3 — DateHeure

Déclaration
PROCÉDURE ORM_Quote(
  LOCAL dhDate est une DateHeure,
  LOCAL bNullable est un booléen = Vrai,
  LOCAL bModeStrict est un booléen = Vrai
) : chaîne
ParamètreTypeDéfautRôle
dhDateDateHeureDate et heure à quoter pour insertion SQL.
bNullablebooléenVraiVoir §04.
bModeStrictbooléenVraiActive la détection de dates aberrantes sur la partie date — voir §04.

Variante 4 — Heure

Déclaration
PROCÉDURE ORM_Quote(
  LOCAL hHeure est une Heure,
  LOCAL bNullable est un booléen = Vrai
) : chaîne
ParamètreTypeDéfautRôle
hHeureHeureHeure à quoter pour insertion SQL.
bNullablebooléenVraiVoir §04.
03

🛡️ Protection contre l'injection SQL

L'injection SQL est l'une des vulnérabilités applicatives les plus classiques. Elle survient quand une valeur fournie par l'utilisateur (ou venant d'une source externe non maîtrisée) est concaténée directement dans le texte d'une requête SQL sans contrôle. Un attaquant peut alors faire exécuter au moteur SQL des instructions qui n'étaient pas prévues par le développeur.

Le risque illustré

Voici le grand classique : une recherche par nom où le code concatène brutalement la saisie utilisateur dans la requête.

// ❌ DANGER : concaténation directe d'une saisie utilisateur sNom est une chaîne = SAI_NomUtilisateur // l'utilisateur a saisi : ' OR '1'='1 sRequête est une chaîne = "SELECT * FROM clients WHERE NOM = '" + sNom + "'" // → Requête réellement envoyée à la base : // SELECT * FROM clients WHERE NOM = '' OR '1'='1' // → Le filtre WHERE devient toujours vrai. // → TOUS les clients sont retournés au lieu d'un seul.

Variantes plus graves : un attaquant peut combiner cette technique avec UNION SELECT pour exfiltrer des données d'autres tables, ou avec ; pour enchaîner des instructions destructrices (DROP TABLE, DELETE...). Toutes ces attaques exploitent le même défaut initial : la valeur utilisateur n'a pas été quotée.

La protection apportée par ORM_Quote

En faisant transiter la valeur par ORM_Quote avec bModeStrict = Vrai (valeur par défaut), on bénéficie de plusieurs protections cumulées :

ProtectionEffet
Encadrement par les quotes SQLLa valeur est entourée de '...' de manière correcte, l'appelant n'a pas à les écrire lui-même.
Échappement des caractères spéciauxLes apostrophes, antislashs et autres caractères dangereux dans la valeur sont échappés selon les règles du provider, empêchant la « casse » de la chaîne.
Détection de patterns d'injectionEn mode strict, la procédure détecte les signatures connues d'attaques SQL (voir catégories ci-dessous) et neutralise la requête sans exécuter l'attaque.
Détection de séquences suspectesSéquences de quotes multiples consécutives, octets nuls, caractères de contrôle anormaux dans la valeur sont également détectés et neutralisés.
Compatibilité providerLes règles d'échappement diffèrent légèrement entre MySQL/MariaDB et PostgreSQL — la procédure applique celles du provider courant.

Catégories de patterns détectés

Le mode strict couvre les principales familles de techniques d'injection SQL connues :

CatégorieType d'attaque visé
Commandes destructricesTentatives d'exécution d'instructions de modification de schéma ou de données massives.
Bypass d'authentificationManipulation des conditions logiques pour contourner les contrôles d'accès.
Injections UNIONExtraction de données depuis d'autres tables via concaténation de jeux de résultats.
Stacked queriesEnchaînement de plusieurs instructions dans une même requête.
Commentaires SQLTerminaison prématurée d'une requête par insertion de marqueurs de commentaire.
Time-based blindAttaques par mesure de temps de réponse (fonctions de pause).
Boolean-based blindAttaques par observation de la véracité de conditions logiques.
Information disclosureTentatives d'accès aux métadonnées système.
File accessTentatives de lecture ou écriture de fichiers via le moteur SQL.

Que se passe-t-il quand un pattern est détecté ?

Le retour de ORM_Quote est alors la chaîne Null (sans quotes). Concaténée dans la requête finale, cette valeur est interprétée par le moteur SQL comme le mot-clé NULL. La requête s'exécute donc sans erreur SQL, mais la comparaison à NULL retourne toujours UNKNOWN en SQL — qui se comporte comme faux dans une clause WHERE. Conséquence : la requête retourne 0 ligne.

🤫 Neutralisation silencieuse

L'attaquant ne reçoit aucun message d'erreur exploitable — la requête semble simplement n'avoir rien trouvé, comme si les critères de recherche n'avaient produit aucun résultat. Ce comportement évite de divulguer l'existence du mécanisme de détection à un attaquant qui pourrait alors essayer de le contourner. La trace de la tentative est en revanche enregistrée côté serveur pour que l'exploitation puisse en être informée.

L'exemple précédent, sécurisé

// ✅ SÉCURISÉ : passage par ORM_Quote (mode strict actif par défaut) sNom est une chaîne = SAI_NomUtilisateur // même contenu malicieux : ' OR '1'='1 sRequête est une chaîne = "SELECT * FROM clients WHERE NOM = " + ORM_Quote(sNom) // → Pattern détecté : ORM_Quote retourne la chaîne Null (sans quotes). // → Requête réellement envoyée à la base : // SELECT * FROM clients WHERE NOM = Null // → Le moteur SQL interprète Null comme le mot-clé NULL. // → La comparaison "= NULL" produit toujours UNKNOWN → 0 ligne retournée. // → Tentative d'injection neutralisée silencieusement.
🛑 Règle générale

Toute valeur qui n'est pas une constante littérale écrite directement dans le code source doit transiter par ORM_Quote avant d'être concaténée dans une requête SQL. Cela inclut, sans s'y limiter : saisies utilisateur (champs IHM, paramètres GET/POST), données venant d'un fichier externe (CSV, JSON, XML), réponses d'API tierces, contenu d'un presse-papiers, données venant d'une autre base que celle du framework.

Les méthodes standard de l'ORM (mth_ChargerSelonClauseWhere, mth_Enregistrer, mth_EffacerSelonID, etc.) appliquent ORM_Quote automatiquement sur les valeurs des membres mappés — le quoting manuel n'est nécessaire que dans le code applicatif qui construit du SQL en dehors de ces méthodes.

04

🧭 Comportement et NULL

Paramètre bNullable

Contrôle le traitement des valeurs vides : si Vrai, elles sont converties en NULL SQL (mot-clé sans quotes). Si Faux, la valeur est traitée littéralement comme une chaîne vide quotée.

Les valeurs par défaut diffèrent selon la variante :

VarianteDéfaut bNullableJustification
ChaîneFauxUne chaîne vide est souvent une valeur métier valide (« nom optionnel non renseigné » par exemple).
DateVraiUne date vide est généralement une absence de valeur, à représenter par NULL en base.
DateHeureVraiIdem — une DateHeure vide signale typiquement une donnée non encore saisie.
HeureVraiIdem.

Paramètre bModeStrict

Active les contrôles de validation. Le comportement diffère selon la variante :

VarianteEffet de bModeStrict = Vrai
ChaîneDétection des patterns d'injection SQL connus (voir §03).
Date / DateHeureDétection des dates aberrantes (années sentinelles, plage déraisonnable).
HeureNon applicable (paramètre absent de la signature).

Conserver la valeur par défaut (Vrai) sauf besoin spécifique justifié.

Validation des dates en mode strict

Pour les variantes Date et DateHeure, le mode strict détecte les dates dont la valeur est plausible techniquement mais aberrante dans un contexte applicatif normal :

CatégorieDescription
Années sentinellesValeurs souvent utilisées pour signaler « infini » ou tester les contrôles de saisie.
Plage trop ancienneDates antérieures à un seuil considéré comme déraisonnable pour la plupart des applications métier.
Plage trop futureDates trop éloignées dans le futur pour avoir une signification métier réaliste.

Quand une date est jugée aberrante, le retour est "Null" (chaîne) — comportement identique à celui décrit pour la chaîne en cas de pattern détecté.

⚠️ Date structurellement invalide

Pour les variantes Date, DateHeure et Heure : une valeur structurellement invalide (par exemple 32 février, ou une Heure mal formée) provoque un retour "NULL" quel que soit l'état de bNullable. Le paramètre bNullable ne s'applique qu'aux dates / heures valides mais vides.

05

📤 Valeur de retour

Les quatre variantes retournent une chaîne directement injectable dans le texte d'une requête SQL.

CasForme du retour
Chaîne non vide'valeur échappée'
Chaîne vide, bNullable = Faux'' (chaîne vide quotée)
Chaîne vide, bNullable = VraiNULL (mot-clé SQL)
Chaîne avec pattern d'injection détecté en mode strictNull (chaîne sans quotes — voir §03)
Date renseignée'AAAA-MM-JJ'
DateHeure renseignée'AAAA-MM-JJ HH:MM:SS'
Heure renseignée'HH:MM:SS'
Date / DateHeure / Heure vide ou invalideNULL (mot-clé SQL — voir §04)
Date / DateHeure aberrante en mode strictNull (chaîne sans quotes)
ℹ️ Quotes incluses dans le retour

Les quotes SQL sont déjà incluses dans la chaîne retournée. Lors de la concaténation dans une requête, ne pas en rajouter manuellement : "WHERE NOM = " + ORM_Quote(sNom) et non "WHERE NOM = '" + ORM_Quote(sNom) + "'". Cette règle vaut aussi pour NULL : la chaîne "NULL" est retournée sans quotes, prête à être concaténée telle quelle.

06

💡 Exemples

Mode 1 — clause WHERE avec valeur chaîne

// ── Recherche d'un client par nom ─────────────────────────────── sNom est une chaîne = SAI_NomRecherché clClient:p_sClauseWhere = "clients.NOM = " + ORM_Quote(sNom) clClient:mth_ChargerSelonClauseWhere()

Mode 2 — clause WHERE avec date

// ── Commandes du jour ─────────────────────────────────────────── clCommande:p_sClauseWhere = "commandes.DATE_CMD = " + ORM_Quote(DateSys()) clCommande:mth_ChargerSelonClauseWhere() // ── Commandes entre deux dates ────────────────────────────────── dDébut est une Date = "20260101" dFin est une Date = "20260131" clCommande:p_sClauseWhere = "commandes.DATE_CMD BETWEEN " + ORM_Quote(dDébut) + " AND " + ORM_Quote(dFin) clCommande:mth_ChargerSelonClauseWhere()

Mode 3 — gestion d'une valeur potentiellement vide (NULL)

// ── Le champ optionnel "Date de relance" peut être non renseigné ─ dRelance est une Date = SAI_DateRelance // peut être vide // ── ORM_Quote retourne 'NULL' si dRelance est vide (bNullable = Vrai par défaut) ─ clClient:p_sClauseWhere = "clients.DATE_RELANCE = " + ORM_Quote(dRelance) // → "clients.DATE_RELANCE = '20260315'" (date renseignée) // → "clients.DATE_RELANCE = NULL" (date vide) clClient:mth_ChargerSelonClauseWhere()

Mode 4 — protection injection SQL avec saisie utilisateur

// ── Recherche libre depuis un champ de saisie ─────────────────── sCritère est une chaîne = SAI_RechercheLibre // contenu non maîtrisé // ✅ Toujours quoter la valeur avant concaténation clArticle:p_sClauseWhere = "articles.DESIGNATION LIKE " + ORM_Quote("%" + sCritère + "%") clArticle:mth_ChargerSelonClauseWhere() // → Si SAI_RechercheLibre contient un pattern d'injection connu, // ORM_Quote retourne la chaîne "Null" (mode strict actif par défaut). // → La requête s'exécute sans erreur et retourne 0 ligne (cf. §03). // → Si la saisie est légitime, l'échappement standard s'applique // et la recherche fonctionne normalement.

Mode 5 — combinaison avec une clause IN générée par mth_ValeursDistinctes

// ── Pas besoin de quoter le résultat de mth_ValeursDistinctes ── // avec bChainesQuotées = Vrai : le quoting est déjà fait. sIDsClients est une chaîne = clCommande:mth_ValeursDistinctes("m_ID_CLIENT") SI SansEspace(sIDsClients) <> "" ALORS clClient:p_sClauseWhere = "clients.ID_CLIENT IN (" + sIDsClients + ")" clClient:mth_ChargerSelonClauseWhere() FIN // ⚠️ En revanche, si le tableau d'IDs vient d'une source externe // (fichier CSV, paramètre HTTP), il FAUT le quoter manuellement — // typiquement en bouclant sur les valeurs et en appelant ORM_Quote // sur chacune avant concaténation.