Préface : pourquoi la sécurité n'est pas une option, mais une hygiène de base
\n\nImaginez : vous développez un super service web en C#. Tout fonctionne parfaitement — les utilisateurs s'inscrivent, laissent des commentaires, recherchent des produits. Puis arrive « le hacker Vasya » (qui en réalité a simplement copié un script trouvé sur Internet) — et il vide toute la base de données. Ou pire encore, il insère dans votre page un script qui vole les cookies de tous les visiteurs.
\n\nCela ressemble à un scénario de film d'horreur ? Pourtant, cela arrive tous les jours. Selon les statistiques de l'OWASP, les injections (SQL, XSS, LDAP) figurent dans le top 3 des vulnérabilités les plus dangereuses des applications web. Et C#, malgré toute sa fiabilité, ne vous offre pas de bouclier d'immunité par défaut.
\n\nDans cet article, nous allons décortiquer les deux attaques les plus courantes — les injections SQL et le XSS (cross-site scripting). Vous n'aurez pas seulement de la théorie, mais des techniques de défense concrètes en C# avec des exemples de code réels. C'est parti !
\n\nInjections SQL : quand l'utilisateur devient un hacker
\n\nÀ quoi cela ressemble en pratique
\n\nSupposons que vous ayez une simple connexion à un système. L'utilisateur saisit un identifiant et un mot de passe, et vous construisez la requête ainsi :
\n\nstring login = txtLogin.Text;\nstring password = txtPassword.Text;\nstring query = "SELECT * FROM Users WHERE Login = '" + login + "' AND Password = '" + password + "'";\nSqlCommand cmd = new SqlCommand(query, connection);\n\nCela vous semble logique ? Erreur ! Si un attaquant saisit dans le champ identifiant : ' OR 1=1 --, la requête devient :
SELECT * FROM Users WHERE Login = '' OR 1=1 --' AND Password = '...'\n\nTout ce qui suit -- est un commentaire. La condition 1=1 est toujours vraie. La requête renvoie tous les utilisateurs, et l'authentification est réussie. Et si, au lieu d'un commentaire, on insère DROP TABLE Users ? Adieu, base de données.
Protection n°1 : les requêtes paramétrées (votre bouclier principal)
\n\nLa meilleure protection contre les injections SQL est de ne jamais concaténer manuellement des chaînes. Utilisez des paramètres. Le serveur SQL échappe lui-même les caractères dangereux, et votre base de données reçoit les données comme des valeurs, et non comme du code.
\n\nstring login = txtLogin.Text;\nstring password = txtPassword.Text;\n\nstring query = "SELECT * FROM Users WHERE Login = @Login AND Password = @Password";\n\nusing (SqlConnection conn = new SqlConnection(connectionString))\n{\n SqlCommand cmd = new SqlCommand(query, conn);\n cmd.Parameters.AddWithValue("@Login", login);\n cmd.Parameters.AddWithValue("@Password", password);\n \n conn.Open();\n SqlDataReader reader = cmd.ExecuteReader();\n // traitement du résultat\n}\n\nNotez bien : @Login et @Password sont des paramètres. Même si l'utilisateur saisit '; DROP TABLE Users; --, le SGBD interprétera cela comme une simple chaîne, et non comme du code. Sûr et élégant.
Protection n°2 : les procédures stockées
\n\nUn autre outil puissant : les procédures stockées. Elles protègent non seulement contre les injections, mais améliorent aussi les performances (les plans d'exécution sont mis en cache).
\n\n-- SQL Server\nCREATE PROCEDURE GetUserByLogin\n @Login NVARCHAR(50)\nAS\nBEGIN\n SELECT * FROM Users WHERE Login = @Login\nEND\n\n// Code C#\nusing (SqlConnection conn = new SqlConnection(connectionString))\n{\n SqlCommand cmd = new SqlCommand("GetUserByLogin", conn);\n cmd.CommandType = CommandType.StoredProcedure;\n cmd.Parameters.AddWithValue("@Login", inputLogin);\n \n conn.Open();\n // ...\n}\n\nProtection n°3 : Entity Framework et les ORM
\n\nSi vous utilisez Entity Framework (ou tout autre ORM), vous êtes déjà partiellement protégé. EF génère automatiquement des requêtes paramétrées. Mais attention : les requêtes SQL brutes via EF (méthode FromSqlRaw) reviennent à faire de la concaténation. Restez vigilant !
// MAUVAIS :\nvar users = context.Users.FromSqlRaw("SELECT * F