Contournement d’authentification
admin' OR '1'='1Tente de contourner la connexion en rendant la condition WHERE toujours vraie
Les valeurs fournies par l’utilisateur ne sont jamais interpolées dans le texte SQL — elles sont liées via des placeholders de paramètres (spécifiques au dialecte, ex. $1 ou ?)
L’injection SQL reste l’une des vulnérabilités les plus critiques des applications web
Une injection SQL survient quand des données non fiables sont incorporées au texte SQL sans liaison de paramètres sûre. Des attaquants peuvent manipuler le sens de la requête pour accéder à des données non autorisées, modifier des enregistrements ou déclencher des opérations non désirées.
admin' OR '1'='1Tente de contourner la connexion en rendant la condition WHERE toujours vraie
' UNION SELECT password FROM users--Utilise UNION pour tenter d’extraire des données d’autres tables
'; DROP TABLE users; --Tente d’ajouter des instructions supplémentaires pour supprimer des données
Exploit : si les placeholders et les params ne correspondent pas, les contrôles d’autorisation peuvent utiliser les mauvaises valeursExemple : WHERE userId=$1 AND isAdmin=$2, mais params=[true, 123] au lieu de [123, true] peut accorder un accès admin à tort
Chaque requête passe par plusieurs couches de validation de sécurité
Toutes les valeurs fournies par l’utilisateur sont converties en paramètres liés. Les valeurs n’entrent pas dans le texte SQL comme littéraux.
Chaque nom de champ est validé contre les métadonnées du schéma Prisma avant la génération de la requête.
Les noms de tables, de colonnes et les alias sont validés et quotés/échappés de manière sûre si nécessaire.
Seuls des opérateurs connus et sûrs sont autorisés pour chaque type de champ.
Un ordre strict garantit que chaque placeholder correspond à exactement un paramètre dans l’ordre d’insertion, ce qui empêche les décalages.
Esquisses de preuve de résistance à l’injection sous les hypothèses de conception énoncées (liaison de paramètres, aucun contournement par SQL brut, usage correct du driver)
Pour toute valeur V fournie par l’utilisateur, il n’existe aucun chemin d’exécution où V apparaît comme texte SQL dans la chaîne de requête générée.
Par construction, tous les chemins de code qui traitent des valeurs utilisateur appellent addParameter(value), qui : 1. Stocke la valeur dans un tableau params séparé 2. Renvoie un jeton placeholder pour le texte SQL 3. Seul le jeton placeholder est ajouté à la chaîne SQL 4. Le driver de base de données reçoit séparément le texte SQL et les valeurs des paramètres ∴ Les valeurs utilisateur ne sont pas analysées comme de la syntaxe SQL
const addParameter = (params: any[], value: any) => {
params.push(value)
const index = params.length
return `$${index}`
}L’ensemble des noms de champs F de toute requête générée est un sous-ensemble des champs S définis dans le schéma, c.-à-d. F ⊆ S.
Pour chaque référence de champ : 1. Le nom du champ est extrait de l’objet de requête 2. Une recherche est effectuée dans les métadonnées du schéma 3. Si le champ n’existe pas dans le schéma, une erreur est levée 4. Seuls les champs validés atteignent la génération SQL ∴ Des noms de champs arbitraires ne peuvent pas apparaître dans le SQL
const validateField = (field: string, model: Model) => {
if (!model.fields.has(field)) {
throw new Error(`Field ${field} does not exist`)
}
return model.fields.get(field)
}Pour tout opérateur O appliqué au champ F, O appartient aux opérateurs autorisés pour le type T de F.
Algorithme de validation des opérateurs : 1. Extraire le type de champ T du schéma 2. Définir allowed_ops(T) = { opérateurs valides pour le type T } 3. Pour l’opérateur O de la requête : - Si O ∉ allowed_ops(T), lever une erreur - Sinon, appliquer l’opérateur avec liaison de paramètres ∴ Seuls des opérateurs adaptés au type peuvent être utilisés
const ALLOWED_OPS: Record<string, string[]> = {
String: ['contains', 'startsWith', 'endsWith'],
Int: ['lt', 'lte', 'gt', 'gte']
}
if (!ALLOWED_OPS[fieldType]?.includes(operator)) {
throw new Error('Invalid operator')
}Tous les identifiants SQL I sont validés pour exclure les caractères de contrôle et garantir une quotation/un échappement sûrs selon le dialecte.
Traitement des identifiants : 1. Rejet s’il contient des caractères de contrôle 2. Échappement des guillemets internes selon les règles du dialecte 3. Quotation des identifiants si nécessaire (mots réservés ou caractères spéciaux) 4. Application des limites de longueur d’identifiant propres au dialecte ∴ Les identifiants ne peuvent ni casser la syntaxe SQL ni introduire de jetons injectés
const quoteIdentifier = (id: string) => {
if (/[\x00-\x1F]/.test(id)) {
throw new Error('Invalid characters')
}
const escaped = id.replace(/"/g, '""')
const needsQuoting = /[^a-z0-9_]/i.test(id) || isReservedKeyword(id)
return needsQuoting ? `"${escaped}"` : id
}Pour chaque position de placeholder N émise dans le texte SQL, params[N-1] contient exactement la valeur prévue pour cette position, sans réordonnancement ni décalage.
Garantie d’ordre des paramètres : 1. Un tracker unique conserve l’ordre d’insertion 2. Chaque appel à add() ajoute la valeur à params et émet immédiatement le jeton placeholder suivant 3. La construction du texte SQL et celle de params avancent en synchronisation 4. Aucune opération de réordonnancement intermédiaire n’a lieu ∴ La correspondance un-à-un est maintenue de bout en bout
class ParameterTracker {
private params: any[] = []
add(value: any): string {
this.params.push(value)
return `$${this.params.length}`
}
getParams(): any[] {
return this.params
}
}
const tracker = new ParameterTracker()
const sql = `WHERE email = ${tracker.add(email)} AND age > ${tracker.add(age)}`
const params = tracker.getParams()La suite actuelle valide la protection contre les vecteurs d’injection SQL courants et les cas limites
const email = req.body.email
const sql = "SELECT * FROM users WHERE email = '" + email + "'"const email = req.body.email
const { sql, params } = toSQL('User', 'findMany', {
where: { email }
})
const result = { sql, params }Un suivi centralisé des paramètres garantit que chaque valeur fournie par l’utilisateur est liée par paramètre et que son ordre reste stable
class ParameterTracker {
private params: any[] = []
add(value: any): string {
this.params.push(value)
return `$${this.params.length}`
}
getParams(): any[] {
return this.params
}
}La validation basée sur le schéma empêche l’accès arbitraire aux champs
function validateField(
fieldName: string,
model: ModelInfo
): FieldInfo {
const field = model.fields.get(fieldName)
if (!field) {
throw new Error(
`Field "${fieldName}" does not exist`
)
}
return field
}Une validation et une quotation des identifiants adaptées au dialecte empêchent l’injection de syntaxe via les identifiants
function quoteIdentifier(identifier: string): string {
if (/[\x00-\x1F]/.test(identifier)) {
throw new Error('Invalid control characters')
}
const escaped = identifier.replace(/"/g, '""')
const needsQuoting = /[^a-z0-9_]/i.test(identifier) || isReservedKeyword(identifier)
if (needsQuoting) {
return `"${escaped}"`
}
return identifier
}Ne contournez pas la validation du schéma ni les vérifications de champs. Ce sont des couches de sécurité critiques.
Assurez-vous que votre schéma Prisma reflète fidèlement la structure de votre base de données.
TypeScript apporte la sécurité à la compilation, mais la validation au runtime ajoute une défense en profondeur.
Loggez et surveillez le SQL généré et les paramètres en production pour détecter les anomalies et les abus.
La protection contre l’injection SQL est intégrée à chaque couche de cette bibliothèque. Consultez directement les tests de sécurité et les détails d’implémentation.