Introduction : pourquoi la sécurité du code C++ est-elle cruciale ?
C++ est l'un des langages les plus puissants et performants, mais sa flexibilité exige une grande responsabilité de la part du développeur. Contrairement à de nombreux langages modernes avec gestion automatique de la mémoire (Java, Python), C++ offre un accès direct à la mémoire et aux ressources système. Cela ouvre de nombreuses possibilités d'attaques : du dépassement de tampon aux fuites de données.
Pour les développeurs C++ débutants, comprendre les vulnérabilités typiques n'est pas seulement une théorie, mais une nécessité. Une erreur de quelques lignes peut entraîner un crash du serveur, une fuite de mots de passe ou l'exécution de code arbitraire par un attaquant. Dans cet article, nous analyserons le top 10 des vulnérabilités C++ les plus courantes avec des exemples et des méthodes de protection.
Top 10 des vulnérabilités C++ avec exemples et protection
1. Dépassement de tampon (Buffer Overflow)
Problème : Écriture de données au-delà des limites du tableau alloué. C'est un classique du C/C++, qui permet de réécrire les variables voisines, l'adresse de retour d'une fonction ou les données dans le tas.
// Exemple de vulnérabilité#include <cstring>#include <iostream>
void vulnerable() { char buffer[10]; std::cout << "Entrez une chaîne : "; std::cin >> buffer; // Danger ! Aucune vérification de la longueur}
int main() { vulnerable(); return 0;}Conséquences : Exécution de code arbitraire (RCE), crash du programme.
Protection : Utilisez std::string, std::vector, des fonctions sécurisées (par exemple, strncpy avec indication de la taille).
// Variante sécurisée#include <string>#include <iostream>
void safe() { std::string input; std::getline(std::cin, input); std::cout << "Vous avez saisi : " << input << std::endl;}2. Fuite de mémoire (Memory Leak)
Problème : La mémoire est allouée via new/malloc, mais n'est pas libérée via delete/free. Avec le temps, l'application consomme toute la mémoire vive.
void leaky() { int* data = new int[1000]; // Oubli de delete[] data;}Conséquences : Déni de service (DoS), ralentissement du système.
Protection : Utilisez des pointeurs intelligents (std::unique_ptr, std::shared_ptr), RAII (Resource Acquisition Is Initialization).
#include <memory>void safe() { std::unique_ptr<int[]> data = std::make_unique<int[]>(1000); // La mémoire sera libérée automatiquement à la sortie de la fonction}3. Déréférencement de pointeur nul (Null Pointer Dereference)
Problème : Tentative d'accès à la mémoire à l'adresse nullptr.
void process(int* ptr) { *ptr = 42; // Si ptr == nullptr, le programme plante}Conséquences : Crash immédiat (segmentation fault), DoS.
Protection : Vérifiez toujours les pointeurs avant utilisation ou utilisez des références.
void safe(int* ptr) { if (ptr) { *ptr = 42; } else { // Gestion de l'erreur }}4. Débordement d'entier (Integer Overflow)
Problème : Le résultat d'une opération arithmétique dépasse les limites du type de données. Cela peut entraîner un comportement imprévisible, notamment lors du travail avec des indices de tableaux.
#include <cstdint>void vulnerable() { uint16_t a = 65535; uint16_t b = 1; uint16_t sum = a + b; // Débordement : 65536 -> 0}Conséquences : Erreurs logiques, dépassement de tampon (si utilisé pour calculer une taille).
Protection : Utilisez des types à dimension fixe (int32_t, int64_t), vérifiez