Seguridad de aplicaciones C++: Top 10 vulnerabilidades y formas de protección
C++ es un potente lenguaje de programación de sistemas, pero su flexibilidad y acceso directo a la memoria hacen que las aplicaciones sean vulnerables a ataques graves. Según OWASP y CWE, muchas vulnerabilidades críticas en software industrial (desde juegos hasta aviónica) surgen precisamente debido a errores en el código C++. En este artículo, analizaremos las 10 vulnerabilidades más peligrosas, proporcionaremos ejemplos de código y mostraremos cómo evitarlas.
1. Desbordamiento de búfer (Buffer Overflow)
Vulnerabilidad clásica que permite a un atacante sobrescribir áreas de memoria adyacentes. Ocurre al copiar datos sin verificar el tamaño.
// Código vulnerable#include <cstring>void vulnerable(char* input) { char buffer[10]; strcpy(buffer, input); // Peligro: si input tiene más de 9 caracteres}// Versión seguravoid secure(char* input, size_t len) { char buffer[10]; if (len < sizeof(buffer)) { strncpy(buffer, input, sizeof(buffer) - 1); buffer[sizeof(buffer) - 1] = '\\0'; }}Protección: use std::string, std::vector o funciones con límite de longitud (strncpy, snprintf). Active las banderas del compilador -fstack-protector-strong y -D_FORTIFY_SOURCE=2.
2. Uso después de liberación (Use-After-Free)
Acceso a memoria que ya ha sido liberada. Provoca lectura/escritura en un área ajena o la ejecución de código arbitrario.
// Código vulnerableint* ptr = new int(42);delete ptr;*ptr = 100; // ¡Error! ptr ya no es válido// Versión seguraint* ptr = new int(42);delete ptr;ptr = nullptr; // Ponemos el puntero a nuloif (ptr) *ptr = 100; // Verificación antes de usarProtección: use punteros inteligentes (std::unique_ptr, std::shared_ptr), ponga a nulo los punteros crudos después de delete, aplique sanitizers (-fsanitize=address).
3. Desbordamiento de enteros (Integer Overflow)
Superar los límites del rango permitido en operaciones aritméticas. A menudo se usa para eludir las comprobaciones de tamaño del búfer.
// Código vulnerablesize_t size = 0;size_t user_input = 4294967295; // 2^32 - 1size_t total = size + user_input + 1; // ¡Desbordamiento!// Versión segura con verificaciónif (user_input > SIZE_MAX - size) { throw std::overflow_error("El tamaño es demasiado grande");}size_t total = size + user_input + 1;Protección: use tipos de ancho fijo (uint32_t), verifique el desbordamiento antes de la operación, aplique __builtin_add_overflow (GCC/Clang).
4. Desreferencia de puntero nulo (Null Pointer Dereference)
Intento de acceder a memoria en la dirección NULL. Provoca la terminación abrupta del programa (segfault) o una vulnerabilidad DoS.
// Código vulnerablevoid process(int* ptr) { *ptr = 10; // Si ptr == nullptr — colapso}// Versión seguravoid process(int* ptr) { if (!ptr) { throw std::invalid_argument("Se recibió un puntero nulo"); } *ptr = 10;}Protección: siempre verifique los punteros con nullptr antes de usarlos, devuelva std::optional en lugar de punteros crudos.
5. Fugas de memoria (Memory Leaks)
Pérdida de acceso a la memoria asignada sin liberarla. Con el tiempo, agota los recursos del sistema.
// Código vulnerablevoid leak() { int* data = new int[100]; // Olvidamos delete[] data — fuga}// Versión seguravoid no_leak() { std::unique_ptr<int[]> data = std::make_unique<int[]>(100); // La memoria se liberará automáticamente}Protección: use RAII (punteros inteligentes, contenedores