Investigadores de la empresa de ciberseguridad GRIMM publicaron recientemente un interesante trío de errores que encontraron en el kernel de Linux …
… En un código que había estado allí sin llamar la atención durante unos 15 años.
Afortunadamente, parecía que nadie más había mirado el código durante todo ese tiempo, al menos no con la diligencia suficiente para detectar los errores, por lo que ahora están parcheados y los tres CVE que encontraron ahora están corregidos:
- CVE-2021-27365. Desbordamiento de búfer de pila explotable debido al uso de sprintf().
- CVE-2021-27363. Pérdida de dirección del kernel debido al puntero utilizado como ID único.
- CVE-2021-27364. Buffer overread que conduce a la fuga de datos o la denegación de servicio (kernel panic).
Los errores se encontraron en el código del kernel que implementa iSCSI, un componente que implementa la venerable interfaz de datos SCSI a través de la red, por lo que puede hablar con dispositivos SCSI como unidades de cinta y disco que no están conectados directamente a su propia computadora.
Por supuesto, si ya no usa SCSI o iSCSI en ninguna parte de su red, probablemente se esté encogiendo de hombros en este momento y pensando: “No se preocupe por mí, no tengo ninguno de los controladores del kernel iSCSI cargado porque ‘ simplemente no los estoy usando «.
Después de todo, el código del kernel con errores no se puede explotar si solo está en el disco; tiene que cargarse en la memoria y usarse activamente antes de que pueda causar algún problema.
Excepto, por supuesto, que la mayoría (o al menos muchos) de los sistemas Linux no solo vienen con cientos o incluso miles de módulos del kernel en el /lib/modulesárbol de directorios, listos para usar en caso de que alguna vez se necesiten, sino que también vienen configurados para permitir aplicaciones debidamente autorizadas. para activar la carga automática de módulos bajo demanda.
Nota. Hasta donde sabemos, estos errores se corrigieron en los siguientes kernels de Linux mantenidos oficialmente, todos con fecha de 2021-03-07: 5.11.4 , 5.10.21 , 5.4.103 , 4.19.179 , 4.1.4.224 , 4.9 .260 , 4.4.260 . Si tiene un kernel modificado por el proveedor o un kernel de serie no oficial que no está en esta lista, consulte a su fabricante de distribución. Para verificar la versión de su kernel, ejecute uname -ren un símbolo del sistema.
Por ejemplo, mi propio sistema Linux viene con casi 4500 módulos de kernel por si acaso los necesita:
root @ slack: /lib/modules/5.10.23# buscar. -nombre ‘* .ko’
./kernel/arch/x86/crypto/aegis128-aesni.ko
./kernel/arch/x86/crypto/blake2s-x86_64.ko
./kernel/arch/x86/crypto/blowfish-x86_64 .ko
[… 4472 líneas eliminadas …]
./kernel/sound/usb/usx2y/snd-usb-usx2y.ko
./kernel/sound/x86/snd-hdmi-lpe-audio.ko
./kernel /virt/lib/irqbypass.ko
#
Supongo que algún día podría necesitar el módulo de cifrado Blowfish, pero como no tengo ningún software que espero usar, probablemente podría prescindir del blowfish-x86_64.kocontrolador.
Y aunque realmente no me importaría tener una de las geniales tarjetas de sonido Ux2y de Tascam (por ejemplo, US122, US224, US428), realmente no necesito ni espacio para una, así que dudo que alguna vez necesite el snd-usb-usx2y.kocontrolador. ya sea.
Sin embargo, ahí están, y por accidente o por diseño, cualquiera de esos controladores podría terminar cargándose automáticamente, dependiendo del software que utilice, incluso si no estoy ejecutando como usuario root en ese momento.
Vale la pena un segundo vistazo
El riesgo potencial que plantean los controladores no amados, no utilizados y en su mayoría pasados por alto es lo que hizo que GRIMM examinara dos veces los errores mencionados anteriormente.
Los investigadores pudieron encontrar software que un atacante sin privilegios podría ejecutar para activar el código del controlador defectuoso que habían encontrado, y pudieron producir exploits funcionales que podrían de diversas formas:
- Realice una escalada de privilegios para promover que un usuario regular tenga superpoderes a nivel de kernel.
- Extraiga las direcciones de la memoria del kernel para facilitar otros ataques que necesiten saber dónde se carga el código del kernel en la memoria.
- Bloquea el kernel y, por lo tanto, todo el sistema.
- Leer fragmentos de datos de la memoria del kernel que se suponía que estaban fuera de los límites.
Tan incierto y de alcance tan limitado como suena el último exploit, parece que los datos que un usuario sin privilegios podría ver podrían incluir fragmentos de datos que se transfieren durante los accesos a dispositivos iSCSI genuinos.
Si es así, esto significa, en teoría, que un delincuente con una cuenta sin privilegios en un servidor donde se usaba iSCSI podría ejecutar un programa de apariencia inocente para sentarse en segundo plano, olfateando una selección aleatoria de datos privilegiados de la memoria. .
Incluso un flujo fragmentado y no estructurado de datos confidenciales extraídos intermitentemente de un proceso privilegiado (¿recuerdas el infame error Heartbleed ?) Podría permitir que se escapen secretos peligrosos.
No olvide lo fácil que es para el software de computadora reconocer y «raspar» patrones de datos a medida que pasan volando en la RAM, como números de tarjetas de crédito y direcciones de correo electrónico.
Los errores revisitados
Arriba, mencionamos que el primer error en este conjunto se debió al «uso de sprintf()».
Esa es una función de C que es la abreviatura de impresión formateada en una cadena , y es una forma de imprimir un mensaje de texto en un bloque de memoria para que pueda usarlo más tarde.
Por ejemplo, este código …
char buf [64]; / * Reserva un bloque de bytes de 64 bytes * /
char * str = «42»; / * En realidad tiene 3 bytes, por lo tanto: ‘4’ ‘2’ NUL * /
/ * Cero final agregado automáticamente: 0x34 0x32 0x00 * /
sprintf (buf, «La respuesta es% s», str)
… Dejaría el bloque de memoria que bufcontiene los 12 caracteres » Answer is 42″, seguido de un terminador de cero bytes (ASCII NUL), seguido de 51 bytes sin tocar al final del búfer de 64 bytes.
Sin embargo, sprintf()siempre es peligroso y nunca debe usarse, porque no verifica si hay suficiente espacio en el bloque de memoria final para que quepan los datos impresos.
Arriba, si la cadena almacenada en la variable strtiene más de 54 bytes, incluido el byte cero al final, entonces no encajará bufjunto con el texto adicional » Answer is «.
Peor aún, si los datos de texto strno tienen un byte cero al final, que es como C indica cuándo dejar de copiar una cadena, podría copiar accidentalmente miles o incluso millones de bytes que siguen stren la memoria hasta que simplemente golpee un byte cero, momento en el que es casi seguro que el kernel se haya bloqueado.
El código moderno no debería usar funciones de C que pueden realizar copias de memoria de longitud ilimitada – uso snprintf(), lo que significa formatear e imprimir como máximo N bytes en una cadena, y sus amigos en su lugar.
No des tu dirección
El segundo error anterior surgió por el uso de direcciones de memoria como identificadores únicos.
Eso suena como una buena idea: si necesita indicar un objeto de datos en su código de kernel con un número de identificación que no chocará con ningún otro objeto en su código, puede usar los números 1, 2, 3 y así sucesivamente. , agregando uno cada vez y resuelva el problema.
Pero si desea un identificador único que no entre en conflicto con ningún otro objeto numerado en el kernel, podría pensar, «¿Por qué no usar la dirección de memoria donde está almacenado mi objeto, porque obviamente es único, dado que dos objetos no pueden estar en el mismo lugar en la RAM del kernel al mismo tiempo? » (No, a menos que ya haya una crisis con el uso de la memoria).
El problema es que si su ID de objeto alguna vez es visible fuera del kernel, por ejemplo, para que los programas que no son de confianza en el área de usuario puedan referirse a él, acaba de dar información sobre el diseño interno de la memoria del kernel, y eso no se supone que suceda.
Los núcleos modernos usan lo que se llama KASLR, abreviatura de aleatorización del diseño del espacio de direcciones del núcleo, específicamente para evitar que los usuarios sin privilegios descubran el diseño interno exacto del núcleo.
Si alguna vez ha abierto cerraduras (es un pasatiempo popular y sorprendentemente relajante entre los piratas informáticos y los investigadores de ciberseguridad; incluso puede comprar cerraduras transparentes para divertirse educativamente), sabrá que es mucho más fácil si ya sabe cómo funciona la cerradura. El mecanismo se presenta internamente.
De manera similar, saber exactamente qué se ha cargado en el interior del kernel casi siempre hace que otros errores, como los desbordamientos del búfer, sean mucho más fáciles de explotar.
¿Qué hacer?
- Actualice su kernel. Si confía en el creador de su distribución para los nuevos núcleos, asegúrese de obtener la última actualización. Consulte más arriba los números de versión en los que se corrigieron estos errores.
- No utilice funciones de programación en C que se sabe que son problemáticas. Evite cualquier función de acceso a la memoria que no realice un seguimiento de la cantidad máxima de datos a utilizar. Mantenga un registro de las «funciones seguras de cadena de C» documentadas oficialmente para su sistema operativo o entorno de programación elegido y utilícelas siempre que pueda. Esto le da una mejor oportunidad de prevenir sobrecargas de memoria.
- No utilice direcciones de memoria como identificadores o identificadores «únicos». Si no puede usar un contador que solo aumente en 1 cada vez, use un número aleatorio de al menos 128 bits en su lugar. Estos a veces se conocen como UUID, para identificadores únicos universales. Utilice una fuente aleatoria de alta calidad, como /dev/urandomen Linux y macOS, o BCryptGenRandom()en Windows.
- Considere bloquear la carga del módulo del kernel para evitar sorpresas. Si configura la variable del sistema Linux una kernel.modules_disable=1vez que su servidor se haya iniciado y esté funcionando correctamente, no se pueden cargar más módulos, ya sea por accidente o por diseño, y esta configuración solo se puede desactivar reiniciando. Utilice sysctl -w kernel.modules_disable=1o echo 1 > /proc/sys/kernel/modules_disable.
- Considere identificar y conservar solo los módulos del kernel que necesita. Puede construir un kernel estático con solo los módulos requeridos compilados, o crear un paquete de kernel para sus servidores con todos los módulos innecesarios eliminados. Con un kernel estático, puede desactivar la carga de módulos por completo si lo desea.
Fuente: Link