Les requêtes avec paramètres sous ASP.NET

ASP.NET met en place plusieurs protections afin de protéger le site Web contre les utilisateurs malveillants. Par exemple, lors de l'entrée de texte dans un formulaire, il est impossible d'entrer des balises HTML, à moins que le programmeur n'en donne spécifiquement l'autorisation.

Mais la présence de protections automatiques n'est pas suffisante. Il faut également adapter nos techniques de programmation pour que notre application Web soit protégée.

Dans cet article, une technique de protection essentielle sera expliquée : l'utilisation des paramètres dans une requête SQL. En fait, tout le processus d'utilisation d'un SqlDataSource sera couvert, de sa déclaration jusqu'à la vérification du résultat de la requête.

▼Publicité

Voici un exemple concret :

Soit la requête suivante :

Fichier .aspx.cs (ASP.NET avec C#)

requete = "SELECT usager_prenom, usager_nomfamille, usager_motpasse FROM usager 

           WHERE usager_login = '" + login + "' AND usager_motpasse = '" + motpasse + "'";

Une requête montée à l'aide d'une concaténation de ce genre peut ouvrir des portes aux utilisateurs malveillants.

Si on permet à l'internaute d'entrer un code d'usager du genre « ' UNION SELECT 'monprenom','monnom','abc' -- », on obtiendra la requête suivante :

MS SQL

SELECT usager_prenom, usager_nomfamille, usager_motpasse 

FROM usager 

WHERE usager_login = '' UNION SELECT 'monprenom','monnom','abc' --' AND usager_motpasse = '...';

N'importe qui pourrait donc être authentifié sans connaître le mot de passe.

Pour se protéger de ce type d'injection, on pourrait échapper par programmation des apostrophes afin qu'ils ne soient pas interprétés par le serveur SQL.

Mais pour être plus efficace, on fera appel aux mécanismes prévus par ASP.NET. Ainsi, on évitera l'utilisation de requêtes SQL contenant des paramètres entrés « à bras » à l'aide d'une concaténation.

On utilisera TOUJOURS le mécanisme des paramètres prévu dans la classe SqlDataSource.

Les requêtes ne contiendront donc JAMAIS de concaténation avec des variables.

Identifier les paramètres dans la requête

Dans une source de données, on peut préciser :

  • une requête pour la sélection, 
  • une autre pour l'insertion, 
  • encore une pour la modification 
  • et une de plus pour la suppression de données. 

Écrire une requête SQL avec des valeurs fixes est simple. Cependant, dans la majorité des cas, les requêtes pour sélectionner, ajouter, modifier ou supprimer des données nécessiteront l'utilisation d'informations lues à l’écran ou fournies par des variables.

Pour utiliser des valeurs variables dans une requête, il faut, dans la requête, remplacer la valeur par un nom commençant par @.

Ex :

Fichier .aspx (ASP.NET)

<asp:SqlDataSource ID="dataSourceProduit" runat="server"

    ConnectionString="<%$ ConnectionStrings:ConnectionStringVotreSite %>"

    SelectCommand="SELECT produit_code, produit_nom, produit_description FROM produit WHERE produit_id=@produit_id ORDER BY produit_code"

    InsertCommand="INSERT INTO produit (produit_code, produit_nom, produit_description, produit_dateajout, produit_actif) VALUES (@produit_code, @produit_nom, @produit_description, GETDATE(), 1)"

    UpdateCommand="UPDATE produit SET produit_nom=@produit_nom, produit_description=@produit_description WHERE produit_id=@produit_id"

    DeleteCommand="DELETE FROM produit WHERE produit_id=@produit_id">

    ...

</asp:SqlDataSource>

Valeurs codées en dur

Il est possible d'utiliser des valeurs codées en dur ou des appels à des fonctions MS SQL pour donner la valeur de certains champs. 

C'est ce qui est illustré dans la source de données précédente : lors de l'insertion, le champ produit_dateajout prendra la valeur de la date du jour (GETDATE()).

On a également utilisé la valeur 1 (True) pour le champ produit_actif.

Valeurs variables

Pour chaque valeur variable rencontrée dans une requête (nom débutant par @), on déclarera le paramètre à l'intérieur des balises <SelectParameters>, <InsertParameters>, <UpdateParameters> ou <DeleteParameters> selon le cas.

Attention : vous ne devez déclarer que les paramètres qui correspondent à un nom débutant par @ dans la requête. Si vous déclarez un paramètre auquel ne correspond aucun @, la requête pourrait donner un résultat inattendu. 

<SelectParameters>

La balise <SelectParameters> doit préciser les informations sur chaque variable présente dans le SelectCommand. On pourra préciser son type, l'endroit d'où sa valeur est tirée. etc.

L'attribut Name doit correspondre au nom de la variable utilisée dans la requête, sans le @.

Dans cet exemple, l'identifiant qui doit être utilisé dans la requête provient de la valeur sélectionnée dans une liste déroulante.

Ex :

Fichier .aspx (ASP.NET)

<asp:SqlDataSource ...>

    ...

    <SelectParameters>

        <asp:ControlParameter Name="produit_id" ControlID="dropDownListProduits" PropertyName="SelectedValue" Type="Int32" />      

    </SelectParameters>

    ...

</asp:SqlDataSource>

<InsertParameters>

Cette balise doit spécifier les paramètres du InsertCommand. 

Dans cet exemple, les valeurs proviennent des différents TextBox permettant de saisir les informations sur le produit.

Ex :

Fichier .aspx (ASP.NET)

<asp:SqlDataSource ...>

    ...

    <InsertParameters>

        <asp:ControlParameter Name="produit_code" ControlID="textBoxCode" PropertyName="Text" Type="String" />      

        <asp:ControlParameter Name="produit_nom" ControlID="textBoxNom" PropertyName="Text" Type="String" />      

        <asp:ControlParameter Name="produit_description" ControlID="textBoxDescription" PropertyName="Text" Type="String" />      

    </InsertParameters>

    ...

</asp:SqlDataSource>

<UpdateParameters>

Même chose, mais pour les paramètres du UpdateCommand.

Dans cet exemple, la valeur de l'identifiant devra être retrouvée dans l'URL de la page Web. C'est pourquoi on laissera le soin au fichier .aspx.cs de lui fournir sa valeur. On utilisera pour cela un <asp:Parameter>.

Les autres paramètres tirent leur valeur des TextBox utilisés pour saisir les informations à modifier.

Ex :

Fichier .aspx (ASP.NET)

<asp:SqlDataSource ...>

    ...

    <UpdateParameters>

        <asp:Parameter Name="produit_id" Type="Int32" />      

        <asp:ControlParameter Name="produit_nom" ControlID="textBoxNom" PropertyName="Text" Type="String" />      

        <asp:ControlParameter Name="produit_description" ControlID="textBoxDescription" PropertyName="Text" Type="String" />      

    </UpdateParameters>

    ...

</asp:SqlDataSource>

<DeleteParameters>

Vous l'aurez deviné : la section <DeleteParameters> doit préciser les paramètres du DeleteCommand.

Dans l'exemple suivant, la valeur du paramètre produit_id sera tiré d'une variable de session nommée produit_id (Session["produit_id"]).

Ex :

Fichier .aspx (ASP.NET)

<asp:SqlDataSource ...>

    ...

    <DeleteParameters>

        <asp:SessionParameter Name="produit_id" SessionField="produit_id" Type="Int32" />      

    </DeleteParameters>

    ...

</asp:SqlDataSource>

Donner une valeur à un <asp:Parameter>

Lorsqu'une variable utilisée dans une source de données est définie par un <asp:Parameter>, il faudra absolument lui donner une valeur par programmation avant que la requête puisse être exécutée.

Dans le cas d'un <asp:ControlParameter>, le paramètre prendra automatiquement sa valeur à partir du contrôle. Il n'y a donc pas lieu de se soucier d'initialiser le paramètre.

La valeur du <asp:Parameter> sera donnée à l'aide de la propriété DefaultValue.

Ex : soit le paramètre produit_id défini comme suit :

Fichier .aspx (ASP.NET)

<asp:SqlDataSource ID="dataSourceProduit" runat="server"

   ConnectionString="<%$ ConnectionStrings:ConnectionStringVotreSite %>"

   UpdateCommand="UPDATE produit SET produit_nom=@produit_nom, produit_description=@produit_description WHERE produit_id=@produit_id">

   <UpdateParameters>

      <asp:Parameter Name="produit_id" Type="Int32" />      

      <asp:ControlParameter Name="produit_nom" ControlID="textBoxNom" PropertyName="Text" Type="String" />      

      <asp:ControlParameter Name="produit_description" ControlID="textBoxDescription" PropertyName="Text" Type="String" />      

   </UpdateParameters>

</asp:SqlDataSource>

On pourra donner la valeur au paramètre produit_id comme suit :

Fichier .aspx.cs (ASP.NET avec C#)

int produit_id = ...

dataSourceProduit.UpdateParameters["produit_id"].DefaultValue = produit_id.ToString();

Remarquez que la propriété DefaultValue nécessite toujours une valeur de type String et ce, même pour initialiser un paramètre numérique.

Et attention : malgré son nom, cette propriété ne sert pas à donner une valeur par défaut mais bien la valeur actuelle du paramètre.

Lancer une action sur une source de données : .Select(), .Insert(), .Update(), .Delete()

Dans votre code, il est possible de lancer manuellement une requête configurée dans une source de données.

Soit la source de données suivante :

Fichier .aspx (ASP.NET)

<asp:SqlDataSource ID="dataSourceProduit" runat="server"

   ConnectionString="<%$ ConnectionStrings:ConnectionStringVotreSite %>"

   SelectCommand="SELECT produit_code, produit_nom, produit_description FROM produit WHERE produit_id=@produit_id"

   InsertCommand="INSERT INTO produit (produit_code, produit_nom, produit_description) VALUES (@produit_code, @produit_nom, @produit_description)"

   UpdateCommand="UPDATE produit SET produit_nom=@produit_nom, produit_description=@produit_description WHERE produit_id=@produit_id"

   DeleteCommand="DELETE FROM produit WHERE produit_id=@produit_id">

   ...

</asp:SqlDataSource>

Par exemple, pour lancer la requête codée dans l'attribut InsertCommand, on procédera comme suit :

Fichier .aspx.cs (ASP.NET avec C#)

dataSourceProduit.Insert();

La requête de suppression sera lancée avec .Delete() et la requête de mise à jour sera lancée avec .Update().

La plupart du temps, il ne sera pas nécessaire de lancer la requête de sélection puisqu'elle est lancée automatiquement avant l'affichage d'une page qui présente des contrôles orientés données. Cependant, si un paramètre changeait en cours de route, ou si on désirait faire un traitement manuel sur les données en les stockant dans un DataView, il serait possible de forcer le lancement de la requête comme suit :

Fichier .aspx.cs (ASP.NET avec C#)

dataView dataViewUsager = (DataView)dataSourceUsager.Select(DataSourceSelectArguments.Empty);

Attention : lorsqu'un paramètre de la requête est défini à l'aide d'un <asp:Parameter>, il faut absolument lui donner une valeur à l'aide de DefaultValue AVANT d'exécuter la requête.

Vérifier si le Insert, le Update ou le Delete a réussi

Lorsqu'on lance une action sur une source de données, le SGBD nous indiquera si l'opération a réussi ou pas. En cas d'échec, le programme ne plantera pas mais une exception sera levée. 

Il est de la responsabilité du développeur de vérifier si une exception a été levée et de réagir en conséquence. 

Ex :

Soit la source de données suivante. On a ajouté un gestionnaire d'événement associé à l'événement OnInserted.

Fichier .aspx (ASP.NET)

<asp:SqlDataSource ID="dataSourceProduit" runat="server"

   ConnectionString="<%$ ConnectionStrings:ConnectionStringVotreSite %>"

   InsertCommand="INSERT INTO produit (produit_code, produit_nom, produit_description) VALUES (@produit_code, @produit_nom, @produit_description)"

   OnInserted="dataSourceProduit_Inserted">

   ...

</asp:SqlDataSource>

Dans le gestionnaire d'événement associé à l'événement OnInserted, on vérifiera s'il y a eu une exception :

Fichier .aspx.cs (ASP.NET avec C#)

protected void dataSourceProduit_Inserted(object sender, SqlDataSourceStatusEventArgs e)

{

    if (e.Exception != null)

    {

        labelMessage.Text = "Un problème empêche l'ajout du produit.";

        labelMessage.CssClass = "messageerreur";

        labelMessage.Visible = true;

        // indique à ASP.NET que l'erreur ne doit pas être remontée plus loin dans le processus 

        // (ultimement, si rien ne traitait l'exception, le programme planterait)

        e.ExceptionHandled = true;

    }

}

Créer une source de données en C#

Dans une page Web ASP.NET, l'ajout d'une source de données peut être effectué autant dans le fichier de balises (.aspx) que dans le fichier de Code Behind (.aspx.cs). L'utilisation de l'un ou de l'autre dépend souvent de préférences personnelles ou de normes établies par l'entreprise.

Il y a pourtant des situations où nous n'avons pas le choix d'ajouter une source de données par programmation C#. C'est le cas notamment lorsqu'une source de données doit être utilisée dans une fonction codée dans une bibliothèque de fonctions.

Peu importe les raisons qui nous poussent à créer la source de données en C#, voici la structure du code qui nous permettra d'y parvenir :

Fichier .cs (ASP.NET avec C#)

SqlDataSource dataSourceProduit = new SqlDataSource();

dataSourceProduit.ConnectionString = ConfigurationManager.ConnectionStrings["ConnectionStringVotreSite"].ToString();

dataSourceProduit.SelectCommand = "SELECT produit_code, produit_nom, produit_description FROM produit WHERE produit_id=@produit_id";

dataSourceProduit.SelectParameters.Clear();

dataSourceProduit.SelectParameters.Add("produit_id", System.Data.DbType.Int32, id);

Dans le cas d'une requête permettant de modifier les données, il faut toujours vérifier si la requête a fonctionné. On peut, pour cela, assigner un gestionnaire à l'événement concerné de la source de données (Updated, Inserted, Deleted).

Le gestionnaire d'événement peut ensuite être programmé comme s'il avait été déclaré dans le fichier .aspx.

Fichier .cs (ASP.NET avec C#)

protected void FaireQuelqueChose()

{

    SqlDataSource dataSourceProduit = new SqlDataSource();

    dataSourceProduit.ConnectionString = ConfigurationManager.ConnectionStrings["ConnectionStringVotreSite"].ToString();

    dataSourceProduit.UpdateCommand = "...";

    dataSourceProduit.UpdateParameters.Clear();

    dataSourceProduit.UpdateParameters.Add("produit_id", System.Data.DbType.Int32, id);

    

    dataSourceProduit.Updated += new SqlDataSourceStatusEventHandler(this.dataSourceProduit_Updated);

}

protected void dataSourceProduit_Updated(object sender, SqlDataSourceStatusEventArgs e)

{

    if (e.Exception != null)

    {

        ...

    }

    else

    {

        if (e.AffectedRows == 0)

        {

            ...

        }

    }

}

Gérer les paramètres par programmation

Il est possible de modifier par programmation la requête d'une source de données. 

Ex :

Fichier .aspx.cs (ASP.NET avec C#)

dataSourceProduit.SelectCommand="SELECT produit_code, produit_nom, produit_description FROM produit WHERE produit_id=@produit_id";

Il est également possible de spécifier par programmation les paramètres de cette requête. La technique dépendra de la déclaration ou non du paramètre dans le fichier .aspx.

  • Dans le cas où le paramètre avait été déclaré comme suit :
    Fichier .aspx (ASP.NET)

    <SelectParameters>

       <asp:Parameter Name="produit_id" Type="Int32" />      

    </SelectParameters>

    On pourra préciser sa valeur à l'aide de DefaultValue.

    Fichier .aspx.cs (ASP.NET avec C#)

    int id = ...

    ...

    dataSourceProduit.SelectParameters["produit_id"].DefaultValue = id.ToString();

  • Dans le cas où le paramètre n'avait pas été déclaré dans le fichier .aspx, on le déclarera dans le fichier .aspx.cs. On lui donnera son type et sa valeur par la même occasion.
    Fichier .aspx.cs (ASP.NET avec C#)

    int id = ...

    ...

    dataSourceProduit.SelectParameters.Clear();

    dataSourceProduit.SelectParameters.Add("produit_id", System.Data.DbType.Int32, id);

Pour plus d'information

« How To: Protect From Injection Attacks in ASP.NET ». MSDN. http://msdn.microsoft.com/en-us/library/ff647397.aspx

Merci de partager ! Share on FacebookTweet about this on TwitterShare on Google+Share on LinkedInPin on PinterestShare on StumbleUponEmail this to someone
Catégories