Tengo el siguiente pseudocódigo resumido en C89 de una biblioteca de servidor ssh que solo proporciona acceso a cosas como git-shell (/bin/bash
se reemplaza con el programa que se ejecutará, por lo que no es posible hacer otra cosa) :
struct _raw_uncapped_ssh_string { // no limit on the size of the string; uint32_t len; char non_null_terminated_string[]; // by protocol design it have a minimum length of 1 }; typedef struct _raw_uncapped_ssh_string raw_ssh_string; union buffer { void * uncapped_zlib_decompressed_network_data; // yes, the size is uncapped, so it’s possible to put 4Gb of // data in it that would be copied later into memory. zlib // allow easily to turn some Mb in Gb of data, but it’s not // the point of the question. raw_ssh_string st; }; get_command (compressed_network_data) { size_t len; char * command; buffer string=uncompress_to_buffer(compressed_network_data); len=ntohl(string.st.len)+1; command=malloc(len+1); command[len]=0; // here’s the point, both the string length and content as // well it’s supplied size is controlled by the attacker. memcpy(command,string.st.data,len); return command; }
Aquí está cómo se ejecuta el comando más tarde (la cadena command
no cambia después de get_command()
) .
const char *args[]={"/bin/bash",command,NULL}; // /bin/bash isn’t the shell, it has been replaced by git‑shell. // redirect the program output to the network. dup2(stdin, 0); dup2(stdout,1); dup2(stdout,2); close(stdin); close(stdout); //if this return execution failed and print an error message return execv(args[0],(char * const *)args); // I don’t know which is the system, so I can’t know about the libc behaviour.
No puedo hacer memcpy(command,string.st.data,0)
ya que el tercer miembro de memcpy
tiene un tamaño mínimo de 1, y en mi contexto, size_t
usa un número entero de 64 bits, no puedo realizar un desbordamiento de búfer, ya que hay len
.
Todo lo que puedo hacer es establecer len
en un valor mayor que el asignado a string.st.data
. Este es un subdesbordamiento del búfer que me permite leer la memoria no asignada.
Puedo leer la memoria del servidor, sin embargo, no puedo ver qué datos confidenciales puede contener un servidor ssh público (en mi caso, la lista de usuarios que puede realizar ssh es público) .
Lo mismo ocurre con un subdesbordamiento del búfer en memcpy
permitir control remoto ejecución de código?
Comentarios
Respuesta
En general, no, un desbordamiento del búfer no se puede utilizar para la ejecución remota de código. Dado que los datos controlados por el atacante nunca dejan el espacio asignado para ellos, nunca tienen la capacidad de tomar el control del flujo de ejecución del programa.
Un subdesbordamiento del búfer tiene el potencial de otros tipos de ataques, como divulgación de información (si el programa cuenta con que los nuevos datos eliminen el contenido original del búfer).
Comentarios
- O como yo visto recientemente en git, se convertirá más tarde en un desbordamiento de búfer.
Respuesta
Cuando escribí originalmente esta respuesta , parece que pasé por alto algunos malentendidos que tiene. Es probable que estos malentendidos le impidan comprender mi respuesta. Para ser claro, voy a hacer este texto grande para resaltar el énfasis, no para ser irrespetuoso:
Muchos de los términos que usa no significan lo que parece pensar que significan.
Por ejemplo:
- «el tercer miembro de
memcpy
» no existe porquememcpy
es una función, no unastruct
ounion
. - «No puedo realizar un desbordamiento del búfer, ya que hay
len
» y no puedo quedarme sin gasolina, ya que hay gasolina. ¿Le estoy rogando al ¿Pregunta? Me parece una analogía adecuada, especialmente porque teóricamente hablando, tu código podría invocar un desbordamiento del búfer. - «Todo lo que puedo hacer es configurar
len
a un valor mayor que el asignado astring.st.data
. Esto es un subdesbordamiento del búfer … « No, esa no es la definición de un subdesbordamiento del búfer. Se produce un subdesbordamiento del búfer cuando accede fuera de los límites a una matriz utilizando un índice negativo o un índice que causaría que se produzca el ajuste aritmético del puntero. Esto último puede ser posible para algunas configuraciones (incluso en «sistemas de 64 bits», lo que sea que eso signifique), pero dudo que esto sea lo que quisiste decir cuando estabas escribiendo estas palabras, porque luego las seguiste con: - «… permitiéndome leer la memoria no asignada». Creo que quizás te refieres a «memoria no inicializada». Para ser claros, creo que querías decir que «ve asignó un exceso y dejó el exceso sin inicializar , y tal vez desee hacer algo al respecto (consulte
calloc
omemset
).
Consideremos char *fubar = NULL;
por un momento … este tipo de puntero generalmente tiene un cero valor.Desreferenciarlo se considera una desreferencia de puntero nulo , pero si escribimos algo como 0["hello"]
obtenemos lo mismo que "hello"[0]
(es decir, "h"
). Por lo tanto, se puede usar una desreferencia de puntero nulo en ataques cuando el atacante controla la expresión entre llaves (como lo hace en su situación).
Volviendo al fubar
cosa; digamos que memset(&fubar, UCHAR_MAX, sizeof fubar);
, ahora fubar
son 1 bits, lo que significa que podría ser una dirección esa es la dirección más grande que nuestro sistema podría (teóricamente) acomodar. ¿Qué pasa si accedemos a fubar[1]
? ¿Cómo puede acceder al elemento después de la dirección ¿Dirección y luego volver a ajustar? Técnicamente, todo esto es un comportamiento indefinido, pero si tuviera que nombrar eso en la arquitectura común, sería:
- Desbordamiento aritmético de un puntero, que lleva a …
- Desreferencia de puntero nulo y / o potencialmente un subdesbordamiento del búfer .
¿un subdesbordamiento del búfer en
memcpy
permite la ejecución remota de código?
Un búfer underflow puede permitir a un atacante sobrescribir punteros de función ubicados en las regiones de la memoria antes de la matriz en cuestión. Si esos punteros de función están hechos para apuntar a shellcode, ese shellcode puede ser ejecutados cuando se invocan más adelante.
En este fragmento de código algo oscuro, parece que existe un riesgo de subdesbordamiento del búfer cuando int
tiene un dominio que uint32_t
, ya que ntohl(string.st.len)+1
haría que el valor uint32_t
se convierta en un int
tipo. Considere, por ejemplo, si INT_MIN
es -4294967296
(que es uno menos que 0 - UINT32_MAX
) y INT_MAX
es 4294967295
… esto esencialmente sería un int
de 33 bits con relleno para rellenar hasta el ancho de un byte; poco común, pero posible. En esta circunstancia, la expresión ntohl(string.st.len)+1
no es uint32_t
de tipo; tiene un tipo «int
, y en lugar de volver a 0 cuando se produce un desbordamiento de enteros sin firmar , probablemente se ajusta a -4294967296
cuando se produce un desbordamiento de enteros con signo .
Si «está buscando una garantía contra el desbordamiento del búfer , utilice el U
sufijo literal entero (es decir, ntohl(string.st.len)+1U
). Luego, en esa situación, la expresión será uint32_t
o unsigned int
(según el tipo que tenga el mayor dominio).
Si considera que ntohl(string.st.len)
puede devolver uno menos que el valor máximo para ese tipo sin firmar (lo que sea), entonces len=ntohl(string.st.len)+1
dará como resultado el valor máximo, malloc(len+1)
causará envoltura sin firmar por lo que» terminará invocando malloc(0)
y luego command[len]=0
escribirá bien y verdaderamente más allá del final de la matriz. Entonces, por supuesto, «también tendrá un problema con memcpy(command,string.st.data,len);
(esto es un desbordamiento del búfer).
El búfer se desborda y los desbordamientos no son el único riesgo. Si no «t verifica el valor de retorno de malloc
y malloc
devuelve NULL
, luego se puede usar una desreferencia de puntero nulo para provocar la ejecución de código arbitrario. Esto implica que debe tener un método para comunicarse malloc
fallas a la persona que llama. También implica que puede usar ese mismo método para verificar y comunicar ese problema de envoltura a la persona que llama.
command[len] = 0
entonces eso es un desbordamiento de búfer, ya que el índice máximo para un búfer de longitudlen
eslen-1
. Alternativamente, si el código real tiene unmalloc(len+1)
en lugar de unmalloc(len)
, entonces puede hacer un desbordamiento masivo de búfer configurando ellen
valor a0xFFFFFFFF
.len+1
para que la configuración en 0 sea válida.string.st.len
en -1.