Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

CoqBlog

.NET is good :-)
{ Blog de Gaël Covain }

Actualités

FxCop 1.36 est bien disponible

J'en avais parlé là suite à l'apparition du téléchargement sur le Download Center, et le team Code Analysis a donner la liste des changements quelques jours après : Code Analysis Team Blog : FxCop 1.36 Released!

Microsoft FxCop 1.36

.NET Reflector change de propriétaire

Pour ceux qui ne l'aurait pas remarqué (ou tout simplement pas fait de mise à jour récente) : vous ne verrez plus "Lutz Roeder's .NET Reflector" dans la barre de titre de votre outil préféré mais bien "Red Gate's .NET Reflector".

En effet, comme annoncé par Lutz Roeder sur son blog, le développement sera maintenant assuré par Red Gate Software, que vous connaissez sans doute pour leurs outils pour SQL Server et .NET.

.NET Reflector, class browser, analyzer and decompiler for .NET

 

Et tant que je parle d'outil, il y en a un nouveau chez Sysinternals (que vous connaissez probablement pour Process Explorer, Process Monitor, etc) : Desktops
Comme son nom l'indique un peu, il s'agit d'une solution de bureaux multiples (4) pour Windows. Jusqu'à maintenant j'étais assez déçu par les outils de ce genre mais celui là je l'ai utilisé toute la journée (sur un Windows XP SP3) et il est plutôt sympa : simple et efficace.

DevExpress : 60 contrôles (WinForm et ASP.NET) gratuits

Je viens de voir celà sur le blog de Greg Duncan : DevExpress propose un téléchargement contenant 60 contrôles gratuits (totalement apparemment, mais bien lire le EULA quand même une fois téléchargé, notamment pour préciser le périmètre d'utilisation), et des versions d'évaluation de leurs autres contrôles.

Depuis le temps que je me dit qu'il faut que je jette un oeil à leur offre, autant en profiter.
Je n'ai pas encore moi même terminé le téléchargement, mais avis aux amateurs (plus rapides que moi, surtout que je n'ai pas la moindre idée de la durée de l'offre).

Developer Express - Over 60 Free Controls from DevExpress

Posted: samedi 16 août 2008 20:46 par coq | 0 commentaire(s)
Classé sous : ,
Executables .NET depuis un partage réseau : le SP1 de .NET 3.5 apporte bien la solution annoncée

Ca se confirme, c'est dans le SP1 de .NET 3.5 qui a été publié cette semaine : si lancées directement depuis un partage réseau, les applications .NET auront maintenant plus ou moins le même comportement que les executables natifs. Ce n'était pour l'instant pas le cas avec l'attribution d'un jeu de permissions réduit, ce que certains considéraient purement et simplement comme un bug.

Il y avait d'abord eu un sondage, puis l'annonce de la présence possible du patch dans le SP1 de .NET 3.5 avec des explications et c'est donc maintenant totalement confirmé.

Si nécessaire, vous pouvez choisir de conserver l'ancien comportement en créant une valeur LegacyMyComputerZone sous la clé HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework, comme indiqué dans le post de Shawn Farkas ou sur cette page de la documentation.

Mise à jour de FxCop 1.36 (version finale ?)

Jusqu'à présent FxCop 1.36 était estampillé "Beta 2", mais la mise à jour disponible aujourd'hui sur le centre de téléchargement ne l'est plus : Microsoft FxCop 1.36

La page n'indique que "This version provides support for .NET 3.5 SP1." en supplément des informations présentes pour la beta 2.
Version finale ?

Je les utilise...

...et j'en suis fier...

Vive les clignotants...

C'est tout...

Little Known Fact : Il existe une documentation C#/.NET/... et une autre mise à jour, une !

Aller, encore une petite mise à jour de ma série de posts Little Known Fact : Il existe une documentation C#/.NET/..., vu que la documentation pour VS2008 SP1 est maintenant disponible, pour accompagnerle SP1 en lui même.

Donc, où est cette fameuse documentation :

 

Et toujours le WebService pour accéder au contenu MSDN/TechNet : MTPS Content Service, dont se servent notamment des outils comme PackageThis qui permet de se générer des fichiers d'aide HTML Help (.chm) ou HTML Help 2.0 (.hxs) à partir de contenu choisi.

Certains noms sont vraiment longs...

Ce post de James Kovacs m'a fait rire :p : Why Developers Are Interested in REST

Du coup si vous vous posez quelques questions sur les longueurs de certains noms de méthodes/champs, vous pouvez obtenir quelques informations très rapidement avec le CQL dans NDepend. Exemple rapide avec un projet NDepend analysant les librairies du Framework : .NET 2.0 SP1 (mscorlib.dll et System*.dll) + .NET 3.0 SP1 (%ProgramFiles%\Reference Assemblies\Microsoft\Framework\v3.0\*.dll) + .NET 3.5 (%ProgramFiles%\Reference Assemblies\Microsoft\Framework\v3.5\*.dll) :

  1. // <Name>Méthodes avec noms long (50 à 70 chars)</Name>
    SELECT METHODS WHERE (IsPublic OR IsProtected ) AND NameLike "^[^\<\(]{50,70}.*$"
  2. // <Name>Champs avec noms long (50 à 70 chars)</Name>
    SELECT FIELDS WHERE (IsPublic OR IsProtected ) AND NameLike "^[^\<\(]{50,70}.*$"
  3. // <Name>Méthodes avec noms long (70 chars et plus)</Name>
    SELECT METHODS WHERE (IsPublic OR IsProtected ) AND NameLike "^[^\<\(]{70,}.*$"
  4. // <Name>Champs avec noms long (70 chars et plus)</Name>
    SELECT FIELDS WHERE (IsPublic OR IsProtected ) AND NameLike "^[^\<\(]{70,}.*$"

 NDepend - Liste des requêtes CQL

Méthodes avec noms long (70 chars et plus) :

  • get_FlowDocumentScrollViewerDocumentBelongsToAnotherFlowDocumentScrollViewerAlready()
  • get_TextElementCollection_PreviousSiblingDoesNotBelongToThisCollection()
  • get_WSSecurity10WSTrust13WSSecureConversation13WSSecurityPolicy12BasicSecurityProfile10()
  • get_WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005BasicSecurityProfile10()
  • get_WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10()
  • get_WSSecurity11WSTrust13WSSecureConversation13WSSecurityPolicy12BasicSecurityProfile10()
  • get_WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005BasicSecurityProfile10()
  • get_WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11()
  • get_WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10()

Champs avec noms long (70 chars et plus) (les 6 résultats sont dûs à la présence de ces définitions à plusieurs endroits) :

  • IIDENTITYAUTHORITY_DOES_DEFINITION_MATCH_REFERENCE_FLAG_EXACT_MATCH_REQUIRED
  • CMS_ASSEMBLY_REFERENCE_DEPENDENT_ASSEMBLY_FLAG_RESOURCE_FALLBACK_CULTURE_INTERNAL
Fatigué de supprimer le fichier "AuthoringTests.txt" (et Co) dans les projets de test ?

Je viens tout juste de découvrir via Greg l'existance de l'option qui va bien : il suffit d'aller décocher la checkbox "About Test Projects introduction file" (et en profiter pour régler 2/3 autres choses) dans les options de VS, section Test Tools/Test Project, que ce soit sous Visual Studio 2005

Options de Visual Studio 2005, Test Tools/Test Project

ou Visual Studio 2008

Options de Visual Studio 2008, Test Tools/Test Project

Voilà, il fallait juste savoir que l'option existait, personnellement je n'ai jamais penser que c'était le cas donc je n'ai jamais réellement chercher après...
On en apprend tous les jours :p

PowerGUI 1.5.1 RTM disponible

Une nouvelle version de PowerGUI est disponible.

Parmis les nouveautés, une qui devrait plaire aux amateurs de langue française : les localisations, dont le français dont je vous avais parler, sont maintenant intégrées au setup et sélectionnables facilement dans les 2 outils :

PowerGUI - sélection de la langue

PowerGUI Script Editor - sélection de la langue

 

Support de l'intellisense sur env et function :

PowerGUI Script Editor

 

Et il est aussi annoncé une intégration aux outils VMWare.
La liste complète des changements est disponible ici.

PowerGUI

Projet CLR Security sur CodePlex

Un nouveau projet Microsoft a été lancé sur CodePlex cette semaine : CLR Security.
Il s'agit tout simplement du projet du team du même nom et devrait être destiné à recevoir divers sous projets visant à étendre et apporter de nouvelles fonctionnalités aux APIs de sécurité intégrées au framework .NET, et probablement fournir des outils liés au développement avec ces APIs.

Le premier élément disponible est la libraire Security.Cryptography 1.0 (Security.Cryptography.dll) qui offre notamment des wrappers autours des APIs CNG (Cryptography API: Next Generation) :

L'utilisation de ces wrappers nécessite bien évidemment de cibler une plateforme Vista / Server 2008 (et supérieurs).

Avis aux amateurs.

.NET Security Blog : CLR Security Team CodePlex Site

PowerShell : Compare-Object est votre amie (exemple sur synchronisation répertoire par répertoire)

Aujourd'hui je me suis retrouvé avec un besoin relativement basique : effectuer un mirroir d'un répertoire donné.

Jusque là, pas de problème, l'ami robocopy et son paramètre MIR sont plutôt sympas pour ce genre de choses :

robocopy.exe ".\Rep" "\\Dest\Mirroirs\Rep" /MIR /FFT

Seulement voilà, executer cette commande avec une destination ne contenant aucun des fichiers de la source équivaut bien évidemment à la copie de toutes les données, dans mon cas ça veut dire 9 sous-répertoires totalisant 60Go de données.
Vu le volume et la vitesse du réseau (un petit 100Mbps), je préfère faire une copie sous-répertoire par sous-répertoire vers la destination avant de mettre en place la commande de mirroir qui se chargera de garder tout celà à jour.

Rien de bien compliqué :

robocopy.exe ".\Rep\SousRep1" "\\Dest\Mirroirs\Rep\SousRep1" /ZB /E

Sauf que je suis du genre flemmard et que je n'ai pas envie de faire 9 fois la modification de la commande à la main.
L'idéal serait donc de pouvoir lancer 9 fois (je veux garder le choix du moment de démarrage de chaque copie) un script qui choissise automatiquement le premier sous-répertoire de la source qui n'existe pas encore dans la destination.
Facile avec PowerShell : vous disposez de base de la sympathique Cmdlet Compare-Object qui comme l'indique permet de faire des comparaisons entre les paramètres ReferenceObject et DifferenceObject.
C'est exactement ce qu'il nous faut, une liste de répertoires pouvant bien entendu servir d'entrée pour cette cmdlet :-)

 

Prenez ce code, Src\Rep contenant 9 sous-répertoires et Dest\Rep étant vide :

$srcDir="D:\Temp\Tests\Src\Rep"
$targetDir="D:\Temp\Tests\Dest\Rep"
Compare-Object $(dir $srcDir | where{$_.PSIsContainer}) $(dir $targetDir | where{$_.PSIsContainer})

Vous obtenez :

InputObject       SideIndicator
-----------       -------------
SousRep1          <=
SousRep2          <=
SousRep3          <=
SousRep4          <=
SousRep5          <=
SousRep6          <=
SousRep7          <=
SousRep8          <=
SousRep9          <=

SideIndicator indique de quel côté se trouve le répertoire qui manque de l'autre côté : ici nous voyons donc que les sous-répertoires n'existent que dans la source.
La même commande executée avec le sous-répertoire "SousRep1" dans le répertoire cible donnera la sortie suivante :

InputObject       SideIndicator
-----------       -------------
SousRep2          <=
SousRep3          <=
SousRep4          <=
SousRep5          <=
SousRep6          <=
SousRep7          <=
SousRep8          <=
SousRep9          <=

Si vous désirez voir les répertoires existants des 2 côtés, le paramètre IncludeEqual est fait pour vous.
Mais là ça ne nous intéresse bien évidemment pas, nous allons de toute façon filtrer sur SideIndicator = "<=".

 

Dans le cas qui nous intéresse l'utilisation sera la suivante :

# répertoire source
$srcDir="D:\Temp\Tests\Src\Rep"

# répertoire cible
$targetDir="D:\Temp\Tests\Dest\Rep"

# définition du template de commande pour Robocopy
$robocopyCommand='robocopy.exe "$srcDir\$currentFolder" "$targetDir\$currentFolder" /ZB /E'

# Execution : 
# 1) Comparaison des listes des répertoires
# 2) Filtrage pour obtenir seulement la liste de ceux absent de la destination
# 3) Sélection du premier qui se trouve dans ce cas
# 4) Execution de la commande Robocopy pour effectuer la copie
Compare-Object $(dir $srcDir | where{$_.PSIsContainer}) $(dir $targetDir | where{$_.PSIsContainer}) | where {$_.SideIndicator -eq "<="} | select -First 1 | % { $currentFolder=$_.InputObject; Invoke-Expression -Command $robocopyCommand }

 

Ce script lancera ainsi une commande différente à chaque execution :

  1. robocopy.exe "D:\Temp\Tests\Src\Rep\SousRep1" "D:\Temp\Tests\Dest\Rep\SousRep1" /ZB /E
  2. robocopy.exe "D:\Temp\Tests\Src\Rep\SousRep2" "D:\Temp\Tests\Dest\Rep\SousRep2" /ZB /E
  3. etc

Ce qui me permet de faire ma mise en place en 9 fois sans me fatiguer plus que ça.

 

L'exemple pris ici n'est pas forcément parlant (et utile) pour tout le monde mais je pense qu'il s'agit cependant d'un exemple assez intéressant de l'utilisation de Compare-Object.

Bon scripting.

PowerGUI en Français

Pour ceux qui préfèrent avoir leurs outils en français (personnellement je me suis habitué aux softs US depuis longtemps), une traduction FR pour PowerGUI est maintenant disponible, grâce au travail de Mathieu Chateau.

PowerGUI avec la traduction FR

Une nouvelle traduction rejoint donc les autres déjà disponibles, avis aux amateurs.

PowerGUI.org - French Localization

 

PS : si vous vous trouvez dans le cas où la bascule ne se fait pas seule vers les ressources FR (le changement semble être basé sur la langue de l'OS et pas les paramètres régionaux), et que en éditant le fichier de configuration (quest.powergui.xml) comme indiqué vous ne trouvez pas la section spécifiant la langue (default ui language), c'est peut être dû à votre utilisation de PowerGUI assez ancienne : pour ma part j'ai opter pour la méthode "douce" suppression du profil, mais il y a peut être moins violent. A tout hasard je vous met la section en question :

<?xml version="1.0" encoding="utf-8"?>
<configuration id="57cbb640-6232-4995-99aa-f68bb734e51e">
 
<items>
   
<container id="0b8eebbc-08c6-472b-ae1b-57f1a96f3467" name="HostFactory" type="Quest.PowerGUI.WinFormFactory">
     
<value>Quest.PowerGUI.UI.WinForm, Version=1.5.0.434, Culture=neutral, PublicKeyToken=a69c32b63191909f</value>
     
<items>
       
<item id="08a2a9f3-6d8e-49cc-a59a-36f34166f603" name="default ui language">
         
<value>fr-FR</value>
       
</item>
     
</items>
   
</container>
...
 
</items>
</configuration>
L'injection SQL n'est PAS un problème QUE pour les développeurs web !

J'ai l'impression que pas mal de personnes sont parties sur une fausse idée avec ce problème d'injection SQL : certains ont l'air de penser qu'il s'agit uniquement d'un problème rencontré avec les applications dotées d'une interface utilisateur web (dans l'écosystème qui nous intéresse : ASP.NET, et toute les technologies reposant dessus).

 

 

L'injection SQL est uniquement un problème de développeur Web ?

Ce n'est absolument pas le cas : une application WinForm/WPF/Console/... sera impactée elle aussi.
Certes une application web offre probablement un risque d'exploitation de la faille plus élevé de par son exposition à un plus grand nombre de sources d'attaques, et certainement un nombre de points d'entrées plus important (zones de saisie, querystring, cookies, etc), mais il n'en demeure pas moins que l'utilisateur agissant de l'intérieur n'est pas plus digne de confiance qu'un anonyme sur le réseau (de l'entreprise ou internet).

 

 

Mais qu'est ce que l'injection SQL ?

L'attaque par injection SQL est une attaque reposant sur une faille de sécurité (défaut de vérification et sécurisation des entrées) dont le but à parvenir à provoquer l'exécution d'un code malicieux, initialement non prévu par le système vulnérable.
Pour cela, on procède tout simplement par insertion de ce code dans des chaînes de caractères qui seront par la suite utilisées pour bâtir un ordre SQL envoyé au serveur SQL pour exécution, en s'arrangeant pour qu'au final l'insertion dans une requête SQL rende notre code exécutable. Le serveur SQL n'a aucune raison valable de ne pas l'exécuter à partir du moment où il est valide.

Il se présente donc notamment si vous utilisez certains types d'informations pour les insérer dans des requêtes SQL sans prendre de précautions particulières :

  • saisies par l'utilisateur dans un formulaire (quelle que soit la technologie utilisée, pas forcément web)
  • provenant de champs cachés dans le formulaire
  • provenant de paramètre d'url (querystring)
  • provenant de cookies
  • ...

Mais recadrons les choses différemment : le problème d'injection SQL n'est pas seulement lié à un type d'interface de saisie, il n'est même pas lié seulement au fait que la donnée est saisie : il est lié à la donnée en elle même.
Le souci peut très bien se présenter dans un processus sans intervention humaine directe, avec par exemple traitement de données issues de fichier CSV/XML/... provenant de sources diverses. Ces données ont donc potentiellement été traitées par un être humain à un lointain bout de la chaîne, directement ou pas : le texte manipulé peut provenir d'une opération d'OCR par exemple.
Un autre point important à garder à l'esprit est que l'attaque par injection SQL n'est pas forcément à effet direct lorsque l'utilisateur saisi son code "malicieux" au travers du moyen approprié : ses effets peuvent être déclenchés durant toute la durée de vie de la donnée.

 

 

Seulement un problème de sécurité ?

Hormis le côté sécurité (vol, destruction, etc) du problème, il y a un autre aspect de la chose identique en tout point hormis le côté volontaire qui caractérise l'attaque par injection SQL : la corruption involontaire de l'ordre SQL. Cet aspect là, tout le monde doit le connaitre.
Certes il ne s'agit pas directement à proprement parler d'injection SQL vu qu'il ne s'agit pas réellement d'une attaque mais le fond est le même, et l'existence de ce problème de corruption rend l'attaque par injection SQL possible.

Un exemple courant est celui tout simple de la gestion de personnes : vous enregistrez des noms et prénoms, avec un code de ce genre pour la production du code SQL :

// NE PAS UTILISER CECI ! / DON'T USE THAT !
String query = String.Format(CultureInfo.InvariantCulture,
  "INSERT INTO [MonSchema].[MaTable] ([FirstName], [LastName]) VALUES ('{0}', '{1}');"
,
 
firstName,
 
lastName
 
);

Pas de chance, un beau jour vous devez enregistrer les informations d'une personne dont le nom comporte une apostrophe, et la requête générée a alors cet aspect là :

INSERT INTO [MonSchema].[MaTable] ([FirstName], [LastName]) VALUES ('Jean', 'D'upont');

Dans le meilleur des cas nous avons un ordre invalide et une erreur d'exécution, causant probablement des blocages, pertes financières, etc le temps de corriger le code mais peut être pas de corruption/destruction de données.
Par contre dans le pire des cas nous avons involontairement un code exécutable par le serveur différent de celui que nous avions prévu, qui ne sera peut être pas détecté dans l'immédiat si les effets produits ne sont pas flagrants, mais qui peut donc présenter un fort risque de corruption/perte de données.
Imaginez ici que l'opération d'OCR d'où provient le texte à persister porte sur un livre parlant de SQL, avec un exemple de code montrant comment supprimer les enregistrements de toutes les tables de la base courante...

Pour la suite de ce post, je considèrerais que les deux aspects du problème ne font qu'un, d'ailleurs les solutions pour l'un empêchent l'autre de se produire.
Les exemples sont quant à eux basés sur .NET (en C#) et SQL Server, mais le problème ne touche bien évidemment pas que ces technologies là.

 

 

Les solutions ?

Comment palier à ce problème ?
"Nettoyer" soit même les entrées est illusoire : vous ne connaissez probablement pas toutes les subtilités des différents moteurs de base de données, et ces moteurs sont de toute façon amenés à évoluer. "Nous modifierons le code à ce moment là" n'est pas une réponse valide : le code risque de mal vieillir.

Attention, soyons clair, je parlais bien ici de nettoyage en vue d'éviter la corruption de l'ordre SQL, pas de la nécessaire validation des entrées qui n'est pas directement attachée au problème dont nous parlons.
Il s'agit par exemple de la vérification des tailles, longueurs : si vous offrez une zone "commentaires", il y a des chances que vous ayez besoin pour elle de plus de 4000 caractères, auquel cas elle sera sans doute persistée en base sous forme d'un type nvarchar(MAX). Mais ça ne veut pas pour autant dire que vous voulez que la personne puisse envoyer 2Go de texte en base.

 

Les solutions en général proposées sont :

  • utiliser des requêtes paramétrées
  • utiliser des procédures stockées

Nous n'entrerons pas ici dans le débat de fond pour ou contre l'utilisation de procédures stockées, ce n'est pas le sujet.
A la liste précédente, nous pouvons ajouter : utiliser un outil de mapping objet/relationnel. Mais nous nous assurerons que le code SQL qu'il génère est bien évidemment paramétré et non pas basé sur de bêtes concaténations. Il ne s'agit pas de déporter le problème loin de nos yeux, mais d'ajouter une chance supplémentaire que les développeurs finaux ne fassent pas d'erreurs.
Cet outil va donc au final reposer sur une des solutions proposées, et donc est plus une couche supplémentaire qu'une solution directe. Sur le sujet qui nous concerne au travers de ce post, il aura surtout l'avantage de permettre aux architectes de limiter encore plus les risques de dérapage de la part des personnes qui exécutent le travail.

Est ce que ces 2 solutions se suffisent à elles mêmes ? Est ce que le simple fait de les utiliser suffit pour garantir la sécurité des données ? Non, il faut réellement que les personnes qui vont intervenir sur l'accès aux données comprennent ce problème.

Concernant les requêtes paramétrées, l'élément le plus proche de la requête dynamique habituelle (et dangereuse), et donc le plus simple à mettre en oeuvre à la place de cette dernière, il n'y a pas (à ma connaissance) grand chose de plus à faire pour sécuriser un peu plus.
Si la requête effectue un ordre INSERT, il faut que l'utilisateur ait directement ce droit sur les objets cibles.
Concernant les procédures stockées, il y a plus à dire. Le simple fait de déporter l'ordre INSERT dans une procédure stockée ne vous permet pas de passer magiquement d'un risque majeur à un risque zéro : encore faut t'il que l'appel de la procédure soit effectué de façon... paramétrée. En effet au final l'utilisation de procédures stockées est plus une couche supplémentaire qu'une solution directe au problème.

Rappelons qu'il y a au moins 2 moyens d'exécuter une procédure stockée depuis du code .NET : utiliser directement les facilités offertes par les objets d'accès aux données au travers de IDbCommand.CommandType en lui affectant CommandType.StoredProcedure, ou utiliser l'ordre EXECUTE dans un requête tout ce qu'il y a de plus commun.
On a tendance à oublier ce second moyen, mais le danger est pourtant bien à ce niveau là.
En utilisant CommandType.StoredProcedure, les données seront forcément spécifiée via l'implémentation de IDataParameter spécifique au provider utilisé, alors qu'au contraire avec l'utilisation de l'ordre EXECUTE vous avez le risque qu'un de vos développeurs écrive quelque chose de ce genre :

// NE PAS UTILISER CECI ! / DON'T USE THAT ! 
String query = String.Format(CultureInfo.InvariantCulture,
  "EXECUTE [MonSchema].[AddPerson] @FirstName='{0}', @LastName='{1}';"
,
 
firstName,
 
lastName
 
);

Du coup, vous avez ici un formidable exemple de fausse impression de sécurité : la personne a utiliser une procédure stockée, c'est donc sécurisé. Ce n'est bien sûr pas du tout le cas, la requête ayant cet aspect là pour notre ami Jean D'upont :

EXECUTE [MonSchema].[AddPerson] @FirstName='Jean', @LastName='D'upont';

Placez maintenant un ordre SQL là où il faut...
Vous devez donc bien faire attention à la façon dont sont compris les conseils que vous donnez.

 

C'est là qu'on arrive sur un autre aspect à prendre en compte lorsque l'on a opté pour l'utilisation exclusive de procédures stockées : la seule permission dont a réellement besoin l'identité utilisée (qui n'a bien entendu pas reçu le rôle db_owner, n'est ce pas) par l'application cliente pour l'accès à la base de données est EXEC sur ces fameuses procédures et rien d'autre, limitant ainsi les impacts d'une éventuelle attaque réussie.
C'est là que vous aurez besoin de dialoguer un peu avec votre DBA préféré, il doit aimer jouer avec ces choses là.

Il s'agit ici d'une autre "solution" directement couplée à l'utilisation de procédures stockées qu'on voit parfois abordée quand on parle de ce problème d'injection SQL : utiliser des permissions en exécution seule.
Je ne l'ai pas citée plus haut car ce n'en est pas réellement une. En effet elle ne permet en rien de résoudre directement le problème mais elle vient plutôt en complément et permet en partie de limiter les impacts d'une attaque réussie.

Attention, comprenons nous bien : la limitation des droits de l'utilisateur à de simples permissions en exécution ne vous permettrons pas pour autant de faire l'appel de la procédure stockée n'importe comment en tout sécurité.
En reprenant notre exemple précédent, si le code SQL injecté par l'assaillant est un ordre INSERT/UPDATE/etc sur une table, il échouera. Mais s'il s'agit encore de notre WHILE, il sera exécuté, provoquant une consommation excessive de ressources.
L'attaquant pourra aussi se reposer sur l'appel d'autres procédures stockées auxquelles à accès l'utilisateur, qui lui permettront peut être d'altérer/détruire les données.
Il se peut aussi qu'au travers d'une attaque il puisse accéder à un serveur lié et que cette liaison aie été effectuée, pour diverses raisons, avec des credentials possédant un niveau de privilèges plus élevé.
Sans parler des manipulations qui pourraient permettre d'arriver à une élévation de privilèges.

De manière générale limiter les privilèges de l'utilisateur au strict nécessaire n'est jamais une mauvaise chose (par exemple empêcher l'utilisation de choses comme xp_cmdshell, Database Mail, ... si l'utilisateur n'a aucune raison valable d'y avoir accès), mais vous ne pouvez pas considérer cette seule action comme suffisante.

 

 

J'ai vérifié le code traitant des données externes, je peux me reposer sur mes lauriers maintenant ?

Absolument pas !
Souvenez vous, j'ai dit plus haut que l'attaque n'était pas forcément à effet direct : si vous avez fait en sorte qu'un code malicieux saisi ne soit pas exécuté lors de l'enregistrement des données en base, vous ne pouvez pas arrêter oublier l'injection SQL et penser que vos données sont maintenant dénuées de tout risque.

Imaginez que votre utilisateur s'est identifié en tant que "Jean" / "Dupont'); WHILE 1=1 DECLARE @nb int; --".
Grâce à votre enregistrement des données avec une requête paramétrée, il dispose maintenant d'un nom assez ridicule dans votre application. Mais justement, son nom est bel et bien "Dupont'); WHILE 1=1 DECLARE @nb int; --" en base, sans aucune conséquence réelle pour l'instant vu que c'est une donnée.

Mais que se passe t'il si vous partez du principe qu'une fois en base vos données sont saines et que fort de ce sentiment vous utilisez une concaténation de chaînes pour bâtir un ordre au lieu de faire encore et toujours une requête paramétrée ?
Dans notre cas le "WHILE 1=1 DECLARE @nb int;" devient exécutable, vous aimez les boucles infinies ? (oui, il y a des timeouts, mais tout de même).

La règle est toujours aussi simple : si vous devez utiliser des données provenant de votre base pour bâtir d'autres requêtes, utilisez des paramètres / procédures stockées (correctement appelées). Et de toute façon, comme dit plus haut, vous n'êtes toujours pas à l'abri d'un apostrophe légitime, donc pourquoi prendre ce risque d'obtenir un ordre SQL invalide même si les données stockées sont dignes de confiance (si ça arrive réellement...) ? Et repensez aussi au coup de l'OCR...

 

 

Et dans le code des procédures stockées ? (ou le restant du batch d'une requête paramétrée)

Voilà un dernier point auquel on ne pense pas forcément, et pourtant le risque est bien là : vous ne devez pas faire n'importe quoi non plus dans le code SQL utilisant les paramètres, que ce soit un simple batch ou une procédure stockée, fonction, ...
La simple utilisation de procédures stockées (et des paramètres en général) ne vous garanti pas que votre donnée est définitivement saine, ça vous garanti juste que la donnée sera transmise en tant que tel.

Pour illustrer, prenons l'exemple d'une procédure stockée permettant de faire une recherche des personnes dont le nom commence par celui d'une autre, et que pour une raison valable (dans l'exemple courant il n'y en a pas réellement) vous effectuez cette recherche au moyen d'une requête dynamique définie dans le corps de la procédure :

-- NE PAS UTILISER CECI ! / DON'T USE THAT 
CREATE PROCEDURE [MonSchema].[FindPersonBAD] 
( 
   
@LastName  nvarchar(256) 
) 
AS 
BEGIN 
    
   
-- NE PAS UTILISER CECI ! / DON'T USE THAT 
    DECLARE @sql nvarchar(4000);
    
   
SET @sql = N'SELECT [FirstName], [LastName] 
        FROM [MonSchema].[MaTable] 
        WHERE [LastName] LIKE '''
+ @LastName + N'%'''; 
    
   
EXECUTE (@sql); 
   
-- NE PAS UTILISER CECI ! / DON'T USE THAT 

END 

Si jamais un agresseur a prévu ce genre de cas, et que son nom est "Dupont%'; WHILE 1=1 DECLARE @nb int; --", vous venez une nouvelle fois de revivre le coup de la boucle infinie (certes vos DBA, si DBA il y a, ont probablement limités les effets de ce code précis en limitant la durée maximum d'exécution des requêtes mais quand même...).
Si ça ne vous suffit pas, imaginez un remplacement de la boucle par un code plus "sympathique", comme un ordre UPDATE : corruption d'informations et impact sur les performances en cas de table très volumineuse.

Si vous devez vraiment utiliser du SQL dynamique dans votre code SQL, ayez au moins le réflexe de passer par des paramètres : la procédure sp_executesql vous permet d'y arriver très simplement :

CREATE PROCEDURE [MonSchema].[FindPerson] 
( 
   
@LastName  nvarchar(256) 
) 
AS 
BEGIN 
    
   
-- TODO : valider les entrées
    
   
DECLARE @sql nvarchar(4000);
    
   
SET @sql = N'SELECT [FirstName], [LastName] 
        FROM [MonSchema].[MaTable] 
        WHERE [LastName] LIKE @Name+''%'''
; 
    
   
EXECUTE sp_executesql 
       
@stmt = @sql, 
       
@params = N'@Name nvarchar(256)', 
       
@Name = @LastName; 

END

Bien sûr, dans l'hypothèse que le SQL dynamique soit réellement nécessaire : dans le cas contraire, passez vous en.

 

 

Et là où on ne peut vraiment pas utiliser de paramètres ?

Il peut se présenter des cas pour lesquels utiliser un paramètre directement dans la requête n'est réellement pas possible, comme par exemple avec une clause TOP avec des versions de SQL Server inférieures à SQL Server 2005.

Dans ce cas vous devrez vous même assurer la sécurité, et donc prendre les mesures qui s'imposent : validation des types, validation des longueurs, ...
Dans le cas présent, il y a de fortes chances que votre valeur aie été passée à la procédure stockée sous forme d'un paramètre type int/bigint/float, mais n'oubliez pas de valider la plage de valeurs possible : si dans votre esprit la taille des pages affichées par votre application peut aller de 10 à 100 lignes, un attaquant aura peut être l'envie d'en demander quelques millions...

Si c'est du SQL dynamique généré côté client, n'insérez pas directement la valeur de filtrage à partir d'une chaîne : passez par Int32/Int64/Double (via TryParse si disponible), ça vous permettra de valider le type et la plage assez facilement.

Si la donnée est typée texte, ne cédez pas pour autant à la fatalité : par exemple s'il s'agit de manipuler des identifiants d'objets, vérifiez que la valeur qui vous a été spécifiée est bien celle d'un objet existant (voir OBJECT_ID, OBJECT_NAME, OBJECT_SCHEMA_NAME, etc)

 

Bonne revue de code.

Sandcastle et CodePlex : le verdict

Je vous en parlais la dernière fois, le projet Sandcastle avait été mis en état non publié pour cause de non respect des termes d'utilisation de CodePlex.

Les choix envisagés par l'équipe du projet était alors :

Et c'est donc la première solution qui a été retenue : Sandcastle Source Code published in Codeplex

Les "Tracepoint" ? C'est sympa ! (et ce n'est pas nouveau)

Ces temps ci je vois certains s'extasier sur une "nouvelle" fonctionnalité de VS2008 : les Tracepoint (points de trace).

Eh bien ce n'est pas nouveau, je vous en avais parler en mai 2006, pour Visual Studio 2005 donc : Les Tracepoint ? C'est sympa !
Ce post ayant relativement mal vécu le changement de skin du blog (les images sont tronquées), je vais le représenter ci dessous :-)

 

Il s'agit, en gros, de l'équivalent d'un point d'arrêt sur lequel vous allez pouvoir notamment choisir d'envoyer un message vers la sortie du debugger, et, partie la plus intéressante, de continuer ou non l'exécution sans marquer d'arrêt.
Dans ce cas, on peut comparer le fonctionnement à l'insertion d'un appel à System.Diagnostics.Debug.WriteLine/System.Diagnostics.Trace.WriteLine, mis à par :

  • vous n'avez pas besoin de modifier le code
  • l'exécution ne se fait que si le debugger est attaché
  • la suppression des constantes DEBUG/TRACE ne supprime pas l'affichage du message si le debugger est attaché (il ne s'agit pas d'une compilation conditionnelle, mais bien d'une fonction propre à Visual Studio)
  • vous avez accès aux possibilités de réglages sur les breakpoint (test de condition, nombre d'accès, etc etc)

Au lieu de modifier votre code comme ceci :
Présentation des points de trace (Tracepoints) - Exemple de code

Vous pouvez maintenant ajouter un Tracepoint : 
Présentation des points de trace (Tracepoints) - Exemple de code avec Tracepoint

Soit en partant de zéro en passant par "Insérer un point de trace" :
Présentation des points de trace (Tracepoints) - Insérer un Tracepoint

Soit en transformant un point d'arrêt existant via "Lorsqu'il est atteint..." (Aaah, le bon vieux F9 ;-) ) : 
Présentation des points de trace (Tracepoints)

Il vous suffit de renseigner les champs comme ceci :
Présentation des points de trace (Tracepoints)

Vous aurez ainsi en sortie : 
Présentation des points de trace (Tracepoints)

Prenons maintenant un cas "plus poussé", c'est à dire celui du travail dans une méthode dont le passage en arrière plan, y compris sur un break donc, provoque un nouveau passage, qui provoque un nouveau passage, qui provoque un nouveau passage, qui [...] (Paint, Focus etc).

Soit un code de dessin de ce genre (très poussé, lui aussi) :
Présentation des points de trace (Tracepoints) - Exemple de code avec OnPaint

Vous voulez afficher sur votre sortie les coordonnées du point de dessin, mais seulement si une CheckBox est cochée.

Avant, vous pouviez écrire ce genre de chose :
Présentation des points de trace (Tracepoints) - Exemple de code avec OnPaint - utilisation de TRACE

Maintenant, il vous suffit de définir votre action comme ceci :
Présentation des points de trace (Tracepoints) - Exemple de code avec OnPaint - paramètre de tracepoint pour remplacer TRACE

Couplée avec la condition suivante pour le point d'arrêt :
Présentation des points de trace (Tracepoints) - Exemple de code avec OnPaint - condition de point d'arrêt

Et le code n'a pas changé :
Présentation des points de trace (Tracepoints) - Exemple de code avec OnPaint - avec Tracepoint

Sympa, non ?

FxCop et Code Analysis : écrire ses propres règles

Voici une documentation qui devrait faire plaisir à ceux qui veulent écrire des règles pour FxCop ou le Code Analysis de Visual Studio : Jason Kresowaty nous offre son "FxCop and Code Analysis: Writing Your Own Custom Rules" (ce n'est pas nouveau, mais ça fait partie des liens qu'on ne retrouve pas facilement quand on en a besoin).
Cette documentation est orientée vers FxCop 1.36 Beta 2 et Visual Studio 2008, mais je rappelle que FxCop 1.36 permet l'analyse d'assemblies .NET 1.x, 2.0 et 3.x.

La version actuelle du document (1.4 datée du 07/05/2008) est consultable soit en ligne, soit sous forme d'un pdf d'une bonne trentaine de pages.

Naturellement, il ne s'agit pas d'une documentation officielle, vous l'utilisez donc bien évidemment en l'état.

FxCop and Code Analysis: Writing Your Own Custom Rules

PowerShell : provider Windows Mobile et management Hyper-V

Voici deux projets autour de PowerShell qui pourrait en intéresser certains :

Le premier est un provider pour travailler avec des appareils Windows Mobile sur un panel assez large (PocketPC/SmartPhone 2002, 2003, 2003SE, Windows Mobile 5, 6 et 6.1), notamment au niveau des manipulations de fichiers.
Je n'ai pas encore eu le temps de tester, mais le post de présentation par Oisin Grehan est assez parlant.
Rien que pour les fichiers c'est intéressant : vous aimez vraiment les copies de fichiers via l'explorer vous ?
PowerShell Windows Mobile Provider</