Seguridad de aplicaciones C++: top 10 vulnerabilidades en la práctica
C++ es un lenguaje potente y de alto rendimiento, pero su flexibilidad exige una gran responsabilidad por parte del desarrollador. Los errores en la gestión de memoria, el trabajo con hilos o la entrada/salida pueden provocar vulnerabilidades graves. En este artículo analizaremos las 10 principales vulnerabilidades de C++ con ejemplos prácticos de código y formas de prevenirlas. El material será útil tanto para desarrolladores principiantes como para experimentados que deseen fortalecer la seguridad de sus proyectos.
1. Desbordamiento de búfer (Buffer Overflow)
Vulnerabilidad clásica que ocurre al escribir datos fuera de los límites de un array asignado. En C++ es especialmente peligrosa al trabajar con arrays sin procesar y funciones de C.
#include <iostream>#include <cstring>
void vulnerable(char* input) { char buffer[10]; strcpy(buffer, input); // PELIGROSO: sin verificación de longitud std::cout << "Buffer: " << buffer << std::endl;}
int main() { char longInput[] = "AAAAAAAABBBBBBBBCCCCCCCC"; // 24 caracteres vulnerable(longInput); return 0;}Solución: Use std::string o strncpy con verificación explícita de longitud. En C++ moderno, son preferibles los contenedores STL.
void safe(const std::string& input) { std::string buffer = input.substr(0, 9); // copia segura std::cout << "Buffer: " << buffer << std::endl;}2. Fugas de memoria (Memory Leaks)
Las fugas ocurren cuando la memoria asignada dinámicamente no se libera. En aplicaciones de larga duración, esto provoca el agotamiento de los recursos.
void leakExample() { int* data = new int[1000]; // asignamos memoria // ... trabajo con datos ... // olvidamos delete[] data; — ¡fuga!}Solución: Use punteros inteligentes (std::unique_ptr, std::shared_ptr) o contenedores STL.
void safeExample() { std::unique_ptr<int[]> data = std::make_unique<int[]>(1000); // la memoria se liberará automáticamente al salir de la función}3. Desreferencia de puntero nulo (Null Pointer Dereference)
Intentar acceder a la memoria en la dirección nullptr provoca un comportamiento indefinido y a menudo causa el fallo del programa.
void process(int* ptr) { *ptr = 42; // PELIGROSO: si ptr == nullptr}
int main() { int* p = nullptr; process(p); // fallo}Solución: Siempre verifique los punteros antes de usarlos. Use referencias si el valor no puede ser nulo.
void safeProcess(int* ptr) { if (ptr == nullptr) { throw std::invalid_argument("Null pointer received"); } *ptr = 42;}4. Condición de carrera (Race Condition)
Ocurre cuando varios hilos acceden simultáneamente a datos compartidos sin sincronización.
#include <thread>int counter = 0;
void increment() { for (int i = 0; i < 100000; ++i) { counter++; // operación no atómica — condición de carrera }}
int main() { std::thread t1(increment); std::thread t2(increment); t1.join(); t2.join(); std::cout << "Counter: " << counter; // resultado impredecible}Solución: Use mutexes u operaciones atómicas.
#include <atomic>std::atomic<int> counter{0};
void safeIncrement() { for (int i = 0; i < 100000; ++i) { counter.fetch_add(1); // operación atómica }}5. Uso de variables no inicializadas
Las variables no inicializadas contienen "basura", lo que puede provocar un comportamiento impredecible.
int main() { in