IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Services Web Discussion :

WCF RIA Services Injection SQL


Sujet :

Services Web

  1. #1
    Membre à l'essai
    Inscrit en
    Mai 2007
    Messages
    31
    Détails du profil
    Informations forums :
    Inscription : Mai 2007
    Messages : 31
    Points : 12
    Points
    12
    Par défaut WCF RIA Services Injection SQL
    Bonsoir.

    Je travail avec Entity Framework 5, Silverlight 5, WCF RIA Services, MySQL 5.6 and MySQLConnector 6.5.6.

    J'ai découvert que la portion de code suivant générait des erreurs de syntax côté serveur MySQL lorsque dans la chaîne de caractère passée en paramètres il y a une quote.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    DomainContext.Load<Product>(DomainContext.GetProductQuery()
                                             .Where<Product>(p => p.name.Contains(parameter))
                                             .Take<Product>(30));
    Ici je recherche tous les produits contenant un certains motif.
    Or si j'utilise une égalité stricte comme ceci je n'ai pas d'erreur.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    DomainContext.Load<Product>(DomainContext.GetProductQuery()
                                             .Where<Product>(p == parameter)
                                             .Take<Product>(30));
    Ceci est la méthode appelée côté serveur.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    public IQueryable<Product> GetProduct()
    {
        return this.ObjectContext.product;
    }
    Je suis pour le moins perplexe. Je pensais vraiment que EF utilisait des requêtes paramétrées.
    Je ne vois vraiment pas comment résoudre ce problème.
    Réécrire des centaines de méthodes côté service pour inclure un filtre avec un résultat hasardeux me parait pour le moins risqué.

    Toute aide sera vraiment grandement appréciée.
    Bonne soirée à tous !

  2. #2
    Rédacteur/Modérateur


    Homme Profil pro
    Développeur .NET
    Inscrit en
    Février 2004
    Messages
    19 875
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2004
    Messages : 19 875
    Points : 39 749
    Points
    39 749
    Par défaut
    Citation Envoyé par Binenebi Voir le message
    Je suis pour le moins perplexe. Je pensais vraiment que EF utilisait des requêtes paramétrées.
    Ca dépend du provider. Celui de SQL Server utilise des requêtes paramétrées, mais historiquement le provider ADO.NET pour MySQL a toujours été un peu bancal, donc ça m'étonne pas vraiment de voir ce genre de chose...

  3. #3
    Membre à l'essai
    Inscrit en
    Mai 2007
    Messages
    31
    Détails du profil
    Informations forums :
    Inscription : Mai 2007
    Messages : 31
    Points : 12
    Points
    12
    Par défaut
    Je vais voir du côté de MySQL dans ce cas.
    Ceci dit, c'est grave si je peux même pas sécuriser mon service WEB face à ce type d'attaque basique.
    Merci !

  4. #4
    Membre éclairé Avatar de chamamo
    Profil pro
    Inscrit en
    Juin 2006
    Messages
    588
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2006
    Messages : 588
    Points : 735
    Points
    735
    Par défaut
    Elle ressemble à quoi la requête générée par ton provider?
    As-tu essayé
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    p.name.Contains(parameter.Replace("'","''"))
    ?

  5. #5
    Membre à l'essai
    Inscrit en
    Mai 2007
    Messages
    31
    Détails du profil
    Informations forums :
    Inscription : Mai 2007
    Messages : 31
    Points : 12
    Points
    12
    Par défaut
    Effectivement, j'ai déjà des solutions de ce type pour protéger le client.
    Le problème c'est que le service est toujours vulnérable puisque n'importe qui peut potentiellement effectuer une requête sur le service.

  6. #6
    Rédacteur/Modérateur


    Homme Profil pro
    Développeur .NET
    Inscrit en
    Février 2004
    Messages
    19 875
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2004
    Messages : 19 875
    Points : 39 749
    Points
    39 749
    Par défaut
    Citation Envoyé par Binenebi Voir le message
    Effectivement, j'ai déjà des solutions de ce type pour protéger le client.
    Le problème c'est que le service est toujours vulnérable puisque n'importe qui peut potentiellement effectuer une requête sur le service.
    Effectivement. Il faudrait donc faire cette manip du côté du service, et non du côté du client... Le problème c'est que tu renvoies directement un IQueryable, et donc tu ne contrôles pas grand chose.

    J'ai une petite idée pour régler ça, mais comme je connais pas trop WCF RIA Services, je suis pas certain que ça marcherait... Enfin je te la donne quand même, tu verras bien ce que ça donne.

    En gros, l'idée serait de ne pas renvoyer directement return this.ObjectContext.product, mais un wrapper qui implémente IQueryable et se charge de faire la correction dans les appels à Contains, avec un ExpressionVisitor.

    Voilà une implémentation un peu expérimentale, qui semble fonctionner au moins avec Linq to Objects :

    Code C# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    class MySqlQueryableWrapper<T> : IQueryable<T>
    {
        private readonly IQueryable<T> _queryable;
        private readonly IQueryProvider _provider;
     
        public MySqlQueryableWrapper(IQueryable<T> queryable)
        {
            _queryable = queryable;
            _provider = new MySqlQueryProviderWrapper(queryable.Provider);
        }
     
        public Type ElementType
        {
            get { return _queryable.ElementType; }
        }
     
        public Expression Expression
        {
            get { return _queryable.Expression; }
        }
     
        public IQueryProvider Provider
        {
            get { return _provider; }
        }
     
        public IEnumerator<T> GetEnumerator()
        {
            return _queryable.GetEnumerator();
        }
     
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
     
    class MySqlQueryProviderWrapper : IQueryProvider
    {
        private readonly MySqlExpressionFixer _visitor = new MySqlExpressionFixer();
        private readonly IQueryProvider _provider;
     
        public MySqlQueryProviderWrapper(IQueryProvider provider)
        {
            _provider = provider;
        }
     
        public IQueryable CreateQuery(Expression expression)
        {
            return _provider.CreateQuery(_visitor.Visit(expression));
        }
     
        public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
        {
            return _provider.CreateQuery<TElement>(_visitor.Visit(expression));
        }
     
        public object Execute(Expression expression)
        {
            return _provider.Execute(_visitor.Visit(expression));
        }
     
        public TResult Execute<TResult>(Expression expression)
        {
            return _provider.Execute<TResult>(_visitor.Visit(expression));
        }
     
    }
     
    class MySqlExpressionFixer : ExpressionVisitor
    {    
        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            if (node.Method.Name == "Contains" &&
                node.Method.DeclaringType == typeof(string) &&
                node.Arguments.Count == 1)
            {
                var c = node.Arguments[0] as ConstantExpression;
                if (c != null)
                {
                    string s = c.Value as string;
                    if (s != null)
                    {
                        s = s.Replace("'", "''");
                        node = Expression.Call(node.Object, node.Method, Expression.Constant(s));
                    }
                }
            }
     
            return base.VisitMethodCall(node);
        }
    }

    Pour l'utiliser, tu remplaces ça :
    Code C# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    public IQueryable<Product> GetProduct()
    {
        return this.ObjectContext.product;
    }

    Par ça :

    Code C# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    public IQueryable<Product> GetProduct()
    {
        return new MySqlQueryableWrapper<Product>(this.ObjectContext.product);
    }

    Et le test que j'ai fait pour vérifier si ça marchait :

    Code C# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
        var q1 = new[] { "salut", "l'ami 1", "l''ami 2" }.AsQueryable();
        var q2 = new MySqlQueryableWrapper<string>(q1);
        var result1 = q1.Where(s => s.Contains("l'ami")).FirstOrDefault(); // "l'ami 1"
        var result2 = q2.Where(s => s.Contains("l'ami")).FirstOrDefault(); // "l''ami 2"

    Bon, ça reste du gros bricolage, il vaudrait mieux corriger le provider MySQL, mais enfin c'est toujours mieux que de laisser une faille de sécurité béante...

  7. #7
    Membre à l'essai
    Inscrit en
    Mai 2007
    Messages
    31
    Détails du profil
    Informations forums :
    Inscription : Mai 2007
    Messages : 31
    Points : 12
    Points
    12
    Par défaut
    Bonsoir.

    Et bien merci bien pour cette ébauche ! Je vais étudier cela de prêt.

    Il semble qu'il existe une version beta du pilote MySQL donc je vais également essayer de voir de ce côté là.
    Cependant, je n'arrive pas à mettre à jours le pilote. J'ai créé un second post sur le forum dans la section IIS.
    Si vous avez un avis sur la question je suis également preneur. Ceci dit on s'écarte un peu du sujet traité ici donc je n'en dirais pas plus ^^.

    Encore merci !

  8. #8
    Membre à l'essai
    Inscrit en
    Mai 2007
    Messages
    31
    Détails du profil
    Informations forums :
    Inscription : Mai 2007
    Messages : 31
    Points : 12
    Points
    12
    Par défaut
    Bonsoir.

    J'ai intégré le code que vous m'avez suggéré.
    Cela semble fonctionner.
    En revanche, je n'ai pas réussi à trouver la parade lorsque j'utilise la méthode StartWith

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    DomainContext.Load<Product>(DomainContext.GetProductQuery()
                                             .Where<Product>(p => p.name.StartWith(parameter))
                                             .Take<Product>(30));
    A priori, l'approche change totalement au niveau du Framework. Il n'est pas possible d'accéder à une quelconque valeur depuis les arguments de l'objet MethodCallExpression.

    Auriez-vous une piste ?
    En tout cas merci beaucoup pour le temps passé à écrire ce code !

  9. #9
    Membre éclairé Avatar de chamamo
    Profil pro
    Inscrit en
    Juin 2006
    Messages
    588
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2006
    Messages : 588
    Points : 735
    Points
    735
    Par défaut
    Il suffit de prendre en compte la Méthode "StartWith" dans la méthode VisitMethodCall

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    if ((node.Method.Name == "Contains" || node.Method.Name == "StartWith") &&
                node.Method.DeclaringType == typeof(string) &&
                node.Arguments.Count == 1)
    Mais ça reste du bricolage car ce n'est pas optimal, la requête exécutée coté MySql ne prend pas en compte ton implémentation, tu vas seulement filtrer le résultat de la requête SQL.

  10. #10
    Rédacteur/Modérateur


    Homme Profil pro
    Développeur .NET
    Inscrit en
    Février 2004
    Messages
    19 875
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2004
    Messages : 19 875
    Points : 39 749
    Points
    39 749
    Par défaut
    En principe ça devrait être à peu près pareil que Contains... la modif proposée par chamamo devrait fonctionner

  11. #11
    Membre éclairé Avatar de chamamo
    Profil pro
    Inscrit en
    Juin 2006
    Messages
    588
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2006
    Messages : 588
    Points : 735
    Points
    735
    Par défaut
    Le mieux c'est de prendre un connecteur commercial (Devart par exemple).

  12. #12
    Membre à l'essai
    Inscrit en
    Mai 2007
    Messages
    31
    Détails du profil
    Informations forums :
    Inscription : Mai 2007
    Messages : 31
    Points : 12
    Points
    12
    Par défaut
    Justement, j'ai regardé avec le debugger. A aucun moment je ne vois passer de "method.name" contenant "StartWith".
    Je me suis dit que ça fonctionnerait mais c'était trop beau !

    En réalité, il semblerait qu'un nouvelle objet implémentant l'interface Expression soit créé à la place avec encore tout un bordel derrière.

    Pour le moment, je n'ai pas réussi à tout identifier.
    J'ai rapporté le bug sur le site officiel. Pour le moment j'attends. On verra s'ils tiennent compte de ce fait ... ou pas.

  13. #13
    Rédacteur/Modérateur


    Homme Profil pro
    Développeur .NET
    Inscrit en
    Février 2004
    Messages
    19 875
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2004
    Messages : 19 875
    Points : 39 749
    Points
    39 749
    Par défaut
    Mais avec Contains ça marche bien ?

    Citation Envoyé par Binenebi Voir le message
    Justement, j'ai regardé avec le debugger. A aucun moment je ne vois passer de "method.name" contenant "StartWith".
    Bizarre... Tu pourrais poster le contenu de l'expression avant qu'elle soit passée au visiteur ? (le résultat de expression.ToString() dans les méthodes de MySqlQueryProviderWrapper)

  14. #14
    Membre à l'essai
    Inscrit en
    Mai 2007
    Messages
    31
    Détails du profil
    Informations forums :
    Inscription : Mai 2007
    Messages : 31
    Points : 12
    Points
    12
    Par défaut
    Je ne sais pas si c'est exactement ce que vous demandez.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    {value(System.Data.Objects.ObjectQuery`1[StockC2L.Web.product]).MergeAs(AppendOnly).IncludeSpan(value(System.Data.Objects.Span)).OrderBy(p => p.name).Where(Param_0 => (Param_0.name.ToLower().StartsWith("te") == True)).Take(30)}

  15. #15
    Membre éclairé Avatar de chamamo
    Profil pro
    Inscrit en
    Juin 2006
    Messages
    588
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2006
    Messages : 588
    Points : 735
    Points
    735
    Par défaut
    Tu l'as dans le Where :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Param_0.name.ToLower().StartsWith("te")
    Et ça marche au moins? ça filtre en fonction du paramètre passé?

  16. #16
    Membre à l'essai
    Inscrit en
    Mai 2007
    Messages
    31
    Détails du profil
    Informations forums :
    Inscription : Mai 2007
    Messages : 31
    Points : 12
    Points
    12
    Par défaut
    Je vais essayé de vous faire un screenshot directement de la différence entre les deux types de requêtes.

  17. #17
    Rédacteur/Modérateur


    Homme Profil pro
    Développeur .NET
    Inscrit en
    Février 2004
    Messages
    19 875
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2004
    Messages : 19 875
    Points : 39 749
    Points
    39 749
    Par défaut
    C'est parce que tu cherchais "StartWith" alors que la méthode s'appelle "StartsWith"

  18. #18
    Membre à l'essai
    Inscrit en
    Mai 2007
    Messages
    31
    Détails du profil
    Informations forums :
    Inscription : Mai 2007
    Messages : 31
    Points : 12
    Points
    12
    Par défaut
    Effectivement, j'avais le nez dans le guidon !
    Je suis aller chercher trop loin.

    Bref, en tout cas c'est sincèrement appréciable.
    Ceci me permettra de colmater le temps de trouver une solution durable.

    Encore merci !

Discussions similaires

  1. Réponses: 27
    Dernier message: 11/08/2011, 22h12
  2. WCF RIA Services
    Par Kais. dans le forum Silverlight
    Réponses: 3
    Dernier message: 26/04/2010, 21h40
  3. Réponses: 6
    Dernier message: 22/12/2009, 21h11

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo