- · Niveau : INTERMÉDIAIRE
- · Compatibilité : Windows NT/2000 IIS3
Saisir le bon type de données ou les convertir et surtout protéger votre base de données contre les attaques.
Déjà s’assurer de saisir le bon type de valeur. En HTML5 nous avons la possibilité de saisir le bon type à la source :
- Type texte : search, email, url, tel, date, week, mont, color, datetime, datetime-local, time
- Type numérique : number, range
- Attribut : « required » pour forcer l’usager à saisir le champ.
- Pattern : pattern="\d{1,2}/\d{1,2}/\d{4} pour saisir une date par exemple.
<form> <fieldset> <legend>Validation HTML5 de la date</legend> <label>Date : <input type="text" size="12" required pattern="\d{1,2}/\d{1,2}/\d{4}" placeholder="dd/mm/yyyy" name="hDate"></label> <label>Heure : <input type="text" size="12" pattern="\d{1,2}:\d{2}([ap]m)?" name="hHeure"></label> <input type="submit" /> </fieldset> </form>
Non seulement on ne s’assure pas des autres types (date, real, money)... Mais en plus le HTML5 n’est pas toujours compatible. On ne peut pas s’y fier! C’est pourquoi il faut TOUJOUR refaire une validation et même deux fois plutôt qu’une! C’est-à-dire une première en JavaScript et une seconde au niveau du serveur.
En JavaScript
// Par exemple vérifier qu'un nombre est bien un nombre :
function twNombre(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
Autre exemple complet pour valider une date
// JavaScript original de Chirp Internet: www.chirp.com.au // Ajusté par les Trucsweb.com <script> function twValideDate(field) { var allowBlank = true; var minYear = 1902; var maxYear = (new Date()).getFullYear(); var errorMsg = ""; // Expression régulière pour matcher le format de date // yyyy-mm-dd re = /^(\d{4})-(\d{1,2})-(\d{1,2})$/ if(field.value != '') { if(regs = field.value.match(re)) { if(regs[3] < 1 || regs[3] > 31) { errorMsg = "Date invalide : " + regs[3]; } else if(regs[2] < 1 || regs[2] > 12) { errorMsg = "Mois invalide : " + regs[2]; } else if(regs[1] < minYear || regs[1] > maxYear) { errorMsg = "Année invalide : " + regs[1] + " - doit être entre " + minYear + " et " + maxYear; } } else { errorMsg = "format de date invalide : " + field.value; } } else if(!allowBlank) { errorMsg = "Vous devez saisir une date valide!"; } if(errorMsg != "") { alert(errorMsg); field.focus(); return false; } return true; } function twValideformulaire(oForm) { if(!twValideDate(oForm.hDate)) return false; return true; } </script> <form onsubmit="return twValideformulair(this);"> <fieldset> <legend>Validation JavaScript de la date</legend> <label>Date : <input type="text" size="12" placeholder="yyyy/mm/dd" name="hDate"> <small>(dd/mm/yyyy)</small></label> <label>Heure : <input type="text" size="12" name="hHeure"> <small>(ex. 18:17 ou 6:17pm)</small></label> <input type="submit"> </fieldset> </form>
C’est déjà ça, et pourquoi stresser le serveur quand la validation peut se faire localement. Malheureusement et malgré de nombreuses applications gratuites sur le Web, l’usager peut désactiver son JavaScript! Consultez « Fonction de validation twValide version 2.2 - Validation de formulaire côté client »
Côté serveur
Efficace, y a pas mieux pour valider les valeurs avant l’ajout dans la base de données. Et d’ailleurs c’est très simple en ASP, il suffit de la bonne méthode pour le bon type de données.
Il y a déjà la fonction VarType
:
dim maVar : maVar = "Texte" response.write VarType(maVar) ' Résultat 4 ' Exemple avec une date if VarType(request.form("date")) = 7 then ' Date valide else ' Date invalide end if
La table de référence :
- 0 = vbEmpty - Indique Empty (noninitialisé)
- 1 = vbNull - Indique Null (valeur non valide)
- 2 = vbInteger - Indique un nombre entier (integer)
- 3 = vbLong - Indique un nombre entier long (long integer)
- 4 = vbSingle - Indique un nombre réel de simple-précision (floating-point number)
- 5 = vbDouble - Indique un nombre réel de double-précision (floating-point number)
- 6 = vbCurrency - Indique une devise
- 7 = vbDate - Indique une date
- 8 = vbString - Indique une chaine de caractères (string)
- 9 = vbObject - Indique un objet
- 10 = vbError - Indique une erreur
- 11 = vbBoolean - Indique une booléenne
- 12 = vbVariant - Indique un « variant »
- 13 = vbDataObject - Indique un objet de données
- 17 = vbByte - Indique un « byte »
- 8192 = vbArray - Indique une matrice (array)
Ou encore directement avec :
- IsDate : Retourne une valeur booléenne qui indique si la variable peut être évaluée comme un type date.
- IsEmpty : Retourne une valeur booléenne qui indique si la variable est initialisée ou pas.
- IsNull : Retourne une valeur booléenne qui indique si la variable ne contient aucune valeur valide (Null).
- IsNumeric : Retourne une valeur booléenne qui indique si la variable peut être évaluée comme un type nombre.
Enfin il y a aussi la méthode de la conversion systématique. Avec la plupart des fonctions VBScript : fonctions conversion, de date et de temps, format, mathématique, de chaine de caractères (string)...
if isNumeric(request.form(champ)) then ' Champ numérique else ' Erreur, champ non numérique end if if isDate(request.form(champ)) then ' Champ date else ' Erreur, champ non date end if
Injections SQL
Oui, surtout, car faire planter la base de données à cause d’une mauvaise valeur est une chose. Mais planter une base de données voir la corrompe à cause d’une attaque en règle en est une autre.
Personnellement je teste toujours et systématiquement chaque requête « querystring » au serveur. Il suffit d’avoir été piraté pour comprendre l’importance de cette validation. Une simple injection SQL peut remplir votre base de données de code JavaScript malsain. Donc en plus de perdre vos données, vous participer à votre tour et à votre insu en propagent l’attaque à votre tour. Exactement comme un virus. Sauf que c’est votre serveur IIS qui sème la terreur à chaque fois qu’un utilisateur ouvre une page! Consultez Wikipedia - Injection SQL
L’idée est simple, détecter ce type d’attaque qui est toujours la même :
Considérez que vous avez une simple connexion qui demande à l’usager un ID. Et que l’usager entre comme ID « 105 or 1 = 1 » :
dim sID, sSQL sID = request.querystring("id"); sSQL = "select * from t_usager where v_usager_id = " + sID; ' Le résulta de la requête sera donc. « select * from t_usager where v_usager_id = 105 or 1 = 1 » ' 1 = 1 étant toujours vrais, il est possible de cette manière d'obtenir d’outrepasser la condition et obtenir ainsi une liste complète des usagers!
Il suffit donc en premier lieu de limiter le nombre de caractères, puis de limiter le type de saisie, c’est-à-dire de type nombre et surtout pas symbole, comme le « = ».
Mais il existe des requêtes beaucoup plus complexe. Je ne donnerais pas ici l’exemple, mais en une seule instruction on peut non seulement demander la structure de la base de données, mais boucler chaque champ de chaque table et modifier le contenu de chacun de ses champs avec une nouvelle valeur, en l’occurrence un JavaScriprt malsain... Et croyez-moi, ce n’est pas une expérience que l’on veut vivre!
C’est pourquoi j’utilise ce petit bout de code dans mon fichier global qui détecte et capture toutes requêtes au serveur et supprimer les mnémoniques et mots-clés les plus dangereux : update delete drop declare nvarchar chr( cast( ;set%
dim sChaineSQComplete sChaineSQComplete = request.servervariables("QUERY_STRING") ' Si je trouve la requête louche, ' je garde une trace dans un fichier log. ' avec une redirection, générallement vers Walt Disney. if twVerifieInjectionSQL(sChaineSQComplete) = false then Dim sChaineQS for each oItem in request.servervariables sChaineQS = sChaineQS & oItem & "=" & request.servervariables(oItem) & vbcrlf next call twLogHack([chemin]","trapHack",sChaineQS) response.redirect("http://www.waltdisney.com/") end if function twVerifieInjectionSQL(sChaineTempHack) sChaineTempHack = lcase(sChaineTempHack) sMotsInjectes = "update delete drop declare nvarchar chr( cast( ;set%" aQS = split(sMotsInjectes, " ") for nIhack =0 to ubound(aQS) if instr(sChaineTempHack, aQS(nIhack)) > 0 then twVerifieInjectionSQL = false exit function end if next twVerifieInjectionSQL = true end function
Et voilà, c’est tout. De cette manière, en la combinant avec les tests de base, vous pouvez éviter la plupart des attaques.
En plus, à chaque saisie, d’un simple ID à un formulaire complexe, dès que je dois ajouter une valeur à la base de données, je lui applique cette conversion supplémentaire :
Function twProtegeSQL(sMots) dim sTemp sTemp = replace(sMots,"^","") sTemp = replace(sTemp,"’","’") sTemp = replace(sTemp,"Or 1=1","HACK") sTemp = replace(sTemp,"--","") sTemp = replace(sTemp,";","") sTemp = replace(sTemp," OR "," ") sTemp = replace(sTemp," or "," ") sTemp = replace(sTemp," NOT "," ") sTemp = replace(sTemp," not "," ") sTemp = replace(sTemp," IN "," ") sTemp = replace(sTemp," in "," ") sTemp = replace(sTemp," = "," ") sTemp = replace(sTemp,"update","HACK") sTemp = replace(sTemp,"UPDATE","HACK") sTemp = replace(sTemp,"declare","HACK") sTemp = replace(sTemp,"DECLARE","HACK") sTemp = replace(sTemp,"nvarchar","HACK") sTemp = replace(sTemp,"NVARCHAR","HACK") sTemp = replace(sTemp,"chr(","HACK") sTemp = replace(sTemp,"CHR(","HACK") sTemp = replace(sTemp,"cast(","HACK") sTemp = replace(sTemp,"CAST(","HACK") sTemp = replace(sTemp,";SET%","HACK") sTemp = replace(sTemp,";set%","HACK") sTemp = replace(sTemp,"delete","HACK") sTemp = replace(sTemp,"DELETE","HACK") 'sTemp = replace(sTemp,"input","HACK") 'sTemp = replace(sTemp,"INPUT","HACK") sTemp = replace(sTemp,"insert into","HACK") sTemp = replace(sTemp,"INSERT INTO","HACK") 'sTemp = replace(sTemp,"insert","HACK") 'sTemp = replace(sTemp,"INSERT","HACK") sTemp = replace(sTemp,"remove","HACK") sTemp = replace(sTemp,"REMOVE","HACK") sTemp = replace(sTemp,"execute","HACK") sTemp = replace(sTemp,"EXECUTE","HACK") sTemp = replace(sTemp,"''","") twProtegeSQL = trim(sTemp) End Function
Conclusion
Et voilà, c’est aussi simple que ça. Pour éviter la grande majorité des problèmes de format, de piratage et d’injection SQL. L’idéale est d’encapsuler le tout en fonction globale appelé systématiquement après chaque saisie de formulaire, de la simple recherche au formulaire de gestion complexe.