Pour renforcer la sécurité dans Dreamweaver 8.0.2 et CS3, nous avons supprimé la possibilité d’utiliser des paramètres SQL dynamiques dans les jeux d’enregistrements standard. Le code d’origine exposait les bases de données à des injections de code SQL. L’utilisation d’instructions préparées pour les modèles de serveur ASP_VBS et ColdFusion a résolu la vulnérabilité de sécurité. Il était nécessaire d’ajouter une fonction à la page lors de l’application du premier jeu d’enregistrements, puis d’appeler cette fonction à partir de tous les jeux d’enregistrements de la page pour PHP_MySQL. Cette technique est similaire aux instructions préparées pour ASP_VBS et ColdFusion.
Dans le but de réduire la vulnérabilité en matière de sécurité, nous avons privilégié la sécurité au détriment d’une certaine flexibilité. Nous voulons donner aux développeurs qui ont déjà développé ou prévoient de développer des extensions qui devront gérer des requêtes SQL dynamiques, ainsi qu’aux utilisateurs finaux qui souhaitent personnaliser leurs jeux d’enregistrements afin qu’ils acceptent des paramètres dynamiques, la possibilité de modifier ces jeux d’enregistrements personnalisés à partir de Dreamweaver.
Une injection de code SQL est une technique d’injection de code qui exploite une vulnérabilité de sécurité se produisant dans la couche de base de données d’une application. La vulnérabilité est présente lorsque l’entrée utilisateur n’est pas correctement filtrée pour des caractères d’échappement littéraux de chaîne intégrés dans des instructions SQL ou lorsque l’entrée utilisateur n’est pas fortement typée et donc exécutée de manière inattendue. Il s’agit en fait d’une instance d’une classe plus générale de vulnérabilités pouvant se produire lorsqu’un langage de programmation ou de script est intégré dans un autre. Pour plus d’informations, consultez Wikipedia.
Exemple de requête SQL standard :
SELECT * FROM company_com WHERE name_com = '$companyName'
Dans l’exemple ci-dessus, la variable $companyName est lue à partir d’un champ de formulaire d’entrée, de sorte que l’utilisateur dispose du contrôle total de la valeur qui sera envoyée. Dans le meilleur des cas, lorsque l’utilisateur saisit « Adobe » (sans les guillemets) dans l’entrée correspondant au nom de la société, la requête SQL aura l’aspect suivant :
SELECT * FROM company_com WHERE name_com = 'Adobe'
Toutefois, dans certains cas, des utilisateurs peuvent vouloir compromettre le site web et voler des informations sensibles et/ou détruire la base de données. En prenant l’exemple ci-dessus, une injection de code SQL sera facile à réaliser en saisissant la chaîne suivante « blabla' OR '1'='1 » (sans les guillemets). Avec cette séquence de caractères, l’utilisateur aura accès à toutes les entreprises au lieu d’une seule. Le code SQL exécuté est :
SELECT * FROM company_com WHERE name_com = 'blabla' OR '1'='1'
En outre, si l’utilisateur souhaite supprimer l’intégralité de la table company_com, il lui suffit de transmettre la chaîne suivante « x'; DROP TABLE company_com; -- » (sans les guillemets). Voici la requête SQL complète :
SELECT * FROM company_com WHERE name_com = 'x'; DROP TABLE company_com; --'
Utilisez l’une des mesures suivantes pour protéger votre site web contre les injections de code SQL :
Nous recommandons d’utiliser des instructions préparées, décrites plus en détail dans la section Utilisation d’instructions préparées. Dreamweaver 8.0.2 et CS3 utilisent l’approche des instructions préparées pour les modèles de serveur ASP_VBS, ASP_JavaScript, Cold Fusion et JSP, ainsi que l’approche d’échappement des entrées de l’utilisateur pour le modèle de serveur PHP_MySQL. Pour plus d’informations sur les attaques par injection de code SQL, reportez-vous à la page SQL Injection Attacks by Example (Attaques par injection SQL par exemple).
Dreamweaver 8.0.2 et CS utilisent des instructions préparées car cette solution nous garantit que le remplacement des paramètres SQL réels par des valeurs données (qui peuvent être envoyées via URL) sera effectué sur le serveur et non sur la page. Les instructions préparées garantissent également que la valeur transmise au SQL lui-même possède le type de données approprié et utilise l’échappement approprié (si un paramètre est de type int, alors l’utilisateur ne pourra pas envoyer de lettre, ou si un paramètre est de type text, alors la chaîne passée entière sera correctement échappée et l’utilisateur n’aura pas la possibilité d’effectuer une injection de code SQL).
Pour ASP_VBS et ASP_JS, nous utilisons la couche de base de données ADODB. L’exemple suivant présente un jeu d’enregistrements ASP_VBS simple qui utilise un paramètre SQL dynamique pour filtrer les résultats :
... <% Dim Recordset1__MMColParam Recordset1__MMColParam = "1" If (Request.QueryString("id_com") <> "") Then Recordset1__MMColParam = Request.QueryString("id_com") End If %> <% Dim Recordset1 Dim Recordset1_cmd Dim Recordset1_numRows Set Recordset1_cmd = Server.CreateObject ("ADODB.Command") Recordset1_cmd.ActiveConnection = MM_connContacts_STRING Recordset1_cmd.CommandText = "SELECT * FROM company_com WHERE id_com = ?" Recordset1_cmd.Prepared = true Recordset1_cmd.Parameters.Append Recordset1_cmd.CreateParameter("param1", 5, 1, -1, Recordset1__MMColParam) ' adDouble Set Recordset1 = Recordset1_cmd.Execute Recordset1_numRows = 0 %> ...ColdFusion offre une prise en charge intégrée des instructions préparées. L’exemple suivant présente un jeu d’enregistrements ColdFusion simple qui utilise un paramètre SQL dynamique pour filtrer les résultats :
... <cfparam name="URL.id_com" default="1"> <cfquery name="Recordset1" datasource="company_employee"> SELECT * FROM company_com WHERE id_com = <cfqueryparam value="#URL.id_com#" cfsqltype="cf_sql_numeric"> </cfquery> ...Les instructions préparées pour PHP sont disponibles depuis la version MySQL 4.1, mais nous souhaitons également prendre en charge les versions précédentes de MySQL, c’est pourquoi nous avons décidé d’implémenter une fonction personnalisée qui a presque le même effet. L’exemple suivant présente un jeu d’enregistrements PHP simple qui utilise un paramètre SQL dynamique pour filtrer les résultats :
... <?php if (!function_exists("GetSQLValueString")) { function GetSQLValueString($theValue, $theType, $theDefinedValue = "", $theNotDefinedValue = "") { $theValue = get_magic_quotes_gpc() ? stripslashes($theValue) : $theValue; $theValue = function_exists("mysql_real_escape_string") ? mysql_real_escape_string($theValue) : mysql_escape_string($theValue); switch ($theType) { case "text": $theValue = ($theValue != "") ? "'" . $theValue . "'" : "NULL"; break; case "long": case "int": $theValue = ($theValue != "") ? intval($theValue) : "NULL"; break; case "double": $theValue = ($theValue != "") ? "'" . doubleval($theValue) . "'" : "NULL"; break; case "date": $theValue = ($theValue != "") ? "'" . $theValue . "'" : "NULL"; break; case "defined": $theValue = ($theValue != "") ? $theDefinedValue : $theNotDefinedValue; break; } return $theValue; } } $colname_Recordset1 = "-1"; if (isset($_GET['id_com'])) { $colname_Recordset1 = $_GET['id_com']; } mysql_select_db($database_connContacts, $connContacts); $query_Recordset1 = sprintf("SELECT * FROM company_com WHERE id_com = %s", GetSQLValueString($colname_Recordset1, "int")); $Recordset1 = mysql_query($query_Recordset1, $connContacts) or die(mysql_error()); $row_Recordset1 = mysql_fetch_assoc($Recordset1); $totalRows_Recordset1 = mysql_num_rows($Recordset1); ?> ...Comment pouvons-nous donc assurer la protection contre les injections de code SQL tout en autorisant les paramètres dynamiques et en assurant que le code résultant reste modifiable via le panneau Comportements de serveur de Dreamweaver ? La solution consiste à insérer les paramètres dynamiques dans la requête SQL elle-même, plutôt que de les transmettre en tant que paramètres SQL classiques (qui seraient soit échappés, soit ajoutés au SQL via des instructions préparées).
Important : les utilisateurs ne peuvent pas générer ce type de code à l’aide des objets et des comportements de serveur fournis avec Dreamweaver 8.0.2 ou CS3 (exemple : insertion d’un jeu d’enregistrements via le panneau Comportements de serveur ou la barre Insertion). Cette solution nécessite que l’utilisateur modifie le code manuellement ou installe une extension tierce qui crée ce code. La responsabilité de rédiger un code sécurisé incombe à l’utilisateur ou au développeur du logiciel tiers.
Cette méthode a pour but de permettre aux développeurs expérimentés de créer des requêtes SQL dynamiques. Une mauvaise utilisation de cette solution donne aux pirates la possibilité d’obtenir des privilèges indésirables et/ou de compromettre les pages web et la base de données. Veuillez vous référer à la section Injections de code SQL et méthodes de prévention pour minimiser les risques d’injections de code SQL.
Nous avons déterminé que cette solution serait utilisée principalement dans les cas suivants :
Les deux cas d’utilisation seront détaillés dans les paragraphes suivants.
Dans l’exemple suivant, l’utilisateur souhaite ajouter une fonctionnalité de tri à une table dynamique. Il décide de recharger la page tout en transmettant les fichiers et le sens de tri à utiliser (croissant ou décroissant) dans l’URL. Voici un exemple d’URL initiale :
http://www.mydomain.com/index.php?sortCol=name_com&sortDir=ascLe codage manuel est nécessaire pour accomplir cette tâche dans Dreamweaver à l’aide du panneau Comportements de serveur. Les extraits de code suivants présentent les modifications qui doivent être apportées à chaque modèle de serveur.
Important : veuillez noter qu’aucun des exemples ci-dessous n’a de validation contre les injections de code SQL. Leur rôle est purement éducatif et ils ne doivent pas être utilisés en l’état dans la production. Les exemples sont aussi simples que possible, ils ne disposent donc pas d’une sécurité optimale. Vous devez mettre en place une protection contre les injections de code SQL. Pour plus d’informations, reportez-vous à la section Injections de code SQL et méthodes de prévention.
Avant :
... <% Dim Recordset1 Dim Recordset1_cmd Dim Recordset1_numRows Set Recordset1_cmd = Server.CreateObject ("ADODB.Command") Recordset1_cmd.ActiveConnection = MM_connContacts_STRING Recordset1_cmd.CommandText = "SELECT * FROM company_com" Recordset1_cmd.Prepared = true Set Recordset1 = Recordset1_cmd.Execute Recordset1_numRows = 0 %> ...Après :
... <% Dim orderBy: orderBy = "" If (Request.QueryString("sortCol") <> "") Then orderBy = "ORDER BY " & Request.QueryString("id_com") If (Request.QueryString("sortDir") <> "") Then orderBy = orderBy & " " & Request.QueryString("sortDir") End If End If %> <% Dim Recordset1 Dim Recordset1_cmd Dim Recordset1_numRows Set Recordset1_cmd = Server.CreateObject ("ADODB.Command") Recordset1_cmd.ActiveConnection = MM_connContacts_STRING Recordset1_cmd.CommandText = "SELECT * FROM company_com " & orderBy & "" Recordset1_cmd.Prepared = true Set Recordset1 = Recordset1_cmd.Execute Recordset1_numRows = 0 %> ...Avant :
... <cfquery name="Recordset1" datasource="company_employee"> SELECT * FROM company_com </cfquery> ...Après :
... <cfparam name="URL.sortCol" default=""> <cfparam name="URL.sortDir" default="ASC"> <cfset orderBy=""> <cfif (#URL.sortCol# NEQ "")> <cfset orderBy="ORDER BY #URL.sortCol# #URL.sortDir#"> </cfif> <cfquery name="Recordset1" datasource="company_employee"> SELECT * FROM company_com #orderBy# </cfquery> ...Avant :
... <?php mysql_select_db($database_connContacts, $connContacts); $query_Recordset1 = "SELECT * FROM company_com"; $Recordset1 = mysql_query($query_Recordset1, $connContacts) or die(mysql_error()); $row_Recordset1 = mysql_fetch_assoc($Recordset1); $totalRows_Recordset1 = mysql_num_rows($Recordset1); ?> ...Après :
... <?php $orderBy = ""; if (isset($_GET['sortCol'])) { $orderBy = "ORDER BY " . $_GET['sortCol']; if (isset($_GET['sortDir'])) { $orderBy .= " " . $_GET['sortDir']; } } ?> <?php mysql_select_db($database_connContacts, $connContacts); $query_Recordset1 = "SELECT * FROM company_com " . $orderBy . ""; $Recordset1 = mysql_query($query_Recordset1, $connContacts) or die(mysql_error()); $row_Recordset1 = mysql_fetch_assoc($Recordset1); $totalRows_Recordset1 = mysql_num_rows($Recordset1); ?> ...Considérez maintenant le même scénario que ci-dessus, sauf que l’utilisateur souhaite créer une extension qui générera le code approprié plutôt que de compter sur une combinaison de fonctionnalités intégrées et de modification manuelle. La nouvelle extension doit générer le code correct pour les modèles de serveur ASP_VBS, ColdFusion et PHP_MySQL.
Important : dans les sections suivantes, nous allons uniquement nous concentrer sur la génération du code qui utilisera les valeurs des paramètres d’URL pour créer une requête SQL dynamique. L’objectif principal est de générer le code de manière à ce qu’il soit toujours reconnu par Dreamweaver et modifiable à partir d’interfaces Dreamweaver standard. Ce tutoriel ne prend pas en charge la validation des données d’entrée (via les paramètres d’URL) et ne protège pas le code SQL final contre toute tentative d’injection de code SQL, car la complexité de ce type de code n’entre pas dans le cadre de ce tutoriel. Le développeur est seul responsable de la protection du code SQL final contre les injections de code SQL.
Nous fournissons une extension de démonstration qui prend une connexion et une table comme entrées et génère une table dynamique affichant tous les enregistrements. Elle met également à jour le code SQL généré afin de contenir la variable orderBy, comme indiqué dans les exemples ci-dessus. L’extension a été conçue pour Dreamweaver 8.0.2 et CS3. Le code généré ne dispose pas d’une sécurité optimale, car l’objectif est de créer des jeux d’enregistrements contenant des paramètres dynamiques qui restent modifiables lors de l’utilisation d’interfaces Dreamweaver standard. Le code source de l’extension se trouve dans le dossier Configuration de l’utilisateur.
Dans tous les exemples de code ci-dessous, les sections mises en surbrillance ont été ajoutées pour activer la fonctionnalité de tri via les paramètres d’URL sortCol et sortDir.
Le code qui génère la requête SQL appropriée pour le modèle de serveur ASP_VBS se trouve dans le fichier « [DOSSIER_CONFIGURATION_DE_L’UTILISATEUR]/Commands/My Dynamic Table.js » dans le dossier Configuration de l’utilisateur. Le changement notable se trouve à la ligne 130 :
... 130: paramObj.encodedSQL = "SELECT * FROM " + paramObj.table + " \" & orderBy & \""; ...Le fichier des participants pour le comportement de serveur ASP_VBS MyDynamicTable comprend un participant supplémentaire qui ajoute la définition de variable « orderBy » à la page :
<group name="MyDynamicTable" version="9.0"> <groupParticipants> <groupParticipant name="connectionref_statement" /> <groupParticipant name="MyDynamicTable_orderBy" /> <groupParticipant name="recordset_main" /> <groupParticipant name="repeatedRegion_init2" /> <groupParticipant name="DynamicTable_main" /> <groupParticipant name="recordset_close" /> </groupParticipants> </group>Le même fichier « [DOSSIER_CONFIGURATION_DE_L’UTILISATEUR]/Commands/My Dynamic Table.js » dans le dossier Configuration de l’utilisateur contient également le code du modèle de serveur ColdFusion. Dans ce cas, le changement significatif se trouve à la ligne 138 :
... 138: paramObj.SQLStatement = "SELECT * FROM " + paramObj.table + " #orderBy#"; ...Le fichier des participants pour le comportement de serveur ColdFusion MyDynamicTable comprend un participant supplémentaire qui ajoute la définition de variable « orderBy » à la page :
<group name="MyDynamicTable" version="9.0"> <groupParticipants> <groupParticipant name="MyDynamicTable_orderBy" /> <groupParticipant name="Recordset_main" /> <groupParticipant name="DynamicTable_main" /> <groupParticipant name="RepeatedRegion_pageNum" /> <groupParticipant name="RepeatedRegion_maxRows" /> <groupParticipant name="RepeatedRegion_startRow" /> <groupParticipant name="RepeatedRegion_endRow" /> <groupParticipant name="RepeatedRegion_totalPages" /> </groupParticipants> </group>Le dernier modèle de serveur de l’extension est PHP_MySQL. La modification significative du fichier « [DOSSIER_CONFIGURATION_DE_L’UTILISATEUR]/Commands/My Dynamic Table.js » dans le dossier Configuration de l’utilisateur se trouve à la ligne 122 :
... 122: paramObj.SQLStatement = "SELECT * FROM " + paramObj.table+ " \" . $orderBy . \""; ...Le fichier des participants pour le comportement de serveur PHP_MySQL MyDynamicTable comprend un participant supplémentaire qui ajoute la définition de variable « orderBy » à la page :
<group name="MyDynamicTable" version="9.0"> <groupParticipants> <groupParticipant name="Connection_include" /> <groupParticipant name="MyDynamicTable_orderBy" /> <groupParticipant name="EditOps_SQLValueString" /> <groupParticipant name="Recordset_main" /> <groupParticipant name="DynamicTable_main" /> <groupParticipant name="Recordset_close" /> </groupParticipants> </group>La méthode décrite ci-dessus présente plusieurs avantages, notamment :
Dreamweaver n’a pas été conçu par défaut pour prendre en charge cette méthode et présente donc également quelques inconvénients, notamment :
Accéder à votre compte