Sécurité des applications C++ : top 10 des vulnérabilités et méthodes de protection
C++ est un langage de programmation système puissant, mais sa flexibilité et son accès direct à la mémoire rendent les applications vulnérables à des attaques sérieuses. Selon OWASP et CWE, de nombreuses vulnérabilités critiques dans les logiciels industriels (des jeux à l'avionique) proviennent précisément d'erreurs dans le code C++. Dans cet article, nous allons analyser le top 10 des vulnérabilités les plus dangereuses, fournir des exemples de code et montrer comment les éviter.
1. Dépassement de tampon (Buffer Overflow)
Vulnérabilité classique permettant à un attaquant de réécrire les zones mémoire adjacentes. Elle survient lors de la copie de données sans vérification de taille.
// Code vulnérable#include <cstring>void vulnerable(char* input) { char buffer[10]; strcpy(buffer, input); // Danger : si input est plus long que 9 caractères}// Version sécuriséevoid secure(char* input, size_t len) { char buffer[10]; if (len < sizeof(buffer)) { strncpy(buffer, input, sizeof(buffer) - 1); buffer[sizeof(buffer) - 1] = '\\0'; }}Protection : utilisez std::string, std::vector ou des fonctions avec limitation de longueur (strncpy, snprintf). Activez les flags du compilateur -fstack-protector-strong et -D_FORTIFY_SOURCE=2.
2. Utilisation après libération (Use-After-Free)
Accès à une mémoire déjà libérée. Cela entraîne une lecture/écriture dans une zone étrangère ou l'exécution de code arbitraire.
// Code vulnérableint* ptr = new int(42);delete ptr;*ptr = 100; // Erreur ! ptr n'est plus valide// Version sécuriséeint* ptr = new int(42);delete ptr;ptr = nullptr; // Mise à zéro du pointeurif (ptr) *ptr = 100; // Vérification avant utilisationProtection : utilisez des pointeurs intelligents (std::unique_ptr, std::shared_ptr), mettez à zéro les pointeurs bruts après delete, appliquez les sanitizers (-fsanitize=address).
3. Dépassement d'entier (Integer Overflow)
Sortie des limites de la plage autorisée lors d'opérations arithmétiques. Souvent utilisé pour contourner les vérifications de taille de tampon.
// Code vulnérablesize_t size = 0;size_t user_input = 4294967295; // 2^32 - 1size_t total = size + user_input + 1; // Dépassement !// Version sécurisée avec vérificationif (user_input > SIZE_MAX - size) { throw std::overflow_error("Taille trop grande");}size_t total = size + user_input + 1;Protection : utilisez des types à largeur fixe (uint32_t), vérifiez le dépassement avant l'opération, appliquez __builtin_add_overflow (GCC/Clang).
4. Déréférencement de pointeur nul (Null Pointer Dereference)
Tentative d'accès à la mémoire à l'adresse NULL. Provoque un arrêt brutal du programme (segfault) ou une vulnérabilité DoS.
// Code vulnérablevoid process(int* ptr) { *ptr = 10; // Si ptr == nullptr — crash}// Version sécuriséevoid process(int* ptr) { if (!ptr) { throw std::invalid_argument("Pointeur nul reçu"); } *ptr = 10;}Protection : vérifiez toujours les pointeurs pour nullptr avant utilisation, retournez std::optional au lieu de pointeurs bruts.
5. Fuites de mémoire (Memory Leaks)
Perte d'accès à la mémoire allouée sans sa libération. Épuise les ressources système avec le temps.
// Code vulnérablevoid leak() { int* data = new int[100]; // Oubli de delete[] data — fuite}// Version sécuriséevoid no_leak() { std::unique_ptr<int[]> data = std::make_unique<int[]>(100); // La mémoire sera libérée automatiquement}Protection : utilisez RAII (pointeurs intelligents, conteneurs