Examiner les problèmes courants de sécurité du code
Comprendre les vulnérabilités de sécurité courantes est essentiel pour identifier et résoudre efficacement les problèmes de sécurité du code. Cette unité couvre les problèmes de sécurité courants dans le code, leurs effets et pourquoi les traiter rapidement est essentiel pour la sécurité des applications.
Pourquoi se concentrer sur les problèmes de sécurité ?
Les vulnérabilités de sécurité représentent l’une des catégories les plus critiques de défauts logiciels. Une vulnérabilité unique peut entraîner :
- Violations de données : Exposition des données sensibles des clients ou des données métier.
- Pertes financières : coûts directs des violations, amendes réglementaires et dépenses de correction.
- Dommages à la réputation : perte de confiance des clients et crédibilité de l’entreprise.
- Interruption opérationnelle : les compromissions du système peuvent interrompre les opérations commerciales.
Les bogues fonctionnels peuvent être embarrassants pour un développeur, mais les bogues de sécurité peuvent avoir des conséquences graves pour une organisation et des utilisateurs. Chaque développeur doit être conscient de la sécurité, quel que soit son rôle ou sa spécialisation.
Open Web Application Security Project (OWASP)
Open Web Application Security Project (OWASP) est une organisation à but non lucratif axée sur l’amélioration de la sécurité logicielle. OWASP gère le « OWASP Top 10 » largement reconnu : une liste régulièrement mise à jour des risques de sécurité des applications web les plus critiques en fonction des données des organisations de sécurité dans le monde entier.
Le OWASP Top 10 sert de base de sécurité pour les développeurs et les organisations, ce qui permet de hiérarchiser les vulnérabilités à résoudre en premier. Les classements évoluent au fil du temps à mesure que les modèles d’attaque évoluent. Par exemple:
- 2017 OWASP Top 10 : Les vulnérabilités d’injection occupaient la première position.
- 2021 OWASP Top 10 : l’injection a été déplacée vers #3, car de nouvelles menaces telles qu’un contrôle d’accès défaillant ont émergé.
Le OWASP Top 10 reflète les données d’attaque réelles, et non les préoccupations théoriques. Les vulnérabilités du code telles que l’injection SQL et le chiffrement faible se classent de manière cohérente parmi les préoccupations de sécurité les plus critiques du secteur.
Attaques par injection
Les attaques par injection se produisent lorsque des données non approuvées sont envoyées à un interpréteur dans le cadre d’une commande ou d’une requête. Les données hostiles de l’attaquant insécutent l’interpréteur en exécutant des commandes involontaires ou en accédant à des données non autorisées.
Injection de code SQL
L’injection SQL est l’une des attaques par injection les plus dangereuses et courantes. Elle se produit lorsqu’une application incorpore une entrée non approuvée directement dans des requêtes SQL sans validation ou paramétrage appropriée.
Considérez l’exemple de code suivant :
// DANGEROUS: Concatenating user input directly into SQL
string query = "SELECT * FROM Users WHERE Username = '" + userInput + "' AND Password = '" + passwordInput + "'";
SqlCommand command = new SqlCommand(query, connection);
SqlDataReader reader = command.ExecuteReader();
Un attaquant peut entrer ' OR '1'='1 en tant qu’entrée de nom d’utilisateur, en transformant la requête en :
SELECT * FROM Users WHERE Username = '' OR '1'='1' AND Password = ''
Étant donné qu’elle '1'='1' est toujours vraie, cette requête retourne tous les utilisateurs, en contournant entièrement l’authentification.
Effets réels
Les attaques par injection SQL ont provoqué de nombreuses violations de profil élevé. Les attaquants peuvent :
- Contourner les mécanismes d’authentification.
- Extrayez des bases de données entières contenant des informations sensibles.
- Modifiez ou supprimez des données.
- Exécutez des opérations d’administration sur la base de données.
Implémentation sécurisée
La façon sécurisée de gérer les requêtes SQL consiste à utiliser des requêtes paramétrables (également appelées instructions préparées).
Par exemple:
// SECURE: Using parameterized queries
string query = "SELECT * FROM Users WHERE Username = @username AND Password = @password";
SqlCommand command = new SqlCommand(query, connection);
command.Parameters.AddWithValue("@username", userInput);
command.Parameters.AddWithValue("@password", passwordInput);
SqlDataReader reader = command.ExecuteReader();
Les requêtes paramétrables séparent le code des données. La base de données traite les valeurs des paramètres comme des données uniquement, jamais en tant que code SQL exécutable, empêchant les attaques par injection.
Autres types d’injection
L’injection SQL n’est qu’une seule forme d’attaque par injection, et les développeurs doivent connaître d’autres vulnérabilités d’injection qui peuvent compromettre la sécurité des applications.
Bien que l’injection SQL soit la plus courante, d’autres vulnérabilités d’injection existent :
- Injection de commandes : insertion de commandes système dans des entrées d’application qui exécutent des commandes shell.
- Injection LDAP (Lightweight Directory Access Protocol) : manipulation de requêtes LDAP pour accéder aux informations d’annuaire non autorisées.
- Injection NoSQL : Exploitation des bases de données NoSQL par le biais de requêtes malveillantes.
- Injection XML : insertion de contenu XML malveillant pour accéder ou modifier des données.
Modèle universel : Chaque fois que vous insérez une entrée non approuvée dans une commande ou une requête qui est interprétée, vous risquez d’injection. Le modèle de solution est toujours similaire : nettoyer, valider ou paramétrer pour séparer le code des données.
Chiffrement faible des données sensibles
Le stockage ou la transmission de données sensibles sans chiffrement approprié l’expose à un accès non autorisé. Cette catégorie inclut à la fois des méthodes de chiffrement insuffisantes et un manque complet de chiffrement.
Stockage de mot de passe non sécurisé
Les mots de passe nécessitent une protection spéciale, car ils servent de mécanisme d’authentification principal pour la plupart des applications.
Le stockage incorrect des mots de passe est une vulnérabilité critique.
Stockage en texte clair (jamais acceptable)
// DANGEROUS: Storing passwords in plaintext
string password = userInput;
database.SavePassword(username, password);
Si la base de données est compromise, tous les mots de passe utilisateur sont immédiatement exposés.
Hachage faible (insuffisant)
// INSUFFICIENT: Using MD5 or SHA1 without salt
using (MD5 md5 = MD5.Create())
{
byte[] hash = md5.ComputeHash(Encoding.UTF8.GetBytes(password));
string hashedPassword = Convert.ToBase64String(hash);
}
MD5 et SHA1 sont rompus par chiffrement. Les GPU modernes peuvent tester des milliards de combinaisons de mots de passe par seconde sur ces hachages rapides. En outre, sans sel, les attaquants peuvent utiliser des tables arc-en-ciel précomputées pour fissurer instantanément les mots de passe.
Hachage sécurisé (recommandé)
// SECURE: Using bcrypt with automatic salt generation
string hashedPassword = BCrypt.Net.BCrypt.HashPassword(password);
// Later, for verification:
bool isValid = BCrypt.Net.BCrypt.Verify(userInput, storedHash);
Le hachage de mot de passe sécurisé nécessite :
- Salt : données aléatoires ajoutées aux mots de passe avant le hachage, empêchant les attaques par table arc-en-ciel.
- Algorithme lent : les fonctions telles que bcrypt, scrypt ou Esb2 sont coûteuses en calcul, limitant les tentatives de force brute à des centaines ou des milliers par seconde au lieu de milliards.
Chiffrement des données au repos
Au-delà de la sécurité des mots de passe, toutes les données sensibles stockées sur le disque ou dans les bases de données ont besoin d’une protection par le biais d’un chiffrement approprié.
Les données sensibles stockées sans chiffrement sont vulnérables si le support de stockage est compromis.
Scénario vulnérable
// VULNERABLE: Writing sensitive data in plaintext
File.WriteAllText("customer_data.txt", sensitiveInformation);
Si un ordinateur portable contenant ce fichier est volé ou si un attaquant obtient l’accès au système de fichiers, les données sont immédiatement lisibles.
Approche sécurisée
// SECURE: Encrypting data before storage
using (Aes aes = Aes.Create())
{
aes.Key = GetEncryptionKey(); // Securely managed key
aes.GenerateIV();
using (FileStream fileStream = new FileStream("customer_data.enc", FileMode.Create))
{
fileStream.Write(aes.IV, 0, aes.IV.Length);
using (CryptoStream cryptoStream = new CryptoStream(fileStream, aes.CreateEncryptor(), CryptoStreamMode.Write))
using (StreamWriter writer = new StreamWriter(cryptoStream))
{
writer.Write(sensitiveInformation);
}
}
}
Le chiffrement approprié fournit une couche de défense même si le stockage est compromis, en supposant que les clés de chiffrement sont correctement gérées séparément.
Problèmes de journalisation et de gestion des erreurs
La journalisation incorrecte et la gestion des erreurs peuvent exposer par inadvertance des informations sensibles ou des détails du système qui aident les attaquants.
Journalisation des données sensibles
Bien que la journalisation soit essentielle pour le débogage et la surveillance, elle peut devenir une vulnérabilité de sécurité lorsque des informations sensibles sont capturées.
Les applications ne doivent jamais enregistrer les informations sensibles en texte clair.
Pratiques de journalisation dangereuses
// DANGEROUS: Logging sensitive information
logger.LogInformation($"User {username} logged in with password: {password}");
logger.LogInformation($"Credit card processed: {cardNumber}");
logger.LogInformation($"API Key: {apiKey}");
Ce code expose des données sensibles dans les journaux, qui peuvent être accessibles à des utilisateurs non autorisés ou divulguées via des systèmes de gestion des journaux.
Pratiques de journalisation sécurisées
// SECURE: Logging without sensitive data
logger.LogInformation($"User {username} logged in successfully");
logger.LogInformation($"Payment processed for order {orderId}");
logger.LogInformation($"API call authenticated successfully");
Meilleures pratiques
- Ne journalise jamais les mots de passe, les jetons d’authentification ou les clés API.
- Masquez ou réactez des informations sensibles telles que des numéros de carte de crédit ou des numéros de sécurité sociale.
- Journaliser les événements et les résultats, et non les valeurs de données sensibles.
Divulgation excessive d’informations sur les erreurs
Les messages d’erreur servent à un objectif de débogage important, mais ils doivent être soigneusement conçus pour éviter de révéler les éléments internes du système aux attaquants potentiels.
Les messages d’erreur détaillés peuvent révéler l’architecture système, les chemins d’accès aux fichiers, les schémas de base de données et d’autres informations utiles aux attaquants.
Gestion des erreurs problématiques
// PROBLEMATIC: Exposing detailed error information to users
catch (Exception ex)
{
return $"Error: {ex.Message}\nStack Trace: {ex.StackTrace}\nConnection String: {connectionString}";
}
Cela révèle les détails internes du système que les attaquants peuvent utiliser pour créer des attaques plus sophistiquées.
Sécurisation de la gestion des erreurs
// SECURE: User-friendly messages with detailed internal logging
catch (Exception ex)
{
logger.LogError(ex, "Failed to process user request");
return "An error occurred while processing your request. Please try again or contact support.";
}
Les utilisateurs reçoivent des messages d’erreur conviviaux et minimes tandis que les développeurs obtiennent des informations détaillées sur les erreurs via des journaux sécurisés.
Attaque par traversée de chemins d’accès
Le chemin d’accès (également appelé traversée de répertoire) se produit lorsqu’une application utilise une entrée fournie par l’utilisateur pour construire des chemins de fichier sans validation appropriée. Les attaquants peuvent utiliser des séquences de caractères spéciales pour accéder aux fichiers en dehors du répertoire prévu.
Tenez compte du code vulnérable suivant :
// VULNERABLE: Using user input directly in file paths
string filename = Request.Query["file"];
string filePath = Path.Combine(@"C:\uploads\", filename);
string content = File.ReadAllText(filePath);
Un attaquant peut fournir une entrée comme ../../../Windows/System32/config/SAM accéder aux fichiers système sensibles ou ../../web.config lire la configuration de l’application contenant des secrets.
Le code vulnérable permet le mécanisme d’attaque suivant :
-
Les séquences
..naviguent vers le haut des niveaux de répertoire. - Les attaquants peuvent placer un échappement sur le bac à sable du répertoire prévu.
- Accédez aux fichiers sensibles, aux fichiers de configuration ou aux fichiers système.
- Potentiellement remplacer les fichiers d’application critiques.
Tenez compte de l’implémentation sécurisée suivante :
// SECURE: Validating and constraining file paths
string filename = Request.Query["file"];
// Remove path traversal sequences
filename = Path.GetFileName(filename);
// Construct full path
string uploadsDirectory = Path.GetFullPath(@"C:\uploads\");
string filePath = Path.GetFullPath(Path.Combine(uploadsDirectory, filename));
// Verify the resulting path is still within the uploads directory
if (!filePath.StartsWith(uploadsDirectory))
{
throw new SecurityException("Invalid file path");
}
string content = File.ReadAllText(filePath);
Les implémentations sécurisées illustrent les stratégies de défense suivantes :
- Utilisez
Path.GetFileName()pour supprimer les informations du répertoire. - La liste d’autorisation a autorisé des fichiers ou des modèles au lieu de bloquer les caractères dangereux.
- Vérifiez que les chemins résolus restent dans les répertoires prévus.
- Implémentez des autorisations d’accès aux fichiers strictes au niveau du système d’exploitation.
Autres considérations relatives à la sécurité
En plus des vulnérabilités couvertes dans les sections précédentes, plusieurs autres problèmes de sécurité nécessitent une prise en charge des développeurs.
Scripting inter-site (XSS)
Le script intersites permet aux attaquants d’injecter du code malveillant dans des applications web, ce qui peut compromettre les données utilisateur et les sessions.
Bien qu’il ne s’applique pas aux applications console, les développeurs web doivent valider et encoder toutes les entrées utilisateur avant de les afficher dans les navigateurs.
Secrets codés en dur
Les informations d’identification et les valeurs de configuration sensibles incorporées directement dans le code source représentent un risque de sécurité critique qui peut exposer des systèmes entiers.
L’incorporation de clés d’API, de mots de passe ou de jetons directement dans le code source les expose à toute personne disposant d’un accès au référentiel. Les secrets doivent être les suivants :
- Stockés dans des systèmes de configuration sécurisés ou des coffres.
- Jamais engagé dans le système de gestion de versions.
- Rotation régulière.
- Géré avec des contrôles d’accès appropriés.
Épuisement des ressources et déni de service
Les attaquants exploitent souvent des applications qui ne gèrent pas correctement les ressources. Les attaques peuvent entraîner des interruptions de service ou des incidents système.
Une mauvaise gestion des ressources peut activer des attaques par déni de service. Voici quelques exemples :
- Lecture de fichiers volumineux entiers dans la mémoire (à l’origine d’erreurs de mémoire insuffisante).
- Ne limitez pas les tailles de demande ou les fréquences.
- Algorithmes inefficaces qui consomment un processeur excessif.
- Incapacité à libérer correctement les ressources.
Comment identifier les problèmes de sécurité dans le code
L’identification des vulnérabilités de sécurité dans le code nécessite une approche systématique.
Analyser la gestion des entrées utilisateur
L’entrée utilisateur représente le vecteur d’attaque principal pour la plupart des vulnérabilités de sécurité, ce qui permet d’examiner la façon dont votre code traite les données externes.
Chaque point où votre code accepte l’entrée utilisateur est un point d’entrée potentiel pour les attaques :
- Recherchez : entrée utilisée dans les requêtes SQL, les chemins d’accès aux fichiers, les commandes système ou la logique critique.
- Demandez : « Est-ce que je fais confiance à cette entrée trop ? »
- Prenons l’exemple suivant : vulnérabilités d’injection, traversée de chemin d’accès, injection de commandes.
Passer en revue les opérations de chiffrement
Les implémentations de sécurité impliquant le chiffrement, le hachage et l’authentification nécessitent un examen supplémentaire, car le chiffrement faible peut compromettre l’ensemble des systèmes.
Le code de chiffrement nécessite un examen spécial :
-
Recherchez :
MD5.Create(),SHA1.Create(), stockage de mot de passe en texte clair. - Demandez : « Cette méthode de chiffrement est-elle toujours considérée comme sécurisée ? »
- Prenons l’exemple suivant : utilisation de bcrypt, de scrypt ou de Argo2 pour les mots de passe ; SHA-256 ou mieux pour les vérifications d’intégrité.
Examiner les déclarations de journalisation
Les journaux peuvent devenir par inadvertance des vulnérabilités de sécurité lorsqu’ils capturent des informations sensibles qui doivent rester protégées.
Analysez votre base de code pour rechercher des données sensibles dans les logs :
- Recherchez : instructions de journal contenant des variables nommées de mot de passe, secret, jeton, apiKey, cardNumber.
- Demandez : « Quelles informations suis-je en train d'exposer dans les journaux de log ? »
- Envisagez : que se passe-t-il si ces journaux d’activité sont compromis ou exposés accidentellement ?
Inspecter les opérations de fichier
Le code de gestion des fichiers présente des défis de sécurité uniques, car il peut exposer les ressources système au-delà de l’étendue prévue de votre application.
Le code de gestion des fichiers a besoin d’une validation minutieuse :
-
Recherchez :
Path.Combineavec l’entrée utilisateur, les opérations de fichier basées sur les chemins d’accès fournis par l’utilisateur. - Demandez : « Un utilisateur peut-il échapper au répertoire prévu ? »
- Considérez : attaques par traversée de chemins d’accès et techniques d’évasion de répertoire.
Utiliser des outils automatisés
Bien que la révision manuelle du code soit essentielle, les outils automatisés peuvent analyser efficacement de grandes bases de code et identifier les modèles de vulnérabilité courants susceptibles d’être manqués lors de l’inspection manuelle.
Combinez la révision manuelle du code avec l’analyse automatisée :
- Analyse statique : les outils comme GitHub CodeQL analysent le code pour détecter les modèles de vulnérabilité connus.
- GitHub Copilot : Utilisez le mode Demander pour analyser les sections de code : « Existe-t-il des problèmes de sécurité dans ce code ? »
- Linters de sécurité : les outils spécifiques au langage peuvent signaler des erreurs de sécurité évidentes.
GitHub Copilot peut identifier de nombreux problèmes de sécurité courants lorsque vous lui demandez d’analyser le code. Il s’appuie sur des modèles provenant de millions de codebases pour reconnaître les vulnérabilités.
Approche de sécurité décalée vers la gauche
Le principe de « déplacement vers la gauche » signifie traiter la sécurité plus tôt dans le cycle de vie du développement :
- Phase de conception : tenez compte des implications en matière de sécurité des décisions architecturales.
- Phase de développement : écrire du code sécurisé à partir du début. Interceptez les problèmes lors de la révision du code.
- Phase de test : incluez les tests de sécurité en même temps que les tests fonctionnels.
- Phase de déploiement : analysez les vulnérabilités avant la mise en production.
L’interception des problèmes de sécurité pendant le développement est beaucoup moins coûteuse que de les découvrir en production. Le coût de la correction des vulnérabilités augmente de façon exponentielle avec chaque étape dans laquelle ils progressent.
Résumé
Les vulnérabilités de sécurité courantes telles que les attaques par injection, le chiffrement faible, la journalisation incorrecte et le chemin d’accès représentent des menaces graves pour la sécurité des applications. La compréhension de ces vulnérabilités vous aide à les reconnaître dans le code et à hiérarchiser leur correction. En combinant les connaissances des modèles de vulnérabilité courants avec des outils tels que GitHub Copilot, vous pouvez identifier et résoudre les problèmes de sécurité plus efficacement.